Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Content.Client/Chat/TypingIndicator/TypingIndicatorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
namespace Content.Client.Chat.TypingIndicator;

// Client-side typing system tracks user input in chat box
public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
public sealed partial class TypingIndicatorSystem : SharedTypingIndicatorSystem // DeltaV - Made partial
{
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
Expand All @@ -34,6 +34,7 @@ public override void Initialize()
base.Initialize();

Subs.CVar(_cfg, CCVars.ChatShowTypingIndicator, OnShowTypingChanged);
InitializeAlternateTyping(); // DeltaV
}

public void ClientChangedChatText()
Expand Down Expand Up @@ -113,4 +114,4 @@ private void OnShowTypingChanged(bool showTyping)
ClientUpdateTyping();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorCompone
if (overrideIndicator != null)
currentTypingIndicator = overrideIndicator.Value;

// Begin DeltaV Additions - AAC TypingIndicator Override
if (component.TypingIndicatorOverridePrototype != null)
{
currentTypingIndicator = component.TypingIndicatorOverridePrototype.Value;
}
// End DeltaV Additions

if (!_prototypeManager.TryIndex(currentTypingIndicator, out var proto))
{
Log.Error($"Unknown typing indicator id: {component.TypingIndicatorPrototype}");
Expand Down Expand Up @@ -67,4 +74,4 @@ protected override void OnAppearanceChange(EntityUid uid, TypingIndicatorCompone
break;
}
}
}
}
55 changes: 55 additions & 0 deletions Content.Client/_DV/AACTablet/UI/AACBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Content.Client.Chat.TypingIndicator;
using Content.Shared._DV.AACTablet;
using Content.Shared._DV.QuickPhrase;
using Content.Shared.Chat.TypingIndicator;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;

namespace Content.Client._DV.AACTablet.UI;

public sealed class AACBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private AACWindow? _window;

private static readonly ProtoId<TypingIndicatorPrototype> AACTypingIndicator = "aac";

private TypingIndicatorSystem? _typing;

protected override void Open()
{
base.Open();
_window = new AACWindow(Owner);
_window.OpenCentered();
_window.OnClose += Close;
_window.PhraseButtonPressed += OnPhraseButtonPressed;
_window.Typing += OnTyping;
_window.SubmitPressed += OnSubmit;
}

private void OnPhraseButtonPressed(List<ProtoId<QuickPhrasePrototype>> phraseId)
{
SendMessage(new AACTabletSendPhraseMessage(phraseId));
}

private void OnTyping()
{
_typing ??= EntMan.System<TypingIndicatorSystem>();
_typing?.ClientAlternateTyping(AACTypingIndicator);
}

private void OnSubmit()
{
_typing ??= EntMan.System<TypingIndicatorSystem>();
_typing?.ClientSubmittedChatText();
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;

_window?.Parent?.RemoveChild(_window);
}
}
23 changes: 23 additions & 0 deletions Content.Client/_DV/AACTablet/UI/AACWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'aac-tablet-title'}"
Resizable="True"
SetSize="780 420"
MinSize="540 300">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<CheckBox Name="ShouldBuffer" Text="{Loc 'aac-tablet-combine'}"/>
<LineEdit Name="BufferedString" Editable="False" HorizontalExpand="True"/>
<Button Name="ClearButton" Text="{Loc 'aac-tablet-backspace'}" TextAlign="Center"/>
<Button Name="SendButton" Text="{Loc 'aac-tablet-send'}" TextAlign="Center"/>
</BoxContainer>
<TabContainer Name="WindowBody" TabTitle="Search" VerticalExpand="True" MouseFilter="Pass">
<BoxContainer Orientation="Vertical" Name="Search" MinSize="540 200">
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True" Margin="4 4" />
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Name="SearchResults" Orientation="Vertical" HorizontalExpand="True" />
</ScrollContainer>
</BoxContainer>
</TabContainer>
</BoxContainer>
</controls:FancyWindow>
262 changes: 262 additions & 0 deletions Content.Client/_DV/AACTablet/UI/AACWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
using Content.Client.Salvage.UI;
using Content.Client.UserInterface.Controls;
using Content.Shared._DV.AACTablet;
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.GameObjects;
using Robust.Shared.Prototypes;
using System.Linq;
using System.Numerics;

namespace Content.Client._DV.AACTablet.UI;

[GenerateTypedNameReferences]
public sealed partial class AACWindow : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
private readonly List<QuickPhrasePrototype> _phrases = [];
private readonly Dictionary<string, List<QuickPhrasePrototype>> _filteredPhrases = new();
public event Action<List<ProtoId<QuickPhrasePrototype>>>? PhraseButtonPressed;
public event Action? Typing;
public event Action? SubmitPressed;

private const float SpaceWidth = 3f;
private const float ParentWidth = 470f;
private const int ColumnCount = 3;

private const int ButtonWidth =
(int)((ParentWidth - SpaceWidth * 2) / ColumnCount - SpaceWidth * ((ColumnCount - 1f) / ColumnCount));

private const int ButtonHeight =
(int)(ButtonWidth * .5f);

public const int MaxPhrases = 10; // no writing novels

private readonly List<ProtoId<QuickPhrasePrototype>> _phraseBuffer = [];
private readonly List<ProtoId<QuickPhrasePrototype>> _phraseSingle = [];

public AACWindow(EntityUid owner)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

// begin imp
if (!_entMan.TryGetComponent<AACTabletComponent>(owner, out var comp))
return;

List<QuickPhrasePrototype> protosAsList = [];
var phraseProtos = _prototype.Index(comp.PhraseGroup).Prototypes;
foreach (var protoId in phraseProtos)
{
protosAsList.Add(_prototype.Index(protoId));
}

_phrases = protosAsList;
// end imp
_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<QuickPhrasePrototype> { 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<string, Dictionary<string, List<QuickPhrasePrototype>>> 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<string, List<QuickPhrasePrototype>> 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<QuickPhrasePrototype> 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<QuickPhrasePrototype>(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,
MinSize = new Vector2(ButtonWidth, ButtonHeight),
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<QuickPhrasePrototype> 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();
}
}
}
Loading
Loading