* UI is an abbreviation, in XAML. * Chat improvements. Changing the "selected" channel on the chat box is now only done via the tab cycle or clicking the button. Prefix chars like [ will temporarily replace the active chat channel. This is based 100% on message box contents so there's no input eating garbage or anything. Pressing specific channel focusing keys inserts the correct prefix character, potentially replacing an existing one. Existing chat contents are left in place just fine and selected so you can easily delete them (but are not forced to). Channel focusing keys now match the QWERTY key codes. Deadchat works now. Console can no longer be selected as a chat channel, but you can still use it with the / prefix. Refactored the connection between chat manager and chat box so that it's event based, reducing tons of spaghetti everywhere. Main chat box control uses XAML now. General cleanup. Added focus hotkeys for deadchat/console. Also added prefix for deadchat. Local chat is mapped to deadchat when a ghost. Probably more stuff I can't think of right now. * Add preferred channel system to chat box to automatically select local. I can't actually test this works because the non-lobby chat box code is complete disastrous spaghetti and i need to refactor it. * Move chatbox resizing and all that to a subclass. Refine preferred channel & deadchat mapping code further. * Don't do prefixes for channels you don't have access to. * Change format on channel select popup. * Clean up code with console handling somewhat.
236 lines
7.5 KiB
C#
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 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;
|
|
}
|
|
}
|
|
}
|