mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* 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>
337 lines
12 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|