riversong code showcase
This commit is contained in:
292
Source/Riversong/Game/UI/DayNightUITheme.cs
Normal file
292
Source/Riversong/Game/UI/DayNightUITheme.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[CreateAssetMenu(fileName = "DayNightUITheme", menuName = "Riversong Code Showcase/Day Night UI Theme")]
|
||||
public class DayNightUITheme : ScriptableObject
|
||||
{
|
||||
private const string CommonUssAssetPath = "_Project/GameAssets/UI/Common.uss";
|
||||
|
||||
private const string CommonUssEditorAssetPath = "Assets/_Project/GameAssets/UI/Common.uss";
|
||||
|
||||
private static readonly Regex RootColorVariableRegex = new(@"^(?<name>--[a-z0-9-]+)\s*:\s*(?<value>#[0-9A-Fa-f]{6,8})\s*;$", RegexOptions.Compiled);
|
||||
|
||||
[TableList(AlwaysExpanded = true, ShowIndexLabels = true, HideToolbar = true)]
|
||||
[ListDrawerSettings(DefaultExpandedState = true, DraggableItems = false, HideAddButton = true, HideRemoveButton = true, ShowFoldout = false)]
|
||||
public List<ThemeColorProperty> Properties = new();
|
||||
|
||||
internal IReadOnlyList<ThemeColorProperty> OrderedProperties => Properties;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EnsureProperties();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
EnsureProperties();
|
||||
}
|
||||
|
||||
private void EnsureProperties()
|
||||
{
|
||||
Properties ??= new List<ThemeColorProperty>();
|
||||
|
||||
var definitions = GetThemePropertyDefinitions();
|
||||
var existingProperties = Properties;
|
||||
var existingPropertiesByName = CreatePropertiesByName(existingProperties);
|
||||
var definitionNames = CreateDefinitionNames(definitions);
|
||||
|
||||
var orderedProperties = new List<ThemeColorProperty>(definitions.Count);
|
||||
|
||||
for (var i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
var definition = definitions[i];
|
||||
var property = TryGetProperty(existingPropertiesByName, definition.Name) ?? TryGetSameIndexFallbackProperty(existingProperties, definitionNames, i);
|
||||
|
||||
property ??= new ThemeColorProperty(definition.Name, Color.white, Color.white);
|
||||
property.Name = definition.Name;
|
||||
|
||||
orderedProperties.Add(property);
|
||||
}
|
||||
|
||||
Properties = orderedProperties;
|
||||
}
|
||||
|
||||
private static Dictionary<string, ThemeColorProperty> CreatePropertiesByName(IEnumerable<ThemeColorProperty> properties)
|
||||
{
|
||||
var propertiesByName = new Dictionary<string, ThemeColorProperty>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property == null || string.IsNullOrWhiteSpace(property.Name)) continue;
|
||||
|
||||
propertiesByName.TryAdd(property.Name, property);
|
||||
}
|
||||
|
||||
return propertiesByName;
|
||||
}
|
||||
|
||||
private static HashSet<string> CreateDefinitionNames(IEnumerable<ThemePropertyDefinition> definitions)
|
||||
{
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var definition in definitions) names.Add(definition.Name);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private static ThemeColorProperty TryGetProperty(Dictionary<string, ThemeColorProperty> propertiesByName, string propertyName)
|
||||
{
|
||||
return propertiesByName.GetValueOrDefault(propertyName);
|
||||
}
|
||||
|
||||
private static ThemeColorProperty TryGetSameIndexFallbackProperty(IReadOnlyList<ThemeColorProperty> existingProperties, HashSet<string> definitionNames, int index)
|
||||
{
|
||||
if (index >= existingProperties.Count) return null;
|
||||
|
||||
var property = existingProperties[index];
|
||||
if (property == null) return null;
|
||||
|
||||
return string.IsNullOrWhiteSpace(property.Name) || !definitionNames.Contains(property.Name) ? property : null;
|
||||
}
|
||||
|
||||
private static List<ThemePropertyDefinition> GetThemePropertyDefinitions()
|
||||
{
|
||||
return TryReadThemePropertyDefinitionsFromCommonUss();
|
||||
}
|
||||
|
||||
private static List<ThemePropertyDefinition> TryReadThemePropertyDefinitionsFromCommonUss()
|
||||
{
|
||||
var path = GetCommonUssFullPath();
|
||||
if (!File.Exists(path)) return new List<ThemePropertyDefinition>();
|
||||
|
||||
var definitions = new List<ThemePropertyDefinition>();
|
||||
var lines = File.ReadAllLines(path);
|
||||
|
||||
var inRootBlock = false;
|
||||
|
||||
foreach (var rawLine in lines)
|
||||
{
|
||||
var line = rawLine.Trim();
|
||||
|
||||
if (!inRootBlock)
|
||||
{
|
||||
if (line == ":root {") inRootBlock = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line == "}") break;
|
||||
|
||||
var match = RootColorVariableRegex.Match(line);
|
||||
if (!match.Success) continue;
|
||||
|
||||
var name = match.Groups["name"].Value;
|
||||
var dayColor = FromHex(match.Groups["value"].Value);
|
||||
var nightColor = GetDefaultNightColor(name, dayColor);
|
||||
|
||||
definitions.Add(new ThemePropertyDefinition(name, dayColor, nightColor));
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private static Color GetDefaultNightColor(string name, Color dayColor)
|
||||
{
|
||||
return name switch
|
||||
{
|
||||
"--wood-color" => FromHex("#424C54"),
|
||||
_ => dayColor
|
||||
};
|
||||
}
|
||||
|
||||
private static Color FromHex(string hex)
|
||||
{
|
||||
if (ColorUtility.TryParseHtmlString(hex, out var color)) return color;
|
||||
|
||||
throw new ArgumentException($"Invalid color value: {hex}", nameof(hex));
|
||||
}
|
||||
|
||||
private static string GetCommonUssFullPath()
|
||||
{
|
||||
return Path.Combine(Application.dataPath, CommonUssAssetPath.Replace('/', Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
private static string ToHex(Color color)
|
||||
{
|
||||
var color32 = (Color32)color;
|
||||
|
||||
return color32.a == byte.MaxValue ? $"#{color32.r:X2}{color32.g:X2}{color32.b:X2}" : $"#{color32.r:X2}{color32.g:X2}{color32.b:X2}{color32.a:X2}";
|
||||
}
|
||||
|
||||
private readonly struct ThemePropertyDefinition
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public readonly Color DayColor;
|
||||
|
||||
public readonly Color NightColor;
|
||||
|
||||
public ThemePropertyDefinition(string name, Color dayColor, Color nightColor)
|
||||
{
|
||||
Name = name;
|
||||
DayColor = dayColor;
|
||||
NightColor = nightColor;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ThemeColorProperty
|
||||
{
|
||||
[ReadOnly]
|
||||
public string Name;
|
||||
|
||||
public Color DayColor = Color.white;
|
||||
|
||||
public Color NightColor = Color.white;
|
||||
|
||||
public ThemeColorProperty()
|
||||
{
|
||||
}
|
||||
|
||||
public ThemeColorProperty(string name, Color dayColor, Color nightColor)
|
||||
{
|
||||
Name = name;
|
||||
DayColor = dayColor;
|
||||
NightColor = nightColor;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[HorizontalGroup("Sync")]
|
||||
[Button("Copy Day Colors From USS", ButtonSizes.Large)]
|
||||
[GUIColor("cyan")]
|
||||
private void CopyDayColorsFromUss()
|
||||
{
|
||||
EnsureProperties();
|
||||
|
||||
var definitions = TryReadThemePropertyDefinitionsFromCommonUss();
|
||||
if (definitions.Count == 0)
|
||||
{
|
||||
Debug.LogWarning($"Could not read any theme properties from '{CommonUssEditorAssetPath}'.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
var propertiesByName = CreatePropertiesByName(Properties);
|
||||
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
if (!propertiesByName.TryGetValue(definition.Name, out var property)) continue;
|
||||
|
||||
property.DayColor = definition.DayColor;
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssetIfDirty(this);
|
||||
}
|
||||
|
||||
[HorizontalGroup("Sync")]
|
||||
[Button("Apply Day Colors To USS", ButtonSizes.Large)]
|
||||
[GUIColor("cyan")]
|
||||
private void ApplyDayColorsToUss()
|
||||
{
|
||||
EnsureProperties();
|
||||
|
||||
var path = GetCommonUssFullPath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Debug.LogWarning($"Could not find '{CommonUssEditorAssetPath}'.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
var propertiesByName = CreatePropertiesByName(Properties);
|
||||
var lines = File.ReadAllLines(path);
|
||||
var updatedAnyLine = false;
|
||||
var inRootBlock = false;
|
||||
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var rawLine = lines[i];
|
||||
var trimmedLine = rawLine.Trim();
|
||||
|
||||
if (!inRootBlock)
|
||||
{
|
||||
if (trimmedLine == ":root {") inRootBlock = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmedLine == "}") break;
|
||||
|
||||
var match = RootColorVariableRegex.Match(trimmedLine);
|
||||
if (!match.Success) continue;
|
||||
|
||||
var propertyName = match.Groups["name"].Value;
|
||||
if (!propertiesByName.TryGetValue(propertyName, out var property)) continue;
|
||||
|
||||
var indentationLength = rawLine.Length - rawLine.TrimStart().Length;
|
||||
var indentation = rawLine.Substring(0, indentationLength);
|
||||
lines[i] = $"{indentation}{propertyName}: {ToHex(property.DayColor)};";
|
||||
updatedAnyLine = true;
|
||||
}
|
||||
|
||||
if (!updatedAnyLine)
|
||||
{
|
||||
Debug.LogWarning($"No matching ':root' theme color properties were updated in '{CommonUssEditorAssetPath}'.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
File.WriteAllLines(path, lines);
|
||||
AssetDatabase.ImportAsset(CommonUssEditorAssetPath);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user