Compare commits
25 Commits
standard-r
...
09c24e7123
| Author | SHA1 | Date | |
|---|---|---|---|
| 09c24e7123 | |||
| 87e0a69dcf | |||
| b810e1b882 | |||
| 26cb66dbe0 | |||
| b3c1db3145 | |||
| 9bc9810c8f | |||
| 0ec4e45c38 | |||
| 6b108ba56c | |||
| e0e8d6e9ff | |||
| 552e05d498 | |||
| 17196c9437 | |||
| 4b2aa31b63 | |||
| 90fe38b017 | |||
| 8a1e359c22 | |||
| 64d3dba42d | |||
| 5bf052db96 | |||
| 389a73cf24 | |||
| d44341974f | |||
| b2f3e1c351 | |||
| 255dea138b | |||
| 9fa6b45cea | |||
| 5871e8966b | |||
| ed9f17e6c4 | |||
| 4362e88eab | |||
| 58efd449a8 |
14
TODO.md
14
TODO.md
@@ -69,6 +69,18 @@
|
|||||||
- Make action system use an InputMap resource instead.
|
- Make action system use an InputMap resource instead.
|
||||||
- Gamepad support
|
- Gamepad support
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
- Implement Profiler class.
|
||||||
|
- Generate a Speedscope report.
|
||||||
|
- Collect reports for systems
|
||||||
|
- Game
|
||||||
|
- Render
|
||||||
|
- Audio
|
||||||
|
- ResourceManager
|
||||||
|
- UI
|
||||||
|
- Particles
|
||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
- ~~Layout~~
|
- ~~Layout~~
|
||||||
@@ -80,6 +92,8 @@
|
|||||||
- ~~Positioning (anchors)~~
|
- ~~Positioning (anchors)~~
|
||||||
- ~~Move layouting to Render instead of Update, use Update for input.~~
|
- ~~Move layouting to Render instead of Update, use Update for input.~~
|
||||||
- Input propagation
|
- Input propagation
|
||||||
|
- Use GridSet to efficiently query inputtable UI elements.
|
||||||
|
- Add element focus logic, make them focusable with action inputs.
|
||||||
- Basic input elements (button, text field, toggle).
|
- Basic input elements (button, text field, toggle).
|
||||||
- Styling
|
- Styling
|
||||||
- Add style settings for UI panels (for buttons, labels, etc.).
|
- Add style settings for UI panels (for buttons, labels, etc.).
|
||||||
|
|||||||
BIN
TestGame/Resources/fonts/NotoSansJP-Regular.ttf
Normal file
BIN
TestGame/Resources/fonts/NotoSansJP-Regular.ttf
Normal file
Binary file not shown.
3
TestGame/Resources/fonts/default.fontset.toml
Normal file
3
TestGame/Resources/fonts/default.fontset.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[FontSet]
|
||||||
|
|
||||||
|
fonts = ["Inter-Regular.ttf", "NotoSansJP-Regular.ttf"]
|
||||||
@@ -20,7 +20,7 @@ public class TestGame : Game
|
|||||||
InitializeSystemsDefault();
|
InitializeSystemsDefault();
|
||||||
|
|
||||||
_uiSystem = new UISystem(Input, ResourceRef<Style>.Empty());
|
_uiSystem = new UISystem(Input, ResourceRef<Style>.Empty());
|
||||||
_uiSystem.RenderDebugRects = true;
|
// _uiSystem.RenderDebugRects = true;
|
||||||
|
|
||||||
_particleSystem = new ParticleSystem();
|
_particleSystem = new ParticleSystem();
|
||||||
|
|
||||||
@@ -42,6 +42,11 @@ public class TestGame : Game
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ResourceManager.TryLoad("fonts/NotoSansJP-Regular.ttf", out _jpFont))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ResourceManager.TryLoad("icon.png", out _icon);
|
ResourceManager.TryLoad("icon.png", out _icon);
|
||||||
ResourceManager.TryLoad("sounds/test_sound_mono.ogg", out _sound);
|
ResourceManager.TryLoad("sounds/test_sound_mono.ogg", out _sound);
|
||||||
|
|
||||||
@@ -49,6 +54,8 @@ public class TestGame : Game
|
|||||||
{
|
{
|
||||||
throw new Exception("Failed to load emitter settings!");
|
throw new Exception("Failed to load emitter settings!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_defaultFontSet = new([_font, _jpFont]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Ready()
|
protected override void Ready()
|
||||||
@@ -56,9 +63,32 @@ public class TestGame : Game
|
|||||||
Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) });
|
Input.AddInputMapping("reload", new IInputAction[] { new KeyInputAction(KeyboardKey.R) });
|
||||||
_emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect);
|
_emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect);
|
||||||
|
|
||||||
_frame.AddChild(_container);
|
var addButton = new Button("Add element", _defaultFontSet, () => { _container.AddChild(new Label("Hello, World!", _defaultFontSet)); });
|
||||||
|
addButton.Padding = new Margin(8.0f);
|
||||||
|
|
||||||
_uiSystem.AddElement(_frame);
|
var removeButton = new Button("Remove element", _defaultFontSet, () =>
|
||||||
|
{
|
||||||
|
if (_container.Children.Count == 0) return;
|
||||||
|
var lastChild = _container.Children.Last();
|
||||||
|
_container.RemoveChild(lastChild);
|
||||||
|
});
|
||||||
|
|
||||||
|
removeButton.Padding = new Margin(8.0f);
|
||||||
|
|
||||||
|
_buttonContainer.AddChild(addButton);
|
||||||
|
_buttonContainer.AddChild(removeButton);
|
||||||
|
|
||||||
|
var c = new VerticalContainer();
|
||||||
|
|
||||||
|
var m = new MarginContainer();
|
||||||
|
m.AddChild(_container);
|
||||||
|
|
||||||
|
c.AddChild(_buttonContainer);
|
||||||
|
c.AddChild(m);
|
||||||
|
|
||||||
|
_rootFill.AddChild(c);
|
||||||
|
|
||||||
|
_uiSystem.AddElement(_rootFill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -69,32 +99,16 @@ public class TestGame : Game
|
|||||||
ResourceManager.Reload();
|
ResourceManager.Reload();
|
||||||
_particleSystem!.RestartEmitter(_emitterId);
|
_particleSystem!.RestartEmitter(_emitterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input.IsActionPressed("accept"))
|
|
||||||
{
|
|
||||||
_container.AddChild(new RectangleWidget(new Rect(32.0f, 32.0f), MathUtils.RandomColor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.IsActionPressed("cancel") && _container.Children.Count != 0)
|
|
||||||
{
|
|
||||||
var lastChild = _container.Children.Last();
|
|
||||||
_container.RemoveChild(lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.IsMouseButtonDown(MouseButton.Left))
|
|
||||||
{
|
|
||||||
var mousePos = Input.GetMousePosition();
|
|
||||||
_frame.Size = new Rect(mousePos.X, mousePos.Y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Render(double deltaTime)
|
protected override void Render(double deltaTime)
|
||||||
{
|
{
|
||||||
Renderer.ClearBackground(Color.Black);
|
Renderer.ClearBackground(Color.Black);
|
||||||
foreach (var emitter in _particleSystem!.Emitters)
|
|
||||||
{
|
// foreach (var emitter in _particleSystem!.Emitters)
|
||||||
DrawEmitter(emitter);
|
// {
|
||||||
}
|
// DrawEmitter(emitter);
|
||||||
|
// }
|
||||||
|
|
||||||
Renderer.ResetTransform();
|
Renderer.ResetTransform();
|
||||||
_uiSystem.Render(Renderer);
|
_uiSystem.Render(Renderer);
|
||||||
@@ -124,25 +138,28 @@ public class TestGame : Game
|
|||||||
private int _emitterId;
|
private int _emitterId;
|
||||||
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
||||||
private ResourceRef<Font> _font;
|
private ResourceRef<Font> _font;
|
||||||
|
private ResourceRef<Font> _jpFont;
|
||||||
|
|
||||||
|
private FontSet _defaultFontSet;
|
||||||
|
|
||||||
private ResourceRef<Sound> _sound;
|
private ResourceRef<Sound> _sound;
|
||||||
private ResourceRef<Texture2d> _icon;
|
private ResourceRef<Texture2d> _icon;
|
||||||
|
|
||||||
private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new())
|
private FlexContainer _container = new(minimumSize: new Rect(256.0f, 256.0f), new())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Center,
|
Anchor = Anchor.Center,
|
||||||
Size = new Rect(500, 300),
|
|
||||||
Direction = FlexDirection.Column,
|
Direction = FlexDirection.Column,
|
||||||
Justify = JustifyContent.Start,
|
Justify = JustifyContent.Start,
|
||||||
Align = AlignItems.Center,
|
Align = AlignItems.Center,
|
||||||
Wrap = true,
|
Wrap = true,
|
||||||
Gap = 10f
|
Gap = 8.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
private Frame _frame = new();
|
[NotNull] private Label _label;
|
||||||
// private VerticalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
|
|
||||||
// {
|
private FillContainer _rootFill = new();
|
||||||
// ConfineToContents = true,
|
private HorizontalContainer _buttonContainer = new(16)
|
||||||
// Anchor = Anchor.CenterLeft,
|
{
|
||||||
// AnchorOffset = new Vector2(0.5f, 0.0f)
|
ConfineToContents = true,
|
||||||
// };
|
};
|
||||||
}
|
}
|
||||||
37
Voile/GridSet.cs
Normal file
37
Voile/GridSet.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Voile.Extensions;
|
||||||
|
|
||||||
|
namespace Voile;
|
||||||
|
|
||||||
|
public class GridSet<T>
|
||||||
|
{
|
||||||
|
public float GridSize { get; }
|
||||||
|
public GridSet(float gridSize = 32.0f)
|
||||||
|
{
|
||||||
|
GridSize = gridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(Vector2 position, T child)
|
||||||
|
{
|
||||||
|
var snap = Vector2.One * GridSize;
|
||||||
|
position = position.Snapped(snap);
|
||||||
|
|
||||||
|
if (_values.TryGetValue(position, out var list))
|
||||||
|
{
|
||||||
|
list.Add(child);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_values.Add(position, new List<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(T child)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<Vector2, List<T>> _values = new();
|
||||||
|
}
|
||||||
301
Voile/Source/Color.cs
Normal file
301
Voile/Source/Color.cs
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
namespace Voile
|
||||||
|
{
|
||||||
|
// Based on https://github.com/ppr-game/PPR/blob/engine/PER.Util/src/Color.cs
|
||||||
|
/// <summary>
|
||||||
|
/// A record struct representing a color.
|
||||||
|
/// </summary>
|
||||||
|
public record struct Color
|
||||||
|
{
|
||||||
|
public static readonly Color AliceBlue = new(0xF0F8FF);
|
||||||
|
public static readonly Color AntiqueWhite = new(0xFAEBD7);
|
||||||
|
public static readonly Color Aqua = new(0x00FFFF);
|
||||||
|
public static readonly Color Aquamarine = new(0x7FFFD4);
|
||||||
|
public static readonly Color Azure = new(0xF0FFFF);
|
||||||
|
public static readonly Color Beige = new(0xF5F5DC);
|
||||||
|
public static readonly Color Bisque = new(0xFFE4C4);
|
||||||
|
public static readonly Color Black = new(0x000000);
|
||||||
|
public static readonly Color BlanchedAlmond = new(0xFFEBCD);
|
||||||
|
public static readonly Color Blue = new(0x0000FF);
|
||||||
|
public static readonly Color BlueViolet = new(0x8A2BE2);
|
||||||
|
public static readonly Color Brown = new(0xA52A2A);
|
||||||
|
public static readonly Color BurlyWood = new(0xDEB887);
|
||||||
|
public static readonly Color CadetBlue = new(0x5F9EA0);
|
||||||
|
public static readonly Color Chartreuse = new(0x7FFF00);
|
||||||
|
public static readonly Color Chocolate = new(0xD2691E);
|
||||||
|
public static readonly Color Coral = new(0xFF7F50);
|
||||||
|
public static readonly Color CornflowerBlue = new(0x6495ED);
|
||||||
|
public static readonly Color Cornsilk = new(0xFFF8DC);
|
||||||
|
public static readonly Color Crimson = new(0xDC143C);
|
||||||
|
public static readonly Color Cyan = new(0x00FFFF);
|
||||||
|
public static readonly Color DarkBlue = new(0x00008B);
|
||||||
|
public static readonly Color DarkCyan = new(0x008B8B);
|
||||||
|
public static readonly Color White = new(0xFFFFFF);
|
||||||
|
public static readonly Color Green = new(0x00FF00);
|
||||||
|
public static readonly Color Red = new(0xFF0000);
|
||||||
|
public static readonly Color DarkGoldenRod = new(0xB8860B);
|
||||||
|
public static readonly Color DarkGray = new(0xA9A9A9);
|
||||||
|
public static readonly Color DarkGreen = new(0x006400);
|
||||||
|
public static readonly Color DarkKhaki = new(0xBDB76B);
|
||||||
|
public static readonly Color DarkMagenta = new(0x8B008B);
|
||||||
|
public static readonly Color DarkOliveGreen = new(0x556B2F);
|
||||||
|
public static readonly Color DarkOrange = new(0xFF8C00);
|
||||||
|
public static readonly Color DarkOrchid = new(0x9932CC);
|
||||||
|
public static readonly Color DarkRed = new(0x8B0000);
|
||||||
|
public static readonly Color DarkSalmon = new(0xE9967A);
|
||||||
|
public static readonly Color DarkSeaGreen = new(0x8FBC8F);
|
||||||
|
public static readonly Color DarkSlateBlue = new(0x483D8B);
|
||||||
|
public static readonly Color DarkSlateGray = new(0x2F4F4F);
|
||||||
|
public static readonly Color DarkTurquoise = new(0x00CED1);
|
||||||
|
public static readonly Color DarkViolet = new(0x9400D3);
|
||||||
|
public static readonly Color DeepPink = new(0xFF1493);
|
||||||
|
public static readonly Color DeepSkyBlue = new(0x00BFFF);
|
||||||
|
public static readonly Color DimGray = new(0x696969);
|
||||||
|
public static readonly Color DodgerBlue = new(0x1E90FF);
|
||||||
|
public static readonly Color FireBrick = new(0xB22222);
|
||||||
|
public static readonly Color FloralWhite = new(0xFFFAF0);
|
||||||
|
public static readonly Color ForestGreen = new(0x228B22);
|
||||||
|
public static readonly Color Gainsboro = new(0xDCDCDC);
|
||||||
|
public static readonly Color GhostWhite = new(0xF8F8FF);
|
||||||
|
public static readonly Color Gold = new(0xFFD700);
|
||||||
|
public static readonly Color GoldenRod = new(0xDAA520);
|
||||||
|
public static readonly Color Gray = new(0x808080);
|
||||||
|
public static readonly Color GreenYellow = new(0xADFF2F);
|
||||||
|
public static readonly Color HoneyDew = new(0xF0FFF0);
|
||||||
|
public static readonly Color HotPink = new(0xFF69B4);
|
||||||
|
public static readonly Color IndianRed = new(0xCD5C5C);
|
||||||
|
public static readonly Color Indigo = new(0x4B0082);
|
||||||
|
public static readonly Color Ivory = new(0xFFFFF0);
|
||||||
|
public static readonly Color Khaki = new(0xF0E68C);
|
||||||
|
public static readonly Color Lavender = new(0xE6E6FA);
|
||||||
|
public static readonly Color LavenderBlush = new(0xFFF0F5);
|
||||||
|
public static readonly Color LawnGreen = new(0x7CFC00);
|
||||||
|
public static readonly Color LemonChiffon = new(0xFFFACD);
|
||||||
|
public static readonly Color LightBlue = new(0xADD8E6);
|
||||||
|
public static readonly Color LightCoral = new(0xF08080);
|
||||||
|
public static readonly Color LightCyan = new(0xE0FFFF);
|
||||||
|
public static readonly Color LightGoldenRodYellow = new(0xFAFAD2);
|
||||||
|
public static readonly Color LightGray = new(0xD3D3D3);
|
||||||
|
public static readonly Color LightGreen = new(0x90EE90);
|
||||||
|
public static readonly Color LightPink = new(0xFFB6C1);
|
||||||
|
public static readonly Color LightSalmon = new(0xFFA07A);
|
||||||
|
public static readonly Color LightSeaGreen = new(0x20B2AA);
|
||||||
|
public static readonly Color LightSkyBlue = new(0x87CEFA);
|
||||||
|
public static readonly Color LightSlateGray = new(0x778899);
|
||||||
|
public static readonly Color LightSteelBlue = new(0xB0C4DE);
|
||||||
|
public static readonly Color LightYellow = new(0xFFFFE0);
|
||||||
|
public static readonly Color Lime = new(0x00FF00);
|
||||||
|
public static readonly Color LimeGreen = new(0x32CD32);
|
||||||
|
public static readonly Color Linen = new(0xFAF0E6);
|
||||||
|
public static readonly Color Magenta = new(0xFF00FF);
|
||||||
|
public static readonly Color Maroon = new(0x800000);
|
||||||
|
public static readonly Color MediumAquaMarine = new(0x66CDAA);
|
||||||
|
public static readonly Color MediumBlue = new(0x0000CD);
|
||||||
|
public static readonly Color MediumOrchid = new(0xBA55D3);
|
||||||
|
public static readonly Color MediumPurple = new(0x9370DB);
|
||||||
|
public static readonly Color MediumSeaGreen = new(0x3CB371);
|
||||||
|
public static readonly Color MediumSlateBlue = new(0x7B68EE);
|
||||||
|
public static readonly Color MediumSpringGreen = new(0x00FA9A);
|
||||||
|
public static readonly Color MediumTurquoise = new(0x48D1CC);
|
||||||
|
public static readonly Color MediumVioletRed = new(0xC71585);
|
||||||
|
public static readonly Color MidnightBlue = new(0x191970);
|
||||||
|
public static readonly Color MintCream = new(0xF5FFFA);
|
||||||
|
public static readonly Color MistyRose = new(0xFFE4E1);
|
||||||
|
public static readonly Color Moccasin = new(0xFFE4B5);
|
||||||
|
public static readonly Color NavajoWhite = new(0xFFDEAD);
|
||||||
|
public static readonly Color Navy = new(0x000080);
|
||||||
|
public static readonly Color OldLace = new(0xFDF5E6);
|
||||||
|
public static readonly Color Olive = new(0x808000);
|
||||||
|
public static readonly Color OliveDrab = new(0x6B8E23);
|
||||||
|
public static readonly Color Orange = new(0xFFA500);
|
||||||
|
public static readonly Color OrangeRed = new(0xFF4500);
|
||||||
|
public static readonly Color Orchid = new(0xDA70D6);
|
||||||
|
public static readonly Color PaleGoldenRod = new(0xEEE8AA);
|
||||||
|
public static readonly Color PaleGreen = new(0x98FB98);
|
||||||
|
public static readonly Color PaleTurquoise = new(0xAFEEEE);
|
||||||
|
public static readonly Color PaleVioletRed = new(0xDB7093);
|
||||||
|
public static readonly Color PapayaWhip = new(0xFFEFD5);
|
||||||
|
public static readonly Color PeachPuff = new(0xFFDAB9);
|
||||||
|
public static readonly Color Peru = new(0xCD853F);
|
||||||
|
public static readonly Color Pink = new(0xFFC0CB);
|
||||||
|
public static readonly Color Plum = new(0xDDA0DD);
|
||||||
|
public static readonly Color PowderBlue = new(0xB0E0E6);
|
||||||
|
public static readonly Color Purple = new(0x800080);
|
||||||
|
public static readonly Color RebeccaPurple = new(0x663399);
|
||||||
|
public static readonly Color RosyBrown = new(0xBC8F8F);
|
||||||
|
public static readonly Color RoyalBlue = new(0x4169E1);
|
||||||
|
public static readonly Color SaddleBrown = new(0x8B4513);
|
||||||
|
public static readonly Color Salmon = new(0xFA8072);
|
||||||
|
public static readonly Color SandyBrown = new(0xF4A460);
|
||||||
|
public static readonly Color SeaGreen = new(0x2E8B57);
|
||||||
|
public static readonly Color Seashell = new(0xFFF5EE);
|
||||||
|
public static readonly Color Sienna = new(0xA0522D);
|
||||||
|
public static readonly Color Silver = new(0xC0C0C0);
|
||||||
|
public static readonly Color SkyBlue = new(0x87CEEB);
|
||||||
|
public static readonly Color SlateBlue = new(0x6A5ACD);
|
||||||
|
public static readonly Color SlateGray = new(0x708090);
|
||||||
|
public static readonly Color Snow = new(0xFFFAFA);
|
||||||
|
public static readonly Color SpringGreen = new(0x00FF7F);
|
||||||
|
public static readonly Color SteelBlue = new(0x4682B4);
|
||||||
|
public static readonly Color Tan = new(0xD2B48C);
|
||||||
|
public static readonly Color Teal = new(0x008080);
|
||||||
|
public static readonly Color Thistle = new(0xD8BFD8);
|
||||||
|
public static readonly Color Tomato = new(0xFF6347);
|
||||||
|
public static readonly Color Turquoise = new(0x40E0D0);
|
||||||
|
public static readonly Color Violet = new(0xEE82EE);
|
||||||
|
public static readonly Color Wheat = new(0xF5DEB3);
|
||||||
|
public static readonly Color WhiteSmoke = new(0xF5F5F5);
|
||||||
|
public static readonly Color Yellow = new(0xFFFF00);
|
||||||
|
public static readonly Color YellowGreen = new(0x9ACD32);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Red component of this <see cref="Color"/>.
|
||||||
|
/// </summary>
|
||||||
|
public byte R { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Green component of this <see cref="Color"/>.
|
||||||
|
/// </summary>
|
||||||
|
public byte G { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Blue component of this <see cref="Color"/>.
|
||||||
|
/// </summary>
|
||||||
|
public byte B { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Alpha component of this <see cref="Color"/>.
|
||||||
|
/// </summary>/// <summary>
|
||||||
|
/// Gets the color as a 32-bit ARGB integer in the format 0xAARRGGBB.
|
||||||
|
/// </summary>
|
||||||
|
public byte A { get; set; } = 255;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the color as a 32-bit ARGB integer in the format 0xAARRGGBB.
|
||||||
|
/// </summary>
|
||||||
|
public int Argb
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int a = A << 24;
|
||||||
|
int r = R << 16;
|
||||||
|
int g = G << 8;
|
||||||
|
int b = B;
|
||||||
|
|
||||||
|
return a | r | g | b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Color"/> struct using float RGB(A) values between 0 and 1.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="r">The red component (0.0 to 1.0).</param>
|
||||||
|
/// <param name="g">The green component (0.0 to 1.0).</param>
|
||||||
|
/// <param name="b">The blue component (0.0 to 1.0).</param>
|
||||||
|
/// <param name="a">The alpha component (0.0 to 1.0), default is 1.0 (fully opaque).</param>
|
||||||
|
public Color(float r, float g, float b, float a = 1.0f)
|
||||||
|
{
|
||||||
|
R = (byte)Math.Clamp(r * 255, 0, 255);
|
||||||
|
G = (byte)Math.Clamp(g * 255, 0, 255);
|
||||||
|
B = (byte)Math.Clamp(b * 255, 0, 255);
|
||||||
|
A = (byte)Math.Clamp(a * 255, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Color"/> struct using byte RGB(A) values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="r">The red component (0 to 255).</param>
|
||||||
|
/// <param name="g">The green component (0 to 255).</param>
|
||||||
|
/// <param name="b">The blue component (0 to 255).</param>
|
||||||
|
/// <param name="a">The alpha component (0 to 255), default is 255 (fully opaque).</param>
|
||||||
|
public Color(byte r, byte g, byte b, byte a = 255)
|
||||||
|
{
|
||||||
|
R = r;
|
||||||
|
G = g;
|
||||||
|
B = b;
|
||||||
|
A = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Color"/> struct using a hexadecimal value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hex">
|
||||||
|
/// A 24-bit (RRGGBB) or 32-bit (AARRGGBB) integer representing the color.
|
||||||
|
/// Alpha is assumed to be 255 if not included.
|
||||||
|
/// </param>
|
||||||
|
public Color(int hex)
|
||||||
|
{
|
||||||
|
A = 255; // Default alpha to 255 if not provided
|
||||||
|
B = (byte)(hex & 0xFF);
|
||||||
|
G = (byte)((hex >> 8) & 0xFF);
|
||||||
|
R = (byte)((hex >> 16) & 0xFF);
|
||||||
|
if (hex > 0xFFFFFF) // If the hex value includes alpha
|
||||||
|
{
|
||||||
|
A = (byte)((hex >> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a color from a hexadecimal string in the format "#RRGGBB" or "#AARRGGBB".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hex">The hex string representing the color.</param>
|
||||||
|
/// <returns>A <see cref="Color"/> instance parsed from the string.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if the format is invalid.</exception>
|
||||||
|
public static Color FromHexString(string hex)
|
||||||
|
{
|
||||||
|
if (hex.StartsWith("#"))
|
||||||
|
{
|
||||||
|
hex = hex[1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hex.Length == 6)
|
||||||
|
{
|
||||||
|
int rgb = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
||||||
|
return new Color(rgb);
|
||||||
|
}
|
||||||
|
else if (hex.Length == 8)
|
||||||
|
{
|
||||||
|
int rgba = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
||||||
|
return new Color(rgba);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid hex color format. Use #RRGGBB or #RRGGBBAA.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a lightened version of the color by interpolating toward white.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount">A value from 0.0 (no change) to 1.0 (fully white).</param>
|
||||||
|
/// <returns>A lighter <see cref="Color"/>.</returns>
|
||||||
|
public Color Lightened(float amount)
|
||||||
|
{
|
||||||
|
var result = this;
|
||||||
|
result.R = (byte)Math.Min(255, R + (255 - R) * amount);
|
||||||
|
result.G = (byte)Math.Min(255, G + (255 - G) * amount);
|
||||||
|
result.B = (byte)Math.Min(255, B + (255 - B) * amount);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a darkened version of the color by interpolating toward black.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount">A value from 0.0 (no change) to 1.0 (fully black).</param>
|
||||||
|
/// <returns>A darker <see cref="Color"/>.</returns>
|
||||||
|
public Color Darkened(float amount)
|
||||||
|
{
|
||||||
|
var result = this;
|
||||||
|
result.R = (byte)(R * (1.0f - amount));
|
||||||
|
result.G = (byte)(G * (1.0f - amount));
|
||||||
|
result.B = (byte)(B * (1.0f - amount));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts this color to a <see cref="System.Drawing.Color"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="System.Drawing.Color"/> with equivalent ARGB values.</returns>
|
||||||
|
public System.Drawing.Color ToSystemColor()
|
||||||
|
{
|
||||||
|
var result = System.Drawing.Color.FromArgb(Argb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,5 +8,31 @@ namespace Voile.Extensions
|
|||||||
{
|
{
|
||||||
return new Vector2((float)MathUtils.Lerp(a.X, b.X, t), (float)MathUtils.Lerp(a.Y, b.Y, t));
|
return new Vector2((float)MathUtils.Lerp(a.X, b.X, t), (float)MathUtils.Lerp(a.Y, b.Y, t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Vector2 Snapped(this Vector2 a, Vector2 snap)
|
||||||
|
{
|
||||||
|
var x = a.X % snap.X;
|
||||||
|
var y = a.Y % snap.Y;
|
||||||
|
|
||||||
|
if (x == 0)
|
||||||
|
{
|
||||||
|
x = a.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = a.X - x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y == 0)
|
||||||
|
{
|
||||||
|
y = a.Y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
y = a.Y - y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Vector2(x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Voile.UI;
|
namespace Voile;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a rectangle. Used to determine widget confines for UI layout.
|
/// Represents a rectangle. Used to determine widget confines for UI layout.
|
||||||
@@ -81,7 +81,7 @@ namespace Voile.Rendering
|
|||||||
Raylib.InitWindow((int)_windowSize.X, (int)_windowSize.Y, windowSettings.Title);
|
Raylib.InitWindow((int)_windowSize.X, (int)_windowSize.Y, windowSettings.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raylib.SetWindowState(windowFlags);
|
Raylib.SetWindowState(windowFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@@ -244,6 +244,12 @@ namespace Voile.Rendering
|
|||||||
LoadFont(font);
|
LoadFont(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (font.Dirty && font.Handle != -1)
|
||||||
|
{
|
||||||
|
UnloadFont(font);
|
||||||
|
LoadFont(font);
|
||||||
|
}
|
||||||
|
|
||||||
var rayFont = _fontPool[font.Handle];
|
var rayFont = _fontPool[font.Handle];
|
||||||
|
|
||||||
Raylib.DrawTextPro(rayFont, text, transformPosition, transformPivot, transformRotation, font.Size, 0.0f, VoileColorToRaylibColor(color));
|
Raylib.DrawTextPro(rayFont, text, transformPosition, transformPivot, transformRotation, font.Size, 0.0f, VoileColorToRaylibColor(color));
|
||||||
@@ -268,10 +274,10 @@ namespace Voile.Rendering
|
|||||||
{
|
{
|
||||||
Raylib_cs.Font fontRay;
|
Raylib_cs.Font fontRay;
|
||||||
|
|
||||||
string ext = ".ttf"; // TODO: don't use a hardcoded extension.
|
string ext = ".ttf";
|
||||||
int fontChars = 250; // TODO: control this dynamically to not load the entire font.
|
int fontChars = font.Codepoints.Count;
|
||||||
|
|
||||||
fontRay = Raylib.LoadFontFromMemory(ext, font.Buffer, font.Size, null, fontChars);
|
fontRay = Raylib.LoadFontFromMemory(ext, font.Buffer, font.Size, font.Codepoints.ToArray(), fontChars);
|
||||||
|
|
||||||
Raylib.GenTextureMipmaps(ref fontRay.Texture);
|
Raylib.GenTextureMipmaps(ref fontRay.Texture);
|
||||||
Raylib.SetTextureFilter(fontRay.Texture, TextureFilter.Bilinear);
|
Raylib.SetTextureFilter(fontRay.Texture, TextureFilter.Bilinear);
|
||||||
@@ -281,6 +287,14 @@ namespace Voile.Rendering
|
|||||||
font.Handle = _fontPool.Count - 1;
|
font.Handle = _fontPool.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UnloadFont(Font font)
|
||||||
|
{
|
||||||
|
var fontRay = _fontPool[font.Handle];
|
||||||
|
Raylib.UnloadFont(fontRay);
|
||||||
|
|
||||||
|
_fontPool.RemoveAt(font.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadTexture(Texture2d texture)
|
private void LoadTexture(Texture2d texture)
|
||||||
{
|
{
|
||||||
Image image = new();
|
Image image = new();
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ namespace Voile.Rendering
|
|||||||
{
|
{
|
||||||
public string Title;
|
public string Title;
|
||||||
public Vector2 Size = new Vector2(1280, 720);
|
public Vector2 Size = new Vector2(1280, 720);
|
||||||
public bool Resizable { get; set; }
|
public bool Resizable { get; set; } = true;
|
||||||
|
|
||||||
public WindowSettings(string title, Vector2 size)
|
public WindowSettings(string title, Vector2 size)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using FreeTypeSharp;
|
||||||
|
using static FreeTypeSharp.FT;
|
||||||
|
using static FreeTypeSharp.FT_LOAD;
|
||||||
|
|
||||||
namespace Voile;
|
namespace Voile;
|
||||||
|
|
||||||
|
public struct Glyph
|
||||||
|
{
|
||||||
|
public int TextureId { get; set; } = -1;
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
public Vector2 Bearing { get; set; }
|
||||||
|
public int Advance { get; set; }
|
||||||
|
|
||||||
|
public Glyph() { }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents font data.
|
/// Represents font data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Font : Resource
|
public class Font : Resource, IUpdatableResource, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1.
|
/// Internal handle for the font. If it got successfully loaded into the GPU, the value will be other than -1.
|
||||||
@@ -14,8 +31,162 @@ public class Font : Resource
|
|||||||
public byte[]? Buffer { get; private set; }
|
public byte[]? Buffer { get; private set; }
|
||||||
public long BufferSize { get; set; }
|
public long BufferSize { get; set; }
|
||||||
|
|
||||||
|
public bool Dirty => _dirty;
|
||||||
|
|
||||||
|
internal float UnitsPerEm;
|
||||||
|
|
||||||
|
internal nint FacePtr;
|
||||||
|
internal nint LibraryPtr;
|
||||||
|
|
||||||
|
internal List<int> Codepoints => _glyphs.Keys.ToList();
|
||||||
|
|
||||||
public Font(string path, byte[] buffer) : base(path)
|
public Font(string path, byte[] buffer) : base(path)
|
||||||
{
|
{
|
||||||
Buffer = buffer;
|
Buffer = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (FacePtr != IntPtr.Zero)
|
||||||
|
FT_Done_Face((FT_FaceRec_*)FacePtr);
|
||||||
|
|
||||||
|
if (LibraryPtr != IntPtr.Zero)
|
||||||
|
FT_Done_FreeType((FT_LibraryRec_*)LibraryPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a basic ASCII charset for this font.
|
||||||
|
/// </summary>
|
||||||
|
public void LoadAsciiData()
|
||||||
|
{
|
||||||
|
for (char c = ' '; c < 127; c++)
|
||||||
|
{
|
||||||
|
GetGlyph(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Measures a given string using the font metrics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text to measure.</param>
|
||||||
|
/// <returns>A <see cref="Rect"/> with the sizes of a given text using this font.</returns>
|
||||||
|
public Rect Measure(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return Rect.Zero;
|
||||||
|
|
||||||
|
float totalWidth = 0;
|
||||||
|
float maxAscent = 0;
|
||||||
|
float maxDescent = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
char c = text[i];
|
||||||
|
Glyph glyph = GetGlyph(c);
|
||||||
|
|
||||||
|
totalWidth += glyph.Advance;
|
||||||
|
|
||||||
|
float ascent = glyph.Bearing.Y;
|
||||||
|
float descent = glyph.Height - glyph.Bearing.Y;
|
||||||
|
|
||||||
|
if (ascent > maxAscent)
|
||||||
|
maxAscent = ascent;
|
||||||
|
if (descent > maxDescent)
|
||||||
|
maxDescent = descent;
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
char prevChar = text[i - 1];
|
||||||
|
totalWidth += GetKerning(prevChar, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalHeight = maxAscent + maxDescent;
|
||||||
|
return new Rect(totalWidth, totalHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetKerning(char left, char right)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (FacePtr == IntPtr.Zero)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
FT_FaceRec_* face = (FT_FaceRec_*)FacePtr;
|
||||||
|
|
||||||
|
uint leftIndex = FT_Get_Char_Index(face, left);
|
||||||
|
uint rightIndex = FT_Get_Char_Index(face, right);
|
||||||
|
|
||||||
|
if (leftIndex == 0 || rightIndex == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
FT_Vector_ kerning;
|
||||||
|
if (FT_Get_Kerning(face, leftIndex, rightIndex, FT_Kerning_Mode_.FT_KERNING_DEFAULT, &kerning) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (int)kerning.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Glyph GetGlyph(char character)
|
||||||
|
{
|
||||||
|
if (!HasGlyph(character))
|
||||||
|
{
|
||||||
|
_glyphs.TryGetValue('?', out var defGlyph);
|
||||||
|
return defGlyph;
|
||||||
|
}
|
||||||
|
if (_glyphs.TryGetValue(character, out var glyph))
|
||||||
|
return glyph;
|
||||||
|
|
||||||
|
var loaded = LoadGlyph(character);
|
||||||
|
_glyphs[character] = loaded;
|
||||||
|
|
||||||
|
_dirty = true;
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasGlyph(char character)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var face = (FT_FaceRec_*)FacePtr;
|
||||||
|
return FT_Get_Char_Index(face, character) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe Glyph LoadGlyph(char character)
|
||||||
|
{
|
||||||
|
var face = (FT_FaceRec_*)FacePtr;
|
||||||
|
|
||||||
|
// TODO: for now, we're loading glyphs for metrics, but when implementing WebGPU rendering, we want to somehow render them to SDF or bitmap.
|
||||||
|
FT_Error error = FT_Set_Pixel_Sizes(face, 0, (uint)Size);
|
||||||
|
error = FT_Load_Char(face, character, FT_LOAD_NO_BITMAP);
|
||||||
|
|
||||||
|
if (error != 0)
|
||||||
|
throw new Exception($"Failed to load glyph for '{character}'");
|
||||||
|
|
||||||
|
var glyph = face->glyph;
|
||||||
|
var bitmap = glyph->bitmap;
|
||||||
|
var metrics = glyph->metrics;
|
||||||
|
|
||||||
|
return new Glyph
|
||||||
|
{
|
||||||
|
Width = metrics.width >> 6,
|
||||||
|
Height = metrics.height >> 6,
|
||||||
|
Bearing = new Vector2(metrics.horiBearingX >> 6, metrics.horiBearingY >> 6),
|
||||||
|
Advance = (int)metrics.horiAdvance >> 6,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkUpdated()
|
||||||
|
{
|
||||||
|
_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _dirty;
|
||||||
|
private Dictionary<int, Glyph> _glyphs = new();
|
||||||
}
|
}
|
||||||
45
Voile/Source/Resources/FontSet.cs
Normal file
45
Voile/Source/Resources/FontSet.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Voile.Resources;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains a set of multiple fonts. Used to fetch fonts based on availability of glyphs.
|
||||||
|
/// </summary>
|
||||||
|
public class FontSet
|
||||||
|
{
|
||||||
|
public FontSet() { }
|
||||||
|
|
||||||
|
public FontSet(IEnumerable<ResourceRef<Font>> fonts)
|
||||||
|
{
|
||||||
|
_fonts = fonts.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFont(ResourceRef<Font> font) => _fonts.Add(font);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get a suitable font that has a given character.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">Character to get a suitable font for.</param>
|
||||||
|
/// <param name="result">Font that contains this character.</param>
|
||||||
|
/// <returns><c>true</c> if a font that contains this character exists, <c>false</c> otherwise.</returns>
|
||||||
|
public bool TryGetFontFor(char c, [NotNullWhen(true)] out ResourceRef<Font>? result)
|
||||||
|
{
|
||||||
|
result = ResourceRef<Font>.Empty();
|
||||||
|
foreach (var fontRef in _fonts)
|
||||||
|
{
|
||||||
|
if (!fontRef.TryGetValue(out var font))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font.HasGlyph(c))
|
||||||
|
{
|
||||||
|
result = fontRef;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ResourceRef<Font>> _fonts = new();
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
|
|
||||||
|
using System.Numerics;
|
||||||
|
using FreeTypeSharp;
|
||||||
using Voile.VFS;
|
using Voile.VFS;
|
||||||
|
|
||||||
|
using static FreeTypeSharp.FT;
|
||||||
|
using static FreeTypeSharp.FT_LOAD;
|
||||||
|
|
||||||
namespace Voile.Resources;
|
namespace Voile.Resources;
|
||||||
|
|
||||||
public class FontLoader : ResourceLoader<Font>
|
public class FontLoader : ResourceLoader<Font>
|
||||||
@@ -10,7 +15,6 @@ public class FontLoader : ResourceLoader<Font>
|
|||||||
".ttf"
|
".ttf"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
protected override Font LoadResource(string path)
|
protected override Font LoadResource(string path)
|
||||||
{
|
{
|
||||||
using Stream stream = VirtualFileSystem.Read(path);
|
using Stream stream = VirtualFileSystem.Read(path);
|
||||||
@@ -21,6 +25,30 @@ public class FontLoader : ResourceLoader<Font>
|
|||||||
|
|
||||||
result.BufferSize = bytesRead;
|
result.BufferSize = bytesRead;
|
||||||
|
|
||||||
|
LoadFaceData(result);
|
||||||
|
result.LoadAsciiData();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void LoadFaceData(Font font)
|
||||||
|
{
|
||||||
|
FT_LibraryRec_* lib;
|
||||||
|
FT_Error error = FT_Init_FreeType(&lib);
|
||||||
|
if (error != 0)
|
||||||
|
throw new Exception("Failed to init FreeType");
|
||||||
|
|
||||||
|
font.LibraryPtr = (nint)lib;
|
||||||
|
|
||||||
|
fixed (byte* data = font.Buffer)
|
||||||
|
{
|
||||||
|
FT_FaceRec_* face;
|
||||||
|
error = FT_New_Memory_Face(lib, data, (nint)font.BufferSize, 0, &face);
|
||||||
|
if (error != 0)
|
||||||
|
throw new Exception("Failed to load font face");
|
||||||
|
|
||||||
|
font.UnitsPerEm = face->units_per_EM;
|
||||||
|
font.FacePtr = (nint)face;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,12 @@ namespace Voile.Resources
|
|||||||
var resource = ResourceManager.LoadedResources[resourceGuid];
|
var resource = ResourceManager.LoadedResources[resourceGuid];
|
||||||
|
|
||||||
ResourceManager.RemoveResource(resourceGuid);
|
ResourceManager.RemoveResource(resourceGuid);
|
||||||
resource.Dispose();
|
|
||||||
|
if (resource is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Voile.Resources;
|
using Voile.Resources;
|
||||||
|
|
||||||
namespace Voile
|
namespace Voile
|
||||||
@@ -13,11 +14,35 @@ namespace Voile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Guid Guid = Guid.Empty;
|
public readonly Guid Guid = Guid.Empty;
|
||||||
public bool HasValue => Guid != Guid.Empty;
|
public bool HasValue => Guid != Guid.Empty;
|
||||||
/// <summary>
|
|
||||||
/// Retrieve a reference.
|
|
||||||
/// </summary>
|
|
||||||
public T Value => ResourceManager.GetResource<T>(Guid);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="Resource"/>.<br />
|
||||||
|
/// This will throw an <see cref="InvalidOperationException"/> if the resource wasn't loaded or is invalid. <br />
|
||||||
|
/// You can check if resource was loaded with <see cref="HasValue"/>, or consider using <see cref="TryGetValue"/>.
|
||||||
|
/// </summary>
|
||||||
|
public T Value => ResourceManager.GetResource<T>(Guid)
|
||||||
|
?? throw new InvalidOperationException($"Resource with GUID {Guid} is not loaded or invalid.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a resource or <c>null</c> if the resource wasn't loaded or is invalid.
|
||||||
|
/// </summary>
|
||||||
|
public T? ValueOrNull => ResourceManager.GetResource<T>(Guid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to retrieve a <see cref="Resource"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">An instance of a retrieved <see cref="Resource"/>.</param>
|
||||||
|
/// <returns><c>true</c> if the resource was successfully retrieved, otherwise <c>false</c>.</returns>
|
||||||
|
public bool TryGetValue([NotNullWhen(true)] out T? value)
|
||||||
|
{
|
||||||
|
value = ResourceManager.GetResource<T>(Guid);
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an empty <see cref="ResourceRef"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
public static ResourceRef<T> Empty()
|
public static ResourceRef<T> Empty()
|
||||||
{
|
{
|
||||||
return new ResourceRef<T>(Guid.Empty);
|
return new ResourceRef<T>(Guid.Empty);
|
||||||
@@ -27,12 +52,34 @@ namespace Voile
|
|||||||
{
|
{
|
||||||
Guid = guid;
|
Guid = guid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is ResourceRef<T> other && Guid.Equals(other.Guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Guid.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(ResourceRef<T>? left, ResourceRef<T>? right)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(left, right)) return true;
|
||||||
|
if (left is null || right is null) return false;
|
||||||
|
return left.Guid == right.Guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(ResourceRef<T>? left, ResourceRef<T>? right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents data usable by Voile.
|
/// Represents data usable by Voile.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Resource : IDisposable
|
public abstract class Resource
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path to this resource.
|
/// Path to this resource.
|
||||||
@@ -43,9 +90,21 @@ namespace Voile
|
|||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
/// <summary>
|
||||||
|
/// Represents a Resource that requires systems to react to its changes.
|
||||||
|
/// </summary>
|
||||||
|
public interface IUpdatableResource
|
||||||
{
|
{
|
||||||
}
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this resource's state has changed and needs to be reloaded.
|
||||||
|
/// </summary>
|
||||||
|
bool Dirty { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks this resource as updated.
|
||||||
|
/// </summary>
|
||||||
|
void MarkUpdated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ namespace Voile.Resources
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to the resource.</param>
|
/// <param name="path">Path to the resource.</param>
|
||||||
/// <returns>True if a resource at the specified path is loaded, otherwise false.</returns>
|
/// <returns>True if a resource at the specified path is loaded, otherwise false.</returns>
|
||||||
public bool IsResourceLoaded(string path) => _resourcePathMap.ContainsKey(path);
|
public static bool IsResourceLoaded(string path) => _resourcePathMap.ContainsKey(path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a resource loader associated with a resource type.
|
/// Adds a resource loader associated with a resource type.
|
||||||
|
|||||||
@@ -2,23 +2,76 @@ using System.Numerics;
|
|||||||
|
|
||||||
namespace Voile.UI;
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies predefined anchor points used to position UI elements relative to their parent container.
|
||||||
|
/// </summary>
|
||||||
public enum Anchor
|
public enum Anchor
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the top-left corner of the parent.
|
||||||
|
/// </summary>
|
||||||
TopLeft,
|
TopLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the top-center of the parent.
|
||||||
|
/// </summary>
|
||||||
TopCenter,
|
TopCenter,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the top-right corner of the parent.
|
||||||
|
/// </summary>
|
||||||
TopRight,
|
TopRight,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the center-left edge of the parent.
|
||||||
|
/// </summary>
|
||||||
CenterLeft,
|
CenterLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the exact center of the parent.
|
||||||
|
/// </summary>
|
||||||
Center,
|
Center,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the center-right edge of the parent.
|
||||||
|
/// </summary>
|
||||||
CenterRight,
|
CenterRight,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the bottom-left corner of the parent.
|
||||||
|
/// </summary>
|
||||||
BottomLeft,
|
BottomLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the bottom-center of the parent.
|
||||||
|
/// </summary>
|
||||||
BottomCenter,
|
BottomCenter,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anchors the element to the bottom-right corner of the parent.
|
||||||
|
/// </summary>
|
||||||
BottomRight,
|
BottomRight,
|
||||||
Fill
|
Fill
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for calculating anchored positions of UI elements.
|
||||||
|
/// </summary>
|
||||||
public static class AnchorExtensions
|
public static class AnchorExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the offset position for an element based on the specified <see cref="Anchor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anchor">The anchor mode to use.</param>
|
||||||
|
/// <param name="parentPosition">The absolute position of the parent container (top-left corner).</param>
|
||||||
|
/// <param name="parentRect">The bounding rectangle of the parent container.</param>
|
||||||
|
/// <param name="elementRect">The size of the element being anchored.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="Vector2"/> representing the local offset position where the element should be placed inside the parent.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The result is the relative offset from the parent's origin, not a global position.
|
||||||
|
/// </remarks>
|
||||||
public static Vector2 Calculate(this Anchor anchor, Vector2 parentPosition, Rect parentRect, Rect elementRect)
|
public static Vector2 Calculate(this Anchor anchor, Vector2 parentPosition, Rect parentRect, Rect elementRect)
|
||||||
{
|
{
|
||||||
var size = new Vector2(elementRect.Width, elementRect.Height);
|
var size = new Vector2(elementRect.Width, elementRect.Height);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Voile.Rendering;
|
|||||||
|
|
||||||
namespace Voile.UI.Containers;
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
// TODO: make Container extend Widget, it already implements similar behaviors.
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A base class for all UI containers, used to position and rendering child <see cref="IElement">s.
|
/// A base class for all UI containers, used to position and rendering child <see cref="IElement">s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,7 +44,6 @@ public abstract class Container : UIElement, IParentableElement
|
|||||||
{
|
{
|
||||||
if (child is not IUpdatableElement updatable) continue;
|
if (child is not IUpdatableElement updatable) continue;
|
||||||
|
|
||||||
updatable.MarkDirty();
|
|
||||||
updatable.Update();
|
updatable.Update();
|
||||||
|
|
||||||
if (child is IAnchorableElement anchorable)
|
if (child is IAnchorableElement anchorable)
|
||||||
@@ -62,6 +60,18 @@ public abstract class Container : UIElement, IParentableElement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void MarkDirty()
|
||||||
|
{
|
||||||
|
base.MarkDirty();
|
||||||
|
|
||||||
|
foreach (var child in _children)
|
||||||
|
{
|
||||||
|
if (child is not IUpdatableElement updatable) continue;
|
||||||
|
|
||||||
|
updatable.MarkDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when this <see cref="Container"/> has to rearrange its children.
|
/// Called when this <see cref="Container"/> has to rearrange its children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -97,7 +107,12 @@ public abstract class Container : UIElement, IParentableElement
|
|||||||
float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
|
float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
|
||||||
float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
|
float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
|
||||||
|
|
||||||
Size = new Rect(finalWidth, finalHeight);
|
var finalSize = new Rect(finalWidth, finalHeight);
|
||||||
|
|
||||||
|
if (finalSize != Size)
|
||||||
|
{
|
||||||
|
Size = finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
if (_minimumSize > Size)
|
if (_minimumSize > Size)
|
||||||
{
|
{
|
||||||
|
|||||||
58
Voile/Source/UI/Containers/FillContainer.cs
Normal file
58
Voile/Source/UI/Containers/FillContainer.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A special container that occupies the entire available size of the parent. <br />
|
||||||
|
/// Usually used as a root element for the UI system.
|
||||||
|
/// </summary>
|
||||||
|
public class FillContainer : Container
|
||||||
|
{
|
||||||
|
public FillContainer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public FillContainer(Rect minimumSize) : base(minimumSize)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
|
{
|
||||||
|
base.Render(renderer, style);
|
||||||
|
|
||||||
|
Rect parentSize;
|
||||||
|
|
||||||
|
if (Parent != null)
|
||||||
|
{
|
||||||
|
parentSize = Parent.Size;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var windowSize = renderer.WindowSize;
|
||||||
|
var windowRect = new Rect(windowSize.X, windowSize.Y);
|
||||||
|
|
||||||
|
parentSize = windowRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastParentSize != parentSize)
|
||||||
|
{
|
||||||
|
Size = parentSize;
|
||||||
|
_lastParentSize = parentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
base.OnUpdate();
|
||||||
|
Size = _lastParentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rect _lastParentSize = Rect.Zero;
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace Voile.UI.Containers;
|
|
||||||
|
|
||||||
public class Frame : Container
|
|
||||||
{
|
|
||||||
public Frame()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Frame(Rect minimumSize) : base(minimumSize)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Arrange()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
Voile/Source/UI/Containers/MarginContainer.cs
Normal file
88
Voile/Source/UI/Containers/MarginContainer.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the margin offsets applied around an element.
|
||||||
|
/// </summary>
|
||||||
|
public struct Margin
|
||||||
|
{
|
||||||
|
public float Left;
|
||||||
|
public float Right;
|
||||||
|
public float Top;
|
||||||
|
public float Bottom;
|
||||||
|
|
||||||
|
public Margin(float uniform)
|
||||||
|
{
|
||||||
|
Left = Right = Top = Bottom = uniform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Margin(float horizontal, float vertical)
|
||||||
|
{
|
||||||
|
Left = Right = horizontal;
|
||||||
|
Top = Bottom = vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Margin(float left, float right, float top, float bottom)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Right = right;
|
||||||
|
Top = top;
|
||||||
|
Bottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Margin Zero => new Margin(0);
|
||||||
|
|
||||||
|
public static Rect operator +(Margin margin, Rect rect) =>
|
||||||
|
new Rect(rect.Width + margin.Left + margin.Right,
|
||||||
|
rect.Height + margin.Top + margin.Bottom);
|
||||||
|
|
||||||
|
public static Rect operator +(Rect rect, Margin margin) =>
|
||||||
|
margin + rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MarginContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The margin to apply around the contents of this container.
|
||||||
|
/// </summary>
|
||||||
|
public Margin Margin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this <see cref="MarginContainer"/> will fill to parent size.
|
||||||
|
/// </summary>
|
||||||
|
public bool Fill { get; set; } = true;
|
||||||
|
|
||||||
|
public MarginContainer() : this(new Margin()) { }
|
||||||
|
|
||||||
|
public MarginContainer(Margin margin)
|
||||||
|
{
|
||||||
|
Margin = margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
base.OnUpdate();
|
||||||
|
if (Parent == null) return;
|
||||||
|
|
||||||
|
if (Size != Parent.Size)
|
||||||
|
{
|
||||||
|
Size = Parent.Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
var newPosition = new Vector2(Margin.Left, Margin.Top);
|
||||||
|
var newSize = new Rect(
|
||||||
|
Size.Width - Margin.Left - Margin.Right,
|
||||||
|
Size.Height - Margin.Top - Margin.Bottom
|
||||||
|
);
|
||||||
|
|
||||||
|
child.Size = newSize;
|
||||||
|
child.LocalPosition = newPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Voile.Input;
|
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
|
|
||||||
namespace Voile.UI;
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a basic UI element with position and size information.
|
||||||
|
/// </summary>
|
||||||
public interface IElement
|
public interface IElement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -16,6 +18,9 @@ public interface IElement
|
|||||||
public Rect Size { get; set; }
|
public Rect Size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UI element that can contain child elements.
|
||||||
|
/// </summary>
|
||||||
public interface IParentableElement
|
public interface IParentableElement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -34,6 +39,10 @@ public interface IParentableElement
|
|||||||
public void RemoveChild(UIElement child);
|
public void RemoveChild(UIElement child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UI element that can provide a minimum size constraint.<br />
|
||||||
|
/// Implement this interface if your UI element is expected to be resizeable.
|
||||||
|
/// </summary>
|
||||||
public interface IResizeableElement
|
public interface IResizeableElement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,10 +51,13 @@ public interface IResizeableElement
|
|||||||
public abstract Rect MinimumSize { get; }
|
public abstract Rect MinimumSize { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UI element that supports updates when its state changes.
|
||||||
|
/// </summary>
|
||||||
public interface IUpdatableElement
|
public interface IUpdatableElement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies if this element's properties have changed, making it necessary to update it.
|
/// Gets a value indicating whether the element's state has changed and needs to be updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Dirty { get; }
|
public bool Dirty { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,6 +70,9 @@ public interface IUpdatableElement
|
|||||||
void MarkDirty();
|
void MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UI element that can be rendered to the screen.
|
||||||
|
/// </summary>
|
||||||
public interface IRenderableElement
|
public interface IRenderableElement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -77,6 +92,9 @@ public interface IRenderableElement
|
|||||||
public void DrawSize(RenderSystem renderer);
|
public void DrawSize(RenderSystem renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UI element that can receive and process user input.
|
||||||
|
/// </summary>
|
||||||
public interface IInputElement
|
public interface IInputElement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,9 +108,23 @@ public interface IInputElement
|
|||||||
void Input(UIInputContext action);
|
void Input(UIInputContext action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UI element that supports positional anchoring within a parent.
|
||||||
|
/// </summary>
|
||||||
public interface IAnchorableElement
|
public interface IAnchorableElement
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the anchor point relative to the parent container.
|
||||||
|
/// </summary>
|
||||||
public Anchor Anchor { get; set; }
|
public Anchor Anchor { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an additional offset to apply after anchoring, in pixels.
|
||||||
|
/// </summary>
|
||||||
public Vector2 AnchorOffset { get; set; }
|
public Vector2 AnchorOffset { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the current anchor settings based on the parent's position and size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parentPosition">The parent's top-left global position.</param>
|
||||||
|
/// <param name="parentRect">The bounding rectangle of the parent container.</param>
|
||||||
public void ApplyAnchor(Vector2 parentPosition, Rect parentRect);
|
public void ApplyAnchor(Vector2 parentPosition, Rect parentRect);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,9 @@ using Voile.Rendering;
|
|||||||
|
|
||||||
namespace Voile.UI;
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for all UI elements.
|
||||||
|
/// </summary>
|
||||||
public abstract class UIElement : IElement, IRenderableElement, IResizeableElement, IUpdatableElement, IAnchorableElement
|
public abstract class UIElement : IElement, IRenderableElement, IResizeableElement, IUpdatableElement, IAnchorableElement
|
||||||
{
|
{
|
||||||
public bool Visible { get; set; } = true;
|
public bool Visible { get; set; } = true;
|
||||||
@@ -10,13 +13,16 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
|||||||
public Vector2 LocalPosition { get; set; } = Vector2.Zero;
|
public Vector2 LocalPosition { get; set; } = Vector2.Zero;
|
||||||
public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;
|
public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parent <see cref="UIElement"/> of this element.
|
||||||
|
/// </summary>
|
||||||
|
public UIElement? Parent => _parent;
|
||||||
|
|
||||||
public Rect Size
|
public Rect Size
|
||||||
{
|
{
|
||||||
get => _size;
|
get => _size;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_size = value;
|
|
||||||
|
|
||||||
if (value.Width < MinimumSize.Width)
|
if (value.Width < MinimumSize.Width)
|
||||||
{
|
{
|
||||||
_size.Width = MinimumSize.Width;
|
_size.Width = MinimumSize.Width;
|
||||||
@@ -27,8 +33,13 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
|||||||
_size.Height = MinimumSize.Height;
|
_size.Height = MinimumSize.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_size != value)
|
||||||
|
{
|
||||||
MarkDirty();
|
MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_size = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
|
public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
|
||||||
public Anchor Anchor { get; set; } = Anchor.TopLeft;
|
public Anchor Anchor { get; set; } = Anchor.TopLeft;
|
||||||
@@ -36,11 +47,24 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
|
|||||||
public abstract Rect MinimumSize { get; }
|
public abstract Rect MinimumSize { get; }
|
||||||
public bool Dirty => _dirty;
|
public bool Dirty => _dirty;
|
||||||
|
|
||||||
public virtual void MarkDirty() => _dirty = true;
|
public virtual void MarkDirty()
|
||||||
|
{
|
||||||
|
if (Parent != null && !Parent.Dirty)
|
||||||
|
{
|
||||||
|
Parent.MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a parent element for this <see cref="UIElement"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">Element to parent this <see cref="UIElement"/> to.</param>
|
||||||
public void SetParent(UIElement parent)
|
public void SetParent(UIElement parent)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
MarkDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
public IReadOnlyList<IElement> Elements => _elements;
|
public IReadOnlyList<IElement> Elements => _elements;
|
||||||
|
|
||||||
public bool RenderDebugRects { get; set; }
|
public bool RenderDebugRects { get; set; }
|
||||||
|
public Color DebugSizeRectColor { get; set; } = Color.Red;
|
||||||
|
public Color DebugDirtyRectColor { get; set; } = new Color(1.0f, 1.0f, 0.0f, 0.5f);
|
||||||
|
|
||||||
public UISystem(InputSystem inputSystem)
|
public UISystem(InputSystem inputSystem)
|
||||||
{
|
{
|
||||||
@@ -29,7 +31,11 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
_elements = elements;
|
_elements = elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddElement(UIElement element) => _elements.Add(element);
|
public void AddElement(UIElement element)
|
||||||
|
{
|
||||||
|
_elements.Add(element);
|
||||||
|
_inputElementIndices.Add(element.GlobalPosition, _elements.Count - 1);
|
||||||
|
}
|
||||||
public void RemoveElement(UIElement element) => _elements.Remove(element);
|
public void RemoveElement(UIElement element) => _elements.Remove(element);
|
||||||
|
|
||||||
public void Update(double deltaTime)
|
public void Update(double deltaTime)
|
||||||
@@ -50,22 +56,44 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
{
|
{
|
||||||
if (element is IRenderableElement renderable)
|
if (element is IRenderableElement renderable)
|
||||||
{
|
{
|
||||||
renderable.Render(renderer, _style.Value);
|
// TODO: normally you'd load a default style if the one supplied is empty,
|
||||||
|
// but for now this will do.
|
||||||
if (!RenderDebugRects) return;
|
if (!_style.TryGetValue(out var value))
|
||||||
renderable.DrawSize(renderer);
|
{
|
||||||
|
value = new Style(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element is IParentableElement parentable)
|
renderable.Render(renderer, value);
|
||||||
{
|
}
|
||||||
foreach (var child in parentable.Children)
|
}
|
||||||
{
|
|
||||||
if (child is not IRenderableElement renderableChild) continue;
|
|
||||||
|
|
||||||
if (!RenderDebugRects) return;
|
if (!RenderDebugRects) return;
|
||||||
renderableChild.DrawSize(renderer);
|
|
||||||
|
foreach (var element in _elements)
|
||||||
|
{
|
||||||
|
if (element is not UIElement uiElement) continue;
|
||||||
|
DrawDebugForElement(renderer, uiElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawDebugForElement(RenderSystem renderer, UIElement element)
|
||||||
|
{
|
||||||
|
var size = new Vector2(element.Size.Width, element.Size.Height);
|
||||||
|
renderer.SetTransform(element.GlobalPosition, Vector2.Zero);
|
||||||
|
renderer.DrawRectangleOutline(size, DebugSizeRectColor);
|
||||||
|
|
||||||
|
if (element.Dirty)
|
||||||
|
{
|
||||||
|
renderer.DrawRectangle(size, DebugDirtyRectColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is IParentableElement parentableElement)
|
||||||
|
{
|
||||||
|
foreach (var child in parentableElement.Children)
|
||||||
|
{
|
||||||
|
if (child is not UIElement childElement) continue;
|
||||||
|
DrawDebugForElement(renderer, childElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +150,8 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
{
|
{
|
||||||
var element = elements[i];
|
var element = elements[i];
|
||||||
|
|
||||||
|
// if (!element.ContainsPoint(context.MousePosition)) continue;
|
||||||
|
|
||||||
if (element is IInputElement inputElement && !inputElement.IgnoreInput)
|
if (element is IInputElement inputElement && !inputElement.IgnoreInput)
|
||||||
{
|
{
|
||||||
inputElement.Input(context);
|
inputElement.Input(context);
|
||||||
@@ -143,5 +173,7 @@ public class UISystem : IUpdatableSystem, IRenderableSystem
|
|||||||
private List<UIElement> _elements = new();
|
private List<UIElement> _elements = new();
|
||||||
private InputSystem _input;
|
private InputSystem _input;
|
||||||
|
|
||||||
|
private GridSet<int> _inputElementIndices = new();
|
||||||
|
|
||||||
private Vector2 _lastMousePosition = Vector2.Zero;
|
private Vector2 _lastMousePosition = Vector2.Zero;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Voile.Input;
|
using Voile.Input;
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
|
using Voile.Resources;
|
||||||
|
using Voile.UI.Containers;
|
||||||
|
|
||||||
namespace Voile.UI.Widgets;
|
namespace Voile.UI.Widgets;
|
||||||
|
|
||||||
@@ -17,13 +19,42 @@ public enum ButtonState
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Button : Widget
|
public class Button : Widget
|
||||||
{
|
{
|
||||||
public string Label { get; set; } = "Button";
|
public string Text
|
||||||
public override Rect MinimumSize => new Rect(Width: 128.0f, Height: 64.0f);
|
|
||||||
|
|
||||||
public Button(string label, Action pressedAction)
|
|
||||||
{
|
{
|
||||||
Label = label;
|
get => _text; set
|
||||||
|
{
|
||||||
|
_text = value;
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override Rect MinimumSize => Padding + _textSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="FontSet"/> to use with this button.
|
||||||
|
/// </summary>
|
||||||
|
public FontSet FontSet { get; set; } = new();
|
||||||
|
public Margin Padding { get; set; } = Margin.Zero;
|
||||||
|
|
||||||
|
public Button(string text, ResourceRef<Font> fontOverride, Action pressedAction)
|
||||||
|
{
|
||||||
|
_text = text;
|
||||||
_pressedAction = pressedAction;
|
_pressedAction = pressedAction;
|
||||||
|
|
||||||
|
FontSet.AddFont(fontOverride);
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button(string text, FontSet fontSet, Action pressedAction)
|
||||||
|
{
|
||||||
|
_text = text;
|
||||||
|
_pressedAction = pressedAction;
|
||||||
|
|
||||||
|
FontSet = fontSet;
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Render(RenderSystem renderer, Style style)
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
@@ -31,17 +62,46 @@ public class Button : Widget
|
|||||||
// TODO: use a button color from style.
|
// TODO: use a button color from style.
|
||||||
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||||
renderer.DrawRectangle(new Vector2(MinimumSize.Width, MinimumSize.Height), new Color(0.25f, 0.25f, 0.25f));
|
renderer.DrawRectangle(new Vector2(MinimumSize.Width, MinimumSize.Height), new Color(0.25f, 0.25f, 0.25f));
|
||||||
|
|
||||||
|
var textPosition = new Vector2(GlobalPosition.X + Padding.Left, GlobalPosition.Y + Padding.Top);
|
||||||
|
renderer.SetTransform(textPosition, Vector2.Zero);
|
||||||
|
renderer.DrawText(_suitableFont, _text, Color.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnInput(UIInputContext action)
|
protected override void OnInput(UIInputContext action)
|
||||||
{
|
{
|
||||||
|
if (action.MouseReleased && ContainsPoint(action.MousePosition))
|
||||||
|
{
|
||||||
|
_pressedAction?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
ResourceRef<Font> fontRef = ResourceRef<Font>.Empty();
|
||||||
|
foreach (var c in _text)
|
||||||
|
{
|
||||||
|
if (FontSet.TryGetFontFor(c, out var fallbackFont))
|
||||||
|
{
|
||||||
|
if (fallbackFont != fontRef)
|
||||||
|
{
|
||||||
|
fontRef = fallbackFont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_suitableFont = fontRef;
|
||||||
|
|
||||||
|
var font = _suitableFont.Value;
|
||||||
|
_textSize = font.Measure(_text);
|
||||||
|
|
||||||
|
Size = Padding + _textSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Action _pressedAction;
|
private Action _pressedAction;
|
||||||
|
|
||||||
|
private ResourceRef<Font> _suitableFont = ResourceRef<Font>.Empty();
|
||||||
|
|
||||||
|
private string _text = "Hello, World!";
|
||||||
|
private Rect _textSize = Rect.Zero;
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,85 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Voile.Input;
|
using Voile.Input;
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
|
using Voile.Resources;
|
||||||
|
|
||||||
namespace Voile.UI.Widgets;
|
namespace Voile.UI.Widgets;
|
||||||
|
|
||||||
public class Label : Widget
|
public class Label : Widget
|
||||||
{
|
{
|
||||||
public override Rect MinimumSize => throw new NotImplementedException();
|
public override Rect MinimumSize => _textSize;
|
||||||
|
|
||||||
public string Text { get; set; } = "Hello World!";
|
public string Text
|
||||||
|
|
||||||
public Label(string text)
|
|
||||||
{
|
{
|
||||||
Text = text;
|
get => _text; set
|
||||||
|
{
|
||||||
|
_text = value;
|
||||||
|
MarkDirty();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="FontSet"/> to use with this label.
|
||||||
|
/// </summary>
|
||||||
|
public FontSet FontSet { get; set; } = new();
|
||||||
|
|
||||||
public Label(string text, ResourceRef<Font> fontOverride)
|
public Label(string text, ResourceRef<Font> fontOverride)
|
||||||
{
|
{
|
||||||
Text = text;
|
_text = text;
|
||||||
_fontOverride = fontOverride;
|
|
||||||
|
FontSet.AddFont(fontOverride);
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label(string text, FontSet fontSet)
|
||||||
|
{
|
||||||
|
_text = text;
|
||||||
|
|
||||||
|
FontSet = fontSet;
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnInput(UIInputContext action)
|
protected override void OnInput(UIInputContext action)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Render(RenderSystem renderer, Style style)
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
{
|
{
|
||||||
// TODO: use style here.
|
// TODO: use style here.
|
||||||
if (!_fontOverride.HasValue) return;
|
|
||||||
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||||
renderer.DrawText(_fontOverride, Text, Color.White);
|
renderer.DrawText(_suitableFont, _text, Color.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
ResourceRef<Font> fontRef = ResourceRef<Font>.Empty();
|
||||||
|
foreach (var c in _text)
|
||||||
|
{
|
||||||
|
if (FontSet.TryGetFontFor(c, out var fallbackFont))
|
||||||
|
{
|
||||||
|
if (fallbackFont != fontRef)
|
||||||
|
{
|
||||||
|
fontRef = fallbackFont;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceRef<Font> _fontOverride = ResourceRef<Font>.Empty();
|
_suitableFont = fontRef;
|
||||||
|
|
||||||
|
var font = _suitableFont.Value;
|
||||||
|
_textSize = font.Measure(_text);
|
||||||
|
|
||||||
|
Size = _textSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceRef<Font> _suitableFont = ResourceRef<Font>.Empty();
|
||||||
|
|
||||||
|
private string _text = "Hello, World!";
|
||||||
|
private Rect _textSize = Rect.Zero;
|
||||||
}
|
}
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
namespace Voile
|
|
||||||
{
|
|
||||||
// Based on https://github.com/ppr-game/PPR/blob/engine/PER.Util/src/Color.cs
|
|
||||||
/// <summary>
|
|
||||||
/// A record struct representing a color.
|
|
||||||
/// </summary>
|
|
||||||
public record struct Color
|
|
||||||
{
|
|
||||||
// TODO: add more HTML colors.
|
|
||||||
public static Color AliceBlue = new(0xF0F8FF);
|
|
||||||
public static Color AntiqueWhite = new(0xFAEBD7);
|
|
||||||
public static Color Aqua = new(0x00FFFF);
|
|
||||||
public static Color Aquamarine = new(0x7FFFD4);
|
|
||||||
public static Color Azure = new(0xF0FFFF);
|
|
||||||
public static Color Beige = new(0xF5F5DC);
|
|
||||||
public static Color Bisque = new(0xFFE4C4);
|
|
||||||
public static Color Black = new(0x000000);
|
|
||||||
public static Color BlanchedAlmond = new(0xFFEBCD);
|
|
||||||
public static Color Blue = new(0x0000FF);
|
|
||||||
public static Color BlueViolet = new(0x8A2BE2);
|
|
||||||
public static Color Brown = new(0xA52A2A);
|
|
||||||
public static Color BurlyWood = new(0xDEB887);
|
|
||||||
public static Color CadetBlue = new(0x5F9EA0);
|
|
||||||
public static Color Chartreuse = new(0x7FFF00);
|
|
||||||
public static Color Chocolate = new(0xD2691E);
|
|
||||||
public static Color Coral = new(0xFF7F50);
|
|
||||||
public static Color CornflowerBlue = new(0x6495ED);
|
|
||||||
public static Color Cornsilk = new(0xFFF8DC);
|
|
||||||
public static Color Crimson = new(0xDC143C);
|
|
||||||
public static Color Cyan = new(0x00FFFF);
|
|
||||||
public static Color DarkBlue = new(0x00008B);
|
|
||||||
public static Color DarkCyan = new(0x008B8B);
|
|
||||||
public static Color White = new(0xFFFFFF);
|
|
||||||
public static Color Green = new(0x00FF00);
|
|
||||||
public static Color Red = new(0xFF0000);
|
|
||||||
|
|
||||||
public byte R { get; set; }
|
|
||||||
public byte G { get; set; }
|
|
||||||
public byte B { get; set; }
|
|
||||||
public byte A { get; set; } = 255;
|
|
||||||
|
|
||||||
public int Argb
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
int a = A << 24;
|
|
||||||
int r = R << 16;
|
|
||||||
int g = G << 8;
|
|
||||||
int b = B;
|
|
||||||
|
|
||||||
return a | r | g | b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color(float r, float g, float b, float a = 1.0f)
|
|
||||||
{
|
|
||||||
R = (byte)Math.Clamp(r * 255, 0, 255);
|
|
||||||
G = (byte)Math.Clamp(g * 255, 0, 255);
|
|
||||||
B = (byte)Math.Clamp(b * 255, 0, 255);
|
|
||||||
A = (byte)Math.Clamp(a * 255, 0, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color(byte r, byte g, byte b, byte a = 255)
|
|
||||||
{
|
|
||||||
R = r;
|
|
||||||
G = g;
|
|
||||||
B = b;
|
|
||||||
A = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color(int hex)
|
|
||||||
{
|
|
||||||
A = 255; // Default alpha to 255 if not provided
|
|
||||||
B = (byte)(hex & 0xFF);
|
|
||||||
G = (byte)((hex >> 8) & 0xFF);
|
|
||||||
R = (byte)((hex >> 16) & 0xFF);
|
|
||||||
if (hex > 0xFFFFFF) // If the hex value includes alpha
|
|
||||||
{
|
|
||||||
A = (byte)((hex >> 24) & 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Color FromHexString(string hex)
|
|
||||||
{
|
|
||||||
if (hex.StartsWith("#"))
|
|
||||||
{
|
|
||||||
hex = hex[1..];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hex.Length == 6)
|
|
||||||
{
|
|
||||||
int rgb = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
|
||||||
return new Color(rgb);
|
|
||||||
}
|
|
||||||
else if (hex.Length == 8)
|
|
||||||
{
|
|
||||||
int rgba = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
|
|
||||||
return new Color(rgba);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid hex color format. Use #RRGGBB or #RRGGBBAA.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color Lightened(float amount)
|
|
||||||
{
|
|
||||||
var result = this;
|
|
||||||
result.R = (byte)Math.Min(255, R + (255 - R) * amount);
|
|
||||||
result.G = (byte)Math.Min(255, G + (255 - G) * amount);
|
|
||||||
result.B = (byte)Math.Min(255, B + (255 - B) * amount);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color Darkened(float amount)
|
|
||||||
{
|
|
||||||
var result = this;
|
|
||||||
result.R = (byte)(R * (1.0f - amount));
|
|
||||||
result.G = (byte)(G * (1.0f - amount));
|
|
||||||
result.B = (byte)(B * (1.0f - amount));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public System.Drawing.Color ToSystemColor()
|
|
||||||
{
|
|
||||||
var result = System.Drawing.Color.FromArgb(Argb);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FreeTypeSharp" Version="3.0.0" />
|
||||||
<PackageReference Include="Silk.NET.WebGPU" Version="2.20.0" />
|
<PackageReference Include="Silk.NET.WebGPU" Version="2.20.0" />
|
||||||
<PackageReference Include="Silk.NET.WebGPU.Native.WGPU" Version="2.20.0" />
|
<PackageReference Include="Silk.NET.WebGPU.Native.WGPU" Version="2.20.0" />
|
||||||
<PackageReference Include="Silk.NET.Windowing" Version="2.20.0" />
|
<PackageReference Include="Silk.NET.Windowing" Version="2.20.0" />
|
||||||
@@ -16,8 +17,6 @@
|
|||||||
<PackageReference Include="Tommy" Version="3.1.2" />
|
<PackageReference Include="Tommy" Version="3.1.2" />
|
||||||
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
||||||
<PackageReference Include="Raylib-cs" Version="7.0.1" />
|
<PackageReference Include="Raylib-cs" Version="7.0.1" />
|
||||||
<PackageReference Include="SharpFont" Version="4.0.1" />
|
|
||||||
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta19" />
|
|
||||||
<PackageReference Include="StbImageSharp" Version="2.27.13" />
|
<PackageReference Include="StbImageSharp" Version="2.27.13" />
|
||||||
<PackageReference Include="StbVorbisSharp" Version="1.22.4" />
|
<PackageReference Include="StbVorbisSharp" Version="1.22.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user