Implement Entity List Display and rework StorageComponent window (#4140)

* Create EntityListDisplay

* Rework ClientStorage window

* Add styling

* Remove unnecessary colors

* Rename list

* Make scrollbar push content

* Change children update a bit

* Add old index

* Localize ClientStorageComponent

* Add size return

* Remove spaces

* Fix usings
This commit is contained in:
ShadowCommander
2021-08-12 10:05:02 -07:00
committed by GitHub
parent fe4bf059b9
commit 6a1ca13111
4 changed files with 389 additions and 133 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Client.Animations; using Content.Client.Animations;
using Content.Client.Items.Components;
using Content.Client.Hands; using Content.Client.Hands;
using Content.Client.UserInterface.Controls;
using Content.Shared.DragDrop; using Content.Shared.DragDrop;
using Content.Shared.Storage; using Content.Shared.Storage;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
@@ -13,9 +14,11 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Players; using Robust.Shared.Players;
using static Robust.Client.UserInterface.Control;
using static Robust.Client.UserInterface.Controls.BoxContainer; using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Storage namespace Content.Client.Storage
@@ -47,6 +50,8 @@ namespace Content.Client.Storage
base.OnAdd(); base.OnAdd();
_window = new StorageWindow(this) {Title = Owner.Name}; _window = new StorageWindow(this) {Title = Owner.Name};
_window.EntityList.GenerateItem += GenerateButton;
_window.EntityList.ItemPressed += Interact;
} }
protected override void OnRemove() protected override void OnRemove()
@@ -104,7 +109,7 @@ namespace Content.Client.Storage
_storedEntities = storageState.StoredEntities.Select(id => Owner.EntityManager.GetEntity(id)).ToList(); _storedEntities = storageState.StoredEntities.Select(id => Owner.EntityManager.GetEntity(id)).ToList();
StorageSizeUsed = storageState.StorageSizeUsed; StorageSizeUsed = storageState.StorageSizeUsed;
StorageCapacityMax = storageState.StorageSizeMax; StorageCapacityMax = storageState.StorageSizeMax;
_window?.BuildEntityList(); _window?.BuildEntityList(storageState.StoredEntities.ToList());
} }
/// <summary> /// <summary>
@@ -172,14 +177,53 @@ namespace Content.Client.Storage
return false; return false;
} }
/// <summary>
/// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label
/// </summary>
private void GenerateButton(EntityUid entityUid, Control button)
{
if (!Owner.EntityManager.TryGetEntity(entityUid, out var entity))
return;
entity.TryGetComponent(out ISpriteComponent? sprite);
entity.TryGetComponent(out ItemComponent? item);
button.AddChild(new HBoxContainer
{
SeparationOverride = 2,
Children =
{
new SpriteView
{
HorizontalAlignment = HAlignment.Left,
VerticalAlignment = VAlignment.Center,
MinSize = new Vector2(32.0f, 32.0f),
OverrideDirection = Direction.South,
Sprite = sprite
},
new Label
{
HorizontalExpand = true,
ClipText = true,
Text = entity.Name
},
new Label
{
Align = Label.AlignMode.Right,
Text = item?.Size.ToString() ?? Loc.GetString("no-item-size")
}
}
});
}
/// <summary> /// <summary>
/// GUI class for client storage component /// GUI class for client storage component
/// </summary> /// </summary>
private class StorageWindow : SS14Window private class StorageWindow : SS14Window
{ {
private Control VSplitContainer; private Control _vBox;
private readonly BoxContainer _entityList;
private readonly Label _information; private readonly Label _information;
public readonly EntityListDisplay EntityList;
public ClientStorageComponent StorageEntity; public ClientStorageComponent StorageEntity;
private readonly StyleBoxFlat _hoveredBox = new() { BackgroundColor = Color.Black.WithAlpha(0.35f) }; private readonly StyleBoxFlat _hoveredBox = new() { BackgroundColor = Color.Black.WithAlpha(0.35f) };
@@ -189,20 +233,21 @@ namespace Content.Client.Storage
{ {
StorageEntity = storageEntity; StorageEntity = storageEntity;
SetSize = (200, 320); SetSize = (200, 320);
Title = "Storage Item"; Title = Loc.GetString("comp-storage-window-title");
RectClipContent = true; RectClipContent = true;
var containerButton = new ContainerButton var containerButton = new ContainerButton
{ {
Name = "StorageContainerButton",
MouseFilter = MouseFilterMode.Pass, MouseFilter = MouseFilterMode.Pass,
}; };
Contents.AddChild(containerButton);
var innerContainerButton = new PanelContainer var innerContainerButton = new PanelContainer
{ {
PanelOverride = _unHoveredBox, PanelOverride = _unHoveredBox,
}; };
containerButton.AddChild(innerContainerButton); containerButton.AddChild(innerContainerButton);
containerButton.OnPressed += args => containerButton.OnPressed += args =>
{ {
@@ -214,42 +259,30 @@ namespace Content.Client.Storage
} }
}; };
VSplitContainer = new BoxContainer _vBox = new BoxContainer()
{ {
Orientation = LayoutOrientation.Vertical, Orientation = LayoutOrientation.Vertical,
MouseFilter = MouseFilterMode.Ignore, MouseFilter = MouseFilterMode.Ignore,
}; };
containerButton.AddChild(VSplitContainer); containerButton.AddChild(_vBox);
_information = new Label _information = new Label
{ {
Text = "Items: 0 Volume: 0/0 Stuff", Text = Loc.GetString("comp-storage-window-volume", ("itemCount", 0), ("usedVolume", 0), ("maxVolume", 0)),
VerticalAlignment = VAlignment.Center VerticalAlignment = VAlignment.Center
}; };
VSplitContainer.AddChild(_information); _vBox.AddChild(_information);
var listScrollContainer = new ScrollContainer EntityList = new EntityListDisplay
{ {
VerticalExpand = true, Name = "EntityListContainer",
HorizontalExpand = true,
HScrollEnabled = false,
VScrollEnabled = true,
}; };
_entityList = new BoxContainer _vBox.AddChild(EntityList);
{ EntityList.OnMouseEntered += args =>
Orientation = LayoutOrientation.Vertical,
HorizontalExpand = true
};
listScrollContainer.AddChild(_entityList);
VSplitContainer.AddChild(listScrollContainer);
Contents.AddChild(containerButton);
listScrollContainer.OnMouseEntered += args =>
{ {
innerContainerButton.PanelOverride = _hoveredBox; innerContainerButton.PanelOverride = _hoveredBox;
}; };
listScrollContainer.OnMouseExited += args => EntityList.OnMouseExited += args =>
{ {
innerContainerButton.PanelOverride = _unHoveredBox; innerContainerButton.PanelOverride = _unHoveredBox;
}; };
@@ -264,122 +297,21 @@ namespace Content.Client.Storage
/// <summary> /// <summary>
/// Loops through stored entities creating buttons for each, updates information labels /// Loops through stored entities creating buttons for each, updates information labels
/// </summary> /// </summary>
public void BuildEntityList() public void BuildEntityList(List<EntityUid> entityUids)
{ {
_entityList.DisposeAllChildren(); EntityList.PopulateList(entityUids);
var storageList = StorageEntity.StoredEntities;
var storedGrouped = storageList.GroupBy(e => e).Select(e => new
{
Entity = e.Key,
Amount = e.Count()
});
foreach (var group in storedGrouped)
{
var entity = group.Entity;
var button = new EntityButton()
{
EntityUid = entity.Uid,
MouseFilter = MouseFilterMode.Stop,
};
button.ActualButton.OnToggled += OnItemButtonToggled;
//Name and Size labels set
button.EntityName.Text = entity.Name;
button.EntitySize.Text = group.Amount.ToString();
//Gets entity sprite and assigns it to button texture
if (entity.TryGetComponent(out ISpriteComponent? sprite))
{
button.EntitySpriteView.Sprite = sprite;
}
_entityList.AddChild(button);
}
//Sets information about entire storage container current capacity //Sets information about entire storage container current capacity
if (StorageEntity.StorageCapacityMax != 0) if (StorageEntity.StorageCapacityMax != 0)
{ {
_information.Text = String.Format("Items: {0}, Stored: {1}/{2}", storageList.Count, _information.Text = Loc.GetString("comp-storage-window-volume", ("itemCount", entityUids.Count),
StorageEntity.StorageSizeUsed, StorageEntity.StorageCapacityMax); ("usedVolume", StorageEntity.StorageSizeUsed), ("maxVolume", StorageEntity.StorageCapacityMax));
} }
else else
{ {
_information.Text = String.Format("Items: {0}", storageList.Count); _information.Text = Loc.GetString("comp-storage-window-volume-unlimited", ("itemCount", entityUids.Count));
} }
} }
/// <summary>
/// Function assigned to button toggle which removes the entity from storage
/// </summary>
/// <param name="args"></param>
private void OnItemButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
if (args.Button.Parent is not EntityButton button)
{
return;
}
args.Button.Pressed = false;
StorageEntity.Interact(button.EntityUid);
}
}
/// <summary>
/// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label
/// </summary>
private class EntityButton : Control
{
public EntityUid EntityUid { get; set; }
public Button ActualButton { get; }
public SpriteView EntitySpriteView { get; }
public Label EntityName { get; }
public Label EntitySize { get; }
public EntityButton()
{
ActualButton = new Button
{
HorizontalExpand = true,
VerticalExpand = true,
ToggleMode = true,
MouseFilter = MouseFilterMode.Stop
};
AddChild(ActualButton);
var hBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
};
EntitySpriteView = new SpriteView
{
MinSize = new Vector2(32.0f, 32.0f),
OverrideDirection = Direction.South
};
EntityName = new Label
{
VerticalAlignment = VAlignment.Center,
HorizontalExpand = true,
Margin = new Thickness(0, 0, 6, 0),
Text = "Backpack",
ClipText = true
};
hBoxContainer.AddChild(EntitySpriteView);
hBoxContainer.AddChild(EntityName);
EntitySize = new Label
{
VerticalAlignment = VAlignment.Bottom,
Text = "Size 6",
Align = Label.AlignMode.Right,
};
hBoxContainer.AddChild(EntitySize);
AddChild(hBoxContainer);
}
} }
} }
} }

View File

@@ -5,6 +5,7 @@ using Content.Client.HUD;
using Content.Client.HUD.UI; using Content.Client.HUD.UI;
using Content.Client.Resources; using Content.Client.Resources;
using Content.Client.Targeting; using Content.Client.Targeting;
using Content.Client.UserInterface.Controls;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -38,6 +39,7 @@ namespace Content.Client.Stylesheets
public const string StyleClassChatChannelSelectorButton = "chatSelectorOptionButton"; public const string StyleClassChatChannelSelectorButton = "chatSelectorOptionButton";
public const string StyleClassChatFilterOptionButton = "chatFilterOptionButton"; public const string StyleClassChatFilterOptionButton = "chatFilterOptionButton";
public const string StyleClassContextMenuCount = "contextMenuCount"; public const string StyleClassContextMenuCount = "contextMenuCount";
public const string StyleClassStorageButton = "storageButton";
public const string StyleClassSliderRed = "Red"; public const string StyleClassSliderRed = "Red";
public const string StyleClassSliderGreen = "Green"; public const string StyleClassSliderGreen = "Green";
@@ -139,6 +141,12 @@ namespace Content.Client.Stylesheets
hotbarBackground.SetPatchMargin(StyleBox.Margin.All, 2); hotbarBackground.SetPatchMargin(StyleBox.Margin.All, 2);
hotbarBackground.SetExpandMargin(StyleBox.Margin.All, 4); hotbarBackground.SetExpandMargin(StyleBox.Margin.All, 4);
var buttonStorage = new StyleBoxTexture(BaseButton);
buttonStorage.SetPatchMargin(StyleBox.Margin.All, 10);
buttonStorage.SetPadding(StyleBox.Margin.All, 0);
buttonStorage.SetContentMarginOverride(StyleBox.Margin.Vertical, 0);
buttonStorage.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
var buttonRectTex = resCache.GetTexture("/Textures/Interface/Nano/light_panel_background_bordered.png"); var buttonRectTex = resCache.GetTexture("/Textures/Interface/Nano/light_panel_background_bordered.png");
var buttonRect = new StyleBoxTexture(BaseButton) var buttonRect = new StyleBoxTexture(BaseButton)
{ {
@@ -496,6 +504,26 @@ namespace Content.Client.Stylesheets
new StyleProperty("font-color", Color.FromHex("#E5E5E581")), new StyleProperty("font-color", Color.FromHex("#E5E5E581")),
}), }),
// Thin buttons (No padding nor vertical margin)
Element<EntityContainerButton>().Class(StyleClassStorageButton)
.Prop(ContainerButton.StylePropertyStyleBox, buttonStorage),
Element<EntityContainerButton>().Class(StyleClassStorageButton)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, ButtonColorDefault),
Element<EntityContainerButton>().Class(StyleClassStorageButton)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ButtonColorHovered),
Element<EntityContainerButton>().Class(StyleClassStorageButton)
.Pseudo(ContainerButton.StylePseudoClassPressed)
.Prop(Control.StylePropertyModulateSelf, ButtonColorPressed),
Element<EntityContainerButton>().Class(StyleClassStorageButton)
.Pseudo(ContainerButton.StylePseudoClassDisabled)
.Prop(Control.StylePropertyModulateSelf, ButtonColorDisabled),
// action slot hotbar buttons // action slot hotbar buttons
new StyleRule(new SelectorElement(typeof(ActionSlot), null, null, new[] {ContainerButton.StylePseudoClassNormal}), new[] new StyleRule(new SelectorElement(typeof(ActionSlot), null, null, new[] {ContainerButton.StylePseudoClassNormal}), new[]
{ {

View File

@@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.Stylesheets;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface.Controls
{
public class EntityListDisplay : Control
{
public const string StylePropertySeparation = "separation";
public int? SeparationOverride { get; set; }
public Action<EntityUid, Control>? GenerateItem;
public Action<EntityUid>? ItemPressed;
private const int DefaultSeparation = 3;
private readonly VScrollBar _vScrollBar;
private List<EntityUid>? _entityUids;
private int _count = 0;
private float _itemHeight = 0;
private float _totalHeight = 0;
private int _topIndex = 0;
private int _bottomIndex = 0;
private bool _updateChildren = false;
private bool _suppressScrollValueChanged;
public int ScrollSpeedY { get; set; } = 50;
private int ActualSeparation
{
get
{
if (TryGetStyleProperty(StylePropertySeparation, out int separation))
{
return separation;
}
return SeparationOverride ?? DefaultSeparation;
}
}
public EntityListDisplay()
{
HorizontalExpand = true;
VerticalExpand = true;
RectClipContent = true;
MouseFilter = MouseFilterMode.Pass;
_vScrollBar = new VScrollBar
{
HorizontalExpand = false,
HorizontalAlignment = HAlignment.Right
};
AddChild(_vScrollBar);
_vScrollBar.OnValueChanged += ScrollValueChanged;
}
public void PopulateList(List<EntityUid> entities)
{
if (_count == 0 && entities.Count > 0)
{
EntityContainerButton control = new(entities[0]);
GenerateItem?.Invoke(entities[0], control);
control.Measure(Vector2.Infinity);
_itemHeight = control.DesiredSize.Y;
control.Dispose();
}
_count = entities.Count;
_entityUids = entities;
_updateChildren = true;
InvalidateArrange();
}
private void OnItemPressed(BaseButton.ButtonEventArgs args)
{
if (args.Button is not EntityContainerButton button)
return;
ItemPressed?.Invoke(button.EntityUid);
}
[Pure]
private Vector2 GetScrollValue()
{
var v = _vScrollBar.Value;
if (!_vScrollBar.Visible)
{
v = 0;
}
return new Vector2(0, v);
}
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var separation = (int) (ActualSeparation * UIScale);
#region Scroll
var cHeight = _totalHeight;
var vBarSize = _vScrollBar.DesiredSize.X;
var (sWidth, sHeight) = finalSize;
try
{
// Suppress events to avoid weird recursion.
_suppressScrollValueChanged = true;
if (sHeight < cHeight)
sWidth -= vBarSize;
if (sHeight < cHeight)
{
_vScrollBar.Visible = true;
_vScrollBar.Page = sHeight;
_vScrollBar.MaxValue = cHeight;
}
else
_vScrollBar.Visible = false;
}
finally
{
_suppressScrollValueChanged = false;
}
if (_vScrollBar.Visible)
{
_vScrollBar.Arrange(UIBox2.FromDimensions(Vector2.Zero, finalSize));
}
#endregion
#region Rebuild Children
/*
* Example:
*
* var _itemHeight = 32;
* var separation = 3;
* 32 | 32 | Control.Size.Y 0
* 35 | 3 | Padding
* 67 | 32 | Control.Size.Y 1
* 70 | 3 | Padding
* 102 | 32 | Control.Size.Y 2
* 105 | 3 | Padding
* 137 | 32 | Control.Size.Y 3
*
* If viewport height is 60
* visible should be 2 items (start = 0, end = 1)
*
* scroll.Y = 11
* visible should be 3 items (start = 0, end = 2)
*
* start expected: 11 (item: 0)
* var start = (int) (scroll.Y
*
* if (scroll == 32) then { start = 1 }
* var start = (int) (scroll.Y + separation / (_itemHeight + separation));
* var start = (int) (32 + 3 / (32 + 3));
* var start = (int) (35 / 35);
* var start = (int) (1);
*
* scroll = 0, height = 36
* if (scroll + height == 36) then { end = 2 }
* var end = (int) Math.Ceiling(scroll.Y + height / (_itemHeight + separation));
* var end = (int) Math.Ceiling(0 + 36 / (32 + 3));
* var end = (int) Math.Ceiling(36 / 35);
* var end = (int) Math.Ceiling(1.02857);
* var end = (int) 2;
*
*/
var scroll = GetScrollValue();
var oldTopIndex = _topIndex;
_topIndex = (int) ((scroll.Y + separation) / (_itemHeight + separation));
if (_topIndex != oldTopIndex)
_updateChildren = true;
var oldBottomIndex = _bottomIndex;
_bottomIndex = (int) Math.Ceiling((scroll.Y + Height) / (_itemHeight + separation));
_bottomIndex = Math.Min(_bottomIndex, _count);
if (_bottomIndex != oldBottomIndex)
_updateChildren = true;
// When scrolling only rebuild visible list when a new item should be visible
if (_updateChildren)
{
_updateChildren = false;
foreach (var child in Children.ToArray())
{
if (child == _vScrollBar)
continue;
RemoveChild(child);
}
if (_entityUids != null)
{
for (var i = _topIndex; i < _bottomIndex; i++)
{
var entity = _entityUids[i];
var button = new EntityContainerButton(entity);
button.OnPressed += OnItemPressed;
GenerateItem?.Invoke(entity, button);
AddChild(button);
}
}
_vScrollBar.SetPositionLast();
}
#endregion
#region Layout Children
// Use pixel position
var pixelWidth = (int)(sWidth * UIScale);
var offset = (int) -((scroll.Y - _topIndex * (_itemHeight + separation)) * UIScale);
var first = true;
foreach (var child in Children)
{
if (child == _vScrollBar)
continue;
if (!first)
offset += separation;
first = false;
var size = child.DesiredPixelSize.Y;
var targetBox = new UIBox2i(0, offset, pixelWidth, offset + size);
child.ArrangePixel(targetBox);
offset += size;
}
#endregion
return finalSize;
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
_vScrollBar.Measure(availableSize);
availableSize.X -= _vScrollBar.DesiredSize.X;
var constraint = new Vector2(availableSize.X, float.PositiveInfinity);
var childSize = Vector2.Zero;
foreach (var child in Children)
{
child.Measure(constraint);
if (child == _vScrollBar)
continue;
childSize = Vector2.ComponentMax(childSize, child.DesiredSize);
}
_totalHeight = childSize.Y * _count + ActualSeparation * (_count - 1);
return new Vector2(childSize.X, 0f);
}
private void ScrollValueChanged(Robust.Client.UserInterface.Controls.Range _)
{
if (_suppressScrollValueChanged)
{
return;
}
InvalidateArrange();
}
protected override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
_vScrollBar.ValueTarget -= args.Delta.Y * ScrollSpeedY;
args.Handle();
}
}
public class EntityContainerButton : ContainerButton
{
public EntityUid EntityUid;
public EntityContainerButton(EntityUid entityUid)
{
EntityUid = entityUid;
AddStyleClass(StyleNano.StyleClassStorageButton);
}
}
}

View File

@@ -0,0 +1,4 @@
comp-storage-no-item-size = None
comp-storage-window-title = Storage Item
comp-storage-window-volume = Items: { $itemCount }, Stored: { $usedVolume }/{ $maxVolume }
comp-storage-window-volume-unlimited = Items: { $itemCount }