Files
Voile/Voile/Source/UI/Style.cs

280 lines
7.9 KiB
C#

using System.Diagnostics.CodeAnalysis;
using Voile.Resources;
using Voile.Resources.DataReaders;
using Voile.UI.Containers;
using Voile.VFS;
namespace Voile.UI;
/// <summary>
/// UI style settings.
/// </summary>
public class Style
{
public enum TransitionType
{
Linear,
EaseIn,
EaseOut,
EaseInOut
}
public float TransitionDuration = 0f;
public TransitionType Transition = TransitionType.Linear;
public Style() { }
public Size? Padding { get; set; }
public Color? BackgroundColor { get; set; }
public Size? BorderSize { get; set; }
public Color? BorderColor { get; set; }
public float CornerRadius { get; set; }
public Color? TextColor { get; set; }
/// <summary>
/// Merges this <see cref="Style"/> with a different one.<br />
/// Properties that are not set for this <see cref="Style"/> will be inherited from <paramref name="overrideStyle"/>.
/// </summary>
/// <param name="other"></param>
/// <returns>A merged <see cref="Style"/>.</returns>
public Style Merge(Style other)
{
return new Style
{
Padding =
other.Padding ?? Padding,
BackgroundColor =
other.BackgroundColor ?? BackgroundColor,
BorderSize =
other.BorderSize ?? BorderSize,
BorderColor =
other.BorderColor ?? BorderColor,
TextColor =
other.TextColor ?? TextColor,
CornerRadius =
other.CornerRadius != default
? other.CornerRadius
: CornerRadius,
TransitionDuration =
other.TransitionDuration != default
? other.TransitionDuration
: TransitionDuration,
Transition =
other.Transition
};
}
}
public class StyleSheetLoader : ResourceLoader<StyleSheet>
{
public override IEnumerable<string> SupportedExtensions => [".toml"];
protected override StyleSheet LoadResource(string path)
{
var result = new StyleSheet(path);
var allStyles = new Dictionary<string, Style>();
using var stream = VirtualFileSystem.Read(path);
_reader.Read(stream);
foreach (var styleKey in _reader.GetSubKeysRecursive())
{
var subReader = _reader.GetSubReader(styleKey);
if (subReader == null || !subReader.Valid())
continue;
var style = ParseStyle(subReader, styleKey);
if (style != null)
{
allStyles[styleKey] = style;
}
}
foreach (var kvp in allStyles)
{
var finalStyle = GetMergedStyle(kvp.Key, allStyles);
result.Add(kvp.Key, finalStyle);
}
return result;
}
private Style ParseStyle(TomlDataReader reader, string input)
{
var style = new Style();
string transitionName = reader.GetString("TransitionType", "Linear");
if (!Enum.TryParse<Style.TransitionType>(transitionName, true, out var transition))
throw new ArgumentException($"\"{transitionName}\" is not a valid TransitionType.");
style.Transition = transition;
if (reader.HasKey("BackgroundColor"))
style.BackgroundColor = reader.GetColor("BackgroundColor", Color.Transparent);
if (reader.HasKey("TextColor"))
style.TextColor = reader.GetColor("TextColor", Color.Black);
if (reader.HasKey("Padding"))
style.Padding = reader.GetSize("Padding", Size.Zero);
if (reader.HasKey("BorderSize"))
style.BorderSize = reader.GetSize("BorderSize", Size.Zero);
if (reader.HasKey("BorderColor"))
style.BorderColor = reader.GetColor("BorderColor", Color.Transparent);
return style;
}
private Style GetMergedStyle(string fullKey, Dictionary<string, Style> allStyles)
{
var parts = fullKey.Split('.');
var merged = new Style();
for (int i = 1; i <= parts.Length; i++)
{
var subKey = string.Join('.', parts.Take(i));
if (allStyles.TryGetValue(subKey, out var parentStyle))
{
merged = merged.Merge(parentStyle);
}
}
return merged;
}
private readonly TomlDataReader _reader = new();
}
public class StyleSheet : Resource
{
public StyleSheet(string path) : base(path)
{
}
public StyleSheet(Dictionary<string, Style> styles) : base(string.Empty)
{
_styles = styles;
}
public void Add(string key, Style style) => _styles.Add(key, style);
public bool TryGet(
string styleName,
[NotNullWhen(true)] out Style? style)
{
style = Resolve(styleName);
return style != null;
}
public static StyleSheet Default => new(new Dictionary<string, Style>()
{
{"Label", new Style()
{
TextColor = Color.FromHexString("#161616"),
BackgroundColor = Color.DarkRed,
BorderSize = new Size(2.0f),
BorderColor = Color.Red
}},
{ "Button", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#0f62fe"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Normal", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#0f62fe"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Hovered", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#0353e9"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Pressed", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#002d9c"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Danger", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#da1e28"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Danger.Normal", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#da1e28"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Danger.Hovered", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#ba1b23"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Button.Danger.Pressed", new Style()
{
Padding = new Size(8.0f),
BackgroundColor = Color.FromHexString("#750e13"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Container", new Style()
{
BackgroundColor = Color.FromHexString("#ffffff"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Container.Layer01", new Style()
{
BackgroundColor = Color.FromHexString("#f4f4f4"),
TextColor = Color.FromHexString("#ffffff"),
}},
{"Container.Layer02", new Style()
{
BackgroundColor = Color.FromHexString("#e8e8e8"),
TextColor = Color.FromHexString("#ffffff"),
}},
});
private Style? Resolve(string fullKey)
{
var parts = fullKey.Split('.');
Style? merged = null;
for (int i = 1; i <= parts.Length; i++)
{
var subKey = string.Join('.',
parts.Take(i));
if (_styles.TryGetValue(
subKey,
out var style))
{
merged ??= new Style();
merged = merged.Merge(style);
}
}
return merged;
}
private Dictionary<string, Style> _styles = new();
}