diff options
| author | hachem <im@hachem.wtf> | 2026-09-18 12:25:58 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-18 12:25:58 +0200 |
| commit | 4f74d1400180ca4ad7cfcd1903386fda8779818d (patch) | |
| tree | 258a332c3076045461b5d6f8592a52140225abd1 /src/ui/ui_preferences.cpp | |
| parent | 0846724177d823b632f2a4deffdcb5b6e226cd56 (diff) | |
feat: improve layout and themes
Diffstat (limited to 'src/ui/ui_preferences.cpp')
| -rw-r--r-- | src/ui/ui_preferences.cpp | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/src/ui/ui_preferences.cpp b/src/ui/ui_preferences.cpp new file mode 100644 index 0000000..a1768a9 --- /dev/null +++ b/src/ui/ui_preferences.cpp @@ -0,0 +1,667 @@ +#include "ui/ui.h" + +#include "debugger/debugger.h" +#include "ui/icons.h" +#include "ui/widgets.h" + +#include <glad/gl.h> +#include <imgui.h> +#include <imgui_internal.h> + +#include <algorithm> +#include <cfloat> +#include <cmath> +#include <format> +#include <iterator> +#include <string> + +#ifndef HSDBG_VERSION + #define HSDBG_VERSION "dev" +#endif + +#ifndef HSDBG_ASSET_DIR + #define HSDBG_ASSET_DIR "" +#endif + +// the preferences window: a category rail on the left and, on the right, that +// category's settings grouped into raised cards of title / description / control +// rows, with theme and accent pickers that show what they pick +namespace Hsdbg +{ + namespace + { + constexpr const char* CATEGORIES[] = { "Appearance", "Editor", "Debugger", "About" }; + constexpr const char* CATEGORY_ICONS[] = { ICON_PH_SLIDERS, ICON_PH_FILE_CODE, ICON_PH_BUG, ICON_PH_INFO }; + constexpr const char* CATEGORY_BLURBS[] = { + "Theme, accent and the shape of the interface", + "How source is drawn and coloured", + "How targets launch and step, and the keys that drive them", + "What this build is made of", + }; + + struct AccentPreset + { + const char* name; + float rgb[3]; + }; + + constexpr AccentPreset ACCENTS[] = { + { "Teal", { 0.302f, 0.714f, 0.769f } }, { "Sky", { 0.357f, 0.659f, 0.961f } }, + { "Indigo", { 0.486f, 0.486f, 0.941f } }, { "Violet", { 0.690f, 0.518f, 0.902f } }, + { "Rose", { 0.886f, 0.478f, 0.604f } }, { "Coral", { 0.941f, 0.541f, 0.365f } }, + { "Amber", { 0.910f, 0.690f, 0.290f } }, { "Mint", { 0.373f, 0.788f, 0.561f } }, + }; + + constexpr float CARD_PAD_X = 14.0f; + constexpr float CARD_PAD_Y = 4.0f; + constexpr float ROW_PAD_Y = 7.0f; + + // the window, rail and controls grow with the ui scale, since imgui only + // scales the fonts and would otherwise leave them cramped at 1.5x + auto ui_scale() -> float + { + return ImGui::GetStyle().FontScaleMain; + } + + auto slider_width() -> float + { + return 190.0f * ui_scale(); + } + + auto rgb(const float (&c)[3]) -> ImVec4 + { + return ImVec4(c[0], c[1], c[2], 1.0f); + } + + auto u32(const ImVec4& colour) -> ImU32 + { + return ImGui::ColorConvertFloat4ToU32(colour); + } + + auto card_rounding() -> float + { + return ImGui::GetStyle().FrameRounding + 3.0f; + } + + // a card is drawn after its rows so it can wrap them: rows go to the upper + // channel, the card body to the lower one once the height is known + struct Card + { + ImVec2 origin; + float width = 0.0f; + int rows = 0; + ImDrawListSplitter splitter; + }; + + Card g_card; + + auto begin_card() -> void + { + ImDrawList* draw = ImGui::GetWindowDrawList(); + g_card.origin = ImGui::GetCursorScreenPos(); + g_card.width = ImGui::GetContentRegionAvail().x; + g_card.rows = 0; + g_card.splitter.Split(draw, 2); + g_card.splitter.SetCurrentChannel(draw, 1); + ImGui::SetCursorScreenPos(ImVec2(g_card.origin.x + CARD_PAD_X, g_card.origin.y + CARD_PAD_Y)); + } + + auto end_card() -> void + { + ImDrawList* draw = ImGui::GetWindowDrawList(); + const ImVec2 end(g_card.origin.x + g_card.width, ImGui::GetCursorScreenPos().y + CARD_PAD_Y); + + g_card.splitter.SetCurrentChannel(draw, 0); + draw->AddRectFilled(g_card.origin, end, ImGui::GetColorU32(ImGuiCol_FrameBg), card_rounding()); + draw->AddRect(g_card.origin, end, ImGui::GetColorU32(ImGuiCol_Border), card_rounding()); + g_card.splitter.Merge(draw); + + ImGui::SetCursorScreenPos(ImVec2(g_card.origin.x, end.y)); + ImGui::Dummy(ImVec2(g_card.width, 0.0f)); + ImGui::Spacing(); + } + + // one setting: title and dim description on the left, the control pinned + // to the right edge, both centred on the row; a hairline rules rows apart + template <typename Control> + auto setting_row(const char* title, const char* description, float control_width, + float control_height, Control&& control) -> void + { + const float base = ImGui::GetStyle().FontSizeBase; + const float title_h = ImGui::GetTextLineHeight(); + const float desc_h = description != nullptr ? std::floor(base * 0.88f) + 3.0f : 0.0f; + const float row_h = std::max(title_h + desc_h, control_height) + ROW_PAD_Y * 2.0f; + const float inner = g_card.width - CARD_PAD_X * 2.0f; + const ImVec2 top(g_card.origin.x + CARD_PAD_X, ImGui::GetCursorScreenPos().y); + ImDrawList* draw = ImGui::GetWindowDrawList(); + + if (g_card.rows > 0) + draw->AddLine(ImVec2(top.x, top.y - 0.5f), ImVec2(top.x + inner, top.y - 0.5f), + ImGui::GetColorU32(ImGuiCol_Border, 0.6f)); + + ImGui::SetCursorScreenPos(ImVec2(top.x, top.y + (row_h - title_h - desc_h) * 0.5f)); + ImGui::TextUnformatted(title); + if (description != nullptr) + { + ImGui::SetCursorScreenPos(ImVec2(top.x, ImGui::GetCursorScreenPos().y)); + ImGui::PushFont(nullptr, base * 0.88f); + ImGui::TextDisabled("%s", description); + ImGui::PopFont(); + } + + ImGui::SetCursorScreenPos(ImVec2(top.x + inner - control_width, top.y + (row_h - control_height) * 0.5f)); + ImGui::PushID(title); + control(); + ImGui::PopID(); + + ImGui::SetCursorScreenPos(ImVec2(top.x, top.y + row_h)); + ImGui::Dummy(ImVec2(inner, 0.0f)); + ++g_card.rows; + } + + auto toggle_row(const char* title, const char* description, bool* value) -> bool + { + bool changed = false; + const float h = ImGui::GetFrameHeight() * 0.78f; + setting_row(title, description, h * 1.8f, h, [&] { changed = Widgets::toggle("##toggle", value); }); + return changed; + } + + auto slider_row(const char* title, const char* description, float* value, float min, float max, + const char* fmt) -> bool + { + bool changed = false; + setting_row(title, description, slider_width(), ImGui::GetFrameHeight(), [&] + { + ImGui::SetNextItemWidth(slider_width()); + changed = Widgets::slider_float("##slider", value, min, max, fmt); + }); + return changed; + } + + auto color_row(const char* title, const char* description, float (&value)[3]) -> bool + { + bool changed = false; + const float h = ImGui::GetFrameHeight(); + setting_row(title, description, h, h, [&] + { + changed = ImGui::ColorEdit3("##colour", value, ImGuiColorEditFlags_NoInputs | + ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoLabel); + }); + return changed; + } + + // a keycap: the key's name in a small sunken box, as on a keyboard legend + auto key_cap(const char* text) -> void + { + const ImGuiStyle& style = ImGui::GetStyle(); + const ImVec2 size = ImGui::CalcTextSize(text); + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImVec2 end(p.x + size.x + 16.0f, p.y + ImGui::GetFrameHeight()); + ImDrawList* draw = ImGui::GetWindowDrawList(); + draw->AddRectFilled(p, end, ImGui::GetColorU32(ImGuiCol_WindowBg), style.FrameRounding); + draw->AddRect(p, end, ImGui::GetColorU32(ImGuiCol_Border), style.FrameRounding); + draw->AddText(ImVec2(p.x + 8.0f, p.y + style.FramePadding.y), ImGui::GetColorU32(ImGuiCol_Text), text); + ImGui::Dummy(ImVec2(end.x - p.x, end.y - p.y)); + } + + auto key_row(const char* title, const char* description, const char* key) -> void + { + const float width = ImGui::CalcTextSize(key).x + 16.0f; + setting_row(title, description, width, ImGui::GetFrameHeight(), [&] { key_cap(key); }); + } + + // a read-only fact; a long value is cut from the left and shown whole on hover + auto value_row(const char* title, std::string value) -> void + { + const float inner = g_card.width - CARD_PAD_X * 2.0f; + const float limit = inner * 0.58f; + const std::string full = value; + while (value.size() > 4 && ImGui::CalcTextSize(value.c_str()).x > limit) + value = "…" + value.substr(4); + + setting_row(title, nullptr, ImGui::CalcTextSize(value.c_str()).x, ImGui::GetTextLineHeight(), [&] + { + ImGui::TextDisabled("%s", value.c_str()); + if (value != full && ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", full.c_str()); + }); + } + + // a theme card: a tiny window in the theme's own palette over its name; the + // chosen one is ringed in the accent. returns true on click. + auto theme_card(const ThemeEntry& entry, const Theme& theme, bool selected, ImVec2 size) -> bool + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::InvisibleButton(entry.id.c_str(), size); + const bool hovered = ImGui::IsItemHovered(); + const bool clicked = ImGui::IsItemClicked(); + + ImDrawList* draw = ImGui::GetWindowDrawList(); + const float r = card_rounding(); + const ImVec2 end(p.x + size.x, p.y + size.y); + draw->AddRectFilled(p, end, ImGui::GetColorU32(ImGuiCol_FrameBg), r); + + // the mock: title stripe, sidebar, a few lines of "code", an accent pill + const ImVec2 m0(p.x + 6.0f, p.y + 6.0f); + const ImVec2 m1(end.x - 6.0f, end.y - 24.0f); + draw->AddRectFilled(m0, m1, u32(theme.bg), 4.0f); + draw->AddRectFilled(m0, ImVec2(m1.x, m0.y + 7.0f), u32(theme.bg_low), 4.0f, ImDrawFlags_RoundCornersTop); + draw->AddRectFilled(ImVec2(m0.x, m0.y + 7.0f), ImVec2(m0.x + 26.0f, m1.y), u32(theme.bg_high), 4.0f, + ImDrawFlags_RoundCornersBottomLeft); + for (int i = 0; i < 3; ++i) + draw->AddRectFilled(ImVec2(m0.x + 5.0f, m0.y + 13.0f + static_cast<float>(i) * 6.0f), + ImVec2(m0.x + 17.0f + static_cast<float>(i % 2) * 4.0f, + m0.y + 15.0f + static_cast<float>(i) * 6.0f), + u32(theme.text_dim)); + + struct Line { float indent, width; ImVec4 colour; }; + const Line lines[] = { + { 0.0f, 22.0f, theme.syntax_preprocessor }, { 0.0f, 40.0f, theme.syntax_comment }, + { 0.0f, 18.0f, theme.syntax_keyword }, { 8.0f, 36.0f, theme.text }, + { 8.0f, 26.0f, theme.syntax_string }, { 0.0f, 12.0f, theme.text }, + }; + float y = m0.y + 13.0f; + for (const Line& line : lines) + { + if (y + 2.0f > m1.y - 4.0f) + break; + draw->AddRectFilled(ImVec2(m0.x + 32.0f + line.indent, y), + ImVec2(m0.x + 32.0f + line.indent + line.width, y + 2.0f), u32(line.colour)); + y += 6.0f; + } + draw->AddRectFilled(ImVec2(m1.x - 24.0f, m1.y - 10.0f), ImVec2(m1.x - 5.0f, m1.y - 4.0f), + u32(theme.accent), 3.0f); + + const ImVec2 ts = ImGui::CalcTextSize(entry.name.c_str()); + draw->PushClipRect(ImVec2(p.x + 4.0f, m1.y), ImVec2(end.x - 4.0f, end.y), true); + draw->AddText(ImVec2(p.x + std::max((size.x - ts.x) * 0.5f, 4.0f), m1.y + (24.0f - ts.y) * 0.5f), + ImGui::GetColorU32(selected ? ImGuiCol_Text : ImGuiCol_TextDisabled), entry.name.c_str()); + draw->PopClipRect(); + + if (selected) + draw->AddRect(p, end, u32(Widgets::accent()), r, 0, 2.0f); + else + draw->AddRect(p, end, ImGui::GetColorU32(hovered ? ImGuiCol_TextDisabled : ImGuiCol_Border), r); + + return clicked; + } + + auto same_colour(const float (&a)[3], const float (&b)[3]) -> bool + { + return std::abs(a[0] - b[0]) < 0.02f && std::abs(a[1] - b[1]) < 0.02f && std::abs(a[2] - b[2]) < 0.02f; + } + + auto gl_renderer() -> std::string + { + const GLubyte* text = glGetString(GL_RENDERER); + return text != nullptr ? reinterpret_cast<const char*>(text) : "unknown"; + } + } + + auto Ui::draw_preferences_window() -> void + { + if (!m_show_preferences) + { + m_prefs_open_prev = false; + return; + } + + // rescan the themes folder when the window opens, so a .toml dropped in + // while the app is running shows up, and load each palette for its card + if (!m_prefs_open_prev) + { + m_themes = list_themes(themes_directory()); + m_theme_previews.clear(); + std::ranges::transform(m_themes, std::back_inserter(m_theme_previews), + [](const ThemeEntry& entry) { return load_theme(entry.path); }); + } + m_prefs_open_prev = true; + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + const ImVec2 center(viewport->WorkPos.x + viewport->WorkSize.x * 0.5f, + viewport->WorkPos.y + viewport->WorkSize.y * 0.5f); + + const float scale = ui_scale(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(860.0f * scale, 600.0f * scale), ImGuiCond_Appearing); + ImGui::SetNextWindowSizeConstraints(ImVec2(660.0f * scale, 440.0f * scale), ImVec2(FLT_MAX, FLT_MAX)); + + if (!ImGui::Begin(ICON_PH_GEAR " Preferences###preferences", &m_show_preferences, + ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoCollapse)) + { + ImGui::End(); + return; + } + + const float base = ImGui::GetStyle().FontSizeBase; + bool changed = false; + bool restyle = false; + + const float footer = ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y; + + // the rail: one roomy row per category on the deepest ground + ImGui::PushStyleColor(ImGuiCol_ChildBg, m_theme.bg_low); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(7.0f, 12.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 8.0f)); + ImGui::BeginChild("##pref_categories", ImVec2(180.0f * scale, -footer), ImGuiChildFlags_AlwaysUseWindowPadding); + for (int index = 0; index < IM_ARRAYSIZE(CATEGORIES); ++index) + { + const std::string label = std::string(CATEGORY_ICONS[index]) + " " + CATEGORIES[index]; + if (Widgets::selectable_row(label.c_str(), m_preferences_tab == index)) + m_preferences_tab = index; + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 12.0f)); + ImGui::BeginChild("##pref_content", ImVec2(0.0f, -footer), ImGuiChildFlags_AlwaysUseWindowPadding); + ImGui::PopStyleVar(); + + const int tab = std::clamp(m_preferences_tab, 0, IM_ARRAYSIZE(CATEGORIES) - 1); + ImGui::PushFont(m_font_strong, base * 1.4f); + ImGui::TextUnformatted(CATEGORIES[tab]); + ImGui::PopFont(); + ImGui::TextDisabled("%s", CATEGORY_BLURBS[tab]); + ImGui::Spacing(); + + if (tab == 0) + { + Widgets::section_header("Theme", false); + + // dark and light themes in their own rows, four cards across; picking + // one seeds the accent and syntax colours below and saves + constexpr int columns = 4; + const float gap = 8.0f; + const float card_w = std::floor((ImGui::GetContentRegionAvail().x - gap * (columns - 1)) / columns); + const ImVec2 card_size(card_w, 72.0f + 20.0f * scale); + + const auto is_light = [](const Theme& theme) + { + return 0.299f * theme.bg.x + 0.587f * theme.bg.y + 0.114f * theme.bg.z > 0.5f; + }; + + for (const bool light : { false, true }) + { + int shown = 0; + for (size_t index = 0; index < m_themes.size(); ++index) + { + if (is_light(m_theme_previews[index]) != light) + continue; + + if (shown == 0) + { + ImGui::PushFont(nullptr, base * 0.85f); + ImGui::TextDisabled("%s", light ? "Light" : "Dark"); + ImGui::PopFont(); + } + else if (shown % columns != 0) + { + ImGui::SameLine(0.0f, gap); + } + + const ThemeEntry& entry = m_themes[index]; + const bool selected = entry.id == m_preferences.theme; + if (theme_card(entry, m_theme_previews[index], selected, card_size) && !selected) + select_theme(entry); + ++shown; + } + + if (shown > 0) + ImGui::Spacing(); + } + if (m_themes.empty()) + ImGui::TextDisabled("No themes found in assets/themes"); + + ImGui::Spacing(); + Widgets::section_header("Accent", false); + + for (const AccentPreset& preset : ACCENTS) + { + const ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::InvisibleButton(preset.name, ImVec2(26.0f, 26.0f)); + const ImVec2 c(p.x + 13.0f, p.y + 13.0f); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddCircleFilled(c, ImGui::IsItemHovered() ? 11.0f : 10.0f, u32(rgb(preset.rgb))); + if (same_colour(preset.rgb, m_preferences.accent)) + draw_list->AddCircle(c, 12.5f, ImGui::GetColorU32(ImGuiCol_Text), 0, 2.0f); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", preset.name); + if (ImGui::IsItemClicked()) + { + std::copy(std::begin(preset.rgb), std::end(preset.rgb), std::begin(m_preferences.accent)); + changed = true; + restyle = true; + } + ImGui::SameLine(0.0f, 8.0f); + } + + if (ImGui::ColorEdit3("##accent_custom", m_preferences.accent, + ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_NoLabel)) + { + changed = true; + restyle = true; + } + ImGui::SameLine(); + ImGui::TextDisabled("Custom"); + + ImGui::Spacing(); + Widgets::section_header("Interface", false); + begin_card(); + changed |= slider_row("UI scale", "Scales every font; text is re-rasterised, so it stays crisp", + &m_preferences.ui_scale, 0.75f, 2.0f, "%.2fx"); + if (slider_row("Corner rounding", "Radius on frames, windows, tabs and buttons", + &m_preferences.rounding, 0.0f, 12.0f, "%.0f px")) + { + changed = true; + restyle = true; + } + changed |= toggle_row("Show FPS in the status bar", "hsdbg's own frame rate, bottom right", + &m_preferences.show_fps); + end_card(); + + Widgets::section_header("Layout", false); + begin_card(); + { + const float w = ImGui::CalcTextSize("Reset layout").x + ImGui::GetStyle().FramePadding.x * 2.0f; + setting_row("Window layout", "Put every panel back where a fresh install has it", w, + ImGui::GetFrameHeight(), [&] + { + if (ImGui::Button("Reset layout")) + m_layout_built = false; + }); + } + end_card(); + + Widgets::section_header("Mascot", false); + begin_card(); + changed |= toggle_row("Show the crying pepe", "He lives in the toolbar and means well", + &m_preferences.show_mascot); + ImGui::BeginDisabled(!m_preferences.show_mascot); + changed |= slider_row("Pepe size", "As a multiple of a toolbar button", &m_preferences.mascot_scale, + 1.0f, 3.0f, "%.1fx"); + ImGui::EndDisabled(); + end_card(); + } + else if (tab == 1) + { + Widgets::section_header("Font", false); + begin_card(); + changed |= slider_row("Source font size", "Cmd/Ctrl + scroll over the source zooms it too; Cmd/Ctrl + 0 resets", + &m_preferences.source_font_size, 9.0f, 32.0f, "%.0f px"); + end_card(); + + Widgets::section_header("Source view", false); + begin_card(); + changed |= toggle_row("Syntax highlighting", "Colour keywords, types, strings and the rest", + &m_preferences.syntax_highlighting); + changed |= toggle_row("Show line numbers", nullptr, &m_preferences.show_line_numbers); + changed |= toggle_row("Highlight the current line", "Tint the line execution has stopped on", + &m_preferences.highlight_current_line); + end_card(); + + Widgets::section_header("Syntax colours", false); + + // a live sample in the source face and size, coloured as the source + // view would colour it right now + { + struct Token { const char* text; int kind; }; + const Token sample[][6] = { + { { "#include", 6 }, { " ", 0 }, { "<cstdio>", 3 }, { nullptr, 0 } }, + { { "// the answer, as a function", 5 }, { nullptr, 0 } }, + { { "auto", 2 }, { " answer(", 0 }, { "int", 2 }, { " base) -> ", 0 }, { "int", 2 }, { nullptr, 0 } }, + { { "{", 0 }, { nullptr, 0 } }, + { { " ", 0 }, { "const", 1 }, { " ", 0 }, { "char", 2 }, { "* text = ", 0 }, { "\"hello\";", 3 } }, + { { " ", 0 }, { "return", 1 }, { " base * ", 0 }, { "42", 4 }, { ";", 0 }, { nullptr, 0 } }, + { { "}", 0 }, { nullptr, 0 } }, + }; + const float (*colours[])[3] = { + nullptr, &m_preferences.color_keyword, &m_preferences.color_type, &m_preferences.color_string, + &m_preferences.color_number, &m_preferences.color_comment, &m_preferences.color_preprocessor, + }; + + ImGui::PushFont(m_font_mono, m_preferences.source_font_size); + const float line_h = ImGui::GetTextLineHeight() + 4.0f; + const float pad = 12.0f; + const ImVec2 p = ImGui::GetCursorScreenPos(); + const ImVec2 size(ImGui::GetContentRegionAvail().x, line_h * IM_ARRAYSIZE(sample) + pad * 2.0f); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(p, ImVec2(p.x + size.x, p.y + size.y), u32(m_theme.bg_low), card_rounding()); + draw_list->AddRect(p, ImVec2(p.x + size.x, p.y + size.y), ImGui::GetColorU32(ImGuiCol_Border), card_rounding()); + + const int current = 5; + if (m_preferences.highlight_current_line) + { + const float y = p.y + pad + line_h * current - 2.0f; + draw_list->AddRectFilled(ImVec2(p.x + 1.0f, y), ImVec2(p.x + size.x - 1.0f, y + line_h), + u32(rgb(m_preferences.color_current_line))); + draw_list->AddRectFilled(ImVec2(p.x + 1.0f, y), ImVec2(p.x + 4.0f, y + line_h), u32(Widgets::accent())); + } + + for (int line = 0; line < IM_ARRAYSIZE(sample); ++line) + { + float x = p.x + pad + 8.0f; + const float y = p.y + pad + line_h * static_cast<float>(line); + for (const Token& token : sample[line]) + { + if (token.text == nullptr) + break; + const bool plain = token.kind == 0 || !m_preferences.syntax_highlighting; + const ImU32 colour = plain ? ImGui::GetColorU32(ImGuiCol_Text) : u32(rgb(*colours[token.kind])); + draw_list->AddText(ImVec2(x, y), colour, token.text); + x += ImGui::CalcTextSize(token.text).x; + } + } + ImGui::PopFont(); + ImGui::Dummy(size); + ImGui::Spacing(); + } + + begin_card(); + ImGui::BeginDisabled(!m_preferences.syntax_highlighting); + changed |= color_row("Keyword", "const, return, if, while…", m_preferences.color_keyword); + changed |= color_row("Type", "int, auto, char, size_t…", m_preferences.color_type); + changed |= color_row("String", "String and character literals, includes", m_preferences.color_string); + changed |= color_row("Number", nullptr, m_preferences.color_number); + changed |= color_row("Comment", nullptr, m_preferences.color_comment); + changed |= color_row("Preprocessor", "#include, #define and friends", m_preferences.color_preprocessor); + ImGui::EndDisabled(); + changed |= color_row("Current line", "Behind the line execution has stopped on", m_preferences.color_current_line); + end_card(); + } + else if (tab == 2) + { + Widgets::section_header("Launching", false); + begin_card(); + changed |= toggle_row("Break at entry point", "Stop on the very first instruction instead of running to your breakpoints", + &m_preferences.stop_at_entry); + end_card(); + + Widgets::section_header("Stepping", false); + begin_card(); + { + static const char* const step_modes[] = { "Line", "Instruction" }; + const int current_mode = m_preferences.step_by_instruction ? 1 : 0; + int next_mode = current_mode; + setting_row("Step granularity", "What Step Over and Step Into advance by", + Widgets::segmented_width(step_modes, 2), ImGui::GetFrameHeight(), [&] + { + next_mode = Widgets::segmented("##step_mode", step_modes, 2, current_mode); + }); + if (next_mode != current_mode) + { + m_preferences.step_by_instruction = next_mode == 1; + changed = true; + } + } + end_card(); + + Widgets::section_header("Keys", false); + begin_card(); + key_row("Continue, or run a loaded target", nullptr, "F5"); + key_row("Step Over", nullptr, "F10"); + key_row("Step Into", nullptr, "F11"); + key_row("Step Out", nullptr, "Shift + F11"); + key_row("Command palette", "Files, symbols and every command, fuzzy-matched", "Cmd/Ctrl + K"); + key_row("Zoom the source", "Over the source view", "Cmd/Ctrl + scroll"); + end_card(); + } + else + { + ImGui::PushFont(m_font_strong, base * 2.0f); + ImGui::TextUnformatted("hsdbg"); + ImGui::PopFont(); + ImGui::SameLine(0.0f, 10.0f); + ImGui::AlignTextToFramePadding(); + Widgets::chip(HSDBG_VERSION, u32(Widgets::accent())); + ImGui::TextDisabled("A GUI debugger for C and C++. LLDB does the debugging; this is the ui."); + ImGui::Spacing(); + + Widgets::section_header("Build", false); + begin_card(); + value_row("LLDB", Debugger::backend_version()); + value_row("Dear ImGui", IMGUI_VERSION); + value_row("OpenGL", gl_renderer()); + value_row("Fonts", "Nunito, JetBrains Mono, Phosphor"); + end_card(); + + Widgets::section_header("Paths", false); + begin_card(); + value_row("Assets", HSDBG_ASSET_DIR); + value_row("Preferences", m_preferences_path.string()); + value_row("Layout", ImGui::GetIO().IniFilename != nullptr ? ImGui::GetIO().IniFilename : "none"); + end_card(); + } + + ImGui::EndChild(); + + ImGui::Separator(); + + if (ImGui::Button("Reset to defaults")) + { + m_preferences = Preferences{}; + // realign the neutral palette with the default theme so the reset + // accent does not sit on top of the previously loaded theme's levels + load_selected_theme(false); + changed = true; + restyle = true; + } + + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + ImGui::TextDisabled("Saved to %s as you change things", m_preferences_path.filename().string().c_str()); + + if (changed) + save_preferences(m_preferences_path, m_preferences); + + if (restyle) + m_restyle_pending = true; + + ImGui::End(); + } +} |
