Port fancy speech bubbles (#29349)

This commit is contained in:
lzk
2025-05-06 19:49:42 +02:00
committed by GitHub
parent a6f2172d05
commit 740ce0e8ad
26 changed files with 273 additions and 39 deletions

View File

@@ -16,6 +16,7 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
private readonly TimeSpan _typingTimeout = TimeSpan.FromSeconds(2); private readonly TimeSpan _typingTimeout = TimeSpan.FromSeconds(2);
private TimeSpan _lastTextChange; private TimeSpan _lastTextChange;
private bool _isClientTyping; private bool _isClientTyping;
private bool _isClientChatFocused;
public override void Initialize() public override void Initialize()
{ {
@@ -31,7 +32,8 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
return; return;
// client typed something - show typing indicator // client typed something - show typing indicator
ClientUpdateTyping(true); _isClientTyping = true;
ClientUpdateTyping();
_lastTextChange = _time.CurTime; _lastTextChange = _time.CurTime;
} }
@@ -42,7 +44,19 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
return; return;
// client submitted text - hide typing indicator // client submitted text - hide typing indicator
ClientUpdateTyping(false); _isClientTyping = false;
ClientUpdateTyping();
}
public void ClientChangedChatFocus(bool isFocused)
{
// don't update it if player don't want to show typing
if (!_cfg.GetCVar(CCVars.ChatShowTypingIndicator))
return;
// client submitted text - hide typing indicator
_isClientChatFocused = isFocused;
ClientUpdateTyping();
} }
public override void Update(float frameTime) public override void Update(float frameTime)
@@ -55,23 +69,25 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
var dif = _time.CurTime - _lastTextChange; var dif = _time.CurTime - _lastTextChange;
if (dif > _typingTimeout) if (dif > _typingTimeout)
{ {
// client didn't typed anything for a long time - hide indicator // client didn't typed anything for a long time - change indicator
ClientUpdateTyping(false); _isClientTyping = false;
ClientUpdateTyping();
} }
} }
} }
private void ClientUpdateTyping(bool isClientTyping) private void ClientUpdateTyping()
{ {
if (_isClientTyping == isClientTyping) // check if player controls any pawn
return;
// check if player controls any entity.
if (_playerManager.LocalEntity == null) if (_playerManager.LocalEntity == null)
return; return;
_isClientTyping = isClientTyping; var state = TypingIndicatorState.None;
RaisePredictiveEvent(new TypingChangedEvent(isClientTyping)); if (_isClientChatFocused)
state = _isClientTyping ? TypingIndicatorState.Typing : TypingIndicatorState.Idle;
// send a networked event to server
RaisePredictiveEvent(new TypingChangedEvent(state));
} }
private void OnShowTypingChanged(bool showTyping) private void OnShowTypingChanged(bool showTyping)
@@ -79,7 +95,8 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
// hide typing indicator immediately if player don't want to show it anymore // hide typing indicator immediately if player don't want to show it anymore
if (!showTyping) if (!showTyping)
{ {
ClientUpdateTyping(false); _isClientTyping = false;
ClientUpdateTyping();
} }
} }
} }

View File

@@ -35,7 +35,6 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
return; return;
} }
AppearanceSystem.TryGetData<bool>(uid, TypingIndicatorVisuals.IsTyping, out var isTyping, args.Component);
var layerExists = args.Sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer); var layerExists = args.Sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer);
if (!layerExists) if (!layerExists)
layer = args.Sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base); layer = args.Sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base);
@@ -44,6 +43,17 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
args.Sprite.LayerSetState(layer, proto.TypingState); args.Sprite.LayerSetState(layer, proto.TypingState);
args.Sprite.LayerSetShader(layer, proto.Shader); args.Sprite.LayerSetShader(layer, proto.Shader);
args.Sprite.LayerSetOffset(layer, proto.Offset); args.Sprite.LayerSetOffset(layer, proto.Offset);
args.Sprite.LayerSetVisible(layer, isTyping);
AppearanceSystem.TryGetData<TypingIndicatorState>(uid, TypingIndicatorVisuals.State, out var state);
args.Sprite.LayerSetVisible(layer, state != TypingIndicatorState.None);
switch (state)
{
case TypingIndicatorState.Idle:
args.Sprite.LayerSetState(layer, proto.IdleState);
break;
case TypingIndicatorState.Typing:
args.Sprite.LayerSetState(layer, proto.TypingState);
break;
}
} }
} }

