From 94a65a1ff7ff21652ffff7084ddddd95be00a438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 5 Nov 2026 18:20:33 +0100 Subject: [PATCH] UI: Let RadioButton show a hint, like CheckBox RadioButton is now a ClickableItem, so it fills its parent's width by default and draws its hint text below the title, lined up with the text after the radio circle. Co-Authored-By: Claude Opus 5.5 --- Common/UI/View.cpp | 27 ++++++++++++++++--------- Common/UI/View.h | 49 +++++++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index de6abe7232..c1d81ad9bb 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -470,7 +470,7 @@ void ClickableItem::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec if (hintText_.empty()) { return; } - float availWidth = measuredWidth_ - HINT_PADDING_X * 2; + float availWidth = measuredWidth_ - HintIndent() - HINT_PADDING_X; if (availWidth <= 0.0f) { availWidth = MAX_ITEM_SIZE; } @@ -487,11 +487,16 @@ void ClickableItem::DrawHint(UIContext &dc, const Style &style) const { dc.SetFontStyle(dc.GetTheme().uiFontTiny); dc.SetFontScale(1.0f, 1.0f); const float textHeight = hintHeight_ - HINT_PADDING_BOTTOM + HINT_TITLE_OVERLAP; - Bounds hintBounds(bounds_.x + HINT_PADDING_X, bounds_.y2() - HINT_PADDING_BOTTOM - textHeight, bounds_.w - HINT_PADDING_X * 2, textHeight); + const float indent = HintIndent(); + Bounds hintBounds(bounds_.x + indent, bounds_.y2() - HINT_PADDING_BOTTOM - textHeight, bounds_.w - indent - HINT_PADDING_X, textHeight); dc.DrawTextRect(hintText_, hintBounds, style.fgColor, ALIGN_TOPLEFT | FLAG_WRAP_TEXT); dc.SetFontStyle(dc.GetTheme().uiFont); } +float ClickableItem::HintIndent() const { + return HINT_PADDING_X; +} + void Choice::ClickInternal() { ClickableItem::ClickInternal(); UI::PlayUISound(UI::UISound::CONFIRM); @@ -1097,11 +1102,13 @@ void RadioButton::Draw(UIContext &dc) { DrawBG(dc, style, dc.GetTheme().choiceRoundRadius); + const Bounds bounds = ContentBounds(); + dc.Flush(); dc.BeginNoTex(); - dc.Draw()->Circle(bounds_.x + paddingW_ + radioRadius_, bounds_.centerY(), radioRadius_, 2.5f, 36, 0, style.fgColor, 1.0f); + dc.Draw()->Circle(bounds.x + paddingW_ + radioRadius_, bounds.centerY(), radioRadius_, 2.5f, 36, 0, style.fgColor, 1.0f); if (checked) { - dc.Draw()->FillCircle(bounds_.x + paddingW_ + radioRadius_, bounds_.centerY(), radioInnerRadius_, 36, style.fgColor); + dc.Draw()->FillCircle(bounds.x + paddingW_ + radioRadius_, bounds.centerY(), radioInnerRadius_, 36, style.fgColor); } dc.Flush(); dc.Begin(); @@ -1109,20 +1116,22 @@ void RadioButton::Draw(UIContext &dc) { float tw, th; dc.MeasureText(dc.GetTheme().uiFont, 1.0f, 1.0f, text_, &tw, &th); - if (tw > bounds_.w) { - dc.PushScissor(bounds_); + if (tw > bounds.w) { + dc.PushScissor(bounds); } dc.SetFontStyle(dc.GetTheme().uiFont); if (!text_.empty()) { - float textX = bounds_.x + paddingW_ * 2.0f + radioRadius_ * 2.0f; - dc.DrawText(text_, textX, bounds_.centerY(), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER); + float textX = bounds.x + paddingW_ * 2.0f + radioRadius_ * 2.0f; + dc.DrawText(text_, textX, bounds.centerY(), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER); } - if (tw > bounds_.w) { + if (tw > bounds.w) { dc.PopScissor(); } + + DrawHint(dc, style); } ImageView::ImageView(ImageID atlasImage, const std::string &text, LayoutParams *layoutParams) diff --git a/Common/UI/View.h b/Common/UI/View.h index 9a2911f898..81a7afd601 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -564,7 +564,6 @@ public: bool CanBeFocused() const override { return false; } }; - // All these light up their background when touched, or have focus. class Clickable : public View { public: @@ -639,26 +638,6 @@ private: bool ignoreText_ = false; }; -class RadioButton : public Clickable { -public: - RadioButton(int *value, int thisButtonValue, std::string_view text, LayoutParams *layoutParams = 0) - : Clickable(layoutParams), value_(value), thisButtonValue_(thisButtonValue), text_(text) {} - void Draw(UIContext &dc) override; - void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; - std::string DescribeText() const override; - -private: - void ClickInternal() override; - int *value_; - int thisButtonValue_; - std::string text_; - const float paddingW_ = 8; - const float paddingH_ = 4; - - const float radioRadius_ = 16.0f; - const float radioInnerRadius_ = 8.0f; -}; - class Slider : public Clickable { public: Slider(int *value, int minValue, int maxValue, int step = 1, LayoutParams *layoutParams = nullptr) @@ -768,7 +747,7 @@ public: void Draw(UIContext &dc) override; // Small explanatory text under the item, part of it (highlighted and clicked along with it). - // Drawn by Choice and CheckBox. + // Drawn by Choice and CheckBox and RadioButton. void SetHintText(std::string_view text) { hintText_ = text; } @@ -784,6 +763,9 @@ protected: return b; } void DrawHint(UIContext &dc, const Style &style) const; + // Where the hint starts, from the item's left edge. Items whose text doesn't start at the + // default padding override this, to line the hint up with it. + virtual float HintIndent() const; std::string hintText_; float hintHeight_ = 0.0f; @@ -1013,6 +995,29 @@ protected: ImageID imageID_; }; +class RadioButton : public ClickableItem { +public: + RadioButton(int *value, int thisButtonValue, std::string_view text, LayoutParams *layoutParams = nullptr) + : ClickableItem(layoutParams), value_(value), thisButtonValue_(thisButtonValue), text_(text) {} + void Draw(UIContext &dc) override; + void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; + std::string DescribeText() const override; + +protected: + float HintIndent() const override { return paddingW_ * 2.0f + radioRadius_ * 2.0f; } + +private: + void ClickInternal() override; + int *value_; + int thisButtonValue_; + std::string text_; + const float paddingW_ = 8; + const float paddingH_ = 4; + + const float radioRadius_ = 16.0f; + const float radioInnerRadius_ = 8.0f; +}; + class CollapsibleHeader : public CheckBox { public: CollapsibleHeader(bool *open, std::string_view text, LayoutParams *layoutParams = nullptr);