]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Various item status fixes/tweaks (#27267)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Wed, 24 Apr 2024 14:01:31 +0000 (16:01 +0200)
committerGitHub <noreply@github.com>
Wed, 24 Apr 2024 14:01:31 +0000 (00:01 +1000)
* Always display item status panel fully

Initial feedback from the UI changes seems to be that a lot of people go "why is there empty space" so let's fix that.

* Fix item status middle hand being on the wrong side

I think I switched this around when fixing the left/right being inverted in the UI code.

* Minor status panel UI tweaks

Bottom-align contents now that the panel itself doesn't dynamically expand, prevent weird gaps.

Clip contents for panel

* Fix clipping on implanters and network configurators.

Made them take less space. For implanters the name has to be cut off, which I did by adding a new ClipControl to achieve that in rich text.

* Update visibility of item status panels based on whether you have hands at all.

This avoids UI for borgs looking silly.

Added a new "HandUILocation" enum that doesn't have middle hands to avoid confusion in UI code.

* Use BulletRender for laser guns too.

Provides all the benefits like fixing layout overflow and allowing multi-line stuff. Looks great now.

This involved generalizing BulletRender a bit so it can be used for not-just-bullets.

* Fix geiger word wrapping if you're really fucked

16 files changed:
Content.Client/Implants/UI/ImplanterStatusControl.cs
Content.Client/Stylesheets/StyleNano.cs
Content.Client/UserInterface/Controls/ClipControl.cs [new file with mode: 0644]
Content.Client/UserInterface/Systems/Hands/Controls/HandButton.cs
Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml
Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml.cs
Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml
Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs
Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs
Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs
Content.Shared/Hands/Components/HandsComponent.cs
Resources/Locale/en-US/devices/network-configurator.ftl
Resources/Locale/en-US/implant/implant.ftl
Resources/Locale/en-US/inventory/item-status.ftl [new file with mode: 0644]
Resources/Locale/en-US/radiation/geiger-component.ftl

index f3f0cdea7d7505e9d237a602b5ac201fd53e38d9..e2ffabd17d9e214b83437b79972959173fcb7c9b 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Client.Message;
 using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
 using Content.Shared.Implants.Components;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
@@ -17,7 +18,7 @@ public sealed class ImplanterStatusControl : Control
         _parent = parent;
         _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
         _label.MaxWidth = 350;
-        AddChild(_label);
+        AddChild(new ClipControl { Children = { _label } });
 
         Update();
     }
@@ -42,17 +43,12 @@ public sealed class ImplanterStatusControl : Control
             _ => Loc.GetString("injector-invalid-injector-toggle-mode")
         };
 
-        var (implantName, implantDescription) = _parent.ImplanterSlot.HasItem switch
-        {
-            false => (Loc.GetString("implanter-empty-text"), ""),
-            true => (_parent.ImplantData.Item1, _parent.ImplantData.Item2),
-        };
-
+        var implantName = _parent.ImplanterSlot.HasItem
+            ? _parent.ImplantData.Item1
+            : Loc.GetString("implanter-empty-text");
 
         _label.SetMarkup(Loc.GetString("implanter-label",
                 ("implantName", implantName),
-                ("implantDescription", implantDescription),
-                ("modeString", modeStringLocalized),
-                ("lineBreak", "\n")));
+                ("modeString", modeStringLocalized)));
     }
 }
index 5fc17447c37756f33b261ee5d88eaf0135b398b4..8707d70766037026e3df88f895d8460c994b46d9 100644 (file)
@@ -136,6 +136,8 @@ namespace Content.Client.Stylesheets
         public const string StyleClassPowerStateGood = "PowerStateGood";
 
         public const string StyleClassItemStatus = "ItemStatus";
+        public const string StyleClassItemStatusNotHeld = "ItemStatusNotHeld";
+        public static readonly Color ItemStatusNotHeldColor = Color.Gray;
 
         //Background
         public const string StyleClassBackgroundBaseDark = "PanelBackgroundBaseDark";
@@ -1234,6 +1236,11 @@ namespace Content.Client.Stylesheets
                     new StyleProperty("font", notoSans10),
                 }),
 