View File

@@ -46,7 +46,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
if (!HasComp<HolopadUserComponent>(uid)) if (!HasComp<HolopadUserComponent>(uid))
return; return;
var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.IsTyping); var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.State);
RaiseNetworkEvent(netEv); RaiseNetworkEvent(netEv);
} }

View File

@@ -918,6 +918,11 @@ public sealed class ChatUIController : UIController
_typingIndicator?.ClientChangedChatText(); _typingIndicator?.ClientChangedChatText();
} }
public void NotifyChatFocus(bool isFocused)
{
_typingIndicator?.ClientChangedChatFocus(isFocused);
}
public void Repopulate() public void Repopulate()
{ {
foreach (var chat in _chats) foreach (var chat in _chats)

View File

@@ -34,6 +34,8 @@ public partial class ChatBox : UIWidget
ChatInput.Input.OnTextEntered += OnTextEntered; ChatInput.Input.OnTextEntered += OnTextEntered;
ChatInput.Input.OnKeyBindDown += OnInputKeyBindDown; ChatInput.Input.OnKeyBindDown += OnInputKeyBindDown;
ChatInput.Input.OnTextChanged += OnTextChanged; ChatInput.Input.OnTextChanged += OnTextChanged;
ChatInput.Input.OnFocusEnter += OnFocusEnter;
ChatInput.Input.OnFocusExit += OnFocusExit;
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect; ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter; ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter;
@@ -174,6 +176,18 @@ public partial class ChatBox : UIWidget
_controller.NotifyChatTextChange(); _controller.NotifyChatTextChange();
} }
private void OnFocusEnter(LineEditEventArgs args)
{
// Warn typing indicator about focus
_controller.NotifyChatFocus(true);
}
private void OnFocusExit(LineEditEventArgs args)
{
// Warn typing indicator about focus
_controller.NotifyChatFocus(false);
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);

View File

@@ -309,7 +309,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
if (receiverHolopad.Comp.Hologram == null) if (receiverHolopad.Comp.Hologram == null)
continue; continue;
_appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, ev.IsTyping); _appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.State, ev.State);
} }
} }
} }
@@ -591,7 +591,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
continue; continue;
if (user == null) if (user == null)
_appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false); _appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.State, false);
linkedHolopad.Comp.Hologram.Value.Comp.LinkedEntity = user; linkedHolopad.Comp.Hologram.Value.Comp.LinkedEntity = user;
Dirty(linkedHolopad.Comp.Hologram.Value); Dirty(linkedHolopad.Comp.Hologram.Value);

View File

@@ -45,7 +45,7 @@ public abstract class SharedTypingIndicatorSystem : EntitySystem
private void OnPlayerDetached(EntityUid uid, TypingIndicatorComponent component, PlayerDetachedEvent args) private void OnPlayerDetached(EntityUid uid, TypingIndicatorComponent component, PlayerDetachedEvent args)
{ {
// player left entity body - hide typing indicator // player left entity body - hide typing indicator
SetTypingIndicatorEnabled(uid, false); SetTypingIndicatorState(uid, TypingIndicatorState.None);
} }
private void OnGotEquipped(Entity<TypingIndicatorClothingComponent> entity, ref ClothingGotEquippedEvent args) private void OnGotEquipped(Entity<TypingIndicatorClothingComponent> entity, ref ClothingGotEquippedEvent args)
@@ -76,18 +76,18 @@ public abstract class SharedTypingIndicatorSystem : EntitySystem
if (!_actionBlocker.CanEmote(uid.Value) && !_actionBlocker.CanSpeak(uid.Value)) if (!_actionBlocker.CanEmote(uid.Value) && !_actionBlocker.CanSpeak(uid.Value))
{ {
// nah, make sure that typing indicator is disabled // nah, make sure that typing indicator is disabled
SetTypingIndicatorEnabled(uid.Value, false); SetTypingIndicatorState(uid.Value, TypingIndicatorState.None);
return; return;
} }
SetTypingIndicatorEnabled(uid.Value, ev.IsTyping); SetTypingIndicatorState(uid.Value, ev.State);
} }
private void SetTypingIndicatorEnabled(EntityUid uid, bool isEnabled, AppearanceComponent? appearance = null) private void SetTypingIndicatorState(EntityUid uid, TypingIndicatorState state, AppearanceComponent? appearance = null)
{ {
if (!Resolve(uid, ref appearance, false)) if (!Resolve(uid, ref appearance, false))
return; return;
_appearance.SetData(uid, TypingIndicatorVisuals.IsTyping, isEnabled, appearance); _appearance.SetData(uid, TypingIndicatorVisuals.State, state, appearance);
} }
} }

