Skip to content

Commit eccb2d1

Browse files
committed
button label text positioning
1 parent 2e3e397 commit eccb2d1

1 file changed

Lines changed: 107 additions & 6 deletions

File tree

OpenRA.Mods.Common/Widgets/Logic/ButtonTooltipLogic.cs

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@
1010
#endregion
1111

1212
using System;
13+
using System.Reflection;
14+
using OpenRA.Primitives;
1315
using OpenRA.Widgets;
1416

1517
namespace OpenRA.Mods.Common.Widgets.Logic
1618
{
1719
public class ButtonTooltipLogic : ChromeLogic
1820
{
21+
const string ColorTagOpen = "<color=";
22+
const string ColorTagClose = "</color>";
23+
1924
[ObjectCreator.UseCtor]
2025
public ButtonTooltipLogic(Widget widget, ButtonWidget button)
2126
{
2227
var label = widget.Get<LabelWidget>("LABEL");
2328
var font = Game.Renderer.Fonts[label.Font];
24-
var text = button.GetTooltipText();
29+
var text = StripInlineColorTags(button.GetTooltipText() ?? string.Empty);
2530
var labelWidth = font.Measure(text).X;
2631
var key = button.Key.GetValue();
2732

@@ -52,17 +57,113 @@ public ButtonTooltipLogic(Widget widget, ButtonWidget button)
5257
var descOffset = descTemplate.Bounds.Y;
5358
foreach (var line in desc.Split('\n', StringSplitOptions.None))
5459
{
55-
descWidth = Math.Max(descWidth, descFont.Measure(line).X);
56-
var lineLabel = (LabelWidget)descTemplate.Clone();
57-
lineLabel.GetText = () => line;
58-
lineLabel.Bounds.Y = descOffset;
59-
widget.AddChild(lineLabel);
60+
if (TryExtractInlineColor(line, out var prefix, out var highlighted, out var suffix, out var colorToken)
61+
&& TryParseColorToken(colorToken, out var highlightColor))
62+
{
63+
var plainLine = prefix + highlighted + suffix;
64+
descWidth = Math.Max(descWidth, descFont.Measure(plainLine).X);
65+
var segmentX = descTemplate.Bounds.X;
66+
67+
if (!string.IsNullOrEmpty(prefix))
68+
{
69+
var prefixLabel = (LabelWidget)descTemplate.Clone();
70+
prefixLabel.GetText = () => prefix;
71+
prefixLabel.Bounds.X = segmentX;
72+
prefixLabel.Bounds.Y = descOffset;
73+
widget.AddChild(prefixLabel);
74+
segmentX += descFont.Measure(prefix).X;
75+
}
76+
77+
if (!string.IsNullOrEmpty(highlighted))
78+
{
79+
var highlightedLabel = (LabelWidget)descTemplate.Clone();
80+
highlightedLabel.GetText = () => highlighted;
81+
highlightedLabel.GetColor = () => highlightColor;
82+
highlightedLabel.Bounds.X = segmentX;
83+
highlightedLabel.Bounds.Y = descOffset;
84+
widget.AddChild(highlightedLabel);
85+
segmentX += descFont.Measure(highlighted).X;
86+
}
87+
88+
if (!string.IsNullOrEmpty(suffix))
89+
{
90+
var suffixLabel = (LabelWidget)descTemplate.Clone();
91+
suffixLabel.GetText = () => suffix;
92+
suffixLabel.Bounds.X = segmentX;
93+
suffixLabel.Bounds.Y = descOffset;
94+
widget.AddChild(suffixLabel);
95+
}
96+
}
97+
else
98+
{
99+
var plainLine = StripInlineColorTags(line);
100+
descWidth = Math.Max(descWidth, descFont.Measure(plainLine).X);
101+
var lineLabel = (LabelWidget)descTemplate.Clone();
102+
lineLabel.GetText = () => plainLine;
103+
lineLabel.Bounds.Y = descOffset;
104+
widget.AddChild(lineLabel);
105+
}
106+
60107
descOffset += descTemplate.Bounds.Height;
61108
}
62109

63110
widget.Bounds.Width = Math.Max(widget.Bounds.Width, descTemplate.Bounds.X * 2 + descWidth);
64111
widget.Bounds.Height += descOffset - descTemplate.Bounds.Y + descTemplate.Bounds.X;
65112
}
66113
}
114+
115+
static string StripInlineColorTags(string input)
116+
{
117+
if (TryExtractInlineColor(input, out var prefix, out var highlighted, out var suffix, out _))
118+
return prefix + highlighted + suffix;
119+
120+
return input;
121+
}
122+
123+
static bool TryExtractInlineColor(string input, out string prefix, out string highlighted, out string suffix, out string colorToken)
124+
{
125+
prefix = highlighted = suffix = colorToken = null;
126+
if (string.IsNullOrEmpty(input))
127+
return false;
128+
129+
var openStart = input.IndexOf(ColorTagOpen, StringComparison.OrdinalIgnoreCase);
130+
if (openStart < 0)
131+
return false;
132+
133+
var openEnd = input.IndexOf('>', openStart + ColorTagOpen.Length);
134+
if (openEnd < 0)
135+
return false;
136+
137+
var closeStart = input.IndexOf(ColorTagClose, openEnd + 1, StringComparison.OrdinalIgnoreCase);
138+
if (closeStart < 0)
139+
return false;
140+
141+
prefix = input[..openStart];
142+
colorToken = input[(openStart + ColorTagOpen.Length)..openEnd].Trim();
143+
highlighted = input[(openEnd + 1)..closeStart];
144+
suffix = input[(closeStart + ColorTagClose.Length)..];
145+
return true;
146+
}
147+
148+
static bool TryParseColorToken(string token, out Color color)
149+
{
150+
color = default;
151+
if (string.IsNullOrWhiteSpace(token))
152+
return false;
153+
154+
var normalized = token.Trim();
155+
if (normalized.StartsWith("#", StringComparison.Ordinal))
156+
normalized = normalized[1..];
157+
158+
if (Color.TryParse(normalized, out color))
159+
return true;
160+
161+
var property = typeof(Color).GetProperty(normalized, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase);
162+
if (property == null || property.PropertyType != typeof(Color))
163+
return false;
164+
165+
color = (Color)property.GetValue(null);
166+
return true;
167+
}
67168
}
68169
}

0 commit comments

Comments
 (0)