+                Element()
+                    .Class(StyleClassItemStatusNotHeld)
+                    .Prop("font", notoSansItalic10)
+                    .Prop("font-color", ItemStatusNotHeldColor),
+
                 Element<RichTextLabel>()
                     .Class(StyleClassItemStatus)
                     .Prop(nameof(RichTextLabel.LineHeightScale), 0.7f)
diff --git a/Content.Client/UserInterface/Controls/ClipControl.cs b/Content.Client/UserInterface/Controls/ClipControl.cs
new file mode 100644 (file)
index 0000000..1fca3c0
--- /dev/null
@@ -0,0 +1,55 @@
+using System.Numerics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.UserInterface.Controls;
+
+/// <summary>
+/// Pretends to child controls that there's infinite space.
+/// This can be used to make something like a <see cref="RichTextLabel"/> clip instead of wrapping.
+/// </summary>
+public sealed class ClipControl : Control
+{
+    private bool _clipHorizontal = true;
+    private bool _clipVertical = true;
+
+    public bool ClipHorizontal
+    {
+        get => _clipHorizontal;
+        set
+        {
+            _clipHorizontal = value;
+            InvalidateMeasure();
+        }
+    }
+
+    public bool ClipVertical
+    {
+        get => _clipVertical;
+        set
+        {
+            _clipVertical = value;
+            InvalidateMeasure();
+        }
+    }
+
+    protected override Vector2 MeasureOverride(Vector2 availableSize)
+    {
+        if (ClipHorizontal)
+            availableSize = availableSize with { X = float.PositiveInfinity };
+        if (ClipVertical)
+            availableSize = availableSize with { Y = float.PositiveInfinity };
+
+        return base.MeasureOverride(availableSize);
+    }
+
+    protected override Vector2 ArrangeOverride(Vector2 finalSize)
+    {
+        foreach (var child in Children)
+        {
+            child.Arrange(UIBox2.FromDimensions(Vector2.Zero, child.DesiredSize));
+        }
+
+        return finalSize;
+    }
+}
index 574e0c47075a16216e19fd9a2ab6efcce40b9b2c..d5794f71955d8e10d27a80760df9786dd6cfcaed 100644 (file)
@@ -5,8 +5,11 @@ namespace Content.Client.UserInterface.Systems.Hands.Controls;
 
 public sealed class HandButton : SlotControl
 {
+    public HandLocation HandLocation { get; }
+
     public HandButton(string handName, HandLocation handLocation)
     {
+        HandLocation = handLocation;
         Name = "hand_" + handName;
         SlotName = handName;
         SetBackground(handLocation);
index e57c15462e32f4812f21af28d96129bf2495d75f..99d7bc77b81a00f111c9c6714652d3f655261d52 100644 (file)
@@ -256,7 +256,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
             _player.LocalSession?.AttachedEntity is { } playerEntity &&
             _handsSystem.TryGetHand(playerEntity, handName, out var hand, _playerHandsComponent))
         {
-            if (hand.Location == HandLocation.Left)
+            var foldedLocation = hand.Location.GetUILocation();
+            if (foldedLocation == HandUILocation.Left)
             {
                 _statusHandLeft = handControl;
                 HandsGui.UpdatePanelEntityLeft(hand.HeldEntity);
@@ -268,7 +269,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
                 HandsGui.UpdatePanelEntityRight(hand.HeldEntity);
             }
 
-            HandsGui.SetHighlightHand(hand.Location);
+            HandsGui.SetHighlightHand(foldedLocation);
         }
     }
 
@@ -299,11 +300,13 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
         // If we don't have a status for this hand type yet, set it.
         // This means we have status filled by default in most scenarios,
         // otherwise the user'd need to switch hands to "activate" the hands the first time.
-        if (location == HandLocation.Left)
+        if (location.GetUILocation() == HandUILocation.Left)
             _statusHandLeft ??= button;
         else
             _statusHandRight ??= button;
 
+        UpdateVisibleStatusPanels();
+
         return button;
     }
 
@@ -369,9 +372,30 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
 
         _handLookup.Remove(handName);
         handButton.Dispose();
+        UpdateVisibleStatusPanels();
         return true;
     }
 
