private readonly TimeSpan _typingTimeout = TimeSpan.FromSeconds(2);
private TimeSpan _lastTextChange;
private bool _isClientTyping;
+ private bool _isClientChatFocused;
public override void Initialize()
{
return;
// client typed something - show typing indicator
- ClientUpdateTyping(true);
+ _isClientTyping = true;
+ ClientUpdateTyping();
_lastTextChange = _time.CurTime;
}
return;
// 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)
var dif = _time.CurTime - _lastTextChange;
if (dif > _typingTimeout)
{
- // client didn't typed anything for a long time - hide indicator
- ClientUpdateTyping(false);
+ // client didn't typed anything for a long time - change indicator
+ _isClientTyping = false;
+ ClientUpdateTyping();
}
}
}
- private void ClientUpdateTyping(bool isClientTyping)
+ private void ClientUpdateTyping()
{
- if (_isClientTyping == isClientTyping)
- return;
-
- // check if player controls any entity.
+ // check if player controls any pawn
if (_playerManager.LocalEntity == null)
return;
- _isClientTyping = isClientTyping;
- RaisePredictiveEvent(new TypingChangedEvent(isClientTyping));
+ var state = TypingIndicatorState.None;
+ if (_isClientChatFocused)
+ state = _isClientTyping ? TypingIndicatorState.Typing : TypingIndicatorState.Idle;
+
+ // send a networked event to server
+ RaisePredictiveEvent(new TypingChangedEvent(state));
}
private void OnShowTypingChanged(bool showTyping)
// hide typing indicator immediately if player don't want to show it anymore
if (!showTyping)
{
- ClientUpdateTyping(false);
+ _isClientTyping = false;
+ ClientUpdateTyping();
}
}
}
return;
}
- AppearanceSystem.TryGetData<bool>(uid, TypingIndicatorVisuals.IsTyping, out var isTyping, args.Component);
var layerExists = args.Sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer);
if (!layerExists)
layer = args.Sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base);
args.Sprite.LayerSetState(layer, proto.TypingState);
args.Sprite.LayerSetShader(layer, proto.Shader);
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;
+ }
}
}
if (!HasComp<HolopadUserComponent>(uid))
return;
- var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.IsTyping);
+ var netEv = new HolopadUserTypingChangedEvent(GetNetEntity(uid.Value), ev.State);
RaiseNetworkEvent(netEv);
}
_typingIndicator?.ClientChangedChatText();
}
+ public void NotifyChatFocus(bool isFocused)
+ {
+ _typingIndicator?.ClientChangedChatFocus(isFocused);
+ }
+
public void Repopulate()
{
foreach (var chat in _chats)
ChatInput.Input.OnTextEntered += OnTextEntered;
ChatInput.Input.OnKeyBindDown += OnInputKeyBindDown;
ChatInput.Input.OnTextChanged += OnTextChanged;
+ ChatInput.Input.OnFocusEnter += OnFocusEnter;
+ ChatInput.Input.OnFocusExit += OnFocusExit;
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter;
_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)
{
base.Dispose(disposing);
if (receiverHolopad.Comp.Hologram == null)
continue;
- _appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, ev.IsTyping);
+ _appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.State, ev.State);
}
}
}
continue;
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;
Dirty(linkedHolopad.Comp.Hologram.Value);
private void OnPlayerDetached(EntityUid uid, TypingIndicatorComponent component, PlayerDetachedEvent args)
{
// player left entity body - hide typing indicator
- SetTypingIndicatorEnabled(uid, false);
+ SetTypingIndicatorState(uid, TypingIndicatorState.None);
}
private void OnGotEquipped(Entity<TypingIndicatorClothingComponent> entity, ref ClothingGotEquippedEvent args)
if (!_actionBlocker.CanEmote(uid.Value) && !_actionBlocker.CanSpeak(uid.Value))
{
// nah, make sure that typing indicator is disabled
- SetTypingIndicatorEnabled(uid.Value, false);
+ SetTypingIndicatorState(uid.Value, TypingIndicatorState.None);
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))
return;
- _appearance.SetData(uid, TypingIndicatorVisuals.IsTyping, isEnabled, appearance);
+ _appearance.SetData(uid, TypingIndicatorVisuals.State, state, appearance);
}
}
[Serializable, NetSerializable]
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;
}
}
[DataField("typingState", required: true)]
public string TypingState = default!;
+ [DataField("idleState", required: true)]
+ public string IdleState = default!;
+
[DataField("offset")]
public Vector2 Offset = new(0, 0);
--- /dev/null
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Chat.TypingIndicator;
+
+[Serializable, NetSerializable]
+public enum TypingIndicatorState
+{
+ None = 0,
+ Idle = 1,
+ Typing = 2,
+}
[Serializable, NetSerializable]
public enum TypingIndicatorVisuals : byte
{
- IsTyping
+ State
}
[Serializable]
+using Content.Shared.Chat.TypingIndicator;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
/// <summary>
/// The typing indicator state
/// </summary>
- public readonly bool IsTyping;
+ public readonly TypingIndicatorState State;
- public HolopadUserTypingChangedEvent(NetEntity user, bool isTyping)
+ public HolopadUserTypingChangedEvent(NetEntity user, TypingIndicatorState state)
{
User = user;
- IsTyping = isTyping;
+ State = state;
}
}
- type: typingIndicator
id: default
typingState: default0
+ idleState: default3
- type: typingIndicator
id: robot
typingState: robot0
+ idleState: robot3
- type: typingIndicator
id: alien
typingState: alien0
+ idleState: alien3
- type: typingIndicator
id: guardian
typingState: guardian0
+ idleState: guardian3
- type: typingIndicator
id: holo
typingState: holo0
+ idleState: holo3
offset: 0, 0.0625
- type: typingIndicator
id: lawyer
typingState: lawyer0
+ idleState: lawyer3
offset: 0, 0.125
- type: typingIndicator
id: moth
typingState: moth0
+ idleState: moth3
offset: 0, 0.125
- type: typingIndicator
id: spider
typingState: spider0
+ idleState: spider3
offset: 0, 0.125
- type: typingIndicator
id: vox
typingState: vox0
+ idleState: vox0 # TODO add idle state sprite
offset: -0.125, 0.125
- type: typingIndicator
id: lizard
typingState: lizard0
+ idleState: lizard3
offset: 0, 0.0625
- type: typingIndicator
id: slime
typingState: slime0
+ idleState: slime3
offset: 0, 0.125
- type: typingIndicator
id: gingerbread
typingState: gingerbread0
+ idleState: gingerbread0
offset: 0, 0.125
- type: typingIndicator
id: diona
typingState: diona0
+ idleState: diona0
offset: 0, 0.125
{
"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": {
"x": 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": [
{
"name": "alien0",
{
"name": "alien2"
},
+ {
+ "name": "alien3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
{
"name": "alienroyal0",
-
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.3,
+ 0.3,
+ 0.5
+ ]
+ ]
+ },
+ {
+ "name": "alienroyal1"
+ },
+ {
+ "name": "alienroyal2"
+ },
+ {
+ "name": "alienroyal3",
"delays": [
[
0.2,
]
]
},
- {
- "name": "alienroyal1"
- },
- {
- "name": "alienroyal2"
- },
{
"name": "blob0",
"delays": [
{
"name": "default2"
},
+ {
+ "name": "default3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
{
"name": "diona0",
"delays": [
{
"name": "guardian2"
},
+ {
+ "name": "guardian3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
{
"name": "holo0",
"delays": [
{
"name": "holo2"
},
+ {
+ "name": "holo3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
{
"name": "lawyer0",
"delays": [
]
]
},
+ {
+ "name": "lawyer3",
+ "delays": [
+ [
+ 0.15,
+ 0.15,
+ 0.15,
+ 0.15,
+ 0.125,
+ 0.1,
+ 0.125,
+ 0.15
+ ]
+ ]
+ },
{
"name": "lizard0",
"delays": [
{
"name": "lizard2"
},
+ {
+ "name": "lizard3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
{
"name": "moth0",
"delays": [
]
]
},
+ {
+ "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",
"delays": [
{
"name": "robot2"
},
+ {
+ "name": "robot3",
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
{
"name": "slime0",
"delays": [
]
]
},
+ {
+ "name": "slime3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
{
"name": "swarmer0",
"delays": [
{
"name": "syndibot2"
},
+ {
+ "name": "syndibot3",
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
{
"name": "spider0",
"delays": [
{
"name": "spider2"
},
- {
+ {
+ "name": "spider3",
+ "delays": [
+ [
+ 0.2,
+ 0.3,
+ 0.3,
+ 0.5,
+ 0.5
+ ]
+ ]
+ },
+ {
"name": "vox0",
"delays": [
[