diff --git a/src/en/robust-toolbox/user-interface.md b/src/en/robust-toolbox/user-interface.md index 12556bfdf..8a6d54f18 100644 --- a/src/en/robust-toolbox/user-interface.md +++ b/src/en/robust-toolbox/user-interface.md @@ -403,3 +403,115 @@ The XAML code is automatically compiled to IL so it can be efficiently construct ``` +## UI Controllers +UI controllers are responsible for creating, updating and removing controls. Entity systems must not do this themselves. +Any data that they use must be obtained by binding their methods to system events (see example below) or by calling methods on IoC services, such as IPlayerManager. +To create a new UI controller, make a new class that inherits `UIController`. This type will then be instantiated automatically as a singleton by the `UserInterfaceManager`. +Widgets are retrieved within UI controllers by calling `UIManager.GetActiveUIWidgetOrNull()`, where T is a widget such as `ActionsBar`. +The lifecycle of an UI controller is longer than that of entity systems; an UI controller may exist before entity systems are loaded, and stay alive after they are unloaded. + +### Dependencies +An UI controller may have dependencies to other IoC services and controllers using the \[Dependency\] syntax. +For systems, \[UISystemDependency\] must be used instead. +Once systems are loaded, UI controller methods may be bound to events declared on them. + +```cs +public sealed class ActionUIController : UIController, IOnSystemChanged, IOnStateEntered +{ + // Dependency is used for IoC services and other controllers + [Dependency] private readonly GameplayStateLoadController _gameplayStateLoad = default!; + + // For entity systems, UISystemDependency is used instead + [UISystemDependency] private readonly ActionsSystem? _actions = default!; + + private ActionsWindow? _window; + + public override void Initialize() + { + base.Initialize(); + + // We can bind methods to event fields on other UI controllers during initialize + _gameplayStateLoad.OnScreenLoad += LoadGui; + _gameplayStateLoad.OnScreenUnload += UnloadGui; + + // UI controllers can also subscribe to local and network entity events + // Local events are events raised on the client using RaiseLocalEvent + // SubscribeLocalEvent(ev => {}); + + // Network events are events raised by the server and sent to the client + // SubscribeNetworkEvent(ev => {}); + } + + private void LoadGui() + { + DebugTools.Assert(_window == null); + _window = UIManager.CreateWindow(); + LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); + } + + private void UnloadGui() + { + if (_window != null) + { + _window.Dispose(); + _window = null; + } + } + + private void ToggleWindow() + { + if (_window == null) + return; + + if (_window.IsOpen) + { + _window.Close(); + return; + } + + _window.Open(); + } + + public void OnSystemLoaded(ActionsSystem system) + { + // We can bind to event fields on entity systems when that entity system is loaded + system.LinkActions += OnComponentLinked; + } + + public void OnSystemUnloaded(ActionsSystem system) + { + // And unbind when the system is unloaded + system.LinkActions -= OnComponentLinked; + } + + // This will be called when ActionsSystem raises an event on its LinkActions event field + private void OnComponentLinked(ActionsComponent component) + { + } + + public void OnStateEntered(GameplayState state) + { + // Bind hotkeys once we enter the gameplay state (start a round and join it as the client) + CommandBinds.Builder + .Bind(ContentKeyFunctions.OpenActionsMenu, InputCmdHandler.FromDelegate(_ => ToggleWindow())) + .Register(); + } + + public void OnStateExited(GameplayState state) + { + // Unbind hotkeys after we exit the round (we enter the lobby or disconnect) + CommandBinds.Unregister(); + } +} +``` + +UI controllers may also implement the following interfaces: +### IOnStateChanged +Implements two methods: `OnStateEntered(T state)` and `OnStateExited(T state)`. +These methods are automatically called when the respective state is entered and exited, for example GameplayState. +If only the entering or exiting logic is needed, `IOnStateEntered` and `IOnStateExited` may be implemented instead. + +### IOnSystemChanged +Implements two methods: `OnSystemLoaded(T system)` and `OnSystemUnloaded(T system)`. +These methods are automatically called when the respective system is loaded and unloaded, for example ActionsSystem. +If only the loaded or unloaded logic is needed, `IOnSystemLoaded` and `IOnStateUnloaded` may be implemented instead.