293 lines
9.7 KiB
C#
293 lines
9.7 KiB
C#
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
|
|
}
|
|
}
|