* 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.
741 lines
26 KiB
C#
741 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Content.Client.Alerts.UI;
|
|
using Content.Client.Chat.Managers;
|
|
using Content.Client.Resources;
|
|
using Content.Client.Stylesheets;
|
|
using Content.Shared.Chat;
|
|
using Content.Shared.Input;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Input;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Localization;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Client.Chat.UI
|
|
{
|
|
[GenerateTypedNameReferences]
|
|
public partial class ChatBox : Control
|
|
{
|
|
[Dependency] protected readonly IChatManager ChatMgr = default!;
|
|
|
|
// order in which the available channel filters show up when available
|
|
private static readonly ChatChannel[] ChannelFilterOrder =
|
|
{
|
|
ChatChannel.Local,
|
|
ChatChannel.Emotes,
|
|
ChatChannel.Radio,
|
|
ChatChannel.OOC,
|
|
ChatChannel.Dead,
|
|
ChatChannel.Admin,
|
|
ChatChannel.Server
|
|
};
|
|
|
|
// order in which the channels show up in the channel selector
|
|
private static readonly ChatSelectChannel[] ChannelSelectorOrder =
|
|
{
|
|
ChatSelectChannel.Local,
|
|
ChatSelectChannel.Emotes,
|
|
ChatSelectChannel.Radio,
|
|
ChatSelectChannel.OOC,
|
|
ChatSelectChannel.Dead,
|
|
ChatSelectChannel.Admin
|
|
// NOTE: Console is not in there and it can never be permanently selected.
|
|
// You can, however, still submit commands as console by prefixing with /.
|
|
};
|
|
|
|
public const char AliasLocal = '.';
|
|
public const char AliasConsole = '/';
|
|
public const char AliasDead = '\\';
|
|
public const char AliasOOC = '[';
|
|
public const char AliasEmotes = '@';
|
|
public const char AliasAdmin = ']';
|
|
public const char AliasRadio = ';';
|
|
|
|
private static readonly Dictionary<char, ChatSelectChannel> PrefixToChannel = new()
|
|
{
|
|
{AliasLocal, ChatSelectChannel.Local},
|
|
{AliasConsole, ChatSelectChannel.Console},
|
|
{AliasOOC, ChatSelectChannel.OOC},
|
|
{AliasEmotes, ChatSelectChannel.Emotes},
|
|
{AliasAdmin, ChatSelectChannel.Admin},
|
|
{AliasRadio, ChatSelectChannel.Radio},
|
|
{AliasDead, ChatSelectChannel.Dead}
|
|
};
|
|
|
|
private static readonly Dictionary<ChatSelectChannel, char> ChannelPrefixes =
|
|
PrefixToChannel.ToDictionary(kv => kv.Value, kv => kv.Key);
|
|
|
|
private const float FilterPopupWidth = 110;
|
|
|
|
/// <summary>
|
|
/// The currently default channel that will be used if no prefix is specified.
|
|
/// </summary>
|
|
public ChatSelectChannel SelectedChannel { get; private set; } = ChatSelectChannel.OOC;
|
|
|
|
/// <summary>
|
|
/// The "preferred" channel. Will be switched to if permissions change and the channel becomes available,
|
|
/// such as by re-entering body. Gets changed if the user manually selects a channel with the buttons.
|
|
/// </summary>
|
|
public ChatSelectChannel PreferredChannel { get; set; } = ChatSelectChannel.OOC;
|
|
|
|
public bool ReleaseFocusOnEnter { get; set; } = true;
|
|
|
|
private readonly Popup _channelSelectorPopup;
|
|
private readonly BoxContainer _channelSelectorHBox;
|
|
private readonly Popup _filterPopup;
|
|
private readonly PanelContainer _filterPopupPanel;
|
|
private readonly BoxContainer _filterVBox;
|
|
|
|
/// <summary>
|
|
/// When lobbyMode is false, will position / add to correct location in StateRoot and
|
|
/// be resizable.
|
|
/// wWen true, will leave layout up to parent and not be resizable.
|
|
/// </summary>
|
|
public ChatBox()
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
RobustXamlLoader.Load(this);
|
|
|
|
LayoutContainer.SetMarginLeft(this, 4);
|
|
LayoutContainer.SetMarginRight(this, 4);
|
|
|
|
_filterPopup = new Popup
|
|
{
|
|
Children =
|
|
{
|
|
(_filterPopupPanel = new PanelContainer
|
|
{
|
|
StyleClasses = {StyleNano.StyleClassBorderedWindowPanel},
|
|
Children =
|
|
{
|
|
new BoxContainer
|
|
{
|
|
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
|
Children =
|
|
{
|
|
new Control {MinSize = (4, 0)},
|
|
(_filterVBox = new BoxContainer
|
|
{
|
|
Margin = new Thickness(0, 10),
|
|
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
|
SeparationOverride = 4
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
};
|
|
|
|
_channelSelectorPopup = new Popup
|
|
{
|
|
Children =
|
|
{
|
|
(_channelSelectorHBox = new BoxContainer
|
|
{
|
|
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
|
SeparationOverride = 1
|
|
})
|
|
}
|
|
};
|
|
|
|
ChannelSelector.OnToggled += OnChannelSelectorToggled;
|
|
FilterButton.OnToggled += OnFilterButtonToggled;
|
|
Input.OnKeyBindDown += InputKeyBindDown;
|
|
Input.OnTextEntered += Input_OnTextEntered;
|
|
Input.OnTextChanged += InputOnTextChanged;
|
|
_channelSelectorPopup.OnPopupHide += OnChannelSelectorPopupHide;
|
|
_filterPopup.OnPopupHide += OnFilterPopupHide;
|
|
}
|
|
|
|
protected override void EnteredTree()
|
|
{
|
|
base.EnteredTree();
|
|
|
|
ChatMgr.MessageAdded += WriteChatMessage;
|
|
ChatMgr.ChatPermissionsUpdated += OnChatPermissionsUpdated;
|
|
ChatMgr.UnreadMessageCountsUpdated += UpdateUnreadMessageCounts;
|
|
ChatMgr.FiltersUpdated += Repopulate;
|
|
|
|
// The chat manager may have messages logged from before there was a chat box.
|
|
// In this case, these messages will be marked as unread despite the filters allowing them through.
|
|
// Tell chat manager to clear these.
|
|
ChatMgr.ClearUnfilteredUnreads();
|
|
|
|
ChatPermissionsUpdated(0);
|
|
UpdateChannelSelectButton();
|
|
Repopulate();
|
|
}
|
|
|
|
protected override void ExitedTree()
|
|
{
|
|
base.ExitedTree();
|
|
|
|
ChatMgr.MessageAdded -= WriteChatMessage;
|
|
ChatMgr.ChatPermissionsUpdated -= OnChatPermissionsUpdated;
|
|
ChatMgr.UnreadMessageCountsUpdated -= UpdateUnreadMessageCounts;
|
|
ChatMgr.FiltersUpdated -= Repopulate;
|
|
}
|
|
|
|
private void OnChatPermissionsUpdated(ChatPermissionsUpdatedEventArgs eventArgs)
|
|
{
|
|
ChatPermissionsUpdated(eventArgs.OldSelectableChannels);
|
|
}
|
|
|
|
private void ChatPermissionsUpdated(ChatSelectChannel oldSelectable)
|
|
{
|
|
// update the channel selector
|
|
_channelSelectorHBox.Children.Clear();
|
|
foreach (var selectableChannel in ChannelSelectorOrder)
|
|
{
|
|
if ((ChatMgr.SelectableChannels & selectableChannel) == 0)
|
|
continue;
|
|
|
|
var newButton = new ChannelItemButton(selectableChannel);
|
|
newButton.OnPressed += OnChannelSelectorItemPressed;
|
|
_channelSelectorHBox.AddChild(newButton);
|
|
}
|
|
|
|
// Selected channel no longer available, switch to OOC?
|
|
if ((ChatMgr.SelectableChannels & SelectedChannel) == 0)
|
|
{
|
|
// Handle local -> dead mapping when you e.g. ghost.
|
|
// Only necessary for admins because they always have deadchat
|
|
// so the normal preferred check won't see it as newly available and do nothing.
|
|
var mappedSelect = MapLocalIfGhost(SelectedChannel);
|
|
if ((ChatMgr.SelectableChannels & mappedSelect) != 0)
|
|
SafelySelectChannel(mappedSelect);
|
|
else
|
|
SafelySelectChannel(ChatSelectChannel.OOC);
|
|
}
|
|
|
|
// If the preferred channel just became available, switch to it.
|
|
var pref = MapLocalIfGhost(PreferredChannel);
|
|
if ((oldSelectable & pref) == 0 && (ChatMgr.SelectableChannels & pref) != 0)
|
|
SafelySelectChannel(pref);
|
|
|
|
// update the channel filters
|
|
_filterVBox.Children.Clear();
|
|
foreach (var channelFilter in ChannelFilterOrder)
|
|
{
|
|
if ((ChatMgr.FilterableChannels & channelFilter) == 0)
|
|
continue;
|
|
|
|
int? unreadCount = null;
|
|
if (ChatMgr.UnreadMessages.TryGetValue(channelFilter, out var unread))
|
|
unreadCount = unread;
|
|
|
|
var newCheckBox = new ChannelFilterCheckbox(channelFilter, unreadCount)
|
|
{
|
|
Pressed = (ChatMgr.ChannelFilters & channelFilter) != 0
|
|
};
|
|
|
|
newCheckBox.OnToggled += OnFilterCheckboxToggled;
|
|
_filterVBox.AddChild(newCheckBox);
|
|
}
|
|
|
|
UpdateChannelSelectButton();
|
|
}
|
|
|
|
private void UpdateUnreadMessageCounts()
|
|
{
|
|
foreach (var channelFilter in _filterVBox.Children)
|
|
{
|
|
if (channelFilter is not ChannelFilterCheckbox filterCheckbox) continue;
|
|
if (ChatMgr.UnreadMessages.TryGetValue(filterCheckbox.Channel, out var unread))
|
|
{
|
|
filterCheckbox.UpdateUnreadCount(unread);
|
|
}
|
|
else
|
|
{
|
|
filterCheckbox.UpdateUnreadCount(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnFilterCheckboxToggled(BaseButton.ButtonToggledEventArgs args)
|
|
{
|
|
if (args.Button is not ChannelFilterCheckbox checkbox)
|
|
return;
|
|
|
|
ChatMgr.OnFilterButtonToggled(checkbox.Channel, checkbox.Pressed);
|
|
}
|
|
|
|
private void OnFilterButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
|
{
|
|
if (args.Pressed)
|
|
{
|
|
var globalPos = FilterButton.GlobalPosition;
|
|
var (minX, minY) = _filterPopupPanel.MinSize;
|
|
var box = UIBox2.FromDimensions(globalPos - (FilterPopupWidth, 0),
|
|
(Math.Max(minX, FilterPopupWidth), minY));
|
|
UserInterfaceManager.ModalRoot.AddChild(_filterPopup);
|
|
_filterPopup.Open(box);
|
|
}
|
|
else
|
|
{
|
|
_filterPopup.Close();
|
|
}
|
|
}
|
|
|
|
private void OnChannelSelectorToggled(BaseButton.ButtonToggledEventArgs args)
|
|
{
|
|
if (args.Pressed)
|
|
{
|
|
var globalLeft = GlobalPosition.X;
|
|
var globalBot = GlobalPosition.Y + Height;
|
|
var box = UIBox2.FromDimensions((globalLeft, globalBot), (SizeBox.Width, AlertsUI.ChatSeparation));
|
|
UserInterfaceManager.ModalRoot.AddChild(_channelSelectorPopup);
|
|
_channelSelectorPopup.Open(box);
|
|
}
|
|
else
|
|
{
|
|
_channelSelectorPopup.Close();
|
|
}
|
|
}
|
|
|
|
private void OnFilterPopupHide()
|
|
{
|
|
OnPopupHide(_filterPopup, FilterButton);
|
|
}
|
|
|
|
private void OnChannelSelectorPopupHide()
|
|
{
|
|
OnPopupHide(_channelSelectorPopup, ChannelSelector);
|
|
}
|
|
|
|
private void OnPopupHide(Control popup, BaseButton button)
|
|
{
|
|
UserInterfaceManager.ModalRoot.RemoveChild(popup);
|
|
|
|
// this weird check here is because the hiding of the popup happens prior to the button
|
|
// receiving the keydown, which would cause it to then become unpressed
|
|
// and reopen immediately. To avoid this, if the popup was hidden due to clicking on the button,
|
|
// we will not auto-unpress the button, instead leaving it up to the button toggle logic
|
|
// (and this requires the button to be set to EnableAllKeybinds = true)
|
|
if (UserInterfaceManager.CurrentlyHovered != button)
|
|
{
|
|
button.Pressed = false;
|
|
}
|
|
}
|
|
|
|
private void OnChannelSelectorItemPressed(BaseButton.ButtonEventArgs obj)
|
|
{
|
|
if (obj.Button is not ChannelItemButton button)
|
|
return;
|
|
|
|
PreferredChannel = button.Channel;
|
|
SafelySelectChannel(button.Channel);
|
|
_channelSelectorPopup.Close();
|
|
}
|
|
|
|
public bool SafelySelectChannel(ChatSelectChannel toSelect)
|
|
{
|
|
toSelect = MapLocalIfGhost(toSelect);
|
|
if ((ChatMgr.SelectableChannels & toSelect) == 0)
|
|
return false;
|
|
|
|
SelectedChannel = toSelect;
|
|
UpdateChannelSelectButton();
|
|
return true;
|
|
}
|
|
|
|
private void UpdateChannelSelectButton()
|
|
{
|
|
var (prefixChannel, _) = SplitInputContents();
|
|
|
|
var channel = prefixChannel == 0 ? SelectedChannel : prefixChannel;
|
|
|
|
ChannelSelector.Text = ChannelSelectorName(channel);
|
|
ChannelSelector.Modulate = ChannelSelectColor(channel);
|
|
}
|
|
|
|
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
base.KeyBindDown(args);
|
|
|
|
if (args.CanFocus)
|
|
{
|
|
Input.GrabKeyboardFocus();
|
|
}
|
|
}
|
|
|
|
public void CycleChatChannel(bool forward)
|
|
{
|
|
Input.IgnoreNext = true;
|
|
|
|
var idx = Array.IndexOf(ChannelSelectorOrder, SelectedChannel);
|
|
do
|
|
{
|
|
// go over every channel until we find one we can actually select.
|
|
idx += forward ? 1 : -1;
|
|
idx = MathHelper.Mod(idx, ChannelSelectorOrder.Length);
|
|
} while ((ChatMgr.SelectableChannels & ChannelSelectorOrder[idx]) == 0);
|
|
|
|
SafelySelectChannel(ChannelSelectorOrder[idx]);
|
|
}
|
|
|
|
private void Repopulate()
|
|
{
|
|
Contents.Clear();
|
|
|
|
foreach (var msg in ChatMgr.History)
|
|
{
|
|
WriteChatMessage(msg);
|
|
}
|
|
}
|
|
|
|
private void WriteChatMessage(StoredChatMessage message)
|
|
{
|
|
Logger.DebugS("chat", $"{message.Channel}: {message.Message}");
|
|
|
|
if (IsFilteredOut(message.Channel))
|
|
return;
|
|
|
|
// TODO: Can make this "smarter" later by only setting it false when the message has been scrolled to
|
|
message.Read = true;
|
|
|
|
var messageText = FormattedMessage.EscapeText(message.Message);
|
|
if (!string.IsNullOrEmpty(message.MessageWrap))
|
|
{
|
|
messageText = string.Format(message.MessageWrap, messageText);
|
|
}
|
|
|
|
var color = message.MessageColorOverride != Color.Transparent
|
|
? message.MessageColorOverride
|
|
: ChatHelper.ChatColor(message.Channel);
|
|
|
|
AddLine(messageText, message.Channel, color);
|
|
}
|
|
|
|
private bool IsFilteredOut(ChatChannel channel)
|
|
{
|
|
return (ChatMgr.ChannelFilters & channel) == 0;
|
|
}
|
|
|
|
private void InputKeyBindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
if (args.Function == EngineKeyFunctions.TextReleaseFocus)
|
|
{
|
|
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 (ChatSelectChannel selChannel, ReadOnlyMemory<char> text) SplitInputContents()
|
|
{
|
|
var text = Input.Text.AsMemory().Trim();
|
|
if (text.Length == 0)
|
|
return default;
|
|
|
|
var prefixChar = text.Span[0];
|
|
var channel = GetChannelFromPrefix(prefixChar);
|
|
|
|
if ((ChatMgr.SelectableChannels & channel) != 0)
|
|
// Cut off prefix if it's valid and we can use the channel in question.
|
|
text = text[1..];
|
|
else
|
|
channel = 0;
|
|
|
|
channel = MapLocalIfGhost(channel);
|
|
|
|
// Trim from start again to cut out any whitespace between the prefix and message, if any.
|
|
return (channel, text.TrimStart());
|
|
}
|
|
|
|
private void InputOnTextChanged(LineEdit.LineEditEventArgs obj)
|
|
{
|
|
// Update channel select button to correct channel if we have a prefix.
|
|
UpdateChannelSelectButton();
|
|
}
|
|
|
|
private static ChatSelectChannel GetChannelFromPrefix(char prefix)
|
|
{
|
|
return PrefixToChannel.GetValueOrDefault(prefix);
|
|
}
|
|
|
|
public static char GetPrefixFromChannel(ChatSelectChannel channel)
|
|
{
|
|
return ChannelPrefixes.GetValueOrDefault(channel);
|
|
}
|
|
|
|
public static string ChannelSelectorName(ChatSelectChannel channel)
|
|
{
|
|
return Loc.GetString($"hud-chatbox-select-channel-{channel}");
|
|
}
|
|
|
|
public static Color ChannelSelectColor(ChatSelectChannel channel)
|
|
{
|
|
return channel switch
|
|
{
|
|
ChatSelectChannel.Radio => Color.Green,
|
|
ChatSelectChannel.OOC => Color.LightSkyBlue,
|
|
ChatSelectChannel.Dead => Color.MediumPurple,
|
|
ChatSelectChannel.Admin => Color.Red,
|
|
_ => Color.DarkGray
|
|
};
|
|
}
|
|
|
|
public void AddLine(string message, ChatChannel channel, Color color)
|
|
{
|
|
DebugTools.Assert(!Disposed);
|
|
|
|
var formatted = new FormattedMessage(3);
|
|
formatted.PushColor(color);
|
|
formatted.AddMarkup(message);
|
|
formatted.Pop();
|
|
Contents.AddMessage(formatted);
|
|
}
|
|
|
|
private void Input_OnTextEntered(LineEdit.LineEditEventArgs args)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(args.Text))
|
|
{
|
|
var (prefixChannel, text) = SplitInputContents();
|
|
|
|
// Check if message is longer than the character limit
|
|
if (text.Length > ChatMgr.MaxMessageLength)
|
|
{
|
|
string locWarning = Loc.GetString(
|
|
"chat-manager-max-message-length",
|
|
("maxMessageLength", ChatMgr.MaxMessageLength));
|
|
|
|
AddLine(locWarning, ChatChannel.Server, Color.Orange);
|
|
return;
|
|
}
|
|
|
|
ChatMgr.OnChatBoxTextSubmitted(this, text, prefixChannel == 0 ? SelectedChannel : prefixChannel);
|
|
}
|
|
|
|
Input.Clear();
|
|
UpdateChannelSelectButton();
|
|
|
|
if (ReleaseFocusOnEnter)
|
|
Input.ReleaseKeyboardFocus();
|
|
}
|
|
|
|
public void Focus(ChatSelectChannel? channel = null)
|
|
{
|
|
var selectStart = Index.End;
|
|
if (channel != null)
|
|
{
|
|
channel = MapLocalIfGhost(channel.Value);
|
|
|
|
// Channel not selectable, just do NOTHING (not even focus).
|
|
if (!((ChatMgr.SelectableChannels & channel.Value) != 0))
|
|
return;
|
|
|
|
var (_, text) = SplitInputContents();
|
|
|
|
var newPrefix = 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);
|
|
}
|
|
|
|
UpdateChannelSelectButton();
|
|
}
|
|
|
|
Input.IgnoreNext = true;
|
|
Input.GrabKeyboardFocus();
|
|
|
|
Input.CursorPosition = Input.Text.Length;
|
|
Input.SelectionStart = selectStart.GetOffset(Input.Text.Length);
|
|
}
|
|
|
|
private ChatSelectChannel MapLocalIfGhost(ChatSelectChannel channel)
|
|
{
|
|
if (channel == ChatSelectChannel.Local && ChatMgr.IsGhost)
|
|
return ChatSelectChannel.Dead;
|
|
|
|
return channel;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Only needed to avoid the issue where right click on the button closes the popup
|
|
/// but leaves the button highlighted.
|
|
/// </summary>
|
|
public sealed class ChannelSelectorButton : Button
|
|
{
|
|
public ChannelSelectorButton()
|
|
{
|
|
// needed so the popup is untoggled regardless of which key is pressed when hovering this button.
|
|
// If we don't have this, then right clicking the button while it's toggled on will hide
|
|
// the popup but keep the button toggled on
|
|
Mode = ActionMode.Press;
|
|
EnableAllKeybinds = true;
|
|
}
|
|
|
|
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
// needed since we need EnableAllKeybinds - don't double-send both UI click and Use
|
|
if (args.Function == EngineKeyFunctions.Use)
|
|
return;
|
|
|
|
base.KeyBindDown(args);
|
|
}
|
|
}
|
|
|
|
public sealed class FilterButton : ContainerButton
|
|
{
|
|
private static readonly Color ColorNormal = Color.FromHex("#7b7e9e");
|
|
private static readonly Color ColorHovered = Color.FromHex("#9699bb");
|
|
private static readonly Color ColorPressed = Color.FromHex("#789B8C");
|
|
|
|
private readonly TextureRect _textureRect;
|
|
|
|
public FilterButton()
|
|
{
|
|
var filterTexture = IoCManager.Resolve<IResourceCache>()
|
|
.GetTexture("/Textures/Interface/Nano/filter.svg.96dpi.png");
|
|
|
|
// needed for same reason as ChannelSelectorButton
|
|
Mode = ActionMode.Press;
|
|
EnableAllKeybinds = true;
|
|
|
|
AddChild(
|
|
(_textureRect = new TextureRect
|
|
{
|
|
Texture = filterTexture,
|
|
HorizontalAlignment = HAlignment.Center,
|
|
VerticalAlignment = VAlignment.Center
|
|
})
|
|
);
|
|
|
|
ToggleMode = true;
|
|
}
|
|
|
|
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
// needed since we need EnableAllKeybinds - don't double-send both UI click and Use
|
|
if (args.Function == EngineKeyFunctions.Use) return;
|
|
base.KeyBindDown(args);
|
|
}
|
|
|
|
private void UpdateChildColors()
|
|
{
|
|
if (_textureRect == null) return;
|
|
switch (DrawMode)
|
|
{
|
|
case DrawModeEnum.Normal:
|
|
_textureRect.ModulateSelfOverride = ColorNormal;
|
|
break;
|
|
|
|
case DrawModeEnum.Pressed:
|
|
_textureRect.ModulateSelfOverride = ColorPressed;
|
|
break;
|
|
|
|
case DrawModeEnum.Hover:
|
|
_textureRect.ModulateSelfOverride = ColorHovered;
|
|
break;
|
|
|
|
case DrawModeEnum.Disabled:
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected override void DrawModeChanged()
|
|
{
|
|
base.DrawModeChanged();
|
|
UpdateChildColors();
|
|
}
|
|
|
|
protected override void StylePropertiesChanged()
|
|
{
|
|
base.StylePropertiesChanged();
|
|
UpdateChildColors();
|
|
}
|
|
}
|
|
|
|
public sealed class ChannelItemButton : Button
|
|
{
|
|
public readonly ChatSelectChannel Channel;
|
|
|
|
public ChannelItemButton(ChatSelectChannel channel)
|
|
{
|
|
Channel = channel;
|
|
AddStyleClass(StyleNano.StyleClassChatChannelSelectorButton);
|
|
Text = ChatBox.ChannelSelectorName(channel);
|
|
|
|
var prefix = ChatBox.GetPrefixFromChannel(channel);
|
|
if (prefix != default)
|
|
Text = Loc.GetString("hud-chatbox-select-name-prefixed", ("name", Text), ("prefix", prefix));
|
|
}
|
|
}
|
|
|
|
public sealed class ChannelFilterCheckbox : CheckBox
|
|
{
|
|
public readonly ChatChannel Channel;
|
|
|
|
public ChannelFilterCheckbox(ChatChannel channel, int? unreadCount)
|
|
{
|
|
Channel = channel;
|
|
|
|
UpdateText(unreadCount);
|
|
}
|
|
|
|
private void UpdateText(int? unread)
|
|
{
|
|
var name = Loc.GetString($"hud-chatbox-channel-{Channel}");
|
|
|
|
if (unread > 0)
|
|
// todo: proper fluent stuff here.
|
|
name += " (" + (unread > 9 ? "9+" : unread) + ")";
|
|
|
|
Text = name;
|
|
}
|
|
|
|
public void UpdateUnreadCount(int? unread)
|
|
{
|
|
UpdateText(unread);
|
|
}
|
|
}
|
|
|
|
public readonly struct ChatResizedEventArgs
|
|
{
|
|
/// new bottom that the chat rect is going to have in virtual pixels
|
|
/// after the imminent relayout
|
|
public readonly float NewBottom;
|
|
|
|
public ChatResizedEventArgs(float newBottom)
|
|
{
|
|
NewBottom = newBottom;
|
|
}
|
|
}
|
|
}
|