View File

@@ -12,11 +12,11 @@ namespace Content.Shared.Chat.TypingIndicator;
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class TypingChangedEvent : EntityEventArgs public sealed class TypingChangedEvent : EntityEventArgs
{ {
public readonly bool IsTyping; public readonly TypingIndicatorState State;
public TypingChangedEvent(bool isTyping) public TypingChangedEvent(TypingIndicatorState state)
{ {
IsTyping = isTyping; State = state;
} }
} }

View File

@@ -19,6 +19,9 @@ public sealed partial class TypingIndicatorPrototype : IPrototype
[DataField("typingState", required: true)] [DataField("typingState", required: true)]
public string TypingState = default!; public string TypingState = default!;
[DataField("idleState", required: true)]
public string IdleState = default!;
[DataField("offset")] [DataField("offset")]
public Vector2 Offset = new(0, 0); public Vector2 Offset = new(0, 0);

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Chat.TypingIndicator;
[Serializable, NetSerializable]
public enum TypingIndicatorState
{
None = 0,
Idle = 1,
Typing = 2,
}

View File

@@ -5,7 +5,7 @@ namespace Content.Shared.Chat.TypingIndicator;
[Serializable, NetSerializable] [Serializable, NetSerializable]
public enum TypingIndicatorVisuals : byte public enum TypingIndicatorVisuals : byte
{ {
IsTyping State
} }
[Serializable] [Serializable]

View File

@@ -1,3 +1,4 @@
using Content.Shared.Chat.TypingIndicator;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -34,11 +35,11 @@ public sealed class HolopadUserTypingChangedEvent : EntityEventArgs
/// <summary> /// <summary>
/// The typing indicator state /// The typing indicator state
/// </summary> /// </summary>
public readonly bool IsTyping; public readonly TypingIndicatorState State;
public HolopadUserTypingChangedEvent(NetEntity user, bool isTyping) public HolopadUserTypingChangedEvent(NetEntity user, TypingIndicatorState state)
{ {
User = user; User = user;
IsTyping = isTyping; State = state;
} }
} }

View File

@@ -1,60 +1,73 @@
- type: typingIndicator - type: typingIndicator
id: default id: default
typingState: default0 typingState: default0
idleState: default3
- type: typingIndicator - type: typingIndicator
id: robot id: robot
typingState: robot0 typingState: robot0
idleState: robot3
- type: typingIndicator - type: typingIndicator
id: alien id: alien
typingState: alien0 typingState: alien0
idleState: alien3
- type: typingIndicator - type: typingIndicator
id: guardian id: guardian
typingState: guardian0 typingState: guardian0
idleState: guardian3
- type: typingIndicator - type: typingIndicator
id: holo id: holo
typingState: holo0 typingState: holo0
idleState: holo3
offset: 0, 0.0625 offset: 0, 0.0625
- type: typingIndicator - type: typingIndicator
id: lawyer id: lawyer
typingState: lawyer0 typingState: lawyer0
idleState: lawyer3
offset: 0, 0.125 offset: 0, 0.125
- type: typingIndicator - type: typingIndicator
id: moth id: moth
typingState: moth0 typingState: moth0
idleState: moth3
offset: 0, 0.125 offset: 0, 0.125
- type: typingIndicator - type: typingIndicator
id: spider id: spider
typingState: spider0 typingState: spider0
idleState: spider3
offset: 0, 0.125 offset: 0, 0.125
- type: typingIndicator - type: typingIndicator
id: vox id: vox
typingState: vox0 typingState: vox0
idleState: vox0 # TODO add idle state sprite
offset: -0.125, 0.125 offset: -0.125, 0.125
- type: typingIndicator - type: typingIndicator
id: lizard id: lizard
typingState: lizard0 typingState: lizard0
idleState: lizard3
offset: 0, 0.0625 offset: 0, 0.0625
- type: typingIndicator - type: typingIndicator
id: slime id: slime
typingState: slime0 typingState: slime0
idleState: slime3
offset: 0, 0.125 offset: 0, 0.125
- type: typingIndicator - type: typingIndicator
id: gingerbread id: gingerbread
typingState: gingerbread0 typingState: gingerbread0
idleState: gingerbread0
offset: 0, 0.125 offset: 0, 0.125
- type: typingIndicator - type: typingIndicator
id: diona id: diona
typingState: diona0 typingState: diona0
idleState: diona0
offset: 0, 0.125 offset: 0, 0.125

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

