diff --git a/Yafc.Model/Math/Bits.cs b/Yafc.Model/Math/Bits.cs index 149d4152..fd5b8028 100644 --- a/Yafc.Model/Math/Bits.cs +++ b/Yafc.Model/Math/Bits.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Numerics; namespace Yafc.Model; @@ -333,4 +334,6 @@ public override readonly string ToString() { return bitsString.ToString(); } + + public readonly int PopCount() => data?.Sum(BitOperations.PopCount) ?? 0; } diff --git a/Yafc.Model/Model/Project.cs b/Yafc.Model/Model/Project.cs index 68d4f075..57fca715 100644 --- a/Yafc.Model/Model/Project.cs +++ b/Yafc.Model/Model/Project.cs @@ -260,6 +260,8 @@ public class ProjectPreferences(Project owner) : ModelObject(owner) { /// The scale to use when drawing icons that have information stored in their background color, stored as a ratio from 0 to 1. /// public float iconScale { get; set; } = .9f; + /// The maximum number of milestone icons in each line when drawing tooltip headers. + public int maxMilestonesPerTooltipLine { get; set; } = 28; public bool showMilestoneOnInaccessible { get; set; } = true; protected internal override void AfterDeserialize() { diff --git a/Yafc/Widgets/ObjectTooltip.cs b/Yafc/Widgets/ObjectTooltip.cs index 60896f71..34e12a45 100644 --- a/Yafc/Widgets/ObjectTooltip.cs +++ b/Yafc/Widgets/ObjectTooltip.cs @@ -45,19 +45,43 @@ private void BuildHeader(ImGui gui) { gui.BuildText(name, new TextBlockDisplayStyle(Font.header, true)); var milestoneMask = Milestones.Instance.GetMilestoneResult(target.target); if (milestoneMask.HighestBitSet() > 0 && (target.target.IsAccessible() || Project.current.preferences.showMilestoneOnInaccessible)) { - float spacing = MathF.Min((22f / Milestones.Instance.currentMilestones.Length) - 1f, 0f); - using (gui.EnterRow(spacing)) { - int maskBit = 1; - foreach (var milestone in Milestones.Instance.currentMilestones) { - if (milestoneMask[maskBit]) { - gui.BuildIcon(milestone.icon, 1f, SchemeColor.Source); - } + int milestoneCount = milestoneMask.PopCount(); + if (milestoneMask[0]) { + milestoneCount--; // Bit 0 is accessibility, not a milestone flag. + } - maskBit++; + // All rows except the last will show at least 22 milestones. + // If displaying more items per row (up to the user's limit) reduces the number of rows, squish the milestones together slightly. + int maxItemsPerRow = Project.current.preferences.maxMilestonesPerTooltipLine; + const int minItemsPerRow = 22; + int rows = (milestoneCount + maxItemsPerRow - 1) / maxItemsPerRow; + int itemsPerRow = Math.Max((milestoneCount + rows - 1) / rows, minItemsPerRow); + // 22.5 is the width of the available area of the tooltip. The original code used spacings from -1 (100% overlap) to 0 + // (no overlap). At the default max of 28 per row, we allow spacings of -0.196 (19.6% overlap) to 0.023 (2.3% stretch). + float spacing = 22.5f / itemsPerRow - 1f; + + using var milestones = Milestones.Instance.currentMilestones.AsEnumerable().GetEnumerator(); + int maskBit = 1; + for (int i = 0; i < rows; i++) { + using (gui.EnterRow(spacing)) { + // Draw itemsPerRow items, then exit this row and allocate a new one + for (int j = 0; j < itemsPerRow; /* increment after drawing a milestone */) { + if (!milestones.MoveNext()) { + goto doneDrawing; + } + if (milestoneMask[maskBit]) { + gui.BuildIcon(milestones.Current.icon, 1f, SchemeColor.Source); + j++; + } + + maskBit++; + } } } +doneDrawing:; } } + if (gui.isBuilding) { gui.DrawRectangle(gui.lastRect, SchemeColor.Primary); } diff --git a/Yafc/Windows/PreferencesScreen.cs b/Yafc/Windows/PreferencesScreen.cs index b5f77c23..9c1ef7cc 100644 --- a/Yafc/Windows/PreferencesScreen.cs +++ b/Yafc/Windows/PreferencesScreen.cs @@ -143,6 +143,20 @@ private static void DrawGeneral(ImGui gui) { } } + // Don't show this preference if it isn't relevant. + // (Takes ~3ms for pY, which would concern me in the regular UI, but should be fine here.) + if (Database.objects.all.Any(o => Milestones.Instance.GetMilestoneResult(o).PopCount() > 22)) { + string overlapMessage = "Some tooltips may want to show multiple rows of milestones. Increasing this number will draw fewer lines in some tooltips, by forcing the milestones to overlap.\n\n" + + "Minimum: 22\nDefault: 28"; + using (gui.EnterRowWithHelpIcon(overlapMessage)) { + gui.BuildText("Maximum milestones per line in tooltips:", topOffset: 0.5f); + if (gui.BuildIntegerInput(preferences.maxMilestonesPerTooltipLine, out int newIntValue) && newIntValue >= 22) { + preferences.RecordUndo().maxMilestonesPerTooltipLine = newIntValue; + gui.Rebuild(); + } + } + } + using (gui.EnterRow()) { gui.BuildText("Reactor layout:", topOffset: 0.5f); if (gui.BuildTextInput(settings.reactorSizeX + "x" + settings.reactorSizeY, out string newSize, null, delayed: true)) { diff --git a/changelog.txt b/changelog.txt index 50bfdb81..ceb8643b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -21,6 +21,7 @@ Date: - Add dependency information for tree and resource spawns, fluid pumping, and asteroid mining. - (SA) Locations (except nauvis) are now part of the default milestone list. - Milestone overlays can be displayed on inaccessible objects. + - With 22+ milestones, tooltip headers don't draw them unnecessarily overlapped, and can use multiple lines. Internal changes: - Dependency and automation analysis allows more ORs, e.g. "(spawner and capture-ammo) or item-to-place". ----------------------------------------------------------------------------------------------------------------------