+    private void UpdateVisibleStatusPanels()
+    {
+        var leftVisible = false;
+        var rightVisible = false;
+
+        foreach (var hand in _handLookup.Values)
+        {
+            if (hand.HandLocation.GetUILocation() == HandUILocation.Left)
+            {
+                leftVisible = true;
+            }
+            else
+            {
+                rightVisible = true;
+            }
+        }
+
+        HandsGui?.UpdateStatusVisibility(leftVisible, rightVisible);
+    }
+
     public string RegisterHandContainer(HandsContainer handContainer)
     {
         var name = "HandContainer_" + _backupSuffix;
index 3afe11ba330420dffaea3187e937e2b5404847f4..00ba1878b48d4e28b5a5da77ae80b730c842e2d3 100644 (file)
@@ -32,7 +32,7 @@
                 Name="StatusPanelRight"
                 HorizontalAlignment="Center" Margin="0 0 -2 2"
                 SetWidth="125"
-                MaxHeight="60"/>
+                SetHeight="60"/>
             <hands:HandsContainer
                 Name="HandContainer"
                 Access="Public"
@@ -43,7 +43,7 @@
                 Name="StatusPanelLeft"
                 HorizontalAlignment="Center" Margin="-2 0 0 2"
                 SetWidth="125"
-                MaxHeight="60"/>
+                SetHeight="60"/>
             <inventory:ItemSlotButtonContainer
                 Name="MainHotbar"
                 SlotGroup="MainHotbar"
index 923262d4eadc02092076933f4efebfb8c8501398..59d86c84b8536d5b6acd85850e61a2ffcdf8e045 100644 (file)
@@ -11,8 +11,8 @@ public sealed partial class HotbarGui : UIWidget
     public HotbarGui()
     {
         RobustXamlLoader.Load(this);
-        StatusPanelRight.SetSide(HandLocation.Right);
-        StatusPanelLeft.SetSide(HandLocation.Left);
+        StatusPanelRight.SetSide(HandUILocation.Right);
+        StatusPanelLeft.SetSide(HandUILocation.Left);
         var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
 
         hotbarController.Setup(HandContainer, StoragePanel);
@@ -29,9 +29,15 @@ public sealed partial class HotbarGui : UIWidget
         StatusPanelRight.Update(entity);
     }
 
-    public void SetHighlightHand(HandLocation? hand)
+    public void SetHighlightHand(HandUILocation? hand)
     {
-        StatusPanelLeft.UpdateHighlight(hand is HandLocation.Left);
-        StatusPanelRight.UpdateHighlight(hand is HandLocation.Middle or HandLocation.Right);
+        StatusPanelLeft.UpdateHighlight(hand is HandUILocation.Left);
+        StatusPanelRight.UpdateHighlight(hand is HandUILocation.Right);
+    }
+
+    public void UpdateStatusVisibility(bool left, bool right)
+    {
+        StatusPanelLeft.Visible = left;
+        StatusPanelRight.Visible = right;
     }
 }
index 81142d64d28c0a573b6f8a296b121c99f9453891..3b1257b44cd03a79557b02537c357afe7db46fa1 100644 (file)
@@ -4,25 +4,26 @@
     xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
     VerticalAlignment="Bottom"
     HorizontalAlignment="Center">
-    <Control Name="VisWrapper" Visible="False">
-        <PanelContainer Name="Panel">
-            <PanelContainer.PanelOverride>
-                <graphics:StyleBoxTexture
-                    PatchMarginBottom="4"
-                    PatchMarginTop="6"
-                    TextureScale="2 2"
-                    Mode="Tile"/>
-            </PanelContainer.PanelOverride>
-        </PanelContainer>
-        <PanelContainer Name="HighlightPanel">
-            <PanelContainer.PanelOverride>
-                <graphics:StyleBoxTexture PatchMarginBottom="4" PatchMarginTop="6" TextureScale="2 2">
-                </graphics:StyleBoxTexture>
-            </PanelContainer.PanelOverride>
-        </PanelContainer>
-        <BoxContainer Name="Contents" Orientation="Vertical" Margin="0 6 0 4">
-            <BoxContainer Name="StatusContents" Orientation="Vertical" />
-            <Label Name="ItemNameLabel" ClipText="True" StyleClasses="ItemStatus" Align="Left" />
-        </BoxContainer>
-    </Control>
+    <PanelContainer Name="Panel">
+        <PanelContainer.PanelOverride>
+            <graphics:StyleBoxTexture
+                PatchMarginBottom="4"
+                PatchMarginTop="6"
+                TextureScale="2 2"
+                Mode="Tile"/>
+        </PanelContainer.PanelOverride>
+    </PanelContainer>
+    <PanelContainer Name="HighlightPanel">
+        <PanelContainer.PanelOverride>
+            <graphics:StyleBoxTexture PatchMarginBottom="4" PatchMarginTop="6" TextureScale="2 2">
+            </graphics:StyleBoxTexture>
+        </PanelContainer.PanelOverride>
+    </PanelContainer>
+    <BoxContainer Name="Contents" Orientation="Vertical" Margin="0 6 0 4" RectClipContent="True">
+        <BoxContainer Name="StatusContents" Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Bottom" />
+        <Control>
+            <Label Name="NoItemLabel" ClipText="True" StyleClasses="ItemStatusNotHeld" Align="Left" Text="{Loc 'item-status-not-held'}" />
+            <Label Name="ItemNameLabel" ClipText="True" StyleClasses="ItemStatus" Align="Left" Visible="False" />
+        </Control>
+    </BoxContainer>
 </controls:ItemStatusPanel>
