#include "ui/ui.h" #include "debugger/debugger.h" #include "ui/icons.h" #include "ui/widgets.h" #include #include #include #include #include #include #include #include #include #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 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(i) * 6.0f), ImVec2(m0.x + 17.0f + static_cast(i % 2) * 4.0f, m0.y + 15.0f + static_cast(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(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 }, { "", 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(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(); } }