Files
RobustToolbox/Robust.Client/UserInterface/Controllers/UserInterfaceManager.Controllers.cs
Jezithyr 710371d7d1 UI refactor and UITheme implementations (#2712)
* UIControllerManager


Implemented UI Controller Manager

* added fetch function

* added note

* Hiding some internal stuff

* Implemented event on gamestate switch for ui

* Fix serialization field assigner emit

* fixing issues with ILEmitter stuff

* AHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH

Blame Smug

* fixing nullref

* Add checking for no backing field / property for ui system dependencies

* fixes Gamestate detection

* Implemented event on UIControllers on system load

* Updated systemload/unload listeners

* Had this backwards lol

* Fix nulling systems before calling OnSystemUnloaded, broke InventoryUIController.Hands.cs

* Created UI Window management system

- A manager that allows for easy creation and access of popup or gamestate windows

* Changing to use basewindow instead of default window

* Implemented UI Theming that isn't ass

* Updated default theme loading and validation

* Added path validation for themes

* Implemented UI Themes

* Implemented UI Theme prototypes

* Implementing theming for texture buttons and Texturerects

* fixing server error

* Remove IUILink

* Implemented default theme overriding and theme colors

* Fixing sandbox lul

* Added error for not finding UITheme

* fixing setting default theme in content

* Move entity and tile spawn window logic to controllers

* Add 2 TODOs

* Merge fixes

* Add IOnStateChanged for UI controllers

* Fix inventory window being slow to open
Caches resources when the UI theme is changed

* Remove caching on theme change
The real fix was fixing the path for resources

* Remove test method

* Fix crash when controllers implement non generic interfaces

* Add controllers frame update

* Split UserInterfaceManager into partials

- Created UI screen

* Converted more UI managers into partials

* Setup UIScreen manager system

* Added some widget utility funcs


updated adding widgets

* Started removing HUDManager

* Moved UiController Manager to Partials


Finished moving all UIController code to UIManager

* Fixed screen loading

* Fixed Screen scaling

* Fixed Screen scaling


cleanup

* wat

* IwantToDie

* Fixed resolving ResourceCache instead of IResourceCache

* Split IOnStateChanged into IOnStateEntered and IOnStateExited

* Implemented helpers for adjusting UIAutoscale for screens

* Fixed autoscale, removed archiving from autoscale

* Implemented popups and adjusted some stuff

* Fixing some popup related shinanegans

* Fixing some draw order issues

* fixing dumb shit

* Fix indentation in UserInterfaceManager.Input.cs

* Moved screen setup to post init (run after content)

* Fix updating theme

* Merge fixes

* Fix resolving sprite system on control creation

* Fix min size of tile spawn window

* Add UIController.Initialize method

* https://tenor.com/view/minor-spelling-mistake-gif-21179057

* Add doc comment to UIController

* Split UIController.cs and UISystemDependency.cs into their own files

* Add more documentation to ui controllers

* Add AttributeUsage to UISystemDependencyAttribute

* Fix method naming

* Add documentation for assigners

* Return casted widgets where relevant

* Fix entity spawner scroll (#1)

* Add CloseOnClick and CloseOnEscape for popups

* Remove named windows and popups

* Cleanup controller code

* Add IOnStateChanged, IOnSystemChanged, IOnSystemLoaded, IOnSystemUnloaded

* Add more docs to state and system change interfaces

* Fixing Window issues

* Fixing some window fuckery

* Added OnOpen event to windows, updated sandbox window

Sandbox windows now persist values and positions

* Recurse through controls to register widgets (#2)

* Allow path to be folder

* Fix local player shutdown

* Fixing escape menu

* Fix backing field in DataDefinition.Emitters

* Ent+Tile spawn no crash

* Skip no-spawn in entity spawn menu

Co-authored-by: Jezithyr <jmaster9999@gmail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Jezithyr <Jezithyr@gmail.com>
Co-authored-by: wrexbe <81056464+wrexbe@users.noreply.github.com>
Co-authored-by: Flipp Syder <76629141+vulppine@users.noreply.github.com>
Co-authored-by: wrexbe <wrexbe@protonmail.com>
2022-09-04 16:10:54 -07:00

337 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Robust.Client.State;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Shared.Serialization.Manager.Definition.DataDefinition;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface;
internal partial class UserInterfaceManager
{
/// <summary>
/// All registered <see cref="UIController"/> instances indexed by type
/// </summary>
private readonly Dictionary<Type, UIController> _uiControllers = new();
/// <summary>
/// Implementations of <see cref="IOnStateEntered{T}"/> to invoke when a state is entered
/// State Type -> (UIController, Caller)
/// </summary>
private readonly Dictionary<Type, Dictionary<UIController, StateChangedCaller>> _onStateEnteredDelegates = new();
/// <summary>
/// Implementations of <see cref="IOnStateExited{T}"/> to invoke when a state is exited
/// State Type -> (UIController, Caller)
/// </summary>
private readonly Dictionary<Type, Dictionary<UIController, StateChangedCaller>> _onStateExitedDelegates = new();
/// <summary>
/// Implementations of <see cref="IOnSystemLoaded{T}"/> to invoke when an entity system is loaded
/// Entity System Type -> (UIController, Caller)
/// </summary>
private readonly Dictionary<Type, Dictionary<UIController, SystemChangedCaller>> _onSystemLoadedDelegates = new();
/// <summary>
/// Implementations of <see cref="IOnSystemUnloaded{T}"/> to invoke when an entity system is unloaded
/// Entity System Type -> (UIController, Caller)
/// </summary>
private readonly Dictionary<Type, Dictionary<UIController, SystemChangedCaller>> _onSystemUnloadedDelegates = new();
/// <summary>
/// Field -> Controller -> Field assigner delegate
/// </summary>
private readonly Dictionary<Type, Dictionary<Type, AssignField<UIController, object?>>> _assignerRegistry = new();
private delegate void StateChangedCaller(object controller, State.State state);
private delegate void SystemChangedCaller(object controller, IEntitySystem system);
private StateChangedCaller EmitStateChangedCaller(Type controller, Type state, bool entered)
{
if (controller.IsValueType)
{
throw new ArgumentException($"Value type controllers are not supported. Controller: {controller}");
}
if (state.IsValueType)
{
throw new ArgumentException($"Value type states are not supported. State: {state}");
}
var method = new DynamicMethod(
"StateChangedCaller",
typeof(void),
new[] {typeof(object), typeof(State.State)},
true
);
var generator = method.GetILGenerator();
Type onStateChangedType;
MethodInfo onStateChangedMethod;
if (entered)
{
onStateChangedType = typeof(IOnStateEntered<>).MakeGenericType(state);
onStateChangedMethod =
controller.GetMethod(nameof(IOnStateEntered<State.State>.OnStateEntered), new[] {state})
?? throw new NullReferenceException();
}
else
{
onStateChangedType = typeof(IOnStateExited<>).MakeGenericType(state);
onStateChangedMethod =
controller.GetMethod(nameof(IOnStateExited<State.State>.OnStateExited), new[] {state})
?? throw new NullReferenceException();
}
generator.Emit(OpCodes.Ldarg_0); // controller
generator.Emit(OpCodes.Castclass, onStateChangedType);
generator.Emit(OpCodes.Ldarg_1); // state
generator.Emit(OpCodes.Castclass, state);
generator.Emit(OpCodes.Callvirt, onStateChangedMethod);
generator.Emit(OpCodes.Ret);
return method.CreateDelegate<StateChangedCaller>();
}
private SystemChangedCaller EmitSystemChangedCaller(Type controller, Type system, bool loaded)
{
if (controller.IsValueType)
{
throw new ArgumentException($"Value type controllers are not supported. Controller: {controller}");
}
if (system.IsValueType)
{
throw new ArgumentException($"Value type systems are not supported. System: {system}");
}
var method = new DynamicMethod(
"SystemChangedCaller",
typeof(void),
new[] {typeof(object), typeof(IEntitySystem)},
true
);
var generator = method.GetILGenerator();
Type onSystemChangedType;
MethodInfo onSystemChangedMethod;
if (loaded)
{
onSystemChangedType = typeof(IOnSystemLoaded<>).MakeGenericType(system);
onSystemChangedMethod =
controller.GetMethod(nameof(IOnSystemLoaded<IEntitySystem>.OnSystemLoaded), new[] {system})
?? throw new NullReferenceException();
}
else
{
onSystemChangedType = typeof(IOnSystemUnloaded<>).MakeGenericType(system);
onSystemChangedMethod =
controller.GetMethod(nameof(IOnSystemUnloaded<IEntitySystem>.OnSystemUnloaded), new[] {system})
?? throw new NullReferenceException();
}
generator.Emit(OpCodes.Ldarg_0); // controller
generator.Emit(OpCodes.Castclass, onSystemChangedType);
generator.Emit(OpCodes.Ldarg_1); // system
generator.Emit(OpCodes.Castclass, system);
generator.Emit(OpCodes.Callvirt, onSystemChangedMethod);
generator.Emit(OpCodes.Ret);
return method.CreateDelegate<SystemChangedCaller>();
}
private void RegisterUIController(Type type, UIController controller)
{
_uiControllers.Add(type, controller);
}
private ref UIController GetUIControllerRef(Type type)
{
return ref CollectionsMarshal.GetValueRefOrNullRef(_uiControllers, type);
}
private UIController GetUIController(Type type)
{
return _uiControllers[type];
}
public T GetUIController<T>() where T : UIController, new()
{
return (T) GetUIController(typeof(T));
}
private void _setupControllers()
{
foreach (var uiControllerType in _reflectionManager.GetAllChildren<UIController>())
{
if (uiControllerType.IsAbstract)
continue;
var newController = _typeFactory.CreateInstanceUnchecked<UIController>(uiControllerType);
RegisterUIController(uiControllerType, newController);
foreach (var fieldInfo in uiControllerType.GetAllPropertiesAndFields())
{
if (!fieldInfo.HasAttribute<UISystemDependencyAttribute>())
{
continue;
}
var backingField = fieldInfo;
if (fieldInfo is SpecificPropertyInfo property)
{
if (property.TryGetBackingField(out var field))
{
backingField = field;
}
else
{
var setter = property.PropertyInfo.GetSetMethod(true);
if (setter == null)
{
throw new InvalidOperationException(
$"Property with {nameof(UISystemDependencyAttribute)} attribute did not have a backing field nor setter");
}
}
}
//Do not do anything if the field isn't an entity system
if (!typeof(IEntitySystem).IsAssignableFrom(backingField.FieldType))
continue;
var typeDict = _assignerRegistry.GetOrNew(fieldInfo.FieldType);
var assigner = EmitFieldAssigner<UIController>(uiControllerType, fieldInfo.FieldType, backingField);
typeDict.Add(uiControllerType, assigner);
}
foreach (var @interface in uiControllerType.GetInterfaces())
{
if (!@interface.IsGenericType)
continue;
var typeDefinition = @interface.GetGenericTypeDefinition();
var genericType = @interface.GetGenericArguments()[0];
if (typeDefinition == typeof(IOnStateEntered<>))
{
var enteredCaller = EmitStateChangedCaller(uiControllerType, genericType, true);
_onStateEnteredDelegates.GetOrNew(genericType).Add(newController, enteredCaller);
}
else if (typeDefinition == typeof(IOnStateExited<>))
{
var exitedCaller = EmitStateChangedCaller(uiControllerType, genericType, false);
_onStateExitedDelegates.GetOrNew(genericType).Add(newController, exitedCaller);
}
else if (typeDefinition == typeof(IOnSystemLoaded<>))
{
var loadedCaller = EmitSystemChangedCaller(uiControllerType, genericType, true);
_onSystemLoadedDelegates.GetOrNew(genericType).Add(newController, loadedCaller);
}
else if (typeDefinition == typeof(IOnSystemUnloaded<>))
{
var unloadedCaller = EmitSystemChangedCaller(uiControllerType, genericType, false);
_onSystemUnloadedDelegates.GetOrNew(genericType).Add(newController, unloadedCaller);
}
}
}
_systemManager.SystemLoaded += OnSystemLoaded;
_systemManager.SystemUnloaded += OnSystemUnloaded;
_stateManager.OnStateChanged += OnStateChanged;
}
private void _initializeControllers()
{
foreach (var controller in _uiControllers.Values)
{
controller.Initialize();
}
}
private void UpdateControllers(FrameEventArgs args)
{
foreach (var controller in _uiControllers.Values)
{
controller.FrameUpdate(args);
}
}
// TODO hud refactor optimize this to use an array
// TODO hud refactor BEFORE MERGE cleanup subscriptions for all implementations when switching out of gameplay state
private void OnStateChanged(StateChangedEventArgs args)
{
if (_onStateExitedDelegates.TryGetValue(args.OldState.GetType(), out var exitedDelegates))
{
foreach (var (controller, caller) in exitedDelegates)
{
caller(controller, args.OldState);
}
}
if (_onStateEnteredDelegates.TryGetValue(args.NewState.GetType(), out var enteredDelegates))
{
foreach (var (controller, caller) in enteredDelegates)
{
caller(controller, args.NewState);
}
}
}
private void OnSystemLoaded(object? sender, SystemChangedArgs args)
{
var systemType = args.System.GetType();
if (_assignerRegistry.TryGetValue(systemType, out var assigners))
{
foreach (var (controllerType, assigner) in assigners)
{
assigner(ref GetUIControllerRef(controllerType), args.System);
}
}
if (_onSystemLoadedDelegates.TryGetValue(systemType, out var delegates))
{
foreach (var (controller, caller) in delegates)
{
caller(controller, args.System);
}
}
}
private void OnSystemUnloaded(object? system, SystemChangedArgs args)
{
var systemType = args.System.GetType();
if (_onSystemUnloadedDelegates.TryGetValue(systemType, out var delegates))
{
foreach (var (controller, caller) in delegates)
{
caller(controller, args.System);
}
}
if (_assignerRegistry.TryGetValue(systemType, out var assigners))
{
foreach (var (controllerType, assigner) in assigners)
{
assigner(ref GetUIControllerRef(controllerType), null);
}
}
}
}