index e1fe6ab246c0ba4c610821daac1acce0db69488b..95951fa1b0d8febe5f91129c422ac617970acbfa 100644 (file)
@@ -1,17 +1,13 @@
-using System.Numerics;
 using Content.Client.Items;
-using Content.Client.Resources;
 using Content.Shared.Hands.Components;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Inventory.VirtualItem;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Graphics;
 using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
-using static Content.Client.IoC.StaticIoC;
 
 namespace Content.Client.UserInterface.Systems.Inventory.Controls;
 
@@ -23,17 +19,15 @@ public sealed partial class ItemStatusPanel : Control
     [ViewVariables] private EntityUid? _entity;
 
     // Tracked so we can re-run SetSide() if the theme changes.
-    private HandLocation _side;
+    private HandUILocation _side;
 
     public ItemStatusPanel()
     {
         RobustXamlLoader.Load(this);
         IoCManager.InjectDependencies(this);
-
-        SetSide(HandLocation.Middle);
     }
 
-    public void SetSide(HandLocation location)
+    public void SetSide(HandUILocation location)
     {
         // AN IMPORTANT REMINDER ABOUT THIS CODE:
         // In the UI, the RIGHT hand is on the LEFT on the screen.
@@ -47,15 +41,14 @@ public sealed partial class ItemStatusPanel : Control
 
         switch (location)
         {
-            case HandLocation.Right:
+            case HandUILocation.Right:
                 texture = Theme.ResolveTexture("item_status_right");
                 textureHighlight = Theme.ResolveTexture("item_status_right_highlight");
                 cutOut = StyleBox.Margin.Left;
                 flat = StyleBox.Margin.Right;
                 contentMargin = MarginFromThemeColor("_itemstatus_content_margin_right");
                 break;
-            case HandLocation.Middle:
-            case HandLocation.Left:
+            case HandUILocation.Left:
                 texture = Theme.ResolveTexture("item_status_left");
                 textureHighlight = Theme.ResolveTexture("item_status_left_highlight");
                 cutOut = StyleBox.Margin.Right;
@@ -104,11 +97,14 @@ public sealed partial class ItemStatusPanel : Control
 
     public void Update(EntityUid? entity)
     {
+        ItemNameLabel.Visible = entity != null;
+        NoItemLabel.Visible = entity == null;
+
         if (entity == null)
         {
+            ItemNameLabel.Text = "";
             ClearOldStatus();
             _entity = null;
-            VisWrapper.Visible = false;
             return;
         }
 
@@ -119,8 +115,6 @@ public sealed partial class ItemStatusPanel : Control
 
             UpdateItemName();
         }
-
-        VisWrapper.Visible = true;
     }
 
     public void UpdateHighlight(bool highlight)
index 492fad387209c9ab3f656a34e83227b4aa79c126..8aea5d7ee627c6b72b58f3a05b03060127ef6bac 100644 (file)
@@ -6,40 +6,10 @@ using Robust.Client.UserInterface;
 
 namespace Content.Client.Weapons.Ranged.ItemStatus;
 
-/// <summary>
-/// Renders one or more rows of bullets for item status.
-/// </summary>
-/// <remarks>
-/// This is a custom control to allow complex responsive layout logic.
-/// </remarks>
-public sealed class BulletRender : Control
+public abstract class BaseBulletRenderer : Control
 {
-    private static readonly Color ColorA = Color.FromHex("#b68f0e");
-    private static readonly Color ColorB = Color.FromHex("#d7df60");
-    private static readonly Color ColorGoneA = Color.FromHex("#000000");
-    private static readonly Color ColorGoneB = Color.FromHex("#222222");
-
-    /// <summary>
-    /// Try to ensure there's at least this many bullets on one row.
-    /// </summary>
-    /// <remarks>
-    /// For example, if there are two rows and the second row has only two bullets,
-    /// we "steal" some bullets from the row below it to make it look nicer.
-    /// </remarks>
-    public const int MinCountPerRow = 7;
-
-    public const int BulletHeight = 12;
-    public const int BulletSeparationNormal = 3;
-    public const int BulletSeparationTiny = 2;
-    public const int BulletWidthNormal = 5;
-    public const int BulletWidthTiny = 2;
-    public const int VerticalSeparation = 2;
-
-    private readonly Texture _bulletTiny;
-    private readonly Texture _bulletNormal;
-
     private int _capacity;
-    private BulletType _type = BulletType.Normal;
+    private LayoutParameters _params;
 
     public int Rows { get; set; } = 2;
     public int Count { get; set; }
@@ -49,35 +19,31 @@ public sealed class BulletRender : Control
         get => _capacity;
         set
         {
+            if (_capacity == value)
+                return;
+
             _capacity = value;
             InvalidateMeasure();
         }
     }
 
-    public BulletType Type
+    protected LayoutParameters Parameters
     {
-        get => _type;
+        get => _params;
         set
         {
-            _type = value;
+            _params = value;
             InvalidateMeasure();
         }
     }
 
-    public BulletRender()
-    {
-        var resC = IoCManager.Resolve<IResourceCache>();
-        _bulletTiny = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/tiny.png");
-        _bulletNormal = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/normal.png");
-    }
-
     protected override Vector2 MeasureOverride(Vector2 availableSize)
     {
         var countPerRow = Math.Min(Capacity, CountPerRow(availableSize.X));
 
         var rows = Math.Min((int) MathF.Ceiling(Capacity / (float) countPerRow), Rows);
 
-        var height = BulletHeight * rows + (BulletSeparationNormal * rows - 1);
+        var height = _params.ItemHeight * rows + (_params.VerticalSeparation * rows - 1);
         var width = RowWidth(countPerRow);
 
         return new Vector2(width, height);
@@ -91,13 +57,8 @@ public sealed class BulletRender : Control
 
         var countPerRow = CountPerRow(Size.X);
 
-        var (separation, _) = BulletParams();
-        var texture = Type == BulletType.Normal ? _bulletNormal : _bulletTiny;
-
         var pos = new Vector2();
 
-        var altColor = false;
-
         var spent = Capacity - Count;
 
         var bulletsDone = 0;
@@ -105,7 +66,7 @@ public sealed class BulletRender : Control
         // Draw by rows, bottom to top.
         for (var row = 0; row < Rows; row++)
         {
-            altColor = false;
+            var altColor = false;
 
             var thisRowCount = Math.Min(countPerRow, Capacity - bulletsDone);
             if (thisRowCount <= 0)
@@ -116,9 +77,10 @@ public sealed class BulletRender : Control
             // 1. The next row would have less than MinCountPerRow bullets.
             // 2. The next row is actually visible (we aren't the last row).
             // 3. MinCountPerRow is actually smaller than the count per row (avoid degenerate cases).
+            // 4. There's enough bullets that at least one will end up on the next row.
             var nextRowCount = Capacity - bulletsDone - thisRowCount;
-            if (nextRowCount < MinCountPerRow && row != Rows - 1 && MinCountPerRow < countPerRow)
-                thisRowCount -= MinCountPerRow - nextRowCount;
+            if (nextRowCount < _params.MinCountPerRow && row != Rows - 1 && _params.MinCountPerRow < countPerRow && nextRowCount > 0)
+                thisRowCount -= _params.MinCountPerRow - nextRowCount;
 
             // Account for row width to right-align.
             var rowWidth = RowWidth(thisRowCount);
@@ -128,46 +90,130 @@ public sealed class BulletRender : Control
             for (var bullet = 0; bullet < thisRowCount; bullet++)
             {
                 var absIdx = Capacity - bulletsDone - thisRowCount + bullet;
-                Color color;
-                if (absIdx >= spent)
-                    color = altColor ? ColorA : ColorB;
-                else
-                    color = altColor ? ColorGoneA : ColorGoneB;
 
                 var renderPos = pos;
-                renderPos.Y = Size.Y - renderPos.Y - BulletHeight;
-                handle.DrawTexture(texture, renderPos, color);
-                pos.X += separation;
+                renderPos.Y = Size.Y - renderPos.Y - _params.ItemHeight;
+
+                DrawItem(handle, renderPos, absIdx < spent, altColor);
+
+                pos.X += _params.ItemSeparation;
                 altColor ^= true;
             }
 
             bulletsDone += thisRowCount;
             pos.X = 0;
-            pos.Y += BulletHeight + VerticalSeparation;
+            pos.Y += _params.ItemHeight + _params.VerticalSeparation;
         }
     }
 
+    protected abstract void DrawItem(DrawingHandleScreen handle, Vector2 renderPos, bool spent, bool altColor);
+
     private int CountPerRow(float width)
     {
-        var (separation, bulletWidth) = BulletParams();
-        return (int) ((width - bulletWidth + separation) / separation);
+        return (int) ((width - _params.ItemWidth + _params.ItemSeparation) / _params.ItemSeparation);
+    }
+
+    private int RowWidth(int count)
+    {
+        return (count - 1) * _params.ItemSeparation + _params.ItemWidth;
     }
 
-    private (int separation, int width) BulletParams()
+    protected struct LayoutParameters
     {
-        return Type switch
+        public int ItemHeight;
+        public int ItemSeparation;
+        public int ItemWidth;
+        public int VerticalSeparation;
+
+        /// <summary>
+        /// Try to ensure there's at least this many bullets on one row.
+        /// </summary>
+        /// <remarks>
+        /// For example, if there are two rows and the second row has only two bullets,
+        /// we "steal" some bullets from the row below it to make it look nicer.
+        /// </remarks>
+        public int MinCountPerRow;
+    }
+}
+
+/// <summary>
+/// Renders one or more rows of bullets for item status.
+/// </summary>
+/// <remarks>
+/// This is a custom control to allow complex responsive layout logic.
+/// </remarks>
+public sealed class BulletRender : BaseBulletRenderer
+{
+    public const int MinCountPerRow = 7;
+
+    public const int BulletHeight = 12;
+    public const int VerticalSeparation = 2;
+
+    private static readonly LayoutParameters LayoutNormal = new LayoutParameters
+    {
+        ItemHeight = BulletHeight,
+        ItemSeparation = 3,
+        ItemWidth = 5,
+        VerticalSeparation = VerticalSeparation,
+        MinCountPerRow = MinCountPerRow
+    };
+
+    private static readonly LayoutParameters LayoutTiny = new LayoutParameters
+    {
+        ItemHeight = BulletHeight,
+        ItemSeparation = 2,
+        ItemWidth = 2,
+        VerticalSeparation = VerticalSeparation,
+        MinCountPerRow = MinCountPerRow
+    };
+
+    private static readonly Color ColorA = Color.FromHex("#b68f0e");
+    private static readonly Color ColorB = Color.FromHex("#d7df60");
+    private static readonly Color ColorGoneA = Color.FromHex("#000000");
+    private static readonly Color ColorGoneB = Color.FromHex("#222222");
+
+    private readonly Texture _bulletTiny;
+    private readonly Texture _bulletNormal;
+
+    private BulletType _type = BulletType.Normal;
+
+    public BulletType Type
+    {
+        get => _type;
+        set
         {
-            BulletType.Normal => (BulletSeparationNormal, BulletWidthNormal),
-            BulletType.Tiny => (BulletSeparationTiny, BulletWidthTiny),
-            _ => throw new ArgumentOutOfRangeException()
-        };
+            if (_type == value)
+                return;
+
+            Parameters = _type switch
+            {
+                BulletType.Normal => LayoutNormal,
+                BulletType.Tiny => LayoutTiny,
+                _ => throw new ArgumentOutOfRangeException()
+            };
+
+            _type = value;
+        }
     }
 
-    private int RowWidth(int count)
+    public BulletRender()
     {
-        var (separation, bulletWidth) = BulletParams();
+        var resC = IoCManager.Resolve<IResourceCache>();
+        _bulletTiny = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/tiny.png");
+        _bulletNormal = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/normal.png");
+        Parameters = LayoutNormal;
+    }
 
-        return (count - 1) * separation + bulletWidth;
+    protected override void DrawItem(DrawingHandleScreen handle, Vector2 renderPos, bool spent, bool altColor)
+    {
+        Color color;
+        if (spent)
+            color = altColor ? ColorGoneA : ColorGoneB;
+        else
+            color = altColor ? ColorA : ColorB;
+
+        var texture = _type == BulletType.Tiny ? _bulletTiny : _bulletNormal;
+        handle.DrawTexture(texture, renderPos, color);
     }
 
     public enum BulletType
@@ -176,3 +222,31 @@ public sealed class BulletRender : Control
         Tiny
     }
 }
+
+public sealed class BatteryBulletRenderer : BaseBulletRenderer
+{
+    private static readonly Color ItemColor = Color.FromHex("#E00000");
+    private static readonly Color ItemColorGone = Color.Black;
+
+    private const int SizeH = 10;
+    private const int SizeV = 10;
+    private const int Separation = 4;
+
+    public BatteryBulletRenderer()
+    {
+        Parameters = new LayoutParameters
+        {
+            ItemWidth = SizeH,
+            ItemHeight = SizeV,
+            ItemSeparation = SizeH + Separation,
+            MinCountPerRow = 3,
+            VerticalSeparation = Separation
+        };
+    }
+
+    protected override void DrawItem(DrawingHandleScreen handle, Vector2 renderPos, bool spent, bool altColor)
+    {
+        var color = spent ? ItemColorGone : ItemColor;
+        handle.DrawRect(UIBox2.FromDimensions(renderPos, new Vector2(SizeH, SizeV)), color);
+    }
+}
index cc7405b04793a2cbb7c221944c82d789f05818c4..84eaa9af1b07caabd1f7cc3cfd7e8d1134402cd8 100644 (file)
@@ -116,7 +116,7 @@ public sealed partial class GunSystem
 
     public sealed class BoxesStatusControl : Control
     {
-        private readonly BoxContainer _bulletsList;
+        private readonly BatteryBulletRenderer _bullets;
         private readonly Label _ammoCount;
 
         public BoxesStatusControl()
@@ -128,27 +128,18 @@ public sealed partial class GunSystem
             AddChild(new BoxContainer
             {
                 Orientation = BoxContainer.LayoutOrientation.Horizontal,
-                HorizontalExpand = true,
                 Children =
                 {
-                    new Control
+                    (_bullets = new BatteryBulletRenderer
                     {
-                        HorizontalExpand = true,
-                        Children =
-                        {
-                            (_bulletsList = new BoxContainer
-                            {
-                                Orientation = BoxContainer.LayoutOrientation.Horizontal,
-                                VerticalAlignment = VAlignment.Center,
-                                SeparationOverride = 4
-                            }),
-                        }
-                    },
-                    new Control() { MinSize = new Vector2(5, 0) },
+                        Margin = new Thickness(0, 0, 5, 0),
+                        HorizontalExpand = true
+                    }),
                     (_ammoCount = new Label
                     {
                         StyleClasses = { StyleNano.StyleClassItemStatus },
                         HorizontalAlignment = HAlignment.Right,
+                        VerticalAlignment = VAlignment.Bottom
                     }),
                 }
             });