View File

@@ -1,11 +1,11 @@
{ {
"version": 1, "version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24 | Moth sprites made by PuroSlavKing (Github) | Spider sprites made by PixelTheKermit (Github) | Lizard sprites made by AmalgoMyte (Github) | Diona and Gingerbread sprites made by YoungThugSS14 (Github)",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
}, },
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24 | Moth sprites made by PuroSlavKing (Github) | Spider sprites made by PixelTheKermit (Github) | Lizard sprites made by AmalgoMyte (Github) | Diona and Gingerbread sprites made by YoungThugSS14 (Github)",
"states": [ "states": [
{ {
"name": "alien0", "name": "alien0",
@@ -26,9 +26,20 @@
{ {
"name": "alien2" "name": "alien2"
}, },
{
"name": "alien3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "alienroyal0", "name": "alienroyal0",
"delays": [ "delays": [
[ [
0.2, 0.2,
@@ -46,6 +57,19 @@
{ {
"name": "alienroyal2" "name": "alienroyal2"
}, },
{
"name": "alienroyal3",
"delays": [
[
0.2,
0.3,
0.3,
0.3,
0.3,
0.5
]
]
},
{ {
"name": "blob0", "name": "blob0",
"delays": [ "delays": [
@@ -125,6 +149,18 @@
{ {
"name": "default2" "name": "default2"
}, },
{
"name": "default3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "diona0", "name": "diona0",
"delays": [ "delays": [
@@ -176,6 +212,18 @@
{ {
"name": "guardian2" "name": "guardian2"
}, },
{
"name": "guardian3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "holo0", "name": "holo0",
"delays": [ "delays": [
@@ -195,6 +243,18 @@
{ {
"name": "holo2" "name": "holo2"
}, },
{
"name": "holo3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "lawyer0", "name": "lawyer0",
"delays": [ "delays": [
@@ -232,6 +292,21 @@
] ]
] ]
}, },
{
"name": "lawyer3",
"delays": [
[
0.15,
0.15,
0.15,
0.15,
0.125,
0.1,
0.125,
0.15
]
]
},
{ {
"name": "lizard0", "name": "lizard0",
"delays": [ "delays": [
@@ -249,6 +324,18 @@
{ {
"name": "lizard2" "name": "lizard2"
}, },
{
"name": "lizard3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "moth0", "name": "moth0",
"delays": [ "delays": [
@@ -286,6 +373,27 @@
] ]
] ]
}, },
{
"name": "moth3",
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{ {
"name": "machine0", "name": "machine0",
"delays": [ "delays": [
@@ -320,6 +428,20 @@
{ {
"name": "robot2" "name": "robot2"
}, },
{
"name": "robot3",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
},
{ {
"name": "slime0", "name": "slime0",
"delays": [ "delays": [
@@ -361,6 +483,18 @@
] ]
] ]
}, },
{
"name": "slime3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "swarmer0", "name": "swarmer0",
"delays": [ "delays": [
@@ -429,6 +563,20 @@
{ {
"name": "syndibot2" "name": "syndibot2"
}, },
{
"name": "syndibot3",
"delays": [
[
0.2,
0.2,
0.2,
0.2,
0.2,
0.2,
0.2
]
]
},
{ {
"name": "spider0", "name": "spider0",
"delays": [ "delays": [
@@ -446,6 +594,18 @@
{ {
"name": "spider2" "name": "spider2"
}, },
{
"name": "spider3",
"delays": [
[
0.2,
0.3,
0.3,
0.5,
0.5
]
]
},
{ {
"name": "vox0", "name": "vox0",
"delays": [ "delays": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B