diff --git a/TestGame/Resources/default.style.toml b/TestGame/Resources/default.style.toml new file mode 100644 index 0000000..97873f4 --- /dev/null +++ b/TestGame/Resources/default.style.toml @@ -0,0 +1,46 @@ +[Button] +BackgroundColor = "#0f62fe" +TextColor = "#37eb34" +Padding = 16.0 + +# Creates an empty rule for Button.Normal. +# This will inherit style from Button, this is a temporary workaround. +[Button.Normal] + +[Button.Hovered] +BackgroundColor = "#0353e9" + +[Button.Pressed] +BackgroundColor = "#002d9c" + +[Button.Danger] +TextColor = "#ffffff" + +[Button.Danger.Normal] +BackgroundColor = "#da1e28" + +[Button.Danger.Hovered] +BackgroundColor = "#ba1b23" + +[Button.Danger.Pressed] +BackgroundColor = "#750e13" + +[Button.Outline] +BackgroundColor = [0, 0, 0, 0] +BorderSize = 1.0 +BorderColor = "#0f62fe" +TextColor = "#0353e9" + +[Button.Outline.Normal] +TextColor = "#0353e9" + +[Button.Outline.Hovered] +BackgroundColor = "#0353e9" + +[Button.Outline.Pressed] +BackgroundColor = "#002d9c" +BorderColor = [0, 0, 0, 0] + +# Default background color for all Container derived classes. +[Container] +BackgroundColor = "#e0e0e0" diff --git a/TestGame/TestGame.cs b/TestGame/TestGame.cs index 81984aa..a385b17 100644 --- a/TestGame/TestGame.cs +++ b/TestGame/TestGame.cs @@ -19,7 +19,7 @@ public class TestGame : Game { InitializeSystemsDefault(); - _uiSystem = new UISystem(Input, StyleSheet.Default); + _uiSystem = new UISystem(Input); // _uiSystem.RenderDebugRects = true; _particleSystem = new ParticleSystem(); @@ -36,6 +36,7 @@ public class TestGame : Game protected override void LoadResources() { ResourceManager.AddResourceLoaderAssociation(new ParticleEmitterSettingsResourceLoader()); + ResourceManager.AddResourceLoaderAssociation(new StyleSheetLoader()); if (!ResourceManager.TryLoad("fonts/Inter-Regular.ttf", out _font)) { @@ -55,27 +56,32 @@ public class TestGame : Game throw new Exception("Failed to load emitter settings!"); } + if (ResourceManager.TryLoad("default.style.toml", out _styleSheet)) + { + + } + _defaultFontSet = new([_font, _jpFont]); } protected override void Ready() { Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) }); - _emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect); + // _emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect); - var addButton = new Button("Add element", _defaultFontSet, () => { _container.AddChild(new Label("Hello, World!", _defaultFontSet)); }); + _uiSystem.SetStyleSheet(_styleSheet); - var removeButton = new Button("Remove element", _defaultFontSet, () => - { - if (_container.Children.Count == 0) return; - var lastChild = _container.Children.Last(); - _container.RemoveChild(lastChild); - }); + var addButton = new Button("Default button", _defaultFontSet); + + var removeButton = new Button("Danger button", _defaultFontSet); removeButton.StyleVariant = "Danger"; - // _buttonContainer.AddChild(addButton); - // _buttonContainer.AddChild(removeButton); + var outlineButton = new Button("Outline button", _defaultFontSet); + outlineButton.StyleVariant = "Outline"; + + _buttonContainer.AddChild(addButton); + _buttonContainer.AddChild(removeButton); var c = new HorizontalContainer() { @@ -86,11 +92,12 @@ public class TestGame : Game c.AddChild(addButton); c.AddChild(removeButton); + c.AddChild(outlineButton); var vc = new VerticalContainer(0.0f); vc.AddChild(c); - var f = new MarginContainer(new Margin(0.0f)); + var f = new MarginContainer(new Size(0.0f)); f.AddChild(_container); vc.AddChild(f); @@ -105,7 +112,7 @@ public class TestGame : Game if (Input.IsActionPressed("reload")) { ResourceManager.Reload(); - _particleSystem!.RestartEmitter(_emitterId); + // _particleSystem!.RestartEmitter(_emitterId); } } @@ -147,6 +154,7 @@ public class TestGame : Game private ResourceRef _fireEffect; private ResourceRef _font; private ResourceRef _jpFont; + private ResourceRef _styleSheet; private FontSet _defaultFontSet; diff --git a/Voile/Source/Color.cs b/Voile/Source/Color.cs index 88e2400..3d4be76 100644 --- a/Voile/Source/Color.cs +++ b/Voile/Source/Color.cs @@ -6,6 +6,7 @@ namespace Voile /// public record struct Color { + public static readonly Color Transparent = new(1.0f, 1.0f, 1.0f, 0.0f); public static readonly Color AliceBlue = new(0xF0F8FF); public static readonly Color AntiqueWhite = new(0xFAEBD7); public static readonly Color Aqua = new(0x00FFFF); diff --git a/Voile/Source/Rect.cs b/Voile/Source/Rect.cs index 5db7712..224b7e0 100644 --- a/Voile/Source/Rect.cs +++ b/Voile/Source/Rect.cs @@ -20,4 +20,62 @@ public record Rect(float Width = 0.0f, float Height = 0.0f) public static bool operator <(Rect left, Rect right) => left.CompareTo(right) < 0; public static bool operator >=(Rect left, Rect right) => left.CompareTo(right) >= 0; public static bool operator <=(Rect left, Rect right) => left.CompareTo(right) <= 0; +} + +/// +/// Represents the size offsets applied around a rectangle. +/// +public struct Size : IEquatable +{ + public float Left; + public float Right; + public float Top; + public float Bottom; + + public Size(float uniform) + { + Left = Right = Top = Bottom = uniform; + } + + public Size(float horizontal, float vertical) + { + Left = Right = horizontal; + Top = Bottom = vertical; + } + + public Size(float left, float right, float top, float bottom) + { + Left = left; + Right = right; + Top = top; + Bottom = bottom; + } + + public static Size Zero => new Size(0); + + public static Rect operator +(Size margin, Rect rect) => + new Rect(rect.Width + margin.Left + margin.Right, + rect.Height + margin.Top + margin.Bottom); + + public static Rect operator +(Rect rect, Size margin) => + margin + rect; + + + public static bool operator ==(Size a, Size b) => +a.Equals(b); + + public static bool operator !=(Size a, Size b) => + !a.Equals(b); + + public bool Equals(Size other) => + Left == other.Left && + Right == other.Right && + Top == other.Top && + Bottom == other.Bottom; + + public override bool Equals(object? obj) => + obj is Size other && Equals(other); + + public override int GetHashCode() => + HashCode.Combine(Left, Right, Top, Bottom); } \ No newline at end of file diff --git a/Voile/Source/Resources/DataReaders/IDataReader.cs b/Voile/Source/Resources/DataReaders/IDataReader.cs index 09aca08..8b80035 100644 --- a/Voile/Source/Resources/DataReaders/IDataReader.cs +++ b/Voile/Source/Resources/DataReaders/IDataReader.cs @@ -79,5 +79,7 @@ namespace Voile.Resources.DataReaders /// Default value in case this getter fails to get data. /// Vector2 GetVector2(string key, Vector2 defaultValue); + + T[] GetArray(string key, T[] defaultValue); } } \ No newline at end of file diff --git a/Voile/Source/Resources/DataReaders/TomlDataReader.cs b/Voile/Source/Resources/DataReaders/TomlDataReader.cs index 078689d..7470a1c 100644 --- a/Voile/Source/Resources/DataReaders/TomlDataReader.cs +++ b/Voile/Source/Resources/DataReaders/TomlDataReader.cs @@ -9,163 +9,114 @@ namespace Voile.Resources.DataReaders; /// public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter { - public string ExpectedHeader { get; private set; } = string.Empty; - public TomlDataReader(string expectedHeader) - { - ExpectedHeader = expectedHeader; - } + public TomlDataReader() { } public void Read(Stream data) { - using (var reader = new StreamReader(data)) + using var reader = new StreamReader(data); + _table = TOML.Parse(reader); + _valid = _table != null; + } + + public bool Valid() => _valid; + + public bool HasKey(string key) + { + return _table != null && + _table.HasKey(key); + } + + public IEnumerable GetSubKeys() + { + if (_table == null) + return Enumerable.Empty(); + + return _table.Keys + .Where(k => _table[k].IsTable) + .ToList(); + } + + public IEnumerable GetSubKeysRecursive(string prefix = "") + { + if (_table == null) + yield break; + + foreach (var key in _table.Keys) { - _table = TOML.Parse(reader); - _valid = _table.HasKey(ExpectedHeader); + var fullKey = string.IsNullOrEmpty(prefix) ? key : $"{prefix}.{key}"; + if (_table[key].IsTable) + { + var subReader = GetSubReader(key); + if (subReader != null) + { + foreach (var subKey in subReader.GetSubKeysRecursive(fullKey)) + yield return subKey; + + yield return fullKey; + } + } } } + public IEnumerable GetSubKeys(string subPath) + { + var subReader = GetSubReader(subPath); + if (subReader?._table == null) + return Enumerable.Empty(); + + return subReader._table.Keys + .Where(k => subReader._table[k].IsTable) + .ToList(); + } + + public TomlDataReader? GetSubReader(string path) + { + var current = _table; + foreach (var part in path.Split('.')) + { + if (current == null || !current.HasKey(part) || !current[part].IsTable) + return null; + + current = current[part].AsTable; + } + + return new TomlDataReader { _table = current, _valid = true }; + } + public bool GetBool(string key, bool defaultValue = false) - { - if (_table is null) - { - return defaultValue; - } - - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) - { - return defaultValue; - } - - var tableValue = dataTable[key]; - - if (!tableValue.IsBoolean) - { - return defaultValue; - } - - return tableValue.AsBoolean; - } + => TryGetNode(key, out var node) && node.IsBoolean ? node.AsBoolean : defaultValue; public int GetInt(string key, int defaultValue = 0) - { - if (_table is null) - { - return defaultValue; - } - - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) - { - return defaultValue; - } - - var tableValue = dataTable[key]; - - if (!tableValue.IsInteger) - { - return defaultValue; - } - - return tableValue.AsInteger; - } + => TryGetNode(key, out var node) && node.IsInteger ? node.AsInteger : defaultValue; public long GetLong(string key, long defaultValue = 0) - { - if (_table is null) - { - return defaultValue; - } - - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) - { - return defaultValue; - } - - var tableValue = dataTable[key]; - - if (!tableValue.IsInteger) - { - return defaultValue; - } - - return tableValue.AsInteger.Value; - } + => TryGetNode(key, out var node) && node.IsInteger ? node.AsInteger.Value : defaultValue; public float GetFloat(string key, float defaultValue = 0) { - if (_table is null) - { + if (!TryGetNode(key, out var node)) return defaultValue; - } - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) - { - return defaultValue; - } - - var tableValue = dataTable[key]; - - if (!tableValue.IsFloat) - { - if (tableValue.IsInteger) return (float)tableValue.AsInteger.Value; - return defaultValue; - } - - return tableValue.AsFloat; + if (node.IsFloat) return node.AsFloat; + if (node.IsInteger) return node.AsInteger; + return defaultValue; } public double GetDouble(string key, double defaultValue = 0) - { - if (_table is null) - { - return defaultValue; - } - - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) - { - return defaultValue; - } - - if (!dataTable.IsFloat) - { - return defaultValue; - } - - return dataTable.AsFloat.Value; - } + => TryGetNode(key, out var node) && node.IsFloat ? node.AsFloat : defaultValue; public Color GetColor(string key, Color defaultValue) { - if (_table is null) - { + if (!TryGetNode(key, out var node)) return defaultValue; - } - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) + if (node.IsInteger) { - return defaultValue; + return new Color(node.AsInteger); } - - var colorNode = dataTable[key]; - - if (colorNode.IsInteger) + else if (node.IsArray) { - return new Color(colorNode.AsInteger); - } - else if (colorNode.IsArray) - { - var colorArray = colorNode.AsArray; + var colorArray = node.AsArray; var rNode = colorArray[0]; var gNode = colorArray[1]; @@ -185,9 +136,9 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue return new Color((byte)r, (byte)g, (byte)b, (byte)a); } - else if (colorNode.IsString) + else if (node.IsString) { - var colorHexString = colorNode.AsString.Value; + var colorHexString = node.AsString.Value; return Color.FromHexString(colorHexString); } else @@ -198,32 +149,56 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue public Vector2 GetVector2(string key, Vector2 defaultValue) { - if (_table is null) - { + if (!TryGetNode(key, out var node) || !node.IsArray || node.AsArray.RawArray.Count != 2) return defaultValue; - } - var dataTable = _table[ExpectedHeader]; - - if (!dataTable.HasKey(key)) - { - return defaultValue; - } - - var vector2Node = dataTable[key]; - - if (!vector2Node.IsArray) - { - return defaultValue; - } - - var vector2Array = vector2Node.AsArray; - - return new Vector2(vector2Array[0], vector2Array[1]); + var arr = node.AsArray; + return new Vector2(arr[0], arr[1]); } - public bool Valid() => _valid; + public T[] GetArray(string key, T[] defaultValue) + { + throw new NotImplementedException("Generic array reading not implemented yet."); + } + + private bool TryGetNode(string key, out TomlNode node) + { + node = null!; + if (_table == null) + return false; + + var current = _table; + var parts = key.Split('.'); + + for (int i = 0; i < parts.Length; i++) + { + if (!current.HasKey(parts[i])) + return false; + + var child = current[parts[i]]; + + if (i == parts.Length - 1) + { + node = child; + return true; + } + + if (!child.IsTable) + return false; + + current = child.AsTable; + } + + return false; + } private TomlTable? _table; private bool _valid; + + // Internal use for subreaders + private TomlDataReader(TomlTable table) + { + _table = table; + _valid = true; + } } \ No newline at end of file diff --git a/Voile/Source/Resources/Loaders/StyleLoader.cs b/Voile/Source/Resources/Loaders/StyleLoader.cs deleted file mode 100644 index 1efece7..0000000 --- a/Voile/Source/Resources/Loaders/StyleLoader.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Voile.UI; - -namespace Voile.Resources; - -public class StyleLoader : ResourceLoader