@@ -156,46 +147,12 @@ public sealed partial class GunSystem
 
         public void Update(int count, int max)
         {
-            _bulletsList.RemoveAllChildren();
-
             _ammoCount.Visible = true;
 
             _ammoCount.Text = $"x{count:00}";
-            max = Math.Min(max, 8);
-            FillBulletRow(_bulletsList, count, max);
-        }
 
-        private static void FillBulletRow(Control container, int count, int capacity)
-        {
-            var colorGone = Color.FromHex("#000000");
-            var color = Color.FromHex("#E00000");
-
-            // Draw the empty ones
-            for (var i = count; i < capacity; i++)
-            {
-                container.AddChild(new PanelContainer
-                {
-                    PanelOverride = new StyleBoxFlat()
-                    {
-                        BackgroundColor = colorGone,
-                    },
-                    MinSize = new Vector2(10, 15),
-                });
-            }
-
-            // Draw the full ones, but limit the count to the capacity
-            count = Math.Min(count, capacity);
-            for (var i = 0; i < count; i++)
-            {
-                container.AddChild(new PanelContainer
-                {
-                    PanelOverride = new StyleBoxFlat()
-                    {
-                        BackgroundColor = color,
-                    },
-                    MinSize = new Vector2(10, 15),
-                });
-            }
+            _bullets.Capacity = max;
+            _bullets.Count = count;
         }
     }
 
