riversong code showcase
This commit is contained in:
228
Source/Riversong/Game/UI/DayNightUIThemeSystem.cs
Normal file
228
Source/Riversong/Game/UI/DayNightUIThemeSystem.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(UISystemGroup))]
|
||||
[InitializeAfter(typeof(UIInitializationSystem))]
|
||||
public class DayNightUIThemeSystem : GameSystem, IInitializable, IDisposable, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private UIService _uiService;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
private VisualElement _root;
|
||||
|
||||
private RuntimeThemeSheet _runtimeThemeSheet;
|
||||
|
||||
private float _lastAppliedNightBlend = -1;
|
||||
|
||||
private bool _refreshClassEnabled;
|
||||
|
||||
public DayNightUIThemeSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_root = _uiService.UIRoot.RootVisualElement;
|
||||
_runtimeThemeSheet = RuntimeThemeSheet.TryCreate(_root, _config.UI.Theme);
|
||||
|
||||
ApplyTheme(true);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_root == null || _runtimeThemeSheet == null) return;
|
||||
|
||||
_runtimeThemeSheet.Apply(0);
|
||||
ToggleRefreshClass();
|
||||
_lastAppliedNightBlend = 0;
|
||||
}
|
||||
|
||||
private void ApplyTheme(bool force = false)
|
||||
{
|
||||
if (_root == null || _config.UI.Theme == null || _runtimeThemeSheet == null) return;
|
||||
|
||||
var nightBlend = _world.TimeState.NightBlend;
|
||||
if (!force && !Application.isEditor && Mathf.Approximately(nightBlend, _lastAppliedNightBlend)) return;
|
||||
|
||||
_runtimeThemeSheet.Apply(nightBlend);
|
||||
ToggleRefreshClass();
|
||||
|
||||
_lastAppliedNightBlend = nightBlend;
|
||||
}
|
||||
|
||||
private void ToggleRefreshClass()
|
||||
{
|
||||
_refreshClassEnabled = !_refreshClassEnabled;
|
||||
_root.EnableInClassList(RuntimeThemeSheet.RefreshClassName, _refreshClassEnabled);
|
||||
}
|
||||
|
||||
private sealed class RuntimeThemeSheet
|
||||
{
|
||||
public const string RefreshClassName = "__day-night-theme-refresh";
|
||||
|
||||
private readonly StyleSheet _styleSheet;
|
||||
|
||||
private readonly RuntimePropertyBinding[] _bindings;
|
||||
|
||||
private RuntimeThemeSheet(StyleSheet styleSheet, RuntimePropertyBinding[] bindings)
|
||||
{
|
||||
_styleSheet = styleSheet;
|
||||
_bindings = bindings;
|
||||
}
|
||||
|
||||
public static RuntimeThemeSheet TryCreate(VisualElement root, DayNightUITheme theme)
|
||||
{
|
||||
if (root == null || theme == null) return null;
|
||||
|
||||
var styleSheets = StyleSheetReflection.GetStyleSheets(root);
|
||||
if (styleSheets == null) return null;
|
||||
|
||||
foreach (var styleSheet in styleSheets)
|
||||
{
|
||||
if (!StyleSheetReflection.TryCreateBindings(styleSheet, theme.OrderedProperties, out var bindings)) continue;
|
||||
|
||||
return new RuntimeThemeSheet(styleSheet, bindings);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Apply(float nightBlend)
|
||||
{
|
||||
foreach (var binding in _bindings) binding.Apply(nightBlend);
|
||||
|
||||
_styleSheet.contentHash = unchecked(_styleSheet.contentHash + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RuntimePropertyBinding
|
||||
{
|
||||
private readonly DayNightUITheme.ThemeColorProperty _themeProperty;
|
||||
|
||||
private readonly object _manipulator;
|
||||
|
||||
private readonly object[] _setColorArguments = new object[2];
|
||||
|
||||
public RuntimePropertyBinding(DayNightUITheme.ThemeColorProperty themeProperty, object manipulator)
|
||||
{
|
||||
_themeProperty = themeProperty;
|
||||
_manipulator = manipulator;
|
||||
}
|
||||
|
||||
public void Apply(float nightBlend)
|
||||
{
|
||||
_setColorArguments[0] = 0;
|
||||
_setColorArguments[1] = Color.Lerp(_themeProperty.DayColor, _themeProperty.NightColor, nightBlend);
|
||||
StyleSheetReflection.SetColor(_manipulator, _setColorArguments);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StyleSheetReflection
|
||||
{
|
||||
private static readonly FieldInfo VisualElementStyleSheetListField = typeof(VisualElement).GetField("styleSheetList", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
private static readonly FieldInfo StyleSheetRulesField = typeof(StyleSheet).GetField("m_Rules", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
private static readonly Type StyleRuleType = StyleSheetRulesField?.FieldType.GetElementType();
|
||||
|
||||
private static readonly MethodInfo StyleRuleGetPropertiesMethod = StyleRuleType?.GetMethod("get_properties", BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
private static readonly Type StylePropertyType = StyleRuleGetPropertiesMethod?.ReturnType.GetElementType();
|
||||
|
||||
private static readonly MethodInfo StylePropertyGetNameMethod = StylePropertyType?.GetMethod("get_name", BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
private static readonly MethodInfo StylePropertyGetManipulatorMethod = StylePropertyType?.GetMethod("GetManipulator", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
private static readonly Type ManipulatorType = StylePropertyGetManipulatorMethod?.ReturnType;
|
||||
|
||||
private static readonly MethodInfo ManipulatorSetColorMethod = ManipulatorType?.GetMethod(
|
||||
"SetColor",
|
||||
BindingFlags.Instance | BindingFlags.Public,
|
||||
null,
|
||||
new[]
|
||||
{
|
||||
typeof(int),
|
||||
typeof(Color)
|
||||
},
|
||||
null);
|
||||
|
||||
public static List<StyleSheet> GetStyleSheets(VisualElement root)
|
||||
{
|
||||
return VisualElementStyleSheetListField?.GetValue(root) as List<StyleSheet>;
|
||||
}
|
||||
|
||||
public static void SetColor(object manipulator, object[] arguments)
|
||||
{
|
||||
ManipulatorSetColorMethod?.Invoke(manipulator, arguments);
|
||||
}
|
||||
|
||||
public static bool TryCreateBindings(StyleSheet styleSheet, IReadOnlyList<DayNightUITheme.ThemeColorProperty> themeProperties, out RuntimePropertyBinding[] bindings)
|
||||
{
|
||||
bindings = null;
|
||||
if (styleSheet == null || themeProperties == null) return false;
|
||||
if (StyleSheetRulesField == null ||
|
||||
StyleRuleGetPropertiesMethod == null ||
|
||||
StylePropertyGetNameMethod == null ||
|
||||
StylePropertyGetManipulatorMethod == null ||
|
||||
ManipulatorSetColorMethod == null)
|
||||
return false;
|
||||
|
||||
var propertiesByName = new Dictionary<string, object>(themeProperties.Count, StringComparer.Ordinal);
|
||||
var rules = StyleSheetRulesField.GetValue(styleSheet) as Array;
|
||||
if (rules == null) return false;
|
||||
|
||||
foreach (var rule in rules)
|
||||
{
|
||||
if (rule == null) continue;
|
||||
|
||||
var styleProperties = StyleRuleGetPropertiesMethod.Invoke(rule, null) as Array;
|
||||
if (styleProperties == null) continue;
|
||||
|
||||
foreach (var styleProperty in styleProperties)
|
||||
{
|
||||
if (styleProperty == null) continue;
|
||||
|
||||
var propertyName = StylePropertyGetNameMethod.Invoke(styleProperty, null) as string;
|
||||
if (string.IsNullOrEmpty(propertyName)) continue;
|
||||
if (propertiesByName.ContainsKey(propertyName)) continue;
|
||||
|
||||
propertiesByName[propertyName] = styleProperty;
|
||||
}
|
||||
}
|
||||
|
||||
var resolvedBindings = new RuntimePropertyBinding[themeProperties.Count];
|
||||
|
||||
for (var i = 0; i < themeProperties.Count; i++)
|
||||
{
|
||||
var themeProperty = themeProperties[i];
|
||||
if (!propertiesByName.TryGetValue(themeProperty.Name, out var styleProperty)) return false;
|
||||
|
||||
var manipulator = StylePropertyGetManipulatorMethod.Invoke(styleProperty, new object[] { styleSheet });
|
||||
resolvedBindings[i] = new RuntimePropertyBinding(themeProperty, manipulator);
|
||||
}
|
||||
|
||||
bindings = resolvedBindings;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user