From 199d6f15346dda994ab12f8c9701e6081d0817ae Mon Sep 17 00:00:00 2001 From: hachem Date: Fri, 4 Sep 2026 17:05:30 +0200 Subject: feat: more widgets and icons --- src/ui/icons.h | 75 ++++++++++++++--------- src/ui/widgets.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++----------- src/ui/widgets.h | 18 +++++- 3 files changed, 199 insertions(+), 65 deletions(-) (limited to 'src/ui') diff --git a/src/ui/icons.h b/src/ui/icons.h index 74b0331..22cb04c 100644 --- a/src/ui/icons.h +++ b/src/ui/icons.h @@ -6,31 +6,50 @@ namespace Hsdbg inline constexpr unsigned int ICON_MAX_PH = 0xEE00; } -#define ICON_PH_PLAY "\xEE\x8F\x90" -#define ICON_PH_PAUSE "\xEE\x8E\x9E" -#define ICON_PH_STOP "\xEE\x91\xAC" -#define ICON_PH_STEP_OVER "\xEE\x80\x96" -#define ICON_PH_STEP_INTO "\xEE\x81\x9C" -#define ICON_PH_STEP_OUT "\xEE\x81\xA6" -#define ICON_PH_FOLDER "\xEE\x89\x8A" -#define ICON_PH_FOLDER_OPEN "\xEE\x89\x96" -#define ICON_PH_FILE "\xEE\x88\xB0" -#define ICON_PH_FILE_CODE "\xEE\xA4\x94" -#define ICON_PH_CARET_RIGHT "\xEE\x84\xBA" -#define ICON_PH_CARET_DOWN "\xEE\x84\xB6" -#define ICON_PH_STACK "\xEE\x91\xA6" -#define ICON_PH_BUG "\xEE\x97\xB4" -#define ICON_PH_SEARCH "\xEE\x8C\x8C" -#define ICON_PH_GEAR "\xEE\x89\xB2" -#define ICON_PH_CPU "\xEE\x98\x90" -#define ICON_PH_LIST "\xEE\x8B\xB0" -#define ICON_PH_FUNCTION "\xEE\xAF\xA4" -#define ICON_PH_SLIDERS "\xEE\x90\xB4" -#define ICON_PH_TERMINAL "\xEE\xAB\xA8" -#define ICON_PH_DOT "\xEE\xB3\x9E" -#define ICON_PH_CIRCLE "\xEE\x86\x8A" -#define ICON_PH_EYE "\xEE\x88\xA0" -#define ICON_PH_CUBE "\xEE\x87\x9A" -#define ICON_PH_GAUGE "\xEE\x98\xA8" -#define ICON_PH_BRANCH "\xEE\x89\xB8" -#define ICON_PH_BRACKETS "\xEE\xA1\xA0" +#define ICON_PH_PLAY "\xEE\x8F\x90" +#define ICON_PH_PAUSE "\xEE\x8E\x9E" +#define ICON_PH_STOP "\xEE\x91\xAC" +#define ICON_PH_STEP_OVER "\xEE\x80\x96" +#define ICON_PH_STEP_INTO "\xEE\x81\x9C" +#define ICON_PH_STEP_OUT "\xEE\x81\xA6" +#define ICON_PH_FOLDER "\xEE\x89\x8A" +#define ICON_PH_FOLDER_OPEN "\xEE\x89\x96" +#define ICON_PH_FILE "\xEE\x88\xB0" +#define ICON_PH_FILE_CODE "\xEE\xA4\x94" +#define ICON_PH_CARET_RIGHT "\xEE\x84\xBA" +#define ICON_PH_CARET_DOWN "\xEE\x84\xB6" +#define ICON_PH_STACK "\xEE\x91\xA6" +#define ICON_PH_BUG "\xEE\x97\xB4" +#define ICON_PH_SEARCH "\xEE\x8C\x8C" +#define ICON_PH_GEAR "\xEE\x89\xB0" +#define ICON_PH_CPU "\xEE\x98\x90" +#define ICON_PH_LIST "\xEE\x8B\xB0" +#define ICON_PH_FUNCTION "\xEE\xAF\xA4" +#define ICON_PH_SLIDERS "\xEE\x90\xB4" +#define ICON_PH_TERMINAL "\xEE\xAB\xA8" +#define ICON_PH_DOT "\xEE\xB3\x9E" +#define ICON_PH_CIRCLE "\xEE\x86\x8A" +#define ICON_PH_EYE "\xEE\x88\xA0" +#define ICON_PH_CUBE "\xEE\x87\x9A" +#define ICON_PH_GAUGE "\xEE\x98\xA8" +#define ICON_PH_BRANCH "\xEE\x89\xB8" +#define ICON_PH_BRACKETS "\xEE\xA1\xA0" +#define ICON_PH_TARGET "\xEE\x91\xBC" +#define ICON_PH_MAP_PIN "\xEE\x8C\x96" +#define ICON_PH_HASH "\xEE\x8A\xA2" +#define ICON_PH_WARNING "\xEE\x93\xA0" +#define ICON_PH_PLUS "\xEE\x8F\x94" +#define ICON_PH_PENCIL "\xEE\x8E\xB4" +#define ICON_PH_CHECK "\xEE\x86\x82" +#define ICON_PH_X "\xEE\x93\xB6" +#define ICON_PH_TIMER "\xEE\x92\x92" +#define ICON_PH_MEMORY "\xEE\xA7\x84" +#define ICON_PH_PULSE "\xEE\x80\x80" +#define ICON_PH_FILE_C "\xEE\xAC\xB2" +#define ICON_PH_FILE_CPP "\xEE\xAC\xAE" +#define ICON_PH_FILE_RS "\xEE\xAC\xA8" +#define ICON_PH_CIRCLE_DASHED "\xEE\x98\x82" +#define ICON_PH_ARROW_RIGHT "\xEE\x81\xAC" +#define ICON_PH_CLOCK "\xEE\x86\x9A" +#define ICON_PH_LIGHTNING "\xEE\x8B\x9E" +#define ICON_PH_INFO "\xEE\x8B\x8E" diff --git a/src/ui/widgets.cpp b/src/ui/widgets.cpp index b44a32e..370ea94 100644 --- a/src/ui/widgets.cpp +++ b/src/ui/widgets.cpp @@ -1,7 +1,9 @@ #include "ui/widgets.h" #include +#include +#include #include #include #include @@ -15,14 +17,9 @@ namespace Hsdbg::Widgets ImFont* g_strong = nullptr; [[maybe_unused]] ImFont* g_mono = nullptr; - auto accent_color() -> ImVec4 - { - return ImGui::GetStyle().Colors[ImGuiCol_CheckMark]; - } - auto accent_ink() -> ImU32 { - const ImVec4 a = accent_color(); + const ImVec4 a = accent(); const float luma = 0.299f * a.x + 0.587f * a.y + 0.114f * a.z; return luma > 0.6f ? IM_COL32(12, 16, 20, 255) : IM_COL32(245, 247, 250, 255); } @@ -32,6 +29,12 @@ namespace Hsdbg::Widgets colour.w = alpha; return ImGui::ColorConvertFloat4ToU32(colour); } + + auto track_fill(ImDrawList* draw, ImVec2 min, ImVec2 max, float rounding) -> void + { + draw->AddRectFilled(min, max, ImGui::GetColorU32(ImGuiCol_FrameBg), rounding); + draw->AddRectFilled(min, max, with_alpha(ImGui::GetStyle().Colors[ImGuiCol_Text], 0.07f), rounding); + } } auto set_fonts(ImFont* ui, ImFont* strong, ImFont* mono) -> void @@ -41,7 +44,12 @@ namespace Hsdbg::Widgets g_mono = mono; } - auto section_header(const char* label) -> void + auto accent() -> ImVec4 + { + return ImGui::GetStyle().Colors[ImGuiCol_CheckMark]; + } + + auto section_header(const char* label, bool rule) -> void { std::string caps; caps.reserve(24); @@ -61,7 +69,7 @@ namespace Hsdbg::Widgets const ImVec2 hi = ImGui::GetItemRectMax(); const float cy = (lo.y + hi.y) * 0.5f; const float right = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; - if (right > hi.x + 10.0f) + if (rule && right > hi.x + 10.0f) ImGui::GetWindowDrawList()->AddLine(ImVec2(hi.x + 8.0f, cy), ImVec2(right, cy), ImGui::GetColorU32(ImGuiCol_Border), 1.0f); } @@ -70,7 +78,13 @@ namespace Hsdbg::Widgets { ImGui::PushID(label); - const float width = ImGui::CalcItemWidth(); + char lo[64]; + char hi[64]; + std::snprintf(lo, sizeof(lo), fmt, static_cast(min)); + std::snprintf(hi, sizeof(hi), fmt, static_cast(max)); + const float value_w = std::max(ImGui::CalcTextSize(lo).x, ImGui::CalcTextSize(hi).x); + const float gap = 10.0f; + const float width = std::max(ImGui::CalcItemWidth() - value_w - gap, 40.0f); const float height = ImGui::GetFrameHeight(); const ImVec2 p = ImGui::GetCursorScreenPos(); @@ -90,33 +104,38 @@ namespace Hsdbg::Widgets const float ty = p.y + (height - th) * 0.5f; const float frac = (max > min) ? ImClamp((*value - min) / (max - min), 0.0f, 1.0f) : 0.0f; const float gx = p.x + frac * width; - const ImU32 accent = ImGui::ColorConvertFloat4ToU32(accent_color()); + const ImU32 fill = ImGui::ColorConvertFloat4ToU32(accent()); - draw->AddRectFilled(ImVec2(p.x, ty), ImVec2(p.x + width, ty + th), - ImGui::GetColorU32(ImGuiCol_FrameBg), th * 0.5f); - draw->AddRectFilled(ImVec2(p.x, ty), ImVec2(gx, ty + th), accent, th * 0.5f); + track_fill(draw, ImVec2(p.x, ty), ImVec2(p.x + width, ty + th), th * 0.5f); + draw->AddRectFilled(ImVec2(p.x, ty), ImVec2(gx, ty + th), fill, th * 0.5f); const float r = (active || hovered) ? height * 0.30f : height * 0.26f; - draw->AddCircleFilled(ImVec2(gx, p.y + height * 0.5f), r, accent); + draw->AddCircleFilled(ImVec2(gx, p.y + height * 0.5f), r, fill); draw->AddCircleFilled(ImVec2(gx, p.y + height * 0.5f), r * 0.42f, accent_ink()); char buf[64]; std::snprintf(buf, sizeof(buf), fmt, static_cast(*value)); const ImVec2 ts = ImGui::CalcTextSize(buf); - draw->AddText(ImVec2(p.x + (width - ts.x) * 0.5f, p.y + (height - ts.y) * 0.5f), + draw->AddText(ImVec2(p.x + width + gap + value_w - ts.x, p.y + (height - ts.y) * 0.5f), ImGui::GetColorU32(ImGuiCol_Text), buf); + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(gap + value_w, height)); + ImGui::PopID(); - ImGui::SameLine(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(label); + if (label[0] != '#' || label[1] != '#') + { + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label); + } return *value != before; } auto icon_button(const char* id, const char* icon, bool active, const char* tooltip, - bool disabled, float size) -> bool + bool disabled, float size, ImVec4 glyph) -> bool { if (disabled) ImGui::BeginDisabled(); @@ -125,7 +144,7 @@ namespace Hsdbg::Widgets ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f); if (active) - ImGui::PushStyleColor(ImGuiCol_Button, accent_color()); + ImGui::PushStyleColor(ImGuiCol_Button, accent()); const ImVec2 origin = ImGui::GetCursorScreenPos(); const bool clicked = ImGui::Button("##btn", ImVec2(size, size)); @@ -134,7 +153,9 @@ namespace Hsdbg::Widgets ImGui::PopStyleColor(); ImGui::PopStyleVar(); - const ImU32 glyph_col = active ? accent_ink() : ImGui::GetColorU32(ImGuiCol_Text); + const bool tinted = glyph.w > 0.0f && !disabled && !active; + const ImU32 glyph_col = active ? accent_ink() + : ImGui::GetColorU32(tinted ? glyph : ImGui::GetStyleColorVec4(ImGuiCol_Text)); const float cx = origin.x + size * 0.5f; const float cy = origin.y + size * 0.5f; @@ -179,12 +200,15 @@ namespace Hsdbg::Widgets const float radius = height * 0.5f; const ImVec2 end(pos.x + width, pos.y + height); - const ImU32 track = *value ? ImGui::ColorConvertFloat4ToU32(accent_color()) - : ImGui::GetColorU32(ImGuiCol_FrameBg); - draw->AddRectFilled(pos, end, track, radius); - if (!*value) - draw->AddRect(pos, end, ImGui::GetColorU32(hovered ? ImGuiCol_Border : ImGuiCol_FrameBg), - radius); + if (*value) + { + draw->AddRectFilled(pos, end, ImGui::ColorConvertFloat4ToU32(accent()), radius); + } + else + { + track_fill(draw, pos, end, radius); + draw->AddRect(pos, end, ImGui::GetColorU32(hovered ? ImGuiCol_TextDisabled : ImGuiCol_Border), radius); + } const float knob = radius - 2.5f; const float knob_x = *value ? (end.x - radius) : (pos.x + radius); @@ -194,6 +218,14 @@ namespace Hsdbg::Widgets return clicked; } + auto segmented_width(const char* const* labels, int count) -> float + { + float total = 4.0f; + for (int i = 0; i < count; ++i) + total += ImGui::CalcTextSize(labels[i]).x + 8.0f * 2.0f + 2.0f; + return total; + } + auto segmented(const char* id, const char* const* labels, int count, int current) -> int { const ImGuiStyle& style = ImGui::GetStyle(); @@ -202,13 +234,9 @@ namespace Hsdbg::Widgets const float height = ImGui::GetFrameHeight(); const float pad = 8.0f; const ImVec2 origin = ImGui::GetCursorScreenPos(); + const float total = segmented_width(labels, count); - float total = 4.0f; - for (int i = 0; i < count; ++i) - total += ImGui::CalcTextSize(labels[i]).x + pad * 2.0f + 2.0f; - - const ImU32 track = ImGui::GetColorU32(ImGuiCol_FrameBg); - draw->AddRectFilled(origin, ImVec2(origin.x + total, origin.y + height), track, height * 0.5f); + track_fill(draw, origin, ImVec2(origin.x + total, origin.y + height), height * 0.5f); int result = current; ImGui::PushID(id); @@ -266,12 +294,12 @@ namespace Hsdbg::Widgets ImVec2 max = ImGui::GetItemRectMax(); min.x += 2.0f; max.x -= 2.0f; - const ImU32 fill = selected ? with_alpha(accent_color(), 0.18f) + const ImU32 fill = selected ? with_alpha(accent(), 0.18f) : with_alpha(ImGui::GetStyle().Colors[ImGuiCol_Text], 0.055f); split.SetCurrentChannel(draw, 0); draw->AddRectFilled(min, max, fill, 7.0f); if (selected) - draw->AddRect(min, max, with_alpha(accent_color(), 0.40f), 7.0f); + draw->AddRect(min, max, with_alpha(accent(), 0.40f), 7.0f); } split.Merge(draw); @@ -348,4 +376,77 @@ namespace Hsdbg::Widgets return clicked; } + + auto input_with_icon(const char* id, const char* icon, const char* hint, std::string* text, + ImGuiInputTextFlags flags) -> bool + { + const ImGuiStyle& style = ImGui::GetStyle(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const float pad_x = style.FramePadding.x; + const float pad_y = style.FramePadding.y; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + ImVec2(pad_x * 2.0f + ImGui::CalcTextSize(icon).x, pad_y)); + const bool changed = ImGui::InputTextWithHint(id, hint, text, flags); + ImGui::PopStyleVar(); + + ImGui::GetWindowDrawList()->AddText(ImVec2(pos.x + pad_x, pos.y + pad_y), + ImGui::GetColorU32(ImGuiCol_TextDisabled), icon); + return changed; + } + + auto dot_toggle(const char* id, bool* value, ImU32 on, ImU32 off, bool pending) -> bool + { + const float s = ImGui::GetFrameHeight(); + const ImVec2 p = ImGui::GetCursorScreenPos(); + + ImGui::InvisibleButton(id, ImVec2(s, s)); + const bool clicked = ImGui::IsItemClicked(); + if (clicked) + *value = !*value; + + const ImVec2 centre(p.x + s * 0.5f, p.y + s * 0.5f); + const ImU32 colour = *value ? on : off; + ImDrawList* draw = ImGui::GetWindowDrawList(); + if (pending) + draw->AddCircle(centre, 5.0f, colour, 0, 1.5f); + else + draw->AddCircleFilled(centre, 5.0f, colour); + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", *value ? "Disable" : "Enable"); + + return clicked; + } + + auto hex_value(unsigned long long value, int digits) -> void + { + char buf[32]; + std::snprintf(buf, sizeof(buf), "%0*llx", digits, value); + + int zeros = 0; + while (zeros < digits - 1 && buf[zeros] == '0') + ++zeros; + + ImGui::TextDisabled("0x%.*s", zeros, buf); + ImGui::SameLine(0.0f, 0.0f); + ImGui::TextUnformatted(buf + zeros); + } + + auto link_text(const char* text) -> bool + { + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextLink)); + ImGui::TextUnformatted(text); + ImGui::PopStyleColor(); + + if (!ImGui::IsItemHovered()) + return false; + + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + const ImVec2 lo = ImGui::GetItemRectMin(); + const ImVec2 hi = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddLine(ImVec2(lo.x, hi.y - 1.0f), ImVec2(hi.x, hi.y - 1.0f), + ImGui::GetColorU32(ImGuiCol_TextLink)); + return ImGui::IsMouseClicked(ImGuiMouseButton_Left); + } } diff --git a/src/ui/widgets.h b/src/ui/widgets.h index dac58e3..366e69a 100644 --- a/src/ui/widgets.h +++ b/src/ui/widgets.h @@ -2,22 +2,27 @@ #include +#include + namespace Hsdbg::Widgets { auto set_fonts(ImFont* ui, ImFont* strong, ImFont* mono) -> void; - auto section_header(const char* label) -> void; + auto accent() -> ImVec4; + + auto section_header(const char* label, bool rule = true) -> void; auto slider_float(const char* label, float* value, float min, float max, const char* fmt) -> bool; auto icon_button(const char* id, const char* icon, bool active = false, const char* tooltip = nullptr, bool disabled = false, - float size = 34.0f) -> bool; + float size = 34.0f, ImVec4 glyph = ImVec4(0.0f, 0.0f, 0.0f, 0.0f)) -> bool; auto toggle(const char* id, bool* value) -> bool; auto segmented(const char* id, const char* const* labels, int count, int current) -> int; + auto segmented_width(const char* const* labels, int count) -> float; auto selectable_row(const char* label, bool selected, ImGuiSelectableFlags flags = 0) -> bool; @@ -27,4 +32,13 @@ namespace Hsdbg::Widgets auto empty_state(const char* icon, const char* text) -> void; auto remove_button(const char* id) -> bool; + + auto input_with_icon(const char* id, const char* icon, const char* hint, std::string* text, + ImGuiInputTextFlags flags = 0) -> bool; + + auto dot_toggle(const char* id, bool* value, ImU32 on, ImU32 off, bool pending = false) -> bool; + + auto hex_value(unsigned long long value, int digits) -> void; + + auto link_text(const char* text) -> bool; } -- cgit v1.3