index f1f25a69f7a58997077cd8f8983ea69843688667..919d55f294aaee88b6149c33018727f44d5d51b6 100644 (file)
@@ -126,9 +126,43 @@ public sealed class HandsComponentState : ComponentState
 /// <summary>
 ///     What side of the body this hand is on.
 /// </summary>
+/// <seealso cref="HandUILocation"/>
+/// <seealso cref="HandLocationExt"/>
 public enum HandLocation : byte
 {
     Left,
     Middle,
     Right
 }
+
+/// <summary>
+/// What side of the UI a hand is on.
+/// </summary>
+/// <seealso cref="HandLocationExt"/>
+/// <seealso cref="HandLocation"/>
+public enum HandUILocation : byte
+{
+    Left,
+    Right
+}
+
+/// <summary>
+/// Helper functions for working with <see cref="HandLocation"/>.
+/// </summary>
+public static class HandLocationExt
+{
+    /// <summary>
+    /// Convert a <see cref="HandLocation"/> into the appropriate <see cref="HandUILocation"/>.
+    /// This maps "middle" hands to <see cref="HandUILocation.Right"/>.
+    /// </summary>
+    public static HandUILocation GetUILocation(this HandLocation location)
+    {
+        return location switch
+        {
+            HandLocation.Left => HandUILocation.Left,
+            HandLocation.Middle => HandUILocation.Right,
+            HandLocation.Right => HandUILocation.Right,
+            _ => throw new ArgumentOutOfRangeException(nameof(location), location, null)
+        };
+    }
+}
index e1bcbc4c9433425600337b853c2638cb28a96047..cd4955ed3655365c37bb80f928ec3bb020622b6c 100644 (file)
@@ -41,5 +41,5 @@ network-configurator-examine-current-mode = Current mode: {$mode}
 network-configurator-examine-switch-modes = Press {$key} to switch modes
 
 # item status
-network-configurator-item-status-label = Current mode: {$mode}
-{$keybinding} to switch mode
+network-configurator-item-status-label = Mode: {$mode}
+    Switch: {$keybinding}
index b93d43105a8374f4eaddb84d246267fd977c4220..c3002a73ae3c2035d5d3db2fc4161d1b0ced1a39 100644 (file)
@@ -10,9 +10,10 @@ implanter-component-implant-already = {$target} already has the {$implant}!
 implanter-draw-text = Draw
 implanter-inject-text = Inject
 
-implanter-empty-text = None
+implanter-empty-text = Empty
 
-implanter-label = Implant: [color=green]{$implantName}[/color] | [color=white]{$modeString}[/color]{$lineBreak}{$implantDescription}
+implanter-label = [color=green]{$implantName}[/color]
+    Mode: [color=white]{$modeString}[/color]
 
 implanter-contained-implant-text = [color=green]{$desc}[/color]
 
diff --git a/Resources/Locale/en-US/inventory/item-status.ftl b/Resources/Locale/en-US/inventory/item-status.ftl
new file mode 100644 (file)
index 0000000..a53ba8b
--- /dev/null
@@ -0,0 +1 @@
+item-status-not-held = No held item
index 0e7d2a8a353c5824e833732e05bd65abbf0a08ac..726c7190f2072a4c0a33ffb609c8d7de5f63639b 100644 (file)
@@ -1,3 +1,3 @@
-geiger-item-control-status = Radiation: [color={$color}]{$rads} rads[/color]
+geiger-item-control-status = [color={$color}]{$rads} rads[/color]
 geiger-item-control-disabled = Disabled
 geiger-component-examine = Current radiation: [color={$color}]{$rads} rads[/color]