diff options
Diffstat (limited to 'src/ui/widgets.cpp')
| -rw-r--r-- | src/ui/widgets.cpp | 178 |
1 files changed, 142 insertions, 36 deletions
diff --git a/src/ui/widgets.cpp b/src/ui/widgets.cpp index 68f4875..7172f88 100644 --- a/src/ui/widgets.cpp +++ b/src/ui/widgets.cpp @@ -1,7 +1,9 @@ #include "ui/widgets.h" #include <imgui_internal.h> +#include <misc/cpp/imgui_stdlib.h> +#include <algorithm> #include <cctype> #include <cstdio> #include <cstring> @@ -15,16 +17,11 @@ namespace Hsdbg::Widgets ImFont* g_strong = nullptr; [[maybe_unused]] ImFont* g_mono = nullptr; - auto accent_color() -> ImVec4 - { - return ImGui::GetStyle().Colors[ImGuiCol_CheckMark]; - } - // a colour that reads on top of the accent: dark ink on a light accent, // light ink on a dark one, so on-accent labels stay legible in any theme 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); } @@ -34,6 +31,14 @@ namespace Hsdbg::Widgets colour.w = alpha; return ImGui::ColorConvertFloat4ToU32(colour); } + + // a track's resting fill: the frame colour plus a whisper of the text + // colour, so it still reads when drawn over a card that is frame-coloured + 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 @@ -43,7 +48,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); @@ -65,7 +75,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); } @@ -74,7 +84,15 @@ namespace Hsdbg::Widgets { ImGui::PushID(label); - const float width = ImGui::CalcItemWidth(); + // the value column is sized for either end of the range, so the grab + // never sits on top of the number + char lo[64]; + char hi[64]; + std::snprintf(lo, sizeof(lo), fmt, static_cast<double>(min)); + std::snprintf(hi, sizeof(hi), fmt, static_cast<double>(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(); @@ -94,33 +112,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<double>(*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(); @@ -129,7 +152,7 @@ namespace Hsdbg::Widgets ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f); if (active) - ImGui::PushStyleColor(ImGuiCol_Button, accent_color()); + ImGui::PushStyleColor(ImGuiCol_Button, accent()); // Button draws the bg + handles the click; the glyph is drawn by hand const ImVec2 origin = ImGui::GetCursorScreenPos(); @@ -141,7 +164,9 @@ namespace Hsdbg::Widgets // centre on the glyph's visual bounds so an asymmetric glyph (the play // triangle) sits dead-centre, not offset by its advance box or line-gap - 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; @@ -186,12 +211,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); @@ -201,6 +229,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(); @@ -209,14 +245,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); - // measure so the track wraps the labels exactly - 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); @@ -276,12 +307,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); @@ -358,4 +389,79 @@ 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; + + // widen the left padding to leave room for the glyph, then set it over + // the frame in the dim text colour + 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); + } } |
