using System; using System.Collections.Generic; using Robust.Client.Player; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Reflection; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Robust.Client.GameObjects { [ComponentReference(typeof(SharedUserInterfaceComponent))] public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks { [Dependency] private readonly IReflectionManager _reflectionManager = default!; [Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; private readonly Dictionary _openInterfaces = new(); private readonly Dictionary _interfaces = new(); [DataField("interfaces", readOnly: true)] private List _interfaceData = new(); [ViewVariables] public IEnumerable Interfaces => _openInterfaces.Values; void ISerializationHooks.AfterDeserialization() { _interfaces.Clear(); foreach (var data in _interfaceData) { _interfaces[data.UiKey] = data; } } internal void MessageReceived(BoundUIWrapMessage msg) { switch (msg.Message) { case OpenBoundInterfaceMessage _: if (_openInterfaces.ContainsKey(msg.UiKey)) { return; } OpenInterface(msg); break; case CloseBoundInterfaceMessage _: Close(msg.UiKey, true); break; default: if (_openInterfaces.TryGetValue(msg.UiKey, out var bi)) { bi.InternalReceiveMessage(msg.Message); } break; } } private void OpenInterface(BoundUIWrapMessage wrapped) { var data = _interfaces[wrapped.UiKey]; // TODO: This type should be cached, but I'm too lazy. var type = _reflectionManager.LooseGetType(data.ClientType); var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey}); boundInterface.Open(); _openInterfaces[wrapped.UiKey] = boundInterface; var playerSession = _playerManager.LocalPlayer?.Session; if(playerSession != null) _entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIOpenedEvent(wrapped.UiKey, Owner, playerSession), true); } internal void Close(object uiKey, bool remoteCall) { if (!_openInterfaces.TryGetValue(uiKey, out var boundUserInterface)) { return; } if (!remoteCall) SendMessage(new CloseBoundInterfaceMessage(), uiKey); _openInterfaces.Remove(uiKey); boundUserInterface.Dispose(); var playerSession = _playerManager.LocalPlayer?.Session; if(playerSession != null) _entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIClosedEvent(uiKey, Owner, playerSession), true); } internal void SendMessage(BoundUserInterfaceMessage message, object uiKey) { EntitySystem.Get() .Send(new BoundUIWrapMessage(Owner, message, uiKey)); } } /// /// An abstract class to override to implement bound user interfaces. /// public abstract class BoundUserInterface : IDisposable { protected ClientUserInterfaceComponent Owner { get; } protected object UiKey { get; } /// /// The last received state object sent from the server. /// protected BoundUserInterfaceState? State { get; private set; } protected BoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) { Owner = owner; UiKey = uiKey; } /// /// Invoked when the UI is opened. /// Do all creation and opening of things like windows in here. /// protected internal virtual void Open() { } /// /// Invoked when the server uses SetState. /// protected virtual void UpdateState(BoundUserInterfaceState state) { } /// /// Invoked when the server sends an arbitrary message. /// protected virtual void ReceiveMessage(BoundUserInterfaceMessage message) { } /// /// Invoked to close the UI. /// protected void Close() { Owner.Close(UiKey, false); } /// /// Sends a message to the server-side UI. /// protected void SendMessage(BoundUserInterfaceMessage message) { Owner.SendMessage(message, UiKey); } internal void InternalReceiveMessage(BoundUserInterfaceMessage message) { switch (message) { case UpdateBoundStateMessage updateBoundStateMessage: State = updateBoundStateMessage.State; UpdateState(State); break; default: ReceiveMessage(message); break; } } ~BoundUserInterface() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } } }