From 31ad501db17eebcc8e38d0ca575b5d294938a688 Mon Sep 17 00:00:00 2001 From: Lightningbulb2 Date: Thu, 7 May 2026 21:58:15 -0400 Subject: [PATCH 1/5] Implement text truncation support and improve scoreboard legibility --- loc/US/strings_db.lua | 4 ++ lua/maui/text.lua | 64 +++++++++++++++++++++++++++++ lua/ui/game/layouts/score_mini.lua | 4 ++ lua/ui/game/score.lua | 65 +++++++++++++++++++++++++----- lua/ui/help/tooltips.lua | 8 ++++ 5 files changed, 135 insertions(+), 10 deletions(-) diff --git a/loc/US/strings_db.lua b/loc/US/strings_db.lua index a3e4e249ebf..d890fe7e3a3 100644 --- a/loc/US/strings_db.lua +++ b/loc/US/strings_db.lua @@ -5951,6 +5951,10 @@ tooltipui0734="No Vote" tooltipui0735="Vote no to your team recalling from battle as a defeat." tooltipui0736="[Hide/Show] Mass Fabricator Panel" tooltipui0737="[Hide/Show] Voting Panel" +tooltipui0738="Game Speed" +tooltipui0739="[your requested speed] / [actual game speed]" +tooltipui0740="Game Quality" +tooltipui0741="Estimated game quality" -- External Factory and Auto-Deploy tooltips tooltipui0750 = "Mobile Factory" diff --git a/lua/maui/text.lua b/lua/maui/text.lua index 4dd34d0c455..8a0cc896525 100644 --- a/lua/maui/text.lua +++ b/lua/maui/text.lua @@ -33,6 +33,34 @@ Text = ClassUI(moho.text_methods, Control) { self._color.OnDirty = function(var) self:SetNewColor(var()) end + self._truncationText = "" + self._fullText = nil + + + -- Direct Engine SetText() that changes what text is displayed + ---@type function + self.SetDisplayText = self.SetText + + + -- FAF extensible SetText() that uses SetDisplayText() but can retain it's original text for fancy text display setups like truncation + self.SetText = function (self, text) + self.SetDisplayText(self,text) + self._fullText = text + if self._truncationText ~= nil and self._truncationText ~= "" then + if self._initialized then + self:_applyTruncation() + end + end + end + + -- Direct Engine GetText() for getting the current displayed value + ---@type function + self.GetDisplayText = self.GetText + + -- FAF extensible GetText() that retrieves raw original text that isn't modified for display + self.GetText = function (self) + return self._fullText + end end, OnInit = function(self) @@ -41,15 +69,51 @@ Text = ClassUI(moho.text_methods, Control) { self:SetClipToWidth(false) end, + -- Sets custom truncation trailing characters like "..." or "-". Set to "" to disable. + ---@param text string | number + SetTruncationText = function (self, text) + self._truncationText = tostring(text) + end, + SetClipToWidth = function(self, clipToWidth) if clipToWidth then self.Width:Set(function() return self.Right() - self.Left() end) + + self.Width.OnDirty = function() + if self._truncationText ~= nil and self._truncationText ~= "" then + self:_applyTruncation() + end + end else self.Width:Set(function() return math.floor(self.TextAdvance()) end) + self.Width.OnDirty = nil end self:SetNewClipToWidth(clipToWidth) end, + _applyTruncation = function(self) + local maxWidth = self.Width() + if maxWidth <= 0 then + return + end + + -- ellipsis is the trailing '...' on truncated text + local ellipsis = self._truncationText + local str = self._fullText + if self:GetStringAdvance(str) <= maxWidth then + return + end + + --iterate until string + ellipsis fit + local i = string.len(str) + while i > 0 and self:GetStringAdvance(str .. ellipsis) > maxWidth do + str = str:sub(1, -2) + i = i - 1 + end + + self.SetDisplayText(self, str .. ellipsis) + end, + -- lazy var support SetFont = function(self, family, pointsize) if self._font then diff --git a/lua/ui/game/layouts/score_mini.lua b/lua/ui/game/layouts/score_mini.lua index 422e50ae299..fc710b56c60 100644 --- a/lua/ui/game/layouts/score_mini.lua +++ b/lua/ui/game/layouts/score_mini.lua @@ -66,6 +66,10 @@ function SetLayout() controls.timeIcon:SetTexture(UIUtil.UIFile('/game/unit_view_icons/time.dds')) LayoutHelpers.RightOf(controls.time, controls.timeIcon) + LayoutHelpers.RightOf(controls.gameSpeed, controls.time, 4) + + LayoutHelpers.AtLeftTopIn(controls.gameQuality, controls.bgTop, 138, 6) + LayoutHelpers.AtRightTopIn(controls.unitIcon, controls.bgTop, 10, 6) controls.unitIcon:SetTexture(UIUtil.UIFile('/dialogs/score-overlay/tank_bmp.dds')) LayoutHelpers.LeftOf(controls.units, controls.unitIcon) diff --git a/lua/ui/game/score.lua b/lua/ui/game/score.lua index 3455a7b79c5..c12a31aa3a1 100644 --- a/lua/ui/game/score.lua +++ b/lua/ui/game/score.lua @@ -67,10 +67,15 @@ function updatePlayerName(line) end if sessionInfo.Options.Divisions then - line.name:SetText(playerClan .. playerName .. playerDivision) + line.division:SetText(playerDivision) else - line.name:SetText(playerClan .. playerName .. playerRating) + line.division:SetText(playerRating) end + + line.name:SetTruncationText("...") + line.name:SetText(playerClan .. playerName) + line.name:SetDropShadow(true) + LayoutHelpers.AnchorToLeft(line.name, line.division) end function armyGroupHeight() @@ -118,14 +123,20 @@ function CreateScoreUI(parent) LayoutHelpers.SetWidth(controls.bgTop, 320) - controls.time = UIUtil.CreateText(controls.bgTop, '0', 12, UIUtil.bodyFont) + controls.time = UIUtil.CreateText(controls.bgTop, '0', 12, UIUtil.bodyFont, true) controls.time:SetColor('ff00dbff') controls.timeIcon = Bitmap(controls.bgTop) Tooltip.AddControlTooltip(controls.timeIcon, 'score_time') Tooltip.AddControlTooltip(controls.time, 'score_time') + controls.gameSpeed = UIUtil.CreateText(controls.bgTop, '', 12, UIUtil.bodyFont, true) + controls.gameSpeed:SetColor('BADAFF') + Tooltip.AddControlTooltip(controls.gameSpeed, 'score_game_speed') + controls.gameQuality = UIUtil.CreateText(controls.bgTop, '', 12, UIUtil.bodyFont, true) + controls.gameQuality:SetColor('ff00dbff') + Tooltip.AddControlTooltip(controls.gameQuality, 'score_game_quality') controls.unitIcon = Bitmap(controls.bgTop) Tooltip.AddControlTooltip(controls.unitIcon, 'score_units') - controls.units = UIUtil.CreateText(controls.bgTop, '0', 12, UIUtil.bodyFont) + controls.units = UIUtil.CreateText(controls.bgTop, '0', 12, UIUtil.bodyFont, true) controls.units:SetColor('ffff9900') Tooltip.AddControlTooltip(controls.units, 'score_units') @@ -302,6 +313,12 @@ function SetupPlayerLines() LayoutHelpers.AtVerticalCenterIn(group.name, group) group.name:SetColor('ffffffff') + group.division = UIUtil.CreateText(group, '', 12, UIUtil.bodyFont, true) + group.division:DisableHitTest() + LayoutHelpers.AtRightIn(group.division, group, 135) + LayoutHelpers.AtVerticalCenterIn(group.division, group) + group.division:SetColor('ffffffff') + group.score = UIUtil.CreateText(group, '', 12, UIUtil.bodyFont) group.score:DisableHitTest() LayoutHelpers.AtRightIn(group.score, group, sw * 2 + 16) @@ -372,6 +389,18 @@ function SetupPlayerLines() Tooltip.AddControlTooltip(group.units, {text = '', body = bodyText}, 1) end + -- hover to see full name (incase they are cut off) + group.nameHover = Bitmap(group) + group.nameHover:SetSolidColor('00000000') -- fully transparent + LayoutHelpers.AnchorToLeft(group.nameHover, group.division) + LayoutHelpers.AtLeftIn(group.nameHover, group, 12) + group.nameHover.Height:Set(group.name.Height) + LayoutHelpers.AtVerticalCenterIn(group.nameHover, group) + Tooltip.AddAutoUpdatedControlTooltip(group.nameHover, + function() return group.name:GetText() or "" end, + function() return "" end, + 0.5) + group.Height:Set(group.faction.Height) group.Width:Set(controls.armyGroup.Width) group.armyID = armyIndex @@ -416,6 +445,7 @@ function SetupPlayerLines() observerLine:Hide() observerLine.OnHide = blockOnHide observerLine.name.Top:Set(observerLine.Top) + observerLine.name:SetDropShadow(true) LayoutHelpers.SetHeight(observerLine, 15) if SessionIsReplay() then @@ -476,7 +506,7 @@ function SetupPlayerLines() end -- ui for share conditions - group.ShareConditions = UIUtil.CreateText(group, data.ShareConditionsTitle, 10, UIUtil.bodyFont) + group.ShareConditions = UIUtil.CreateText(group, data.ShareConditionsTitle, 10, UIUtil.bodyFont, true) Tooltip.AddForcedControlTooltipManual(group.ShareConditions, data.ShareConditionsTitle, data.ShareConditionsDescription) LayoutHelpers.AtLeftIn(group.ShareConditions, group) LayoutHelpers.AtVerticalCenterIn(group.ShareConditions, group) @@ -485,7 +515,7 @@ function SetupPlayerLines() previous = AddDash() -- ui for map size - group.Size = UIUtil.CreateText(group, data.SizeText, 10, UIUtil.bodyFont) + group.Size = UIUtil.CreateText(group, data.SizeText, 10, UIUtil.bodyFont, true) LayoutHelpers.RightOf(group.Size, previous) LayoutHelpers.AtVerticalCenterIn(group.Size, group) group.Size:SetColor('ffffffff') @@ -493,7 +523,7 @@ function SetupPlayerLines() previous = AddDash() -- ui for map name - group.MapName = UIUtil.CreateText(group, data.MapTitle, 10, UIUtil.bodyFont) + group.MapName = UIUtil.CreateText(group, data.MapTitle, 10, UIUtil.bodyFont, true) Tooltip.AddForcedControlTooltipManual(group.MapName, data.MapTitle, data.MapDescription) LayoutHelpers.RightOf(group.MapName, previous) LayoutHelpers.AtVerticalCenterIn(group.MapName, group) @@ -618,11 +648,17 @@ local prevPlayableWidth = sessionInfo.PlayableAreaWidth local prevPlayableHeight = sessionInfo.PlayableAreaHeight function _OnBeat() - local s = string.format("%s (%+d / %+d)", GetGameTime(), gameSpeed, GetSimRate()) + local t = string.format("%s", GetGameTime()) + controls.time:SetText(t) + + local s = string.format("(%+d / %+d)",gameSpeed, GetSimRate()) + controls.gameSpeed:SetText(s) + if sessionInfo.Options.Quality then - s = string.format("%s Q:%.2f%%", s, sessionInfo.Options.Quality) + q = string.format("Q:%.2f%%", sessionInfo.Options.Quality) + controls.gameQuality:SetText(q) end - controls.time:SetText(s) + if sessionInfo.Options.NoRushOption and sessionInfo.Options.NoRushOption ~= 'Off' then local norush = tonumber(sessionInfo.Options.NoRushOption) * 60 @@ -717,26 +753,35 @@ function _OnBeat() if line.armyID == prevArmy then if line.OOG then line.name:SetColor('ffa0a0a0') + line.division:SetColor('ffa0a0a0') line.score:SetColor('ffa0a0a0') else line.name:SetColor('ffffffff') + line.division:SetColor('ffffffff') line.score:SetColor('ffffffff') end line.name:SetFont(UIUtil.bodyFont, 12) + line.division:SetFont(UIUtil.bodyFont, 12) line.score:SetFont(UIUtil.bodyFont, 12) elseif line.armyID == curFA then line.name:SetColor('ffff7f00') + line.division:SetColor('ffff7f00') line.score:SetColor('ffff7f00') + line.name:SetFont('Arial Bold', 12) + line.division:SetFont('Arial Bold', 12) line.score:SetFont('Arial Bold', 12) end end if curFA < 1 then observerLine.name:SetColor('ffff7f00') + observerLine.division:SetColor('ffff7f00') observerLine.name:SetFont('Arial Bold', 12) elseif prevArmy < 1 then observerLine.name:SetColor('ffffffff') + observerLine.division:SetColor('ffffffff') observerLine.name:SetFont(UIUtil.bodyFont, 12) + observerLine.division:SetFont(UIUtil.bodyFont, 12) end if observerLine:IsHidden() and ((curFA < 1) or (sessionInfo.Options.CheatsEnabled == 'true')) then table.insert(controls.armyLines, table.getsize(controls.armyLines), observerLine) diff --git a/lua/ui/help/tooltips.lua b/lua/ui/help/tooltips.lua index a6877f33a2d..66cfae46cca 100644 --- a/lua/ui/help/tooltips.lua +++ b/lua/ui/help/tooltips.lua @@ -1808,6 +1808,14 @@ Tooltips = { title = "Game Time", description = "", }, + score_game_speed ={ + title = "Game Speed", + description = "[your requested speed] / [actual game speed]", + }, + score_game_quality = { + title = "Game Quality", + description = "Estimated game quality", + }, score_units = { title = "Unit Count", description = "Current and maximum unit counts", From 16066fb75705be8ff14d45a7248be20923187f9e Mon Sep 17 00:00:00 2001 From: Lightningbulb2 Date: Thu, 7 May 2026 23:18:36 -0400 Subject: [PATCH 2/5] update annotations --- lua/maui/text.lua | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lua/maui/text.lua b/lua/maui/text.lua index 8a0cc896525..7ad2c0480ce 100644 --- a/lua/maui/text.lua +++ b/lua/maui/text.lua @@ -37,12 +37,14 @@ Text = ClassUI(moho.text_methods, Control) { self._fullText = nil - -- Direct Engine SetText() that changes what text is displayed + --- Direct Engine SetText() that changes what text is displayed ---@type function + ---@param str string | number self.SetDisplayText = self.SetText - -- FAF extensible SetText() that uses SetDisplayText() but can retain it's original text for fancy text display setups like truncation + --- FAF extensible SetText() that uses SetDisplayText() but can retain it's original text for fancy text display setups like truncation + ---@param text string | number self.SetText = function (self, text) self.SetDisplayText(self,text) self._fullText = text @@ -53,11 +55,13 @@ Text = ClassUI(moho.text_methods, Control) { end end - -- Direct Engine GetText() for getting the current displayed value + --- Direct Engine GetText() for getting the current displayed value ---@type function + ---@return string self.GetDisplayText = self.GetText - -- FAF extensible GetText() that retrieves raw original text that isn't modified for display + --- FAF extensible GetText() that retrieves raw original text that isn't modified for display + ---@return string self.GetText = function (self) return self._fullText end @@ -69,7 +73,7 @@ Text = ClassUI(moho.text_methods, Control) { self:SetClipToWidth(false) end, - -- Sets custom truncation trailing characters like "..." or "-". Set to "" to disable. + --- Sets custom truncation trailing characters like "..." or "-". Set to "" to disable. ---@param text string | number SetTruncationText = function (self, text) self._truncationText = tostring(text) @@ -91,6 +95,7 @@ Text = ClassUI(moho.text_methods, Control) { self:SetNewClipToWidth(clipToWidth) end, + --- Internal function to fit the truncation string inside the max width _applyTruncation = function(self) local maxWidth = self.Width() if maxWidth <= 0 then From 5eb1c4d46851d381867668fea80aa91161b34dff Mon Sep 17 00:00:00 2001 From: Lightningbulb2 Date: Fri, 8 May 2026 00:00:26 -0400 Subject: [PATCH 3/5] add changelog snippet --- changelog/snippets/graphics.7105.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 changelog/snippets/graphics.7105.md diff --git a/changelog/snippets/graphics.7105.md b/changelog/snippets/graphics.7105.md new file mode 100644 index 00000000000..02ca0366fa6 --- /dev/null +++ b/changelog/snippets/graphics.7105.md @@ -0,0 +1,11 @@ +- (#7105) Improve scoreboard legibility and internal text functions for mods/devs + + #### UI + + Improve scoreboard legibility with better alignment, tooltips, dropshadows, and text cropping. + + #### Modding/development + + added SetTruncationText() to assign custom trailing characters like "..." when text is cropped + + split SetText() into **SetText()** and its implicitly called **SetDisplayText()**. This allows for fancier text implementations like custom truncation. From 3653d2632f6be0d37aa703f3392b858bf4b2a345 Mon Sep 17 00:00:00 2001 From: Lightningbulb2 Date: Fri, 8 May 2026 00:34:03 -0400 Subject: [PATCH 4/5] code rabbit suggestions --- lua/maui/text.lua | 8 ++++++-- lua/ui/game/score.lua | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lua/maui/text.lua b/lua/maui/text.lua index 7ad2c0480ce..c1355f4f3f0 100644 --- a/lua/maui/text.lua +++ b/lua/maui/text.lua @@ -77,6 +77,9 @@ Text = ClassUI(moho.text_methods, Control) { ---@param text string | number SetTruncationText = function (self, text) self._truncationText = tostring(text) + if self._fullText ~= nil and self._initialized then + self:_applyTruncation() + end end, SetClipToWidth = function(self, clipToWidth) @@ -105,14 +108,15 @@ Text = ClassUI(moho.text_methods, Control) { -- ellipsis is the trailing '...' on truncated text local ellipsis = self._truncationText local str = self._fullText + if str == nil then return end if self:GetStringAdvance(str) <= maxWidth then return end --iterate until string + ellipsis fit - local i = string.len(str) + local i = STR_Utf8Len(str) while i > 0 and self:GetStringAdvance(str .. ellipsis) > maxWidth do - str = str:sub(1, -2) + str = STR_Utf8SubString(str, 1, i - 1) i = i - 1 end diff --git a/lua/ui/game/score.lua b/lua/ui/game/score.lua index c12a31aa3a1..790019d9689 100644 --- a/lua/ui/game/score.lua +++ b/lua/ui/game/score.lua @@ -129,7 +129,7 @@ function CreateScoreUI(parent) Tooltip.AddControlTooltip(controls.timeIcon, 'score_time') Tooltip.AddControlTooltip(controls.time, 'score_time') controls.gameSpeed = UIUtil.CreateText(controls.bgTop, '', 12, UIUtil.bodyFont, true) - controls.gameSpeed:SetColor('BADAFF') + controls.gameSpeed:SetColor('ffbadaff') Tooltip.AddControlTooltip(controls.gameSpeed, 'score_game_speed') controls.gameQuality = UIUtil.CreateText(controls.bgTop, '', 12, UIUtil.bodyFont, true) controls.gameQuality:SetColor('ff00dbff') @@ -655,7 +655,7 @@ function _OnBeat() controls.gameSpeed:SetText(s) if sessionInfo.Options.Quality then - q = string.format("Q:%.2f%%", sessionInfo.Options.Quality) + local q = string.format("Q:%.2f%%", sessionInfo.Options.Quality) controls.gameQuality:SetText(q) end From eb39955ec43dfe595dec6d73391e2989f4eb9e11 Mon Sep 17 00:00:00 2001 From: Lightningbulb2 Date: Fri, 8 May 2026 00:39:31 -0400 Subject: [PATCH 5/5] restore text on resize fix --- lua/maui/text.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/maui/text.lua b/lua/maui/text.lua index c1355f4f3f0..19e3978fd6a 100644 --- a/lua/maui/text.lua +++ b/lua/maui/text.lua @@ -109,7 +109,9 @@ Text = ClassUI(moho.text_methods, Control) { local ellipsis = self._truncationText local str = self._fullText if str == nil then return end + -- restore full text if it now fits if self:GetStringAdvance(str) <= maxWidth then + self.SetDisplayText(self, str) return end