diff options
| author | hachem <im@hachem.wtf> | 2026-09-01 03:49:18 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-01 03:49:18 +0200 |
| commit | 37098d4db1195d3f18f396d839b4eda6942b1f8c (patch) | |
| tree | 9f988de3e33c333e5f1b1696dfb8c8d958ed91fe /src | |
| parent | dc384ed13dc22d2640a20fbb030531b7f5d97502 (diff) | |
feat: add preferences panel
Diffstat (limited to 'src')
| -rw-r--r-- | src/ui/preferences.cpp | 131 | ||||
| -rw-r--r-- | src/ui/preferences.h | 40 | ||||
| -rw-r--r-- | src/ui/source_view.cpp | 43 | ||||
| -rw-r--r-- | src/ui/source_view.h | 16 | ||||
| -rw-r--r-- | src/ui/ui.cpp | 298 | ||||
| -rw-r--r-- | src/ui/ui.h | 9 |
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 |
