aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ui/preferences.cpp131
-rw-r--r--src/ui/preferences.h40
-rw-r--r--src/ui/source_view.cpp43
-rw-r--r--src/ui/source_view.h16
-rw-r--r--src/ui/ui.cpp298
-rw-r--r--src/ui/ui.h9
6 files changed, 473 insertions, 64 deletions
diff --git a/src/ui/preferences.cpp b/src/ui/preferences.cpp
new file mode 100644
index 0000000..0b0d70d
--- /dev/null
+++ b/src/ui/preferences.cpp
@@ -0,0 +1,131 @@
+#include "ui/preferences.h"
+
+#include "core/log.h"
+
+#include <charconv>
+#include <cstdio>
+#include <fstream>
+#include <string>
+#include <string_view>
+
+namespace Hsdbg
+{
+ namespace
+ {
+ auto as_float(std::string_view text, float fallback) -> float
+ {
+ float value = fallback;
+ std::from_chars(text.data(), text.data() + text.size(), value);
+ return value;
+ }
+
+ auto as_bool(std::string_view text, bool fallback) -> bool
+ {
+ if (text == "true" || text == "1")
+ return true;
+
+ if (text == "false" || text == "0")
+ return false;
+
+ return fallback;
+ }
+
+ auto read_color(std::string_view value, float (&out)[3]) -> void
+ {
+ std::sscanf(std::string(value).c_str(), "%f %f %f", &out[0], &out[1], &out[2]);
+ }
+ }
+
+ auto load_preferences(const std::filesystem::path& path) -> Preferences
+ {
+ Preferences preferences;
+
+ std::ifstream file(path);
+ if (!file.is_open())
+ return preferences;
+
+ std::string line;
+ while (std::getline(file, line))
+ {
+ const size_t equals = line.find('=');
+ if (equals == std::string::npos)
+ continue;
+
+ const std::string_view key(line.data(), equals);
+ const std::string_view value(line.data() + equals + 1, line.size() - equals - 1);
+
+ if (key == "ui_scale")
+ preferences.ui_scale = as_float(value, preferences.ui_scale);
+ else if (key == "accent")
+ std::sscanf(std::string(value).c_str(), "%f %f %f",
+ &preferences.accent[0], &preferences.accent[1], &preferences.accent[2]);
+ else if (key == "rounding")
+ preferences.rounding = as_float(value, preferences.rounding);
+ else if (key == "show_mascot")
+ preferences.show_mascot = as_bool(value, preferences.show_mascot);
+ else if (key == "mascot_scale")
+ preferences.mascot_scale = as_float(value, preferences.mascot_scale);
+ else if (key == "syntax_highlighting")
+ preferences.syntax_highlighting = as_bool(value, preferences.syntax_highlighting);
+ else if (key == "show_line_numbers")
+ preferences.show_line_numbers = as_bool(value, preferences.show_line_numbers);
+ else if (key == "highlight_current_line")
+ preferences.highlight_current_line = as_bool(value, preferences.highlight_current_line);
+ else if (key == "color_keyword")
+ read_color(value, preferences.color_keyword);
+ else if (key == "color_type")
+ read_color(value, preferences.color_type);
+ else if (key == "color_string")
+ read_color(value, preferences.color_string);
+ else if (key == "color_number")
+ read_color(value, preferences.color_number);
+ else if (key == "color_comment")
+ read_color(value, preferences.color_comment);
+ else if (key == "color_preprocessor")
+ read_color(value, preferences.color_preprocessor);
+ else if (key == "color_current_line")
+ read_color(value, preferences.color_current_line);
+ else if (key == "stop_at_entry")
+ preferences.stop_at_entry = as_bool(value, preferences.stop_at_entry);
+ else if (key == "step_by_instruction")
+ preferences.step_by_instruction = as_bool(value, preferences.step_by_instruction);
+ else if (key == "show_fps")
+ preferences.show_fps = as_bool(value, preferences.show_fps);
+ }
+
+ return preferences;
+ }
+
+ auto save_preferences(const std::filesystem::path& path, const Preferences& preferences) -> void
+ {
+ std::ofstream file(path, std::ios::trunc);
+ if (!file.is_open())
+ {
+ Log::warn("preferences: could not write '{}'", path.string());
+ return;
+ }
+
+ const auto color = [](const float (&c)[3]) {
+ return std::to_string(c[0]) + ' ' + std::to_string(c[1]) + ' ' + std::to_string(c[2]);
+ };
+
+ file << "ui_scale=" << preferences.ui_scale << '\n'
+ << "accent=" << color(preferences.accent) << '\n'
+ << "rounding=" << preferences.rounding << '\n'
+ << "show_mascot=" << (preferences.show_mascot ? "true" : "false") << '\n'
+ << "mascot_scale=" << preferences.mascot_scale << '\n'
+ << "syntax_highlighting=" << (preferences.syntax_highlighting ? "true" : "false") << '\n'
+ << "show_line_numbers=" << (preferences.show_line_numbers ? "true" : "false") << '\n'
+ << "highlight_current_line=" << (preferences.highlight_current_line ? "true" : "false") << '\n'
+ << "color_keyword=" << color(preferences.color_keyword) << '\n'
+ << "color_type=" << color(preferences.color_type) << '\n'
+ << "color_string=" << color(preferences.color_string) << '\n'
+ << "color_number=" << color(preferences.color_number) << '\n'
+ << "color_comment=" << color(preferences.color_comment) << '\n'
+ << "color_preprocessor=" << color(preferences.color_preprocessor) << '\n'
+ << "color_current_line=" << color(preferences.color_current_line) << '\n'
+ << "stop_at_entry=" << (preferences.stop_at_entry ? "true" : "false") << '\n'
+ << "step_by_instruction=" << (preferences.step_by_instruction ? "true" : "false") << '\n'
+ << "show_fps=" << (preferences.show_fps ? "true" : "false") << '\n';
+ }
+}
diff --git a/src/ui/preferences.h b/src/ui/preferences.h
new file mode 100644
index 0000000..46a8c3f
--- /dev/null
+++ b/src/ui/preferences.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <filesystem>
+
+namespace Hsdbg
+{
+ // user-facing settings edited from the preferences panel and persisted next
+ // to the imgui layout; every field wires to something the ui actually reads
+ struct Preferences
+ {
+ // appearance
+ float ui_scale = 1.0f; // scales every font, 0.75 - 2.0
+ float accent[3] = { 0.30f, 0.72f, 0.78f }; // the teal that runs through the theme
+ float rounding = 4.0f; // corner radius on frames, windows, tabs
+ bool show_mascot = true; // the crying-pepe in the toolbar
+ float mascot_scale = 1.6f; // mascot height as a multiple of a button
+
+ // editor
+ bool syntax_highlighting = true;
+ bool show_line_numbers = true;
+ bool highlight_current_line = true;
+ float color_keyword[3] = { 0.337f, 0.612f, 0.839f };
+ float color_type[3] = { 0.306f, 0.788f, 0.690f };
+ float color_string[3] = { 0.808f, 0.569f, 0.471f };
+ float color_number[3] = { 0.710f, 0.808f, 0.659f };
+ float color_comment[3] = { 0.416f, 0.600f, 0.333f };
+ float color_preprocessor[3] = { 0.773f, 0.525f, 0.753f };
+ float color_current_line[3] = { 0.227f, 0.282f, 0.180f };
+
+ // debugger
+ bool stop_at_entry = false; // break at the entry point on launch
+ bool step_by_instruction = false; // step buttons move one instruction, not a line
+
+ // interface
+ bool show_fps = true; // frames-per-second read-out in the status bar
+ };
+
+ auto load_preferences(const std::filesystem::path& path) -> Preferences;
+ auto save_preferences(const std::filesystem::path& path, const Preferences& preferences) -> void;
+}
diff --git a/src/ui/source_view.cpp b/src/ui/source_view.cpp
index 96a150b..b998e3f 100644
--- a/src/ui/source_view.cpp
+++ b/src/ui/source_view.cpp
@@ -20,23 +20,6 @@ namespace Hsdbg
{
constexpr float BREAKPOINT_RADIUS = 5.0f;
- // a dark palette in the spirit of the source view's existing greens; the
- // default kind carries no colour so ordinary text keeps the theme's own
- auto color_of(SyntaxKind kind) -> ImU32
- {
- switch (kind)
- {
- case SyntaxKind::Keyword: return IM_COL32(86, 156, 214, 255);
- case SyntaxKind::Type: return IM_COL32(78, 201, 176, 255);
- case SyntaxKind::String: return IM_COL32(206, 145, 120, 255);
- case SyntaxKind::Number: return IM_COL32(181, 206, 168, 255);
- case SyntaxKind::Comment: return IM_COL32(106, 153, 85, 255);
- case SyntaxKind::Preprocessor: return IM_COL32(197, 134, 192, 255);
- case SyntaxKind::Default: break;
- }
-
- return 0;
- }
enum class Language : uint8_t
{
@@ -333,7 +316,6 @@ namespace Hsdbg
const ImU32 BREAKPOINT_COLOR = IM_COL32(226, 84, 84, 255);
const ImU32 BREAKPOINT_DISABLED_COLOR = IM_COL32(120, 90, 90, 255);
const ImU32 BREAKPOINT_HOVER_COLOR = IM_COL32(226, 84, 84, 90);
- const ImU32 HIGHLIGHT_COLOR = IM_COL32(58, 72, 46, 255);
auto line_number_width(size_t line_count) -> float
{
@@ -432,7 +414,7 @@ namespace Hsdbg
auto SourceView::draw_lines(Debugger& debugger) -> void
{
const float text_height = ImGui::GetTextLineHeightWithSpacing();
- const float number_width = line_number_width(m_lines.size());
+ const float number_width = m_line_numbers ? line_number_width(m_lines.size()) : 0.0f;
ImDrawList* draw_list = ImGui::GetWindowDrawList();
@@ -459,12 +441,12 @@ namespace Hsdbg
const uint32_t line_number = static_cast<uint32_t>(index) + 1;
const ImVec2 row_start = ImGui::GetCursorScreenPos();
- if (line_number == m_highlighted_line)
+ if (line_number == m_highlighted_line && m_highlight_current_line)
{
draw_list->AddRectFilled(row_start,
ImVec2(row_start.x + ImGui::GetContentRegionAvail().x,
row_start.y + text_height),
- HIGHLIGHT_COLOR);
+ m_current_line_color);
}
ImGui::PushID(index);
@@ -522,18 +504,22 @@ namespace Hsdbg
draw_list->AddCircleFilled(marker_center, BREAKPOINT_RADIUS, BREAKPOINT_HOVER_COLOR);
}
- const std::string number_text = std::to_string(line_number);
+ if (m_line_numbers)
+ {
+ const std::string number_text = std::to_string(line_number);
- ImGui::SameLine(0.0f, 0.0f);
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() + number_width -
- ImGui::CalcTextSize(number_text.c_str()).x);
- ImGui::TextDisabled("%s", number_text.c_str());
+ ImGui::SameLine(0.0f, 0.0f);
+ ImGui::SetCursorPosX(ImGui::GetCursorPosX() + number_width -
+ ImGui::CalcTextSize(number_text.c_str()).x);
+ ImGui::TextDisabled("%s", number_text.c_str());
+ }
ImGui::SameLine(0.0f, GUTTER_MARGIN * 2.0f);
const std::string& text = m_lines[static_cast<size_t>(index)];
- if (!m_highlight || m_spans[static_cast<size_t>(index)].empty())
+ if (!m_highlight || !m_highlighting_enabled ||
+ m_spans[static_cast<size_t>(index)].empty())
{
ImGui::TextUnformatted(text.c_str());
}
@@ -557,7 +543,8 @@ namespace Hsdbg
}
else
{
- ImGui::PushStyleColor(ImGuiCol_Text, color_of(span.kind));
+ ImGui::PushStyleColor(ImGuiCol_Text,
+ m_syntax_colors[static_cast<size_t>(span.kind)]);
ImGui::TextUnformatted(begin, end);
ImGui::PopStyleColor();
}
diff --git a/src/ui/source_view.h b/src/ui/source_view.h
index d3b3ed5..2e1a20f 100644
--- a/src/ui/source_view.h
+++ b/src/ui/source_view.h
@@ -40,6 +40,13 @@ namespace Hsdbg
auto draw(Debugger& debugger) -> void;
auto set_highlighted_line(uint32_t line) -> void;
+ auto set_highlighting(bool enabled) -> void { m_highlighting_enabled = enabled; }
+ auto set_line_numbers(bool enabled) -> void { m_line_numbers = enabled; }
+ auto set_highlight_current_line(bool enabled) -> void { m_highlight_current_line = enabled; }
+
+ // packed ImU32 (0xAABBGGRR) colours, one per SyntaxKind, index 0 unused
+ auto set_syntax_color(size_t kind, unsigned int color) -> void { m_syntax_colors[kind] = color; }
+ auto set_current_line_color(unsigned int color) -> void { m_current_line_color = color; }
auto path() const -> const std::filesystem::path& { return m_path; }
auto line_count() const -> size_t { return m_lines.size(); }
@@ -53,6 +60,15 @@ namespace Hsdbg
std::vector<std::string> m_lines;
std::vector<std::vector<SourceSpan>> m_spans;
bool m_highlight = false;
+ bool m_highlighting_enabled = true;
+ bool m_line_numbers = true;
+ bool m_highlight_current_line = true;
+
+ // indexed by SyntaxKind; the defaults match the built-in dark palette
+ unsigned int m_syntax_colors[7] = {
+ 0, 0xFFD69C56, 0xFFB0C94E, 0xFF7891CE, 0xFFA8CEB5, 0xFF55996A, 0xFFC086C5,
+ };
+ unsigned int m_current_line_color = 0xFF2E483A;
std::string m_path_input;
std::string m_error;
uint32_t m_highlighted_line = 0;
diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp
index de273ff..040f27b 100644
--- a/src/ui/ui.cpp
+++ b/src/ui/ui.cpp
@@ -418,9 +418,19 @@ namespace Hsdbg
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigWindowsMoveFromTitleBarOnly = true;
+ // the scalable default font re-rasterizes at any size; the classic bitmap
+ // one only looks clean at 13px and turns to mush when the ui is scaled
+ io.Fonts->AddFontDefaultVector();
+
// a saved layout beats the built in one, so only build when there is none
m_layout_built = io.IniFilename != nullptr && std::filesystem::exists(io.IniFilename);
+ // sit the settings file next to imgui's own layout file
+ m_preferences_path = io.IniFilename != nullptr
+ ? std::filesystem::path(io.IniFilename).parent_path() / "hsdbg.ini"
+ : std::filesystem::path("hsdbg.ini");
+ m_preferences = load_preferences(m_preferences_path);
+
apply_style();
ImGui_ImplGlfw_InitForOpenGL(m_window->handle(), true);
@@ -551,6 +561,8 @@ namespace Hsdbg
auto Ui::draw(Debugger& debugger) -> void
{
+ apply_preferences();
+
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
@@ -610,6 +622,8 @@ namespace Hsdbg
if (m_visible.demo)
ImGui::ShowDemoWindow(&m_visible.demo);
+ draw_preferences_window();
+
// a window claims its tab when it is first submitted, so this can only be
// asked for once every panel in the node exists
if (m_select_default_tabs)
@@ -690,6 +704,10 @@ namespace Hsdbg
ImGui::Separator();
+ ImGui::MenuItem("preferences...", nullptr, &m_show_preferences);
+
+ ImGui::Separator();
+
if (ImGui::MenuItem("quit", "cmd+q"))
m_window->set_should_close(true);
@@ -711,11 +729,14 @@ namespace Hsdbg
ImGui::Separator();
+ const StepMode step_mode =
+ m_preferences.step_by_instruction ? StepMode::Instruction : StepMode::Line;
+
if (ImGui::MenuItem("step over", "f10", false, has_target))
- report(debugger.step_over(), "step over");
+ report(debugger.step_over(step_mode), "step over");
if (ImGui::MenuItem("step into", "f11", false, has_target))
- report(debugger.step_into(), "step into");
+ report(debugger.step_into(step_mode), "step into");
if (ImGui::MenuItem("step out", "shift+f11", false, has_target))
report(debugger.step_out(), "step out");
@@ -766,8 +787,9 @@ namespace Hsdbg
}
const float button_height = ImGui::GetFrameHeight();
- const bool has_mascot = m_mascot.valid() && m_mascot.height() > 0.0f;
- const float mascot_height = has_mascot ? button_height * 1.6f : 0.0f;
+ const bool has_mascot = m_preferences.show_mascot && m_mascot.valid() &&
+ m_mascot.height() > 0.0f;
+ const float mascot_height = has_mascot ? button_height * m_preferences.mascot_scale : 0.0f;
const float row_height = std::max(button_height, mascot_height);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f));
@@ -777,11 +799,15 @@ namespace Hsdbg
const float row_top = ImGui::GetCursorPosY();
ImGui::SetCursorPosY(row_top + (row_height - button_height) * 0.5f);
+ const StepMode step_mode =
+ m_preferences.step_by_instruction ? StepMode::Instruction : StepMode::Line;
+
ImGui::BeginDisabled(!has_target || running);
if (ImGui::Button("run"))
{
LaunchSpec spec;
spec.executable = debugger.target_path();
+ spec.stop_at_entry = m_preferences.stop_at_entry;
report(debugger.launch(spec), "run");
}
@@ -805,12 +831,12 @@ namespace Hsdbg
ImGui::SameLine();
if (ImGui::Button("step over"))
- report(debugger.step_over(), "step over");
+ report(debugger.step_over(step_mode), "step over");
ImGui::SameLine();
if (ImGui::Button("step into"))
- report(debugger.step_into(), "step into");
+ report(debugger.step_into(step_mode), "step into");
ImGui::SameLine();
@@ -875,11 +901,15 @@ namespace Hsdbg
ImGui::Text("%zu breakpoints", debugger.breakpoints().size());
- const std::string frame_stats = std::format("{:.1f} fps", ImGui::GetIO().Framerate);
- const float text_width = ImGui::CalcTextSize(frame_stats.c_str()).x;
+ if (m_preferences.show_fps)
+ {
+ const std::string frame_stats = std::format("{:.1f} fps", ImGui::GetIO().Framerate);
+ const float text_width = ImGui::CalcTextSize(frame_stats.c_str()).x;
- ImGui::SameLine(ImGui::GetContentRegionMax().x - text_width - ImGui::GetStyle().ItemSpacing.x);
- ImGui::TextDisabled("%s", frame_stats.c_str());
+ ImGui::SameLine(ImGui::GetContentRegionMax().x - text_width -
+ ImGui::GetStyle().ItemSpacing.x);
+ ImGui::TextDisabled("%s", frame_stats.c_str());
+ }
}
auto Ui::draw_load_target_popup(Debugger& debugger) -> void
@@ -928,6 +958,182 @@ namespace Hsdbg
ImGui::EndPopup();
}
+ auto Ui::apply_preferences() -> void
+ {
+ // FontScaleMain re-rasterizes the vector font crisply, unlike the legacy
+ // FontGlobalScale which just stretched the baked atlas
+ ImGui::GetStyle().FontScaleMain = m_preferences.ui_scale;
+
+ m_source_view.set_highlighting(m_preferences.syntax_highlighting);
+ m_source_view.set_line_numbers(m_preferences.show_line_numbers);
+ m_source_view.set_highlight_current_line(m_preferences.highlight_current_line);
+
+ const auto pack = [](const float c[3]) {
+ return IM_COL32(static_cast<int>(c[0] * 255.0f), static_cast<int>(c[1] * 255.0f),
+ static_cast<int>(c[2] * 255.0f), 255);
+ };
+ m_source_view.set_syntax_color(1, pack(m_preferences.color_keyword));
+ m_source_view.set_syntax_color(2, pack(m_preferences.color_type));
+ m_source_view.set_syntax_color(3, pack(m_preferences.color_string));
+ m_source_view.set_syntax_color(4, pack(m_preferences.color_number));
+ m_source_view.set_syntax_color(5, pack(m_preferences.color_comment));
+ m_source_view.set_syntax_color(6, pack(m_preferences.color_preprocessor));
+ m_source_view.set_current_line_color(pack(m_preferences.color_current_line));
+
+ // the theme colours and rounding only need rebuilding when they change
+ if (m_restyle_pending)
+ {
+ apply_style();
+ m_restyle_pending = false;
+ }
+ }
+
+ auto Ui::draw_preferences_window() -> void
+ {
+ if (!m_show_preferences)
+ return;
+
+ const ImGuiViewport* viewport = ImGui::GetMainViewport();
+ const ImVec2 center(viewport->WorkPos.x + viewport->WorkSize.x * 0.5f,
+ viewport->WorkPos.y + viewport->WorkSize.y * 0.5f);
+
+ ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
+ ImGui::SetNextWindowSize(ImVec2(560.0f, 380.0f), ImGuiCond_Appearing);
+ ImGui::SetNextWindowSizeConstraints(ImVec2(460.0f, 300.0f), ImVec2(FLT_MAX, FLT_MAX));
+
+ if (!ImGui::Begin("preferences", &m_show_preferences, ImGuiWindowFlags_NoDocking))
+ {
+ ImGui::End();
+ return;
+ }
+
+ static constexpr const char* CATEGORIES[] = { "appearance", "editor", "debugger" };
+ bool changed = false;
+ bool restyle = false;
+
+ const float footer = ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y;
+
+ // left: the category list; right: that category's settings
+ ImGui::BeginChild("##pref_categories", ImVec2(150.0f, -footer), ImGuiChildFlags_Borders);
+ for (int index = 0; index < IM_ARRAYSIZE(CATEGORIES); ++index)
+ {
+ if (ImGui::Selectable(CATEGORIES[index], m_preferences_tab == index))
+ m_preferences_tab = index;
+ }
+ ImGui::EndChild();
+
+ ImGui::SameLine();
+
+ ImGui::BeginChild("##pref_content", ImVec2(0.0f, -footer));
+ ImGui::PushItemWidth(-150.0f);
+
+ const auto help = [](const char* text) {
+ ImGui::SameLine();
+ ImGui::TextDisabled("(?)");
+ if (ImGui::BeginItemTooltip())
+ {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 20.0f);
+ ImGui::TextUnformatted(text);
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+ };
+
+ const auto accent_swatch = [&](const char* label, float (&value)[3]) {
+ if (ImGui::ColorEdit3(label, value,
+ ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha))
+ changed = true;
+ };
+
+ if (m_preferences_tab == 0)
+ {
+ ImGui::SeparatorText("interface");
+ changed |= ImGui::SliderFloat("ui scale", &m_preferences.ui_scale, 0.75f, 2.0f, "%.2fx");
+ help("scales every font. the text stays crisp because it is re-rasterized, not stretched.");
+
+ if (ImGui::ColorEdit3("accent colour", m_preferences.accent,
+ ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha))
+ {
+ changed = true;
+ restyle = true;
+ }
+
+ if (ImGui::SliderFloat("corner rounding", &m_preferences.rounding, 0.0f, 12.0f, "%.0f px"))
+ {
+ changed = true;
+ restyle = true;
+ }
+
+ changed |= ImGui::Checkbox("show fps in the status bar", &m_preferences.show_fps);
+
+ ImGui::SeparatorText("layout");
+ if (ImGui::Button("reset window layout"))
+ m_layout_built = false;
+ help("restores the default arrangement of all the docked panels.");
+
+ ImGui::SeparatorText("mascot");
+ changed |= ImGui::Checkbox("show the crying pepe", &m_preferences.show_mascot);
+
+ ImGui::BeginDisabled(!m_preferences.show_mascot);
+ changed |= ImGui::SliderFloat("pepe size", &m_preferences.mascot_scale, 1.0f, 3.0f, "%.1fx");
+ ImGui::EndDisabled();
+ }
+ else if (m_preferences_tab == 1)
+ {
+ ImGui::SeparatorText("source view");
+ changed |= ImGui::Checkbox("syntax highlighting", &m_preferences.syntax_highlighting);
+ changed |= ImGui::Checkbox("show line numbers", &m_preferences.show_line_numbers);
+ changed |= ImGui::Checkbox("highlight the current line",
+ &m_preferences.highlight_current_line);
+
+ ImGui::SeparatorText("colours");
+ ImGui::BeginDisabled(!m_preferences.syntax_highlighting);
+ accent_swatch("keyword", m_preferences.color_keyword);
+ accent_swatch("type", m_preferences.color_type);
+ accent_swatch("string", m_preferences.color_string);
+ accent_swatch("number", m_preferences.color_number);
+ accent_swatch("comment", m_preferences.color_comment);
+ accent_swatch("preprocessor", m_preferences.color_preprocessor);
+ ImGui::EndDisabled();
+ accent_swatch("current line", m_preferences.color_current_line);
+ }
+ else if (m_preferences_tab == 2)
+ {
+ ImGui::SeparatorText("launching");
+ changed |= ImGui::Checkbox("break at entry point on launch", &m_preferences.stop_at_entry);
+ help("stop on the very first instruction instead of running to your breakpoints.");
+
+ ImGui::SeparatorText("stepping");
+ changed |= ImGui::Checkbox("step by instruction, not line",
+ &m_preferences.step_by_instruction);
+ help("the step over/into buttons advance one machine instruction at a time.");
+ }
+
+ ImGui::PopItemWidth();
+ ImGui::EndChild();
+
+ ImGui::Separator();
+
+ if (ImGui::Button("reset to defaults"))
+ {
+ m_preferences = Preferences{};
+ changed = true;
+ restyle = true;
+ }
+
+ ImGui::SameLine();
+ ImGui::TextDisabled("saved to %s", m_preferences_path.filename().string().c_str());
+
+ // persist the moment anything changes, so nothing is lost to a crash
+ if (changed)
+ save_preferences(m_preferences_path, m_preferences);
+
+ if (restyle)
+ m_restyle_pending = true;
+
+ ImGui::End();
+ }
+
auto Ui::draw_source_panel(Debugger& debugger) -> void
{
if (!m_visible.source)
@@ -1517,13 +1723,15 @@ namespace Hsdbg
{
ImGuiStyle& style = ImGui::GetStyle();
- style.WindowRounding = 4.0f;
- style.ChildRounding = 4.0f;
- style.FrameRounding = 4.0f;
- style.PopupRounding = 4.0f;
- style.ScrollbarRounding = 6.0f;
- style.GrabRounding = 4.0f;
- style.TabRounding = 4.0f;
+ const float radius = m_preferences.rounding;
+
+ style.WindowRounding = radius;
+ style.ChildRounding = radius;
+ style.FrameRounding = radius;
+ style.PopupRounding = radius;
+ style.ScrollbarRounding = radius + 2.0f;
+ style.GrabRounding = radius;
+ style.TabRounding = radius;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 0.0f;
@@ -1535,6 +1743,24 @@ namespace Hsdbg
style.WindowTitleAlign = ImVec2(0.0f, 0.5f);
style.SeparatorTextBorderSize = 1.0f;
+ // the accent is user-chosen; the hover/active shades are blended from it
+ // toward the button base so any colour stays coherent across the theme
+ const ImVec4 accent(m_preferences.accent[0], m_preferences.accent[1],
+ m_preferences.accent[2], 1.0f);
+ const ImVec4 base(0.18f, 0.18f, 0.22f, 1.0f);
+
+ const auto mix = [](ImVec4 from, ImVec4 to, float t) {
+ return ImVec4(from.x + (to.x - from.x) * t, from.y + (to.y - from.y) * t,
+ from.z + (to.z - from.z) * t, 1.0f);
+ };
+ const auto fade = [](ImVec4 colour, float alpha) {
+ return ImVec4(colour.x, colour.y, colour.z, alpha);
+ };
+
+ const ImVec4 accent_bright = mix(accent, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 0.14f);
+ const ImVec4 accent_hover = mix(base, accent, 0.42f);
+ const ImVec4 accent_active = mix(base, accent, 0.68f);
+
ImVec4* colors = style.Colors;
colors[ImGuiCol_Text] = ImVec4(0.86f, 0.86f, 0.88f, 1.00f);
@@ -1555,35 +1781,35 @@ namespace Hsdbg
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.24f, 0.29f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.36f, 0.43f, 1.00f);
- colors[ImGuiCol_CheckMark] = ImVec4(0.30f, 0.72f, 0.78f, 1.00f);
- colors[ImGuiCol_SliderGrab] = ImVec4(0.30f, 0.72f, 0.78f, 1.00f);
- colors[ImGuiCol_SliderGrabActive] = ImVec4(0.38f, 0.80f, 0.86f, 1.00f);
- colors[ImGuiCol_Button] = ImVec4(0.18f, 0.18f, 0.22f, 1.00f);
- colors[ImGuiCol_ButtonHovered] = ImVec4(0.24f, 0.40f, 0.44f, 1.00f);
- colors[ImGuiCol_ButtonActive] = ImVec4(0.28f, 0.52f, 0.58f, 1.00f);
+ colors[ImGuiCol_CheckMark] = accent;
+ colors[ImGuiCol_SliderGrab] = accent;
+ colors[ImGuiCol_SliderGrabActive] = accent_bright;
+ colors[ImGuiCol_Button] = base;
+ colors[ImGuiCol_ButtonHovered] = accent_hover;
+ colors[ImGuiCol_ButtonActive] = accent_active;
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.25f, 1.00f);
- colors[ImGuiCol_HeaderHovered] = ImVec4(0.24f, 0.40f, 0.44f, 1.00f);
- colors[ImGuiCol_HeaderActive] = ImVec4(0.28f, 0.52f, 0.58f, 1.00f);
+ colors[ImGuiCol_HeaderHovered] = accent_hover;
+ colors[ImGuiCol_HeaderActive] = accent_active;
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
- colors[ImGuiCol_SeparatorHovered] = ImVec4(0.30f, 0.72f, 0.78f, 0.60f);
- colors[ImGuiCol_SeparatorActive] = ImVec4(0.30f, 0.72f, 0.78f, 1.00f);
+ colors[ImGuiCol_SeparatorHovered] = fade(accent, 0.60f);
+ colors[ImGuiCol_SeparatorActive] = accent;
colors[ImGuiCol_ResizeGrip] = ImVec4(0.24f, 0.24f, 0.29f, 1.00f);
- colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.30f, 0.72f, 0.78f, 0.60f);
- colors[ImGuiCol_ResizeGripActive] = ImVec4(0.30f, 0.72f, 0.78f, 1.00f);
+ colors[ImGuiCol_ResizeGripHovered] = fade(accent, 0.60f);
+ colors[ImGuiCol_ResizeGripActive] = accent;
colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.11f, 0.14f, 1.00f);
- colors[ImGuiCol_TabHovered] = ImVec4(0.24f, 0.40f, 0.44f, 1.00f);
- colors[ImGuiCol_TabSelected] = ImVec4(0.18f, 0.30f, 0.34f, 1.00f);
- colors[ImGuiCol_TabSelectedOverline] = ImVec4(0.30f, 0.72f, 0.78f, 1.00f);
+ colors[ImGuiCol_TabHovered] = accent_hover;
+ colors[ImGuiCol_TabSelected] = mix(base, accent, 0.30f);
+ colors[ImGuiCol_TabSelectedOverline] = accent;
colors[ImGuiCol_TabDimmed] = ImVec4(0.09f, 0.09f, 0.11f, 1.00f);
- colors[ImGuiCol_TabDimmedSelected] = ImVec4(0.14f, 0.20f, 0.22f, 1.00f);
- colors[ImGuiCol_DockingPreview] = ImVec4(0.30f, 0.72f, 0.78f, 0.50f);
+ colors[ImGuiCol_TabDimmedSelected] = mix(ImVec4(0.14f, 0.14f, 0.17f, 1.0f), accent, 0.16f);
+ colors[ImGuiCol_DockingPreview] = fade(accent, 0.50f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.14f, 0.14f, 0.17f, 1.00f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.16f, 0.16f, 0.19f, 1.00f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.02f);
- colors[ImGuiCol_TextSelectedBg] = ImVec4(0.30f, 0.72f, 0.78f, 0.35f);
- colors[ImGuiCol_NavCursor] = ImVec4(0.30f, 0.72f, 0.78f, 1.00f);
+ colors[ImGuiCol_TextSelectedBg] = fade(accent, 0.35f);
+ colors[ImGuiCol_NavCursor] = accent;
}
}
diff --git a/src/ui/ui.h b/src/ui/ui.h
index edd343f..436bfc4 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -3,6 +3,7 @@
#include "core/result.h"
#include "ui/animated_image.h"
#include "ui/image_renderer.h"
+#include "ui/preferences.h"
#include "ui/source_view.h"
#include <cstdint>
@@ -58,6 +59,8 @@ namespace Hsdbg
auto draw_toolbar(Debugger& debugger) -> void;
auto draw_status_bar(Debugger& debugger) -> void;
auto draw_load_target_popup(Debugger& debugger) -> void;
+ auto draw_preferences_window() -> void;
+ auto apply_preferences() -> void;
auto draw_source_panel(Debugger& debugger) -> void;
auto draw_breakpoints_panel(Debugger& debugger) -> void;
@@ -81,6 +84,12 @@ namespace Hsdbg
SourceView m_source_view;
PanelVisibility m_visible;
+ Preferences m_preferences;
+ std::filesystem::path m_preferences_path;
+ bool m_show_preferences = false;
+ bool m_restyle_pending = false;
+ int m_preferences_tab = 0;
+
// the crying-pepe that lives in the toolbar; loaded on the first frame
// once there is a gl context to upload its textures to, and drawn with a
// dedicated shader after imgui rather than through ImGui::Image