using System.Linq; using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared._DV.QuickPhrase; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; namespace Content.Client._DV.AACTablet.UI; [GenerateTypedNameReferences] public sealed partial class AACWindow : FancyWindow { [Dependency] private readonly IPrototypeManager _prototype = default!; private readonly List _phrases; private readonly Dictionary> _filteredPhrases = new(); public event Action>>? PhraseButtonPressed; public event Action? Typing; public event Action? SubmitPressed; private const float SpaceWidth = 10f; private const float ParentWidth = 540f; private const int ColumnCount = 4; private const int ButtonWidth = (int)((ParentWidth - SpaceWidth * 2) / ColumnCount - SpaceWidth * ((ColumnCount - 1f) / ColumnCount)); public const int MaxPhrases = 10; // no writing novels private readonly List> _phraseBuffer = []; private readonly List> _phraseSingle = []; public AACWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _phrases = _prototype.EnumeratePrototypes().ToList(); _phrases.Sort((a, b) => string.CompareOrdinal(a.Group, b.Group)); SearchBar.OnTextChanged += FilterSearch; SendButton.OnPressed += SendBuffer; ClearButton.OnPressed += BackspaceBuffer; PopulateGui(); FilterSearch(null); } private void BackspaceBuffer(BaseButton.ButtonEventArgs obj) { if (_phraseBuffer.Count == 0) return; _phraseBuffer.RemoveAt(_phraseBuffer.Count - 1); UpdateBufferText(); Typing?.Invoke(); } private void UpdateBufferText() { BufferedString.Text = string.Empty; foreach (var phraseId in _phraseBuffer) { var phrase = _prototype.Index(phraseId); BufferedString.Text += Loc.GetString(phrase.Text) + " "; } } private void SendBuffer(BaseButton.ButtonEventArgs obj) { PhraseButtonPressed?.Invoke(_phraseBuffer); _phraseBuffer.Clear(); BufferedString.Text = string.Empty; SubmitPressed?.Invoke(); } private void FilterSearch(LineEdit.LineEditEventArgs? obj) { SearchResults.DisposeAllChildren(); _filteredPhrases.Clear(); var emptySearch = string.IsNullOrEmpty(SearchBar.Text); foreach (var phrase in _phrases) { if (!emptySearch && !Loc.GetString(phrase.Text).Contains(SearchBar.Text, StringComparison.CurrentCultureIgnoreCase)) { continue; } if (_filteredPhrases.TryGetValue(phrase.Group, out var group)) { group.Add(phrase); } else { _filteredPhrases.Add(phrase.Group, new List { phrase }); } } foreach (var phraseList in _filteredPhrases.Values) { phraseList.Sort((a, b) => string.Compare(Loc.GetString(a.Text), Loc.GetString(b.Text), StringComparison.CurrentCultureIgnoreCase)); } var boxContainer = CreateBoxContainerForTab(_filteredPhrases); SearchResults.AddChild(boxContainer); } private void PopulateGui() { // take ALL phrases and turn them into tabs and groups, so the buttons are sorted and tabbed var sortedTabs = _phrases .GroupBy(p => p.Tab) .OrderBy(g => g.Key) .ToDictionary( g => g.Key, g => g.GroupBy(p => p.Group) .OrderBy(gg => gg.Key) .ToDictionary( gg => gg.Key, gg => gg.OrderBy(p => Loc.GetString(p.Text)).ToList() ) ); CreateTabContainer(sortedTabs); } private void CreateTabContainer(Dictionary>> sortedTabs) { foreach (var tab in sortedTabs) { var tabName = Loc.GetString(tab.Key); var boxContainer = CreateBoxContainerForTab(tab.Value); var scroll = new ScrollContainer(); scroll.HScrollEnabled = false; scroll.AddChild(boxContainer); WindowBody.AddChild(scroll); WindowBody.SetTabTitle(WindowBody.ChildCount - 1, tabName); } } private BoxContainer CreateBoxContainerForTab(Dictionary> groups) { var boxContainer = new BoxContainer { HorizontalExpand = true, Orientation = BoxContainer.LayoutOrientation.Vertical }; foreach (var group in groups) { var header = CreateHeaderForGroup(group.Key); var buttonContainer = CreateButtonContainerForGroup(group.Value); boxContainer.AddChild(header); boxContainer.AddChild(buttonContainer); } return boxContainer; } private static Label CreateHeaderForGroup(string groupName) { var header = new Label { HorizontalExpand = true, Text = groupName, Margin = new Thickness(10, 10, 10, 0), StyleClasses = { "LabelBig" } }; return header; } private GridContainer CreateButtonContainerForGroup(List phrases) { var buttonContainer = CreateButtonContainer(); foreach (var phrase in phrases) { var text = Loc.GetString(phrase.Text); var button = CreatePhraseButton(text, phrase.StyleClass); button.OnPressed += _ => OnPhraseButtonPressed(new ProtoId(phrase.ID)); buttonContainer.AddChild(button); } return buttonContainer; } private static GridContainer CreateButtonContainer() { var buttonContainer = new GridContainer { Margin = new Thickness(10), Columns = ColumnCount }; return buttonContainer; } private static Button CreatePhraseButton(string text, string styleClass) { var phraseButton = new Button { Access = AccessLevel.Public, MaxSize = new Vector2(ButtonWidth, ButtonWidth), ClipText = false, HorizontalExpand = true, StyleClasses = { styleClass } }; var buttonLabel = new RichTextLabel { Margin = new Thickness(0, 5), StyleClasses = { "WhiteText" } }; buttonLabel.SetMessage(text); phraseButton.AddChild(buttonLabel); return phraseButton; } private void OnPhraseButtonPressed(ProtoId phraseId) { if (ShouldBuffer.Pressed) { // there's no user feedback but you shouldn't be writing novels anyway if (_phraseBuffer.Count >= MaxPhrases) return; _phraseBuffer.Add(phraseId); UpdateBufferText(); Typing?.Invoke(); } else { _phraseSingle.Clear(); _phraseSingle.Add(phraseId); PhraseButtonPressed?.Invoke(_phraseSingle); SubmitPressed?.Invoke(); } } }