Files
tbd-station-14/Content.Client/Chat/UI/HudChatBox.cs
2022-02-16 18:23:23 +11:00

236 lines
7.5 KiB
C#

using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Content.Client.Chat.UI
{
public sealed class HudChatBox : ChatBox
{
// 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!;
public const float InitialChatBottom = 235;
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)
{
base.KeyBindUp(args);
if (args.Function != EngineKeyFunctions.UIClick)
return;
_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();
}
// 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);
ChatMgr.ChatBoxOnResized(new ChatResizedEventArgs(bottom));
}
protected override void MouseExited()
{
base.MouseExited();
if (_currentDrag == DragMode.None)
DefaultCursorShape = CursorShape.Arrow;
}
}
}