Hud refactor (#7202)

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Jezithyr <jmaster9999@gmail.com>
Co-authored-by: Jezithyr <Jezithyr@gmail.com>
Co-authored-by: Visne <39844191+Visne@users.noreply.github.com>
Co-authored-by: wrexbe <wrexbe@protonmail.com>
Co-authored-by: wrexbe <81056464+wrexbe@users.noreply.github.com>
This commit is contained in:
Jezithyr
2022-10-12 01:16:23 -07:00
committed by GitHub
parent d09fbc1849
commit 571dd4e6d5
168 changed files with 6940 additions and 7817 deletions

View File

@@ -0,0 +1,18 @@
<widgets:ChatBox
xmlns="https://spacestation14.io"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Chat.Controls"
MouseFilter="Stop"
MinSize="465 225">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#25252AAA" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" SeparationOverride="4">
<OutputPanel Name="Contents" VerticalExpand="True" />
<controls:ChatInputBox Name="ChatInput" Access="Public" Margin="2"/>
</BoxContainer>
</PanelContainer>
</widgets:ChatBox>

View File

@@ -0,0 +1,214 @@
using Content.Client.Chat;
using Content.Client.Chat.TypingIndicator;
using Content.Client.UserInterface.Systems.Chat.Controls;
using Content.Shared.Chat;
using Content.Shared.Input;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.LineEdit;
namespace Content.Client.UserInterface.Systems.Chat.Widgets;
[GenerateTypedNameReferences]
#pragma warning disable RA0003
public partial class ChatBox : Control
#pragma warning restore RA0003
{
private readonly ChatUIController _controller;
public bool Main { get; set; }
public ChatSelectChannel SelectedChannel => ChatInput.ChannelSelector.SelectedChannel;
public ChatBox()
{
RobustXamlLoader.Load(this);
ChatInput.Input.OnTextEntered += OnTextEntered;
ChatInput.Input.OnKeyBindDown += OnKeyBindDown;
ChatInput.Input.OnTextChanged += OnTextChanged;
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
ChatInput.FilterButton.ChatFilterPopup.OnChannelFilter += OnChannelFilter;
_controller = UserInterfaceManager.GetUIController<ChatUIController>();
_controller.MessageAdded += OnMessageAdded;
_controller.RegisterChat(this);
}
private void OnTextEntered(LineEditEventArgs args)
{
_controller.SendMessage(this, SelectedChannel);
}
private void OnMessageAdded(StoredChatMessage msg)
{
var text = FormattedMessage.EscapeText(msg.Message);
if (!string.IsNullOrEmpty(msg.MessageWrap))
{
text = string.Format(msg.MessageWrap, text);
}
Logger.DebugS("chat", $"{msg.Channel}: {text}");
if (!ChatInput.FilterButton.ChatFilterPopup.IsActive(msg.Channel))
{
return;
}
msg.Read = true;
var color = msg.MessageColorOverride != Color.Transparent
? msg.MessageColorOverride
: msg.Channel.TextColor();
AddLine(text, color);
}
private void OnChannelSelect(ChatSelectChannel channel)
{
UpdateSelectedChannel();
}
private void OnChannelFilter(ChatChannel channel, bool active)
{
Contents.Clear();
foreach (var message in _controller.History)
{
OnMessageAdded(message);
}
if (active)
{
_controller.ClearUnfilteredUnreads(channel);
}
}
public void AddLine(string message, Color color)
{
var formatted = new FormattedMessage(3);
formatted.PushColor(color);
formatted.AddMarkup(message);
formatted.Pop();
Contents.AddMessage(formatted);
}
public void UpdateSelectedChannel()
{
var (prefixChannel, _) = _controller.SplitInputContents(ChatInput.Input.Text);
var channel = prefixChannel == 0 ? SelectedChannel : prefixChannel;
ChatInput.ChannelSelector.UpdateChannelSelectButton(channel);
}
public void Focus(ChatSelectChannel? channel = null)
{
var input = ChatInput.Input;
var selectStart = Index.End;
if (channel != null)
{
channel = _controller.MapLocalIfGhost(channel.Value);
// Channel not selectable, just do NOTHING (not even focus).
if ((_controller.SelectableChannels & channel.Value) == 0)
return;
var (_, text) = _controller.SplitInputContents(input.Text);
var newPrefix = _controller.GetPrefixFromChannel(channel.Value);
DebugTools.Assert(newPrefix != default, "Focus channel must have prefix!");
if (channel == SelectedChannel)
{
// New selected channel is just the selected channel,
// just remove prefix (if any) and leave text unchanged.
input.Text = text.ToString();
selectStart = Index.Start;
}
else
{
// Change prefix to new focused channel prefix and leave text unchanged.
input.Text = string.Concat(newPrefix.ToString(), " ", text.Span);
selectStart = Index.FromStart(2);
}
ChatInput.ChannelSelector.Select(channel.Value);
}
input.IgnoreNext = true;
input.GrabKeyboardFocus();
input.CursorPosition = input.Text.Length;
input.SelectionStart = selectStart.GetOffset(input.Text.Length);
}
public void CycleChatChannel(bool forward)
{
var idx = Array.IndexOf(ChannelSelectorPopup.ChannelSelectorOrder, SelectedChannel);
do
{
// go over every channel until we find one we can actually select.
idx += forward ? 1 : -1;
idx = MathHelper.Mod(idx, ChannelSelectorPopup.ChannelSelectorOrder.Length);
} while ((_controller.SelectableChannels & ChannelSelectorPopup.ChannelSelectorOrder[idx]) == 0);
SafelySelectChannel(ChannelSelectorPopup.ChannelSelectorOrder[idx]);
}
public void SafelySelectChannel(ChatSelectChannel toSelect)
{
toSelect = _controller.MapLocalIfGhost(toSelect);
if ((_controller.SelectableChannels & toSelect) == 0)
return;
ChatInput.ChannelSelector.Select(toSelect);
}
private void OnKeyBindDown(GUIBoundKeyEventArgs args)
{
if (args.Function == EngineKeyFunctions.TextReleaseFocus)
{
ChatInput.Input.ReleaseKeyboardFocus();
args.Handle();
return;
}
if (args.Function == ContentKeyFunctions.CycleChatChannelForward)
{
CycleChatChannel(true);
args.Handle();
return;
}
if (args.Function == ContentKeyFunctions.CycleChatChannelBackward)
{
CycleChatChannel(false);
args.Handle();
}
}
private void OnTextChanged(LineEditEventArgs args)
{
// Update channel select button to correct channel if we have a prefix.
UpdateSelectedChannel();
// Warn typing indicator about change
EntitySystem.Get<TypingIndicatorSystem>().ClientChangedChatText();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_controller.UnregisterChat(this);
ChatInput.Input.OnTextEntered -= OnTextEntered;
ChatInput.Input.OnKeyBindDown -= OnKeyBindDown;
ChatInput.Input.OnTextChanged -= OnTextChanged;
ChatInput.ChannelSelector.OnChannelSelect -= OnChannelSelect;
}
}

View File

@@ -0,0 +1,235 @@
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Systems.Chat.Widgets;
public sealed class ResizableChatBox : ChatBox
{
public ResizableChatBox()
{
IoCManager.InjectDependencies(this);
}
// TODO: Revisit the resizing stuff after https://github.com/space-wizards/RobustToolbox/issues/1392 is done,
// Probably not "supposed" to inject IClyde, but I give up.
// I can't find any other way to allow this control to properly resize when the
// window is resized. Resized() isn't reliably called when resizing the window,
// and layoutcontainer anchor / margin don't seem to adjust how we need
// them to when the window is resized. We need it to be able to resize
// within some bounds so that it doesn't overlap other UI elements, while still
// being freely resizable within those bounds.
[Dependency] private readonly IClyde _clyde = default!;
private const int DragMarginSize = 7;
private const int MinDistanceFromBottom = 255;
private const int MinLeft = 500;
private DragMode _currentDrag = DragMode.None;
private Vector2 _dragOffsetTopLeft;
private Vector2 _dragOffsetBottomRight;
private byte _clampIn;
protected override void EnteredTree()
{
base.EnteredTree();
_clyde.OnWindowResized += ClydeOnOnWindowResized;
}
protected override void ExitedTree()
{
base.ExitedTree();
_clyde.OnWindowResized -= ClydeOnOnWindowResized;
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
if (args.Function == EngineKeyFunctions.UIClick)
{
_currentDrag = GetDragModeFor(args.RelativePosition);
if (_currentDrag != DragMode.None)
{
_dragOffsetTopLeft = args.PointerLocation.Position / UIScale - Position;
_dragOffsetBottomRight = Position + Size - args.PointerLocation.Position / UIScale;
}
}
base.KeyBindDown(args);
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
if (args.Function != EngineKeyFunctions.UIClick)
return;
if (_currentDrag != DragMode.None)
{
_dragOffsetTopLeft = _dragOffsetBottomRight = Vector2.Zero;
_currentDrag = DragMode.None;
// If this is done in MouseDown, Godot won't fire MouseUp as you need focus to receive MouseUps.
UserInterfaceManager.KeyboardFocused?.ReleaseKeyboardFocus();
}
base.KeyBindUp(args);
}
// TODO: this drag and drop stuff is somewhat duplicated from Robust BaseWindow but also modified
[Flags]
private enum DragMode : byte
{
None = 0,
Bottom = 1 << 1,
Left = 1 << 2
}
private DragMode GetDragModeFor(Vector2 relativeMousePos)
{
var mode = DragMode.None;
if (relativeMousePos.Y > Size.Y - DragMarginSize)
{
mode = DragMode.Bottom;
}
if (relativeMousePos.X < DragMarginSize)
{
mode |= DragMode.Left;
}
return mode;
}
protected override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
if (Parent == null)
return;
if (_currentDrag == DragMode.None)
{
var cursor = CursorShape.Arrow;
var previewDragMode = GetDragModeFor(args.RelativePosition);
switch (previewDragMode)
{
case DragMode.Bottom:
cursor = CursorShape.VResize;
break;
case DragMode.Left:
cursor = CursorShape.HResize;
break;
case DragMode.Bottom | DragMode.Left:
cursor = CursorShape.Crosshair;
break;
}
DefaultCursorShape = cursor;
}
else
{
var top = Rect.Top;
var bottom = Rect.Bottom;
var left = Rect.Left;
var right = Rect.Right;
var (minSizeX, minSizeY) = MinSize;
if ((_currentDrag & DragMode.Bottom) == DragMode.Bottom)
{
bottom = Math.Max(args.GlobalPosition.Y + _dragOffsetBottomRight.Y, top + minSizeY);
}
if ((_currentDrag & DragMode.Left) == DragMode.Left)
{
var maxX = right - minSizeX;
left = Math.Min(args.GlobalPosition.X - _dragOffsetTopLeft.X, maxX);
}
ClampSize(left, bottom);
}
}
protected override void UIScaleChanged()
{
base.UIScaleChanged();
ClampAfterDelay();
}
private void ClydeOnOnWindowResized(WindowResizedEventArgs obj)
{
ClampAfterDelay();
}
private void ClampAfterDelay()
{
_clampIn = 2;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
// we do the clamping after a delay (after UI scale / window resize)
// because we need to wait for our parent container to properly resize
// first, so we can calculate where we should go. If we do it right away,
// we won't have the correct values from the parent to know how to adjust our margins.
if (_clampIn <= 0)
return;
_clampIn -= 1;
if (_clampIn == 0)
ClampSize();
}
private void ClampSize(float? desiredLeft = null, float? desiredBottom = null)
{
if (Parent == null)
return;
// var top = Rect.Top;
var right = Rect.Right;
var left = desiredLeft ?? Rect.Left;
var bottom = desiredBottom ?? Rect.Bottom;
// clamp so it doesn't go too high or low (leave space for alerts UI)
var maxBottom = Parent.Size.Y - MinDistanceFromBottom;
if (maxBottom <= MinHeight)
{
// we can't fit in our given space (window made awkwardly small), so give up
// and overlap at our min height
bottom = MinHeight;
}
else
{
bottom = Math.Clamp(bottom, MinHeight, maxBottom);
}
var maxLeft = Parent.Size.X - MinWidth;
if (maxLeft <= MinLeft)
{
// window too narrow, give up and overlap at our max left
left = maxLeft;
}
else
{
left = Math.Clamp(left, MinLeft, maxLeft);
}
LayoutContainer.SetMarginLeft(this, -((right + 10) - left));
LayoutContainer.SetMarginBottom(this, bottom);
}
protected override void MouseExited()
{
base.MouseExited();
if (_currentDrag == DragMode.None)
DefaultCursorShape = CursorShape.Arrow;
}
}