Compare commits
38 Commits
bb6900a60a
...
standard-r
| Author | SHA1 | Date | |
|---|---|---|---|
| dc7122ed26 | |||
| 78b46cb38e | |||
| 6c3576891e | |||
| 03668849bc | |||
| b228f04670 | |||
| a5d2668c18 | |||
| 9a3512702a | |||
| 61ac079f2b | |||
| 95ae2de7ac | |||
| 683656dee8 | |||
| 30c438c407 | |||
| ae1b612524 | |||
| 7e86898e1a | |||
| a1f56f49fb | |||
| 84efb2a3d1 | |||
| 5b29ea012e | |||
| f0c721bb0f | |||
| bc95fff4a3 | |||
| 76dafe9996 | |||
| a9a8113dd9 | |||
| 3154b3fa10 | |||
| 1b09d80f7a | |||
| 63448210e2 | |||
| 3460c124b8 | |||
| 6affded730 | |||
| e499691714 | |||
| 806c9cc1d4 | |||
| a450ed9819 | |||
| 15214c9e21 | |||
| a806e3b764 | |||
| e51d28ce89 | |||
| 99624e152d | |||
| 64d72cc053 | |||
| 07224d684d | |||
| c8f0de6aab | |||
| 503473c6b3 | |||
| 20036be50f | |||
| fdcf29d6e0 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -50,3 +50,5 @@ Voile.Fmod/runtimes/**/*.so*
|
|||||||
# DocFX
|
# DocFX
|
||||||
.cache
|
.cache
|
||||||
/**/_site/
|
/**/_site/
|
||||||
|
|
||||||
|
*.dmp
|
||||||
30
TODO.md
30
TODO.md
@@ -1,5 +1,12 @@
|
|||||||
# Voile MVP
|
# Voile MVP
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- ActionJustPressed and KeyboardKeyJustPressed don't detect inputs consistently.
|
||||||
|
- **Solution**: This is a problem related to custom frame pacing for fixed timestep. Raylib polls input in BeginFrame, which means certain functions related to JustPressed* may work incorrectly when polled in a separate timestep. This can be fixed if the entire input system will be moved to SDL or with a custom render + input backend altogether.
|
||||||
|
- ~~Fix any remaining bugs with anchor positioning system.~~
|
||||||
|
- ~~Containers don't position their chilren correctly when using anchors and adding/removing them.~~
|
||||||
|
|
||||||
## Core
|
## Core
|
||||||
|
|
||||||
- ~~Add and implement interfaces for systems (ISystem, IUpdatableSystem, etc.)~~
|
- ~~Add and implement interfaces for systems (ISystem, IUpdatableSystem, etc.)~~
|
||||||
@@ -7,6 +14,7 @@
|
|||||||
- ~~TextDataResource providing a convenient wrapper around TOML data files.~~
|
- ~~TextDataResource providing a convenient wrapper around TOML data files.~~
|
||||||
- ~~Add documentation for common classes.~~
|
- ~~Add documentation for common classes.~~
|
||||||
- ~~Hot reloading of resources.~~
|
- ~~Hot reloading of resources.~~
|
||||||
|
- Separate render and update thread.
|
||||||
|
|
||||||
## I/O
|
## I/O
|
||||||
|
|
||||||
@@ -14,13 +22,15 @@
|
|||||||
- ~~Reimplement unloading.~~
|
- ~~Reimplement unloading.~~
|
||||||
- Finalize ResourceManager and ResourceLoader APIs for 1.0.
|
- Finalize ResourceManager and ResourceLoader APIs for 1.0.
|
||||||
- Add async API for ResourceManager.
|
- Add async API for ResourceManager.
|
||||||
- Virtual file system.
|
- ~~Virtual file system.~~
|
||||||
|
- Custom PAK format using the VFS implementation.
|
||||||
- (stretch goal) Streamed resource loading.
|
- (stretch goal) Streamed resource loading.
|
||||||
|
|
||||||
## Serialization
|
## Serialization
|
||||||
|
|
||||||
- Serialize attribute.
|
- Serialize attribute.
|
||||||
- Add automatic serialization of resources through source generation and System.Text.Json.
|
- Add automatic serialization of resources through source generation and System.Text.Json.
|
||||||
|
- Make sure this serialization system works well with CLR and NativeAOT.
|
||||||
- ~~Provide means for fetching key/value configuration (INI? TOML?)~~
|
- ~~Provide means for fetching key/value configuration (INI? TOML?)~~
|
||||||
- Expose some sort of ConfigFile class for safe key/value configuration fetching.
|
- Expose some sort of ConfigFile class for safe key/value configuration fetching.
|
||||||
|
|
||||||
@@ -29,7 +39,7 @@
|
|||||||
- ~~API for drawing textured quads~~
|
- ~~API for drawing textured quads~~
|
||||||
- ~~Camera API~~
|
- ~~Camera API~~
|
||||||
- Arbitrary mesh rendering API.
|
- Arbitrary mesh rendering API.
|
||||||
- Create WebGPU renderer (StandardRenderer)
|
- Create WebGPU renderer (StandardRenderSystem)
|
||||||
|
|
||||||
## Audio
|
## Audio
|
||||||
|
|
||||||
@@ -61,7 +71,17 @@
|
|||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
- Immediate mode API.
|
- ~~Layout~~
|
||||||
- Styling.
|
- ~~Containers~~
|
||||||
|
- ~~VerticalContainer~~
|
||||||
|
- ~~HorizontalContainer~~
|
||||||
|
- ~~GridContainer~~
|
||||||
|
- ~~FlexContainer~~
|
||||||
|
- ~~Positioning (anchors)~~
|
||||||
|
- ~~Move layouting to Render instead of Update, use Update for input.~~
|
||||||
|
- Input propagation
|
||||||
- Basic input elements (button, text field, toggle).
|
- Basic input elements (button, text field, toggle).
|
||||||
- Containers (vertical and horizontal).
|
- Styling
|
||||||
|
- Add style settings for UI panels (for buttons, labels, etc.).
|
||||||
|
- Find a way to reference external assets in the style (fonts, textures).
|
||||||
|
- Create a default style for widgets.
|
||||||
@@ -17,4 +17,4 @@ LinearVelocityRandom = 0.5
|
|||||||
ScaleBegin = 0.1
|
ScaleBegin = 0.1
|
||||||
ScaleEnd = 5.0
|
ScaleEnd = 5.0
|
||||||
ColorBegin = [255, 162, 0]
|
ColorBegin = [255, 162, 0]
|
||||||
ColorEnd = [64, 64, 64, 0]
|
ColorEnd = [0, 0, 0, 0]
|
||||||
|
|||||||
BIN
TestGame/Resources/sounds/test_sound_mono.ogg
Normal file
BIN
TestGame/Resources/sounds/test_sound_mono.ogg
Normal file
Binary file not shown.
@@ -5,17 +5,26 @@ using Voile.Systems.Particles;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
|
using Voile.OpenAL;
|
||||||
|
using Voile.UI;
|
||||||
|
using Voile.UI.Widgets;
|
||||||
|
using Voile.UI.Containers;
|
||||||
|
|
||||||
public class TestGame : Game
|
public class TestGame : Game
|
||||||
{
|
{
|
||||||
public override string Name => "Jump Adventures 2";
|
public override string Name => "Test Game";
|
||||||
public override string ResourceRoot => "Resources/";
|
public override string ResourceRoot => "Resources/";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
InitializeSystemsDefault();
|
InitializeSystemsDefault();
|
||||||
|
|
||||||
|
_uiSystem = new UISystem(Input, ResourceRef<Style>.Empty());
|
||||||
|
_uiSystem.RenderDebugRects = true;
|
||||||
|
|
||||||
_particleSystem = new ParticleSystem();
|
_particleSystem = new ParticleSystem();
|
||||||
|
|
||||||
|
AddSystemToUpdate(_uiSystem);
|
||||||
AddSystemToUpdate(_particleSystem);
|
AddSystemToUpdate(_particleSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +43,7 @@ public class TestGame : Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
ResourceManager.TryLoad("icon.png", out _icon);
|
ResourceManager.TryLoad("icon.png", out _icon);
|
||||||
|
ResourceManager.TryLoad("sounds/test_sound_mono.ogg", out _sound);
|
||||||
|
|
||||||
if (!ResourceManager.TryLoad("fire_effect.toml", out _fireEffect))
|
if (!ResourceManager.TryLoad("fire_effect.toml", out _fireEffect))
|
||||||
{
|
{
|
||||||
@@ -43,17 +53,16 @@ public class TestGame : Game
|
|||||||
|
|
||||||
protected override void Ready()
|
protected override void Ready()
|
||||||
{
|
{
|
||||||
Input.AddInputMapping("reload", new InputAction[] { 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);
|
||||||
|
|
||||||
|
_uiSystem.AddElement(_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void Update(double deltaTime)
|
protected override void Update(double deltaTime)
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Render(double deltaTime)
|
|
||||||
{
|
{
|
||||||
if (Input.IsActionPressed("reload"))
|
if (Input.IsActionPressed("reload"))
|
||||||
{
|
{
|
||||||
@@ -61,28 +70,34 @@ public class TestGame : Game
|
|||||||
_particleSystem!.RestartEmitter(_emitterId);
|
_particleSystem!.RestartEmitter(_emitterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input.KeyboardKeyJustPressed(KeyboardKey.One))
|
if (Input.IsActionPressed("accept"))
|
||||||
{
|
{
|
||||||
_particleSystem.CreateEmitter(Input.GetMousePosition(), _fireEffect);
|
_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))
|
if (Input.IsMouseButtonDown(MouseButton.Left))
|
||||||
{
|
{
|
||||||
_particleSystem.SetEmitterPosition(_emitterId, Input.GetMousePosition());
|
var mousePos = Input.GetMousePosition();
|
||||||
|
_frame.Size = new Rect(mousePos.X, mousePos.Y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer.ClearBackground(Color.Black);
|
protected override void Render(double deltaTime)
|
||||||
foreach (var emitter in _particleSystem!.Emitters)
|
|
||||||
{
|
{
|
||||||
DrawEmitter(emitter);
|
Renderer.ClearBackground(Color.CadetBlue);
|
||||||
}
|
// foreach (var emitter in _particleSystem!.Emitters)
|
||||||
|
// {
|
||||||
|
// DrawEmitter(emitter);
|
||||||
|
// }
|
||||||
|
|
||||||
Renderer.ResetTransform();
|
// Renderer.ResetTransform();
|
||||||
|
// _uiSystem.Render(Renderer);
|
||||||
Renderer.DrawText(_font, $"Render: {RenderFrameTime.TotalMilliseconds:F1} ms", Color.White);
|
|
||||||
|
|
||||||
Renderer.SetTransform(new Vector2(0.0f, 16.0f), Vector2.Zero);
|
|
||||||
Renderer.DrawText(_font, $"Update: {UpdateTimeStep * 1000:F1} ms", Color.White);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawEmitter(ParticleEmitter emitter)
|
private void DrawEmitter(ParticleEmitter emitter)
|
||||||
@@ -105,8 +120,29 @@ public class TestGame : Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
[NotNull] private ParticleSystem _particleSystem;
|
[NotNull] private ParticleSystem _particleSystem;
|
||||||
|
[NotNull] private UISystem _uiSystem;
|
||||||
private int _emitterId;
|
private int _emitterId;
|
||||||
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
private ResourceRef<ParticleEmitterSettingsResource> _fireEffect;
|
||||||
private ResourceRef<Font> _font;
|
private ResourceRef<Font> _font;
|
||||||
|
private ResourceRef<Sound> _sound;
|
||||||
private ResourceRef<Texture2d> _icon;
|
private ResourceRef<Texture2d> _icon;
|
||||||
|
|
||||||
|
private FlexContainer _container = new(minimumSize: new Rect(64.0f, 64.0f), new())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Center,
|
||||||
|
Size = new Rect(500, 300),
|
||||||
|
Direction = FlexDirection.Column,
|
||||||
|
Justify = JustifyContent.Start,
|
||||||
|
Align = AlignItems.Center,
|
||||||
|
Wrap = true,
|
||||||
|
Gap = 10f
|
||||||
|
};
|
||||||
|
|
||||||
|
private Frame _frame = new();
|
||||||
|
// private VerticalContainer _container = new(new Rect(128.0f, 64.0f), new(), 16)
|
||||||
|
// {
|
||||||
|
// ConfineToContents = true,
|
||||||
|
// Anchor = Anchor.CenterLeft,
|
||||||
|
// AnchorOffset = new Vector2(0.5f, 0.0f)
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<PublishAot>true</PublishAot>
|
<PublishAot>true</PublishAot>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../Voile/Voile.csproj" />
|
<ProjectReference Include="../Voile/Voile.csproj" />
|
||||||
|
<ProjectReference Include="../Voile.OpenAL/Voile.OpenAL.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
59
Voile.OpenAL/OpenALSystem.cs
Normal file
59
Voile.OpenAL/OpenALSystem.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using Voile.Audio;
|
||||||
|
using Silk.NET.OpenAL;
|
||||||
|
|
||||||
|
namespace Voile.OpenAL;
|
||||||
|
|
||||||
|
public class OpenALSystem : AudioSystem
|
||||||
|
{
|
||||||
|
public OpenALSystem()
|
||||||
|
{
|
||||||
|
_al = AL.GetApi();
|
||||||
|
_alc = ALContext.GetApi();
|
||||||
|
|
||||||
|
Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PlaySound(Sound sound, float volume)
|
||||||
|
{
|
||||||
|
var buffer = CreateAlBuffer(sound.Buffer, sound.SampleRate, sound.Channel);
|
||||||
|
var source = CreateAlSource(buffer);
|
||||||
|
_al.SourcePlay(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(double deltaTime)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void Init()
|
||||||
|
{
|
||||||
|
_device = _alc.OpenDevice("");
|
||||||
|
_context = _alc.CreateContext(_device, null);
|
||||||
|
_alc.MakeContextCurrent(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint CreateAlBuffer(ReadOnlyMemory<short> data, int sampleRate, SoundChannel channels)
|
||||||
|
{
|
||||||
|
var buffer = _al.GenBuffer();
|
||||||
|
var format = channels == SoundChannel.Mono ? BufferFormat.Mono16 : BufferFormat.Stereo16;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
_al.BufferData(buffer, format, data.Pin().Pointer, data.Length, sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint CreateAlSource(uint buffer)
|
||||||
|
{
|
||||||
|
var source = _al.GenSource();
|
||||||
|
_al.SetSourceProperty(source, SourceInteger.Buffer, buffer);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe Device* _device;
|
||||||
|
private ALContext _alc;
|
||||||
|
private unsafe Context* _context;
|
||||||
|
private AL _al;
|
||||||
|
}
|
||||||
17
Voile.OpenAL/Voile.OpenAL.csproj
Normal file
17
Voile.OpenAL/Voile.OpenAL.csproj
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../Voile/Voile.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Silk.NET.OpenAL" Version="2.21.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
16
Voile.sln
16
Voile.sln
@@ -13,19 +13,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Folder", "Solution
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestGame", "TestGame\TestGame.csproj", "{DBA85D7B-0A91-405B-9078-5463F49AE47E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestGame", "TestGame\TestGame.csproj", "{DBA85D7B-0A91-405B-9078-5463F49AE47E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Voile.OpenAL", "Voile.OpenAL\Voile.OpenAL.csproj", "{3ABB7D30-4B64-43AD-A14F-E532B12AFC60}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{393AA04F-A0DE-42F2-AAEC-6B2DCFB7A852}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DA4FDEDC-AA81-4336-844F-562F9E763974}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -34,5 +29,12 @@ Global
|
|||||||
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{DBA85D7B-0A91-405B-9078-5463F49AE47E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3ABB7D30-4B64-43AD-A14F-E532B12AFC60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3ABB7D30-4B64-43AD-A14F-E532B12AFC60}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3ABB7D30-4B64-43AD-A14F-E532B12AFC60}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3ABB7D30-4B64-43AD-A14F-E532B12AFC60}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Voile
|
|
||||||
{
|
|
||||||
public class AudioBus
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Voile
|
|
||||||
{
|
|
||||||
public class AudioEffect { }
|
|
||||||
|
|
||||||
public class AudioEffectReverb : AudioEffect
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +1,7 @@
|
|||||||
using Voile;
|
|
||||||
|
|
||||||
namespace Voile.Audio;
|
namespace Voile.Audio;
|
||||||
|
|
||||||
public abstract class AudioSystem : IStartableSystem, IUpdatableSystem, IDisposable
|
public abstract class AudioSystem : IUpdatableSystem
|
||||||
{
|
{
|
||||||
public void Start() => Initialize();
|
public abstract void Update(double deltaTime);
|
||||||
|
public abstract void PlaySound(Sound sound, float volume);
|
||||||
public void Update(double deltaTime) => Update();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Shutdown();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void Initialize();
|
|
||||||
protected abstract void Update();
|
|
||||||
protected abstract void Shutdown();
|
|
||||||
// BUS
|
|
||||||
public abstract void CreateBus(string busName);
|
|
||||||
public abstract void SetBusVolume(string busName, float volume);
|
|
||||||
public abstract float GetBusVolume(string busName);
|
|
||||||
|
|
||||||
// SOUND
|
|
||||||
public abstract void PlaySound(Sound sound, float pitch, float volume, string bus = "Master");
|
|
||||||
public void PlaySound(Sound sound, string bus = "Master") => PlaySound(sound, 1.0f, 1.0f, bus);
|
|
||||||
|
|
||||||
public SoundInstance CreateInstance(Sound sound)
|
|
||||||
{
|
|
||||||
var instance = new SoundInstance(this, sound);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EFFECTS
|
|
||||||
public abstract void AddBusEffect<T>(T effect, string bus = "Master") where T : AudioEffect;
|
|
||||||
|
|
||||||
|
|
||||||
private LehmerRandom _random = new LehmerRandom();
|
|
||||||
}
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
namespace Voile.Audio
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Dummy audio system.
|
|
||||||
/// </summary>
|
|
||||||
public class DummyAudioSystem : AudioSystem
|
|
||||||
{
|
|
||||||
public override void AddBusEffect<T>(T effect, string bus = "Master")
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CreateBus(string busName)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override float GetBusVolume(string busName)
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Initialize()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetBusVolume(string busName, float volume)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Shutdown()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void PlaySound(Sound sound, float pitch, float volume, string bus = "Master")
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
namespace Voile.Audio
|
|
||||||
{
|
|
||||||
public class SoundInstance
|
|
||||||
{
|
|
||||||
protected virtual Sound Sound => _sound;
|
|
||||||
public SoundInstance(AudioSystem backend, Sound sound)
|
|
||||||
{
|
|
||||||
_backend = backend;
|
|
||||||
_sound = sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SoundInstance PitchVariation(float min, float max)
|
|
||||||
{
|
|
||||||
var random = new LehmerRandom();
|
|
||||||
_pitch = (float)random.NextDouble() * (max - min) + min;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SoundInstance VolumeVariation(float min, float max)
|
|
||||||
{
|
|
||||||
var random = new LehmerRandom();
|
|
||||||
_volume = (float)random.NextDouble() * (max - min) + min;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SoundInstance OnBus(string bus = "Master")
|
|
||||||
{
|
|
||||||
_bus = bus;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Play()
|
|
||||||
{
|
|
||||||
_backend.PlaySound(Sound, _pitch, _volume, _bus);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly AudioSystem _backend;
|
|
||||||
private readonly Sound _sound;
|
|
||||||
private string _bus = "Master";
|
|
||||||
private float _pitch, _volume = 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Voile.Audio;
|
||||||
using Voile.Input;
|
using Voile.Input;
|
||||||
using Voile.Rendering;
|
using Voile.Rendering;
|
||||||
using Voile.Resources;
|
using Voile.Resources;
|
||||||
|
using Voile.VFS;
|
||||||
|
|
||||||
namespace Voile
|
namespace Voile
|
||||||
{
|
{
|
||||||
@@ -41,6 +43,8 @@ namespace Voile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected RenderSystem? Renderer { get; set; }
|
protected RenderSystem? Renderer { get; set; }
|
||||||
|
|
||||||
|
protected AudioSystem? AudioSystem { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name of this game. Also used as a default window title.
|
/// Name of this game. Also used as a default window title.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -88,6 +92,7 @@ namespace Voile
|
|||||||
throw new NullReferenceException("No ResourceManager provided.");
|
throw new NullReferenceException("No ResourceManager provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mount();
|
||||||
LoadResources();
|
LoadResources();
|
||||||
Ready();
|
Ready();
|
||||||
Run();
|
Run();
|
||||||
@@ -103,7 +108,7 @@ namespace Voile
|
|||||||
|
|
||||||
if (Renderer is null)
|
if (Renderer is null)
|
||||||
{
|
{
|
||||||
Renderer = new RaylibRenderSystem();
|
Renderer = new StandardRenderSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input is null)
|
if (Input is null)
|
||||||
@@ -159,6 +164,11 @@ namespace Voile
|
|||||||
throw new NullReferenceException("No renderer provided.");
|
throw new NullReferenceException("No renderer provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Input is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("No input system provided.");
|
||||||
|
}
|
||||||
|
|
||||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||||
double previousTime = stopwatch.Elapsed.TotalSeconds;
|
double previousTime = stopwatch.Elapsed.TotalSeconds;
|
||||||
|
|
||||||
@@ -170,6 +180,8 @@ namespace Voile
|
|||||||
|
|
||||||
_accumulator += elapsedTime;
|
_accumulator += elapsedTime;
|
||||||
|
|
||||||
|
Input.Poll();
|
||||||
|
|
||||||
while (_accumulator >= UpdateTimeStep)
|
while (_accumulator >= UpdateTimeStep)
|
||||||
{
|
{
|
||||||
foreach (var system in _updatableSystems)
|
foreach (var system in _updatableSystems)
|
||||||
@@ -239,6 +251,12 @@ namespace Voile
|
|||||||
Renderer?.Start(renderSettings);
|
Renderer?.Start(renderSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Mount()
|
||||||
|
{
|
||||||
|
var resourceRootMount = new FileSystemMountPoint(ResourceRoot);
|
||||||
|
VirtualFileSystem.Mount(resourceRootMount);
|
||||||
|
}
|
||||||
|
|
||||||
private List<IStartableSystem> _startableSystems = new();
|
private List<IStartableSystem> _startableSystems = new();
|
||||||
private List<IUpdatableSystem> _updatableSystems = new();
|
private List<IUpdatableSystem> _updatableSystems = new();
|
||||||
private List<IUpdatableSystem> _renderableSystems = new();
|
private List<IUpdatableSystem> _renderableSystems = new();
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
namespace Voile.Input
|
namespace Voile.Input
|
||||||
{
|
{
|
||||||
public abstract class InputAction
|
|
||||||
|
public interface IInputAction
|
||||||
{
|
{
|
||||||
public abstract bool IsDown(InputSystem inputHandler);
|
bool IsDown(InputSystem inputSystem);
|
||||||
public abstract bool IsPressed(InputSystem inputHandler);
|
bool IsPressed(InputSystem inputSystem);
|
||||||
public abstract bool IsReleased(InputSystem inputHandler);
|
bool IsReleased(InputSystem inputSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class KeyInputAction : InputAction
|
public struct KeyInputAction : IInputAction
|
||||||
{
|
{
|
||||||
public KeyboardKey Key => _keyboardKey;
|
public KeyboardKey Key => _keyboardKey;
|
||||||
|
|
||||||
@@ -15,22 +16,46 @@ namespace Voile.Input
|
|||||||
{
|
{
|
||||||
_keyboardKey = keyboardKey;
|
_keyboardKey = keyboardKey;
|
||||||
}
|
}
|
||||||
public override bool IsDown(InputSystem inputHandler)
|
public bool IsDown(InputSystem inputSystem)
|
||||||
{
|
{
|
||||||
return inputHandler.IsKeyboardKeyDown(_keyboardKey);
|
return inputSystem.IsKeyboardKeyDown(_keyboardKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsPressed(InputSystem inputHandler)
|
public bool IsPressed(InputSystem inputSystem)
|
||||||
{
|
{
|
||||||
return inputHandler.KeyboardKeyJustPressed(_keyboardKey);
|
return inputSystem.KeyboardKeyJustPressed(_keyboardKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsReleased(InputSystem inputHandler)
|
public bool IsReleased(InputSystem inputSystem)
|
||||||
{
|
{
|
||||||
return inputHandler.KeyboardKeyJustReleased(_keyboardKey);
|
return inputSystem.KeyboardKeyJustReleased(_keyboardKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private KeyboardKey _keyboardKey;
|
private KeyboardKey _keyboardKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct MouseInputAction : IInputAction
|
||||||
|
{
|
||||||
|
public MouseButton MouseButton { get; private set; }
|
||||||
|
|
||||||
|
public MouseInputAction(MouseButton button)
|
||||||
|
{
|
||||||
|
MouseButton = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPressed(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
return inputSystem.IsMousePressed(MouseButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDown(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
return inputSystem.IsMouseButtonDown(MouseButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReleased(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
return inputSystem.IsMouseButtonReleased(MouseButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,32 +12,107 @@ namespace Voile.Input
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of all available input mappings, custom and built-in.
|
/// The list of all available input mappings, custom and built-in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyDictionary<string, List<InputAction>> InputMappings => inputMappings;
|
public static IReadOnlyDictionary<string, List<IInputAction>> InputMappings => inputMappings;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
inputMappings = new Dictionary<string, List<InputAction>>();
|
inputMappings = new Dictionary<string, List<IInputAction>>();
|
||||||
CreateDefaultMappings();
|
CreateDefaultMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forces this input system to poll all of its inputs during current frame.<br />
|
||||||
|
/// Some backends require inputs to be polled once per specific interval. Override this method to implement this behavior.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Poll() { }
|
||||||
|
|
||||||
public void Shutdown() => Dispose();
|
public void Shutdown() => Dispose();
|
||||||
|
|
||||||
public void Dispose() => GC.SuppressFinalize(this);
|
public void Dispose() => GC.SuppressFinalize(this);
|
||||||
|
|
||||||
public bool Handled { get => _handled; set => _handled = value; }
|
public bool Handled { get => _handled; set => _handled = value; }
|
||||||
|
|
||||||
|
public bool IsActionDown(string action)
|
||||||
|
{
|
||||||
|
List<IInputAction>? mappings;
|
||||||
|
|
||||||
|
if (TryGetInputMappings(action, out mappings))
|
||||||
|
{
|
||||||
|
foreach (IInputAction inputAction in mappings)
|
||||||
|
if (inputAction.IsDown(this)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsActionPressed(string action)
|
||||||
|
{
|
||||||
|
List<IInputAction>? mappings;
|
||||||
|
|
||||||
|
if (TryGetInputMappings(action, out mappings))
|
||||||
|
{
|
||||||
|
foreach (IInputAction inputAction in mappings)
|
||||||
|
if (inputAction.IsPressed(this)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsActionReleased(string action)
|
||||||
|
{
|
||||||
|
List<IInputAction>? mappings;
|
||||||
|
|
||||||
|
if (TryGetInputMappings(action, out mappings))
|
||||||
|
{
|
||||||
|
foreach (IInputAction inputAction in mappings)
|
||||||
|
if (inputAction.IsReleased(this)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey)
|
||||||
|
{
|
||||||
|
Vector2 dir = Vector2.Zero;
|
||||||
|
|
||||||
|
if (IsKeyboardKeyDown(leftKey))
|
||||||
|
dir.X -= 1;
|
||||||
|
if (IsKeyboardKeyDown(rightKey))
|
||||||
|
dir.X += 1;
|
||||||
|
if (IsKeyboardKeyDown(upKey))
|
||||||
|
dir.Y -= 1;
|
||||||
|
if (IsKeyboardKeyDown(downKey))
|
||||||
|
dir.Y += 1;
|
||||||
|
|
||||||
|
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
|
||||||
|
}
|
||||||
|
public Vector2 GetInputDirection(string leftAction, string rightAction, string upAction, string downAction)
|
||||||
|
{
|
||||||
|
Vector2 dir = Vector2.Zero;
|
||||||
|
|
||||||
|
if (IsActionDown(leftAction))
|
||||||
|
dir.X -= 1;
|
||||||
|
if (IsActionDown(rightAction))
|
||||||
|
dir.X += 1;
|
||||||
|
if (IsActionDown(upAction))
|
||||||
|
dir.Y -= 1;
|
||||||
|
if (IsActionDown(downAction))
|
||||||
|
dir.Y += 1;
|
||||||
|
|
||||||
|
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract bool IsKeyboardKeyDown(KeyboardKey key);
|
public abstract bool IsKeyboardKeyDown(KeyboardKey key);
|
||||||
public abstract bool KeyboardKeyJustPressed(KeyboardKey key);
|
public abstract bool KeyboardKeyJustPressed(KeyboardKey key);
|
||||||
public abstract bool KeyboardKeyJustReleased(KeyboardKey key);
|
public abstract bool KeyboardKeyJustReleased(KeyboardKey key);
|
||||||
public abstract Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey);
|
|
||||||
public abstract Vector2 GetInputDirection(string leftAction, string rightAction, string upAction, string downAction);
|
|
||||||
public abstract int GetCharPressed();
|
public abstract int GetCharPressed();
|
||||||
|
|
||||||
public abstract bool IsActionDown(string action);
|
public abstract bool IsMousePressed(MouseButton button);
|
||||||
public abstract bool IsActionPressed(string action);
|
|
||||||
public abstract bool IsActionReleased(string action);
|
|
||||||
|
|
||||||
public abstract bool IsMouseButtonDown(MouseButton button);
|
public abstract bool IsMouseButtonDown(MouseButton button);
|
||||||
|
public abstract bool IsMouseButtonReleased(MouseButton button);
|
||||||
public abstract float GetMouseWheelMovement();
|
public abstract float GetMouseWheelMovement();
|
||||||
|
|
||||||
public abstract void SetMousePosition(Vector2 position);
|
public abstract void SetMousePosition(Vector2 position);
|
||||||
public abstract Vector2 GetMousePosition();
|
public abstract Vector2 GetMousePosition();
|
||||||
public abstract void HideCursor();
|
public abstract void HideCursor();
|
||||||
@@ -46,48 +121,53 @@ namespace Voile.Input
|
|||||||
|
|
||||||
public void SetAsHandled() => _handled = true;
|
public void SetAsHandled() => _handled = true;
|
||||||
|
|
||||||
public void AddInputMapping(string actionName, IEnumerable<InputAction> inputActions)
|
public void AddInputMapping(string actionName, IEnumerable<IInputAction> inputActions)
|
||||||
{
|
{
|
||||||
inputMappings.Add(actionName, inputActions.ToList());
|
inputMappings.Add(actionName, inputActions.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateDefaultMappings()
|
private void CreateDefaultMappings()
|
||||||
{
|
{
|
||||||
AddInputMapping("up", new InputAction[] {
|
AddInputMapping("up", [
|
||||||
new KeyInputAction(KeyboardKey.W),
|
new KeyInputAction(KeyboardKey.W),
|
||||||
new KeyInputAction(KeyboardKey.Up),
|
new KeyInputAction(KeyboardKey.Up),
|
||||||
});
|
]);
|
||||||
AddInputMapping("down", new InputAction[] {
|
AddInputMapping("down", [
|
||||||
new KeyInputAction(KeyboardKey.S),
|
new KeyInputAction(KeyboardKey.S),
|
||||||
new KeyInputAction(KeyboardKey.Down),
|
new KeyInputAction(KeyboardKey.Down),
|
||||||
});
|
]);
|
||||||
AddInputMapping("left", new InputAction[] {
|
AddInputMapping("left", [
|
||||||
new KeyInputAction(KeyboardKey.A),
|
new KeyInputAction(KeyboardKey.A),
|
||||||
new KeyInputAction(KeyboardKey.Left),
|
new KeyInputAction(KeyboardKey.Left),
|
||||||
});
|
]);
|
||||||
AddInputMapping("right", new InputAction[] {
|
AddInputMapping("right", [
|
||||||
new KeyInputAction(KeyboardKey.D),
|
new KeyInputAction(KeyboardKey.D),
|
||||||
new KeyInputAction(KeyboardKey.Right),
|
new KeyInputAction(KeyboardKey.Right),
|
||||||
});
|
]);
|
||||||
|
AddInputMapping("accept", [
|
||||||
|
new KeyInputAction(KeyboardKey.Enter),
|
||||||
|
]);
|
||||||
|
AddInputMapping("cancel", [
|
||||||
|
new KeyInputAction(KeyboardKey.Backspace),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool TryGetInputMappings(string forAction, [NotNullWhen(true)] out IEnumerable<InputAction>? inputActions)
|
protected bool TryGetInputMappings(string forAction, [NotNullWhen(true)] out List<IInputAction>? inputActions)
|
||||||
{
|
{
|
||||||
var contains = inputMappings.ContainsKey(forAction);
|
|
||||||
inputActions = null;
|
inputActions = null;
|
||||||
|
|
||||||
if (!contains)
|
if (inputMappings.TryGetValue(forAction, out var actions))
|
||||||
{
|
{
|
||||||
_logger.Error($"The action \"{forAction}\" is not present in the input mappings!");
|
inputActions = actions;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputActions = inputMappings[forAction];
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Dictionary<string, List<InputAction>> inputMappings = new();
|
_logger.Error($"The action \"{forAction}\" is not present in the input mappings!");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Dictionary<string, List<IInputAction>> inputMappings = new();
|
||||||
private bool _handled;
|
private bool _handled;
|
||||||
private Logger _logger = new(nameof(InputSystem));
|
private Logger _logger = new(nameof(InputSystem));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,125 +8,63 @@ namespace Voile.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RaylibInputSystem : InputSystem
|
public class RaylibInputSystem : InputSystem
|
||||||
{
|
{
|
||||||
public override int GetCharPressed()
|
public override void Poll()
|
||||||
{
|
{
|
||||||
return Raylib.GetCharPressed();
|
_justPressedKeys.Clear();
|
||||||
|
_justReleasedKeys.Clear();
|
||||||
|
_downKeys.Clear();
|
||||||
|
|
||||||
|
_pressedMouseButtons.Clear();
|
||||||
|
_releasedMouseButtons.Clear();
|
||||||
|
_downMouseButtons.Clear();
|
||||||
|
|
||||||
|
for (int key = 32; key <= 349; key++)
|
||||||
|
{
|
||||||
|
var k = (KeyboardKey)key;
|
||||||
|
if (Raylib.IsKeyPressed((Raylib_cs.KeyboardKey)k)) _justPressedKeys.Add(k);
|
||||||
|
if (Raylib.IsKeyReleased((Raylib_cs.KeyboardKey)k)) _justReleasedKeys.Add(k);
|
||||||
|
if (Raylib.IsKeyDown((Raylib_cs.KeyboardKey)k)) _downKeys.Add(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 GetInputDirection(KeyboardKey leftKey, KeyboardKey rightKey, KeyboardKey upKey, KeyboardKey downKey)
|
foreach (MouseButton button in System.Enum.GetValues(typeof(MouseButton)))
|
||||||
{
|
{
|
||||||
Vector2 dir = Vector2.Zero;
|
var rayButton = (Raylib_cs.MouseButton)button;
|
||||||
|
if (Raylib.IsMouseButtonPressed(rayButton)) _pressedMouseButtons.Add(button);
|
||||||
if (IsKeyboardKeyDown(leftKey))
|
if (Raylib.IsMouseButtonReleased(rayButton)) _releasedMouseButtons.Add(button);
|
||||||
dir += new Vector2(-1, 0);
|
if (Raylib.IsMouseButtonDown(rayButton)) _downMouseButtons.Add(button);
|
||||||
if (IsKeyboardKeyDown(rightKey))
|
|
||||||
dir += new Vector2(1, 0);
|
|
||||||
if (IsKeyboardKeyDown(upKey))
|
|
||||||
dir += new Vector2(0, -1);
|
|
||||||
if (IsKeyboardKeyDown(downKey))
|
|
||||||
dir += new Vector2(0, 1);
|
|
||||||
|
|
||||||
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 GetInputDirection(string leftAction, string rightAction, string upAction, string downAction)
|
_mousePosition = Raylib.GetMousePosition();
|
||||||
{
|
_mouseWheelMove = Raylib.GetMouseWheelMove();
|
||||||
Vector2 dir = Vector2.Zero;
|
|
||||||
|
|
||||||
if (IsActionDown(leftAction))
|
|
||||||
dir += new Vector2(-1, 0);
|
|
||||||
if (IsActionDown(rightAction))
|
|
||||||
dir += new Vector2(1, 0);
|
|
||||||
if (IsActionDown(upAction))
|
|
||||||
dir += new Vector2(0, -1);
|
|
||||||
if (IsActionDown(downAction))
|
|
||||||
dir += new Vector2(0, 1);
|
|
||||||
|
|
||||||
|
|
||||||
return dir == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 GetMousePosition()
|
public override int GetCharPressed() => Raylib.GetCharPressed();
|
||||||
{
|
public override Vector2 GetMousePosition() => _mousePosition;
|
||||||
return Raylib.GetMousePosition();
|
public override float GetMouseWheelMovement() => _mouseWheelMove;
|
||||||
}
|
|
||||||
|
|
||||||
public override float GetMouseWheelMovement()
|
public override void HideCursor() => Raylib.HideCursor();
|
||||||
{
|
|
||||||
return Raylib.GetMouseWheelMove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HideCursor()
|
|
||||||
{
|
|
||||||
Raylib.HideCursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsActionDown(string action)
|
|
||||||
{
|
|
||||||
IEnumerable<InputAction>? mappings;
|
|
||||||
|
|
||||||
if (TryGetInputMappings(action, out mappings))
|
|
||||||
{
|
|
||||||
foreach (InputAction inputAction in mappings)
|
|
||||||
if (inputAction.IsDown(this)) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsActionPressed(string action)
|
|
||||||
{
|
|
||||||
IEnumerable<InputAction>? mappings;
|
|
||||||
|
|
||||||
if (TryGetInputMappings(action, out mappings))
|
|
||||||
{
|
|
||||||
foreach (InputAction inputAction in mappings)
|
|
||||||
if (inputAction.IsPressed(this)) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsActionReleased(string action)
|
|
||||||
{
|
|
||||||
IEnumerable<InputAction>? mappings;
|
|
||||||
|
|
||||||
if (TryGetInputMappings(action, out mappings))
|
|
||||||
{
|
|
||||||
foreach (InputAction inputAction in mappings)
|
|
||||||
if (inputAction.IsReleased(this)) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsKeyboardKeyDown(KeyboardKey key)
|
|
||||||
{
|
|
||||||
Raylib_cs.KeyboardKey rayKey = (Raylib_cs.KeyboardKey)key;
|
|
||||||
return Raylib.IsKeyDown(rayKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsMouseButtonDown(MouseButton button)
|
|
||||||
{
|
|
||||||
return Raylib.IsMouseButtonDown((Raylib_cs.MouseButton)button);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool KeyboardKeyJustPressed(KeyboardKey key)
|
|
||||||
{
|
|
||||||
Raylib_cs.KeyboardKey rayKey = (Raylib_cs.KeyboardKey)key;
|
|
||||||
return Raylib.IsKeyPressed(rayKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool KeyboardKeyJustReleased(KeyboardKey key)
|
|
||||||
{
|
|
||||||
return Raylib.IsKeyReleased((Raylib_cs.KeyboardKey)key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetMousePosition(Vector2 position)
|
|
||||||
{
|
|
||||||
Raylib.SetMousePosition((int)position.X, (int)position.Y);
|
|
||||||
}
|
|
||||||
public override void ShowCursor() => Raylib.ShowCursor();
|
public override void ShowCursor() => Raylib.ShowCursor();
|
||||||
public override bool IsCursorHidden() => Raylib.IsCursorHidden();
|
public override bool IsCursorHidden() => Raylib.IsCursorHidden();
|
||||||
|
|
||||||
|
public override bool IsKeyboardKeyDown(KeyboardKey key) => _downKeys.Contains(key);
|
||||||
|
public override bool KeyboardKeyJustPressed(KeyboardKey key) => _justPressedKeys.Contains(key);
|
||||||
|
public override bool KeyboardKeyJustReleased(KeyboardKey key) => _justReleasedKeys.Contains(key);
|
||||||
|
|
||||||
|
public override bool IsMousePressed(MouseButton button) => _pressedMouseButtons.Contains(button);
|
||||||
|
public override bool IsMouseButtonReleased(MouseButton button) => _releasedMouseButtons.Contains(button);
|
||||||
|
public override bool IsMouseButtonDown(MouseButton button) => _downMouseButtons.Contains(button);
|
||||||
|
|
||||||
|
public override void SetMousePosition(Vector2 position) => Raylib.SetMousePosition((int)position.X, (int)position.Y);
|
||||||
|
|
||||||
|
private readonly HashSet<KeyboardKey> _justPressedKeys = new();
|
||||||
|
private readonly HashSet<KeyboardKey> _justReleasedKeys = new();
|
||||||
|
private readonly HashSet<KeyboardKey> _downKeys = new();
|
||||||
|
|
||||||
|
private readonly HashSet<MouseButton> _pressedMouseButtons = new();
|
||||||
|
private readonly HashSet<MouseButton> _releasedMouseButtons = new();
|
||||||
|
private readonly HashSet<MouseButton> _downMouseButtons = new();
|
||||||
|
|
||||||
|
private Vector2 _mousePosition;
|
||||||
|
private float _mouseWheelMove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,16 +39,17 @@ namespace Voile.Rendering
|
|||||||
ConfigFlags flags = 0;
|
ConfigFlags flags = 0;
|
||||||
|
|
||||||
// MSAA
|
// MSAA
|
||||||
flags |= settings.Msaa == Msaa.Msaa4x ? ConfigFlags.FLAG_MSAA_4X_HINT : 0;
|
flags |= settings.Msaa == Msaa.Msaa4x ? ConfigFlags.Msaa4xHint : 0;
|
||||||
|
|
||||||
// VSync
|
// VSync
|
||||||
flags |= settings.UseVSync ? ConfigFlags.FLAG_VSYNC_HINT : 0;
|
flags |= settings.UseVSync ? ConfigFlags.VSyncHint : 0;
|
||||||
|
|
||||||
_fullscreen = settings.Fullscreen;
|
_fullscreen = settings.Fullscreen;
|
||||||
|
|
||||||
_defaultFlags = flags;
|
_defaultFlags = flags;
|
||||||
|
|
||||||
Raylib.SetConfigFlags(flags);
|
Raylib.SetConfigFlags(flags);
|
||||||
|
Raylib.SetTargetFPS(settings.TargetFps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void CreateAndInitializeWithWindow(RendererSettings settings)
|
public override void CreateAndInitializeWithWindow(RendererSettings settings)
|
||||||
@@ -59,13 +60,13 @@ namespace Voile.Rendering
|
|||||||
|
|
||||||
public override void CreateWindow(WindowSettings windowSettings)
|
public override void CreateWindow(WindowSettings windowSettings)
|
||||||
{
|
{
|
||||||
Raylib.SetTraceLogLevel(TraceLogLevel.LOG_NONE);
|
Raylib.SetTraceLogLevel(TraceLogLevel.None);
|
||||||
|
|
||||||
_defaultWindowSettings = windowSettings;
|
_defaultWindowSettings = windowSettings;
|
||||||
|
|
||||||
_windowSize = windowSettings.Size;
|
_windowSize = windowSettings.Size;
|
||||||
ConfigFlags windowFlags = 0;
|
ConfigFlags windowFlags = 0;
|
||||||
windowFlags |= windowSettings.Resizable ? ConfigFlags.FLAG_WINDOW_RESIZABLE : 0;
|
windowFlags |= windowSettings.Resizable ? ConfigFlags.ResizableWindow : 0;
|
||||||
|
|
||||||
_windowTitle = windowSettings.Title;
|
_windowTitle = windowSettings.Title;
|
||||||
|
|
||||||
@@ -180,13 +181,24 @@ namespace Voile.Rendering
|
|||||||
{
|
{
|
||||||
Raylib.DrawRectanglePro(new Rectangle()
|
Raylib.DrawRectanglePro(new Rectangle()
|
||||||
{
|
{
|
||||||
x = transformPosition.X,
|
X = transformPosition.X,
|
||||||
y = transformPosition.Y,
|
Y = transformPosition.Y,
|
||||||
width = size.X,
|
Width = size.X,
|
||||||
height = size.Y
|
Height = size.Y
|
||||||
}, transformPivot, transformRotation, VoileColorToRaylibColor(color));
|
}, transformPivot, transformRotation, VoileColorToRaylibColor(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1)
|
||||||
|
{
|
||||||
|
Raylib.DrawRectangleLinesEx(new Rectangle()
|
||||||
|
{
|
||||||
|
X = transformPosition.X,
|
||||||
|
Y = transformPosition.Y,
|
||||||
|
Width = size.X,
|
||||||
|
Height = size.Y
|
||||||
|
}, outlineWidth, VoileColorToRaylibColor(color));
|
||||||
|
}
|
||||||
|
|
||||||
public override void DrawDebugText(string text, int fontSize, Color color)
|
public override void DrawDebugText(string text, int fontSize, Color color)
|
||||||
{
|
{
|
||||||
Raylib.DrawText(text, (int)transformPosition.X, (int)transformPosition.Y, fontSize, VoileColorToRaylibColor(color));
|
Raylib.DrawText(text, (int)transformPosition.X, (int)transformPosition.Y, fontSize, VoileColorToRaylibColor(color));
|
||||||
@@ -252,28 +264,17 @@ namespace Voile.Rendering
|
|||||||
return Raylib.GetCurrentMonitor();
|
return Raylib.GetCurrentMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void LoadFont(Font font)
|
private void LoadFont(Font font)
|
||||||
{
|
{
|
||||||
Raylib_cs.Font fontRay;
|
Raylib_cs.Font fontRay;
|
||||||
|
|
||||||
ReadOnlySpan<char> ext = ".ttf"; // TODO: don't use a hardcoded extension.
|
string ext = ".ttf"; // TODO: don't use a hardcoded extension.
|
||||||
Span<byte> extBytes = new byte[Encoding.Default.GetByteCount(ext) + 1];
|
int fontChars = 250; // TODO: control this dynamically to not load the entire font.
|
||||||
Encoding.Default.GetBytes(ext, extBytes);
|
|
||||||
int fontChars = 2048; // TODO: control this dynamically to not load the entire font.
|
|
||||||
|
|
||||||
unsafe
|
fontRay = Raylib.LoadFontFromMemory(ext, font.Buffer, font.Size, null, fontChars);
|
||||||
{
|
|
||||||
fixed (byte* extP = extBytes)
|
|
||||||
{
|
|
||||||
fixed (byte* bufferP = font.Buffer)
|
|
||||||
{
|
|
||||||
fontRay = Raylib.LoadFontFromMemory((sbyte*)extP, bufferP, (int)font.BufferSize, font.Size, null, fontChars);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Raylib.GenTextureMipmaps(ref fontRay.texture);
|
Raylib.GenTextureMipmaps(ref fontRay.Texture);
|
||||||
Raylib.SetTextureFilter(fontRay.texture, TextureFilter.TEXTURE_FILTER_BILINEAR);
|
Raylib.SetTextureFilter(fontRay.Texture, TextureFilter.Bilinear);
|
||||||
|
|
||||||
_fontPool.Add(fontRay);
|
_fontPool.Add(fontRay);
|
||||||
|
|
||||||
@@ -288,14 +289,15 @@ namespace Voile.Rendering
|
|||||||
{
|
{
|
||||||
fixed (void* dataPtr = texture.Buffer)
|
fixed (void* dataPtr = texture.Buffer)
|
||||||
{
|
{
|
||||||
image.data = dataPtr;
|
image.Data = dataPtr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
image.width = texture.Width;
|
|
||||||
image.height = texture.Height;
|
image.Width = texture.Width;
|
||||||
image.mipmaps = texture.Mipmaps;
|
image.Height = texture.Height;
|
||||||
image.format = (PixelFormat)texture.Format;
|
image.Mipmaps = texture.Mipmaps;
|
||||||
|
image.Format = (PixelFormat)texture.Format;
|
||||||
|
|
||||||
Texture2D rayTexture;
|
Texture2D rayTexture;
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,9 @@ namespace Voile.Rendering
|
|||||||
/// <param name="size">Rectangle size.</param>
|
/// <param name="size">Rectangle size.</param>
|
||||||
/// <param name="color">Fill color.</param>
|
/// <param name="color">Fill color.</param>
|
||||||
public abstract void DrawRectangle(Vector2 size, Color color);
|
public abstract void DrawRectangle(Vector2 size, Color color);
|
||||||
|
|
||||||
|
public abstract void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1.0f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draws a debug text with a default font.
|
/// Draws a debug text with a default font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ namespace Voile.Rendering
|
|||||||
|
|
||||||
private Silk.NET.WebGPU.Color VoileColorToWebGPUColor(Color color)
|
private Silk.NET.WebGPU.Color VoileColorToWebGPUColor(Color color)
|
||||||
{
|
{
|
||||||
return new Silk.NET.WebGPU.Color(color.R, color.G, color.B, color.A);
|
return new Silk.NET.WebGPU.Color((double)color.R / 255, (double)color.G / 255, (double)color.B / 255, (double)color.A / 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe RenderPassColorAttachment CreateClearColorAttachment(TextureView* view, Color clearColor)
|
private unsafe RenderPassColorAttachment CreateClearColorAttachment(TextureView* view, Color clearColor)
|
||||||
@@ -399,6 +399,11 @@ namespace Voile.Rendering
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
private Vector2 _windowSize = Vector2.Zero;
|
private Vector2 _windowSize = Vector2.Zero;
|
||||||
private IWindow? _window;
|
private IWindow? _window;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Voile.Resources.DataReaders;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads key/value data from a TOML file.
|
/// Reads key/value data from a TOML file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter, IDisposable
|
public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValueGetter
|
||||||
{
|
{
|
||||||
public string ExpectedHeader { get; private set; } = string.Empty;
|
public string ExpectedHeader { get; private set; } = string.Empty;
|
||||||
public TomlDataReader(string expectedHeader)
|
public TomlDataReader(string expectedHeader)
|
||||||
@@ -17,14 +17,7 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue
|
|||||||
|
|
||||||
public void Read(Stream data)
|
public void Read(Stream data)
|
||||||
{
|
{
|
||||||
if (data is not FileStream fs)
|
using (var reader = new StreamReader(data))
|
||||||
{
|
|
||||||
throw new ArgumentException("Toml data reader only supports file streams.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_fs = fs;
|
|
||||||
|
|
||||||
using (var reader = new StreamReader(_fs))
|
|
||||||
{
|
{
|
||||||
_table = TOML.Parse(reader);
|
_table = TOML.Parse(reader);
|
||||||
_valid = _table.HasKey(ExpectedHeader);
|
_valid = _table.HasKey(ExpectedHeader);
|
||||||
@@ -231,12 +224,6 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue
|
|||||||
|
|
||||||
public bool Valid() => _valid;
|
public bool Valid() => _valid;
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_fs?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TomlTable? _table;
|
private TomlTable? _table;
|
||||||
private FileStream? _fs;
|
|
||||||
private bool _valid;
|
private bool _valid;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
using Voile.VFS;
|
||||||
|
|
||||||
namespace Voile.Resources;
|
namespace Voile.Resources;
|
||||||
|
|
||||||
public class FontLoader : ResourceLoader<Font>
|
public class FontLoader : ResourceLoader<Font>
|
||||||
@@ -11,9 +13,14 @@ public class FontLoader : ResourceLoader<Font>
|
|||||||
|
|
||||||
protected override Font LoadResource(string path)
|
protected override Font LoadResource(string path)
|
||||||
{
|
{
|
||||||
byte[] fileBuffer = File.ReadAllBytes(path);
|
using Stream stream = VirtualFileSystem.Read(path);
|
||||||
|
|
||||||
|
byte[] fileBuffer = new byte[stream.Length];
|
||||||
|
int bytesRead = stream.Read(fileBuffer, 0, fileBuffer.Length);
|
||||||
var result = new Font(path, fileBuffer);
|
var result = new Font(path, fileBuffer);
|
||||||
result.BufferSize = fileBuffer.Length;
|
|
||||||
|
result.BufferSize = bytesRead;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,6 @@ namespace Voile.Resources
|
|||||||
var resource = LoadResource(path);
|
var resource = LoadResource(path);
|
||||||
var guid = Guid.NewGuid();
|
var guid = Guid.NewGuid();
|
||||||
|
|
||||||
|
|
||||||
var loadedResources = ResourceManager.LoadedResources;
|
var loadedResources = ResourceManager.LoadedResources;
|
||||||
var oldResourceGuid = loadedResources.FirstOrDefault(loadedResource => loadedResource.Value.Path == path).Key;
|
var oldResourceGuid = loadedResources.FirstOrDefault(loadedResource => loadedResource.Value.Path == path).Key;
|
||||||
|
|
||||||
@@ -46,6 +45,7 @@ namespace Voile.Resources
|
|||||||
{
|
{
|
||||||
foreach (var loadedResource in ResourceManager.LoadedResources)
|
foreach (var loadedResource in ResourceManager.LoadedResources)
|
||||||
{
|
{
|
||||||
|
if (loadedResource.Value is not T) continue;
|
||||||
Load(loadedResource.Value.Path);
|
Load(loadedResource.Value.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using StbVorbisSharp;
|
using StbVorbisSharp;
|
||||||
|
using Voile.VFS;
|
||||||
|
|
||||||
namespace Voile.Resources
|
namespace Voile.Resources
|
||||||
{
|
{
|
||||||
@@ -6,7 +7,7 @@ namespace Voile.Resources
|
|||||||
{
|
{
|
||||||
public override IEnumerable<string> SupportedExtensions => new string[]
|
public override IEnumerable<string> SupportedExtensions => new string[]
|
||||||
{
|
{
|
||||||
"ogg"
|
".ogg"
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override Sound LoadResource(string path)
|
protected override Sound LoadResource(string path)
|
||||||
@@ -14,9 +15,11 @@ namespace Voile.Resources
|
|||||||
Vorbis vorbis;
|
Vorbis vorbis;
|
||||||
Sound result;
|
Sound result;
|
||||||
|
|
||||||
var fileBuffer = File.ReadAllBytes(path);
|
using var stream = VirtualFileSystem.Read(path);
|
||||||
vorbis = Vorbis.FromMemory(fileBuffer);
|
byte[] fileBuffer = new byte[stream.Length];
|
||||||
|
int bytesRead = stream.Read(fileBuffer, 0, fileBuffer.Length);
|
||||||
|
|
||||||
|
vorbis = Vorbis.FromMemory(fileBuffer);
|
||||||
vorbis.SubmitBuffer();
|
vorbis.SubmitBuffer();
|
||||||
|
|
||||||
if (vorbis.Decoded == 0)
|
if (vorbis.Decoded == 0)
|
||||||
@@ -27,18 +30,9 @@ namespace Voile.Resources
|
|||||||
|
|
||||||
var audioShort = vorbis.SongBuffer;
|
var audioShort = vorbis.SongBuffer;
|
||||||
int length = vorbis.Decoded * vorbis.Channels;
|
int length = vorbis.Decoded * vorbis.Channels;
|
||||||
byte[] audioData = new byte[length * 2];
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
short[] audioData = new short[length];
|
||||||
{
|
Array.Copy(audioShort, audioData, length);
|
||||||
if (i * 2 >= audioData.Length) break;
|
|
||||||
|
|
||||||
var b1 = (byte)(audioShort[i] >> 8);
|
|
||||||
var b2 = (byte)(audioShort[i] & 256);
|
|
||||||
|
|
||||||
audioData[i * 2] = b2;
|
|
||||||
audioData[i * 2 + 1] = b1;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = new Sound(path, audioData)
|
result = new Sound(path, audioData)
|
||||||
{
|
{
|
||||||
|
|||||||
17
Voile/Source/Resources/Loaders/StyleLoader.cs
Normal file
17
Voile/Source/Resources/Loaders/StyleLoader.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Voile.UI;
|
||||||
|
|
||||||
|
namespace Voile.Resources;
|
||||||
|
|
||||||
|
public class StyleLoader : ResourceLoader<Style>
|
||||||
|
{
|
||||||
|
public override IEnumerable<string> SupportedExtensions =>
|
||||||
|
[
|
||||||
|
".toml"
|
||||||
|
];
|
||||||
|
|
||||||
|
protected override Style LoadResource(string path)
|
||||||
|
{
|
||||||
|
// TODO: implement loading styles.
|
||||||
|
return new Style(string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Voile.Resources;
|
using Voile.Resources;
|
||||||
using StbImageSharp;
|
using StbImageSharp;
|
||||||
|
using Voile.VFS;
|
||||||
|
|
||||||
namespace Voile
|
namespace Voile
|
||||||
{
|
{
|
||||||
@@ -18,7 +19,7 @@ namespace Voile
|
|||||||
protected override Texture2d LoadResource(string path)
|
protected override Texture2d LoadResource(string path)
|
||||||
{
|
{
|
||||||
ImageResult image;
|
ImageResult image;
|
||||||
using (var stream = File.OpenRead(path))
|
using (var stream = VirtualFileSystem.Read(path))
|
||||||
{
|
{
|
||||||
image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
|
image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ namespace Voile
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public T Value => ResourceManager.GetResource<T>(Guid);
|
public T Value => ResourceManager.GetResource<T>(Guid);
|
||||||
|
|
||||||
|
public static ResourceRef<T> Empty()
|
||||||
|
{
|
||||||
|
return new ResourceRef<T>(Guid.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceRef(Guid guid)
|
public ResourceRef(Guid guid)
|
||||||
{
|
{
|
||||||
Guid = guid;
|
Guid = guid;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq.Expressions;
|
using Voile.UI;
|
||||||
|
|
||||||
using Voile.Utils;
|
using Voile.Utils;
|
||||||
|
using Voile.VFS;
|
||||||
|
|
||||||
namespace Voile.Resources
|
namespace Voile.Resources
|
||||||
{
|
{
|
||||||
@@ -92,10 +92,7 @@ namespace Voile.Resources
|
|||||||
T? resource = default;
|
T? resource = default;
|
||||||
result = null;
|
result = null;
|
||||||
|
|
||||||
var fullPath = Path.Combine(ResourceRoot, path);
|
if (!VirtualFileSystem.FileExists(path))
|
||||||
|
|
||||||
// TODO: don't check if file doesn't exist in the file system, make it more generic but for now it's fine
|
|
||||||
if (!File.Exists(fullPath))
|
|
||||||
{
|
{
|
||||||
_logger.Error($"File at \"{path}\" doesn't exist!");
|
_logger.Error($"File at \"{path}\" doesn't exist!");
|
||||||
return false;
|
return false;
|
||||||
@@ -108,7 +105,7 @@ namespace Voile.Resources
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(fullPath);
|
var extension = Path.GetExtension(path);
|
||||||
var hasExtension = loader.SupportedExtensions.Any(ext => ext == extension);
|
var hasExtension = loader.SupportedExtensions.Any(ext => ext == extension);
|
||||||
|
|
||||||
if (!hasExtension)
|
if (!hasExtension)
|
||||||
@@ -116,7 +113,7 @@ namespace Voile.Resources
|
|||||||
_logger.Error($"Extension {extension} is not supported!");
|
_logger.Error($"Extension {extension} is not supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
var resourceGuid = loader.Load(fullPath);
|
var resourceGuid = loader.Load(path);
|
||||||
|
|
||||||
if (!GetResource(resourceGuid, out T? loadedResource))
|
if (!GetResource(resourceGuid, out T? loadedResource))
|
||||||
{
|
{
|
||||||
@@ -210,7 +207,7 @@ namespace Voile.Resources
|
|||||||
/// <param name="resourceGuid">Resource's GUID.</param>
|
/// <param name="resourceGuid">Resource's GUID.</param>
|
||||||
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
/// <param name="resource">Retrieved resource. Otherwise null if nothing got retrieved.</param>
|
||||||
/// <returns>True if resource got successfully retrieved, otherwise false.</returns>
|
/// <returns>True if resource got successfully retrieved, otherwise false.</returns>
|
||||||
public static T GetResource<T>(Guid resourceGuid) where T : Resource
|
public static T? GetResource<T>(Guid resourceGuid) where T : Resource
|
||||||
{
|
{
|
||||||
if (!TryGetLoader(out ResourceLoader<T>? loader))
|
if (!TryGetLoader(out ResourceLoader<T>? loader))
|
||||||
{
|
{
|
||||||
@@ -219,6 +216,11 @@ namespace Voile.Resources
|
|||||||
|
|
||||||
if (!GetResource(resourceGuid, out T? loadedResource))
|
if (!GetResource(resourceGuid, out T? loadedResource))
|
||||||
{
|
{
|
||||||
|
if (resourceGuid == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.Warn("Trying to load a resource with an empty GUID, ignoring.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
throw new Exception($"No resource with GUID \"{resourceGuid}\" found!");
|
throw new Exception($"No resource with GUID \"{resourceGuid}\" found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,11 +345,13 @@ namespace Voile.Resources
|
|||||||
|
|
||||||
private static Logger _logger = new(nameof(ResourceManager));
|
private static Logger _logger = new(nameof(ResourceManager));
|
||||||
|
|
||||||
|
// TODO: don't include types from optional systems. Create a way for third-party systems to register their own loader associations.
|
||||||
private static readonly Dictionary<Type, object> _resourceLoaderAssociations = new()
|
private static readonly Dictionary<Type, object> _resourceLoaderAssociations = new()
|
||||||
{
|
{
|
||||||
{ typeof(Sound), new SoundLoader()},
|
{ typeof(Sound), new SoundLoader()},
|
||||||
{typeof(Texture2d), new Texture2dLoader()},
|
{typeof(Texture2d), new Texture2dLoader()},
|
||||||
{typeof(Font), new FontLoader()}
|
{typeof(Font), new FontLoader()},
|
||||||
|
{ typeof(Style), new StyleLoader()}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly Dictionary<Type, object> _resourceSaverAssociations = new()
|
private static readonly Dictionary<Type, object> _resourceSaverAssociations = new()
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
namespace Voile
|
namespace Voile
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents raw audio samples.
|
/// Represents raw audio samples in 16-bit PCM format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Sound : Resource
|
public class Sound : Resource
|
||||||
{
|
{
|
||||||
public SoundChannel Channel { get; set; }
|
public SoundChannel Channel { get; set; }
|
||||||
public int SampleRate { get; set; }
|
public int SampleRate { get; set; }
|
||||||
|
|
||||||
public byte[]? Buffer { get; private set; }
|
public short[]? Buffer { get; private set; }
|
||||||
public long BufferSize { get; set; }
|
public long BufferSize { get; set; }
|
||||||
|
|
||||||
public Sound(string path, byte[] buffer) : base(path)
|
public Sound(string path, short[] buffer) : base(path)
|
||||||
{
|
{
|
||||||
Buffer = buffer;
|
Buffer = buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
namespace Voile;
|
namespace Voile;
|
||||||
|
|
||||||
public interface IStartableSystem
|
public interface IStartableSystem
|
||||||
@@ -36,3 +38,11 @@ public interface IUpdatableSystem
|
|||||||
/// <param name="deltaTime">Time step.</param>
|
/// <param name="deltaTime">Time step.</param>
|
||||||
void Update(double deltaTime);
|
void Update(double deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A system that renders itself to the screen, ex. UI or particles.
|
||||||
|
/// </summary>
|
||||||
|
public interface IRenderableSystem
|
||||||
|
{
|
||||||
|
void Render(RenderSystem renderer);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Voile.Resources;
|
using Voile.Resources;
|
||||||
using Voile.Resources.DataReaders;
|
using Voile.Resources.DataReaders;
|
||||||
using Voile.Utils;
|
using Voile.VFS;
|
||||||
|
|
||||||
namespace Voile.Systems.Particles;
|
namespace Voile.Systems.Particles;
|
||||||
|
|
||||||
@@ -62,9 +62,8 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
|
|||||||
{
|
{
|
||||||
var settings = new ParticleEmitterSettings();
|
var settings = new ParticleEmitterSettings();
|
||||||
|
|
||||||
using (var reader = new TomlDataReader("ParticleEmitterSettings"))
|
var reader = new TomlDataReader("ParticleEmitterSettings");
|
||||||
{
|
reader.Read(VirtualFileSystem.Read(path));
|
||||||
reader.Read(File.Open(path, FileMode.Open));
|
|
||||||
|
|
||||||
settings.Local = reader.GetBool("Local", true);
|
settings.Local = reader.GetBool("Local", true);
|
||||||
settings.MaxParticles = reader.GetInt("MaxParticles");
|
settings.MaxParticles = reader.GetInt("MaxParticles");
|
||||||
@@ -83,7 +82,6 @@ public class ParticleEmitterSettingsResourceLoader : ResourceLoader<ParticleEmit
|
|||||||
settings.ColorEnd = reader.GetColor("ColorEnd", Color.Black);
|
settings.ColorEnd = reader.GetColor("ColorEnd", Color.Black);
|
||||||
settings.LinearVelocityDamping = reader.GetFloat("LinearVelocityDamping", 1.0f);
|
settings.LinearVelocityDamping = reader.GetFloat("LinearVelocityDamping", 1.0f);
|
||||||
settings.AngularVelocityDamping = reader.GetFloat("AngularVelocityDamping", 1.0f);
|
settings.AngularVelocityDamping = reader.GetFloat("AngularVelocityDamping", 1.0f);
|
||||||
}
|
|
||||||
|
|
||||||
return new ParticleEmitterSettingsResource(path, settings);
|
return new ParticleEmitterSettingsResource(path, settings);
|
||||||
}
|
}
|
||||||
|
|||||||
41
Voile/Source/UI/Anchor.cs
Normal file
41
Voile/Source/UI/Anchor.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public enum Anchor
|
||||||
|
{
|
||||||
|
TopLeft,
|
||||||
|
TopCenter,
|
||||||
|
TopRight,
|
||||||
|
CenterLeft,
|
||||||
|
Center,
|
||||||
|
CenterRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomCenter,
|
||||||
|
BottomRight,
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class AnchorExtensions
|
||||||
|
{
|
||||||
|
public static Vector2 Calculate(this Anchor anchor, Vector2 parentPosition, Rect parentRect, Rect elementRect)
|
||||||
|
{
|
||||||
|
var size = new Vector2(elementRect.Width, elementRect.Height);
|
||||||
|
var parentSize = new Vector2(parentRect.Width, parentRect.Height);
|
||||||
|
|
||||||
|
return anchor switch
|
||||||
|
{
|
||||||
|
Anchor.TopLeft => Vector2.Zero,
|
||||||
|
Anchor.TopCenter => new Vector2((parentSize.X - size.X) / 2, 0),
|
||||||
|
Anchor.TopRight => new Vector2(parentSize.X - size.X, 0),
|
||||||
|
Anchor.CenterLeft => new Vector2(0, (parentSize.Y - size.Y) / 2),
|
||||||
|
Anchor.Center => (parentSize - size) / 2,
|
||||||
|
Anchor.CenterRight => new Vector2(parentSize.X - size.X, (parentSize.Y - size.Y) / 2),
|
||||||
|
Anchor.BottomLeft => new Vector2(0, parentSize.Y - size.Y),
|
||||||
|
Anchor.BottomCenter => new Vector2((parentSize.X - size.X) / 2, parentSize.Y - size.Y),
|
||||||
|
Anchor.BottomRight => parentSize - size,
|
||||||
|
_ => Vector2.Zero
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Voile/Source/UI/Containers/Container.cs
Normal file
136
Voile/Source/UI/Containers/Container.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
// TODO: make Container extend Widget, it already implements similar behaviors.
|
||||||
|
/// <summary>
|
||||||
|
/// A base class for all UI containers, used to position and rendering child <see cref="IElement">s.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Container : UIElement, IParentableElement
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<UIElement> Children => _children;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this <see cref="Container"/>'s minimum size will be confined to contents.
|
||||||
|
/// </summary>
|
||||||
|
public bool ConfineToContents { get; set; } = false;
|
||||||
|
|
||||||
|
public override Rect MinimumSize => _minimumSize;
|
||||||
|
|
||||||
|
public Container()
|
||||||
|
{
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Container(Rect minimumSize)
|
||||||
|
{
|
||||||
|
_minimumSize = minimumSize;
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Container(Rect minimumSize, List<UIElement> children)
|
||||||
|
{
|
||||||
|
_minimumSize = minimumSize;
|
||||||
|
_children = children;
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
foreach (var child in _children)
|
||||||
|
{
|
||||||
|
if (child is not IUpdatableElement updatable) continue;
|
||||||
|
|
||||||
|
updatable.MarkDirty();
|
||||||
|
updatable.Update();
|
||||||
|
|
||||||
|
if (child is IAnchorableElement anchorable)
|
||||||
|
{
|
||||||
|
anchorable.ApplyAnchor(GlobalPosition, Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrange();
|
||||||
|
|
||||||
|
if (ConfineToContents)
|
||||||
|
{
|
||||||
|
RecalculateSizes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when this <see cref="Container"/> has to rearrange its children.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Arrange();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculates sizes for this <see cref="Container"/>.
|
||||||
|
/// This is done automatically if <see cref="ConfineToContents"/> is true.
|
||||||
|
/// </summary>
|
||||||
|
public void RecalculateSizes()
|
||||||
|
{
|
||||||
|
float minX = float.MaxValue;
|
||||||
|
float minY = float.MaxValue;
|
||||||
|
float maxX = float.MinValue;
|
||||||
|
float maxY = float.MinValue;
|
||||||
|
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
var pos = child.GlobalPosition;
|
||||||
|
var size = child.Size;
|
||||||
|
|
||||||
|
minX = MathF.Min(minX, pos.X);
|
||||||
|
minY = MathF.Min(minY, pos.Y);
|
||||||
|
maxX = MathF.Max(maxX, pos.X + size.Width);
|
||||||
|
maxY = MathF.Max(maxY, pos.Y + size.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
float padding = 0f;
|
||||||
|
|
||||||
|
float occupiedWidth = (maxX - minX) + padding * 2;
|
||||||
|
float occupiedHeight = (maxY - minY) + padding * 2;
|
||||||
|
|
||||||
|
float finalWidth = MathF.Max(occupiedWidth, _minimumSize.Width);
|
||||||
|
float finalHeight = MathF.Max(occupiedHeight, _minimumSize.Height);
|
||||||
|
|
||||||
|
Size = new Rect(finalWidth, finalHeight);
|
||||||
|
|
||||||
|
if (_minimumSize > Size)
|
||||||
|
{
|
||||||
|
Size = _minimumSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddChild(UIElement child)
|
||||||
|
{
|
||||||
|
_children.Add(child);
|
||||||
|
child.SetParent(this);
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveChild(UIElement child)
|
||||||
|
{
|
||||||
|
_children.Remove(child);
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
if (child is not IRenderableElement renderable) continue;
|
||||||
|
renderable.Render(renderer, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UIElement> _children = new();
|
||||||
|
private Rect _minimumSize = Rect.Zero;
|
||||||
|
}
|
||||||
185
Voile/Source/UI/Containers/FlexContainer.cs
Normal file
185
Voile/Source/UI/Containers/FlexContainer.cs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
public enum FlexDirection
|
||||||
|
{
|
||||||
|
Row,
|
||||||
|
Column
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum JustifyContent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Align children to the start of the line.
|
||||||
|
/// </summary>
|
||||||
|
Start,
|
||||||
|
/// <summary>
|
||||||
|
/// Align children to the center of the line.
|
||||||
|
/// </summary>
|
||||||
|
Center,
|
||||||
|
/// <summary>
|
||||||
|
/// Align children to the end of the line.
|
||||||
|
/// </summary>
|
||||||
|
End,
|
||||||
|
/// <summary>
|
||||||
|
/// Equal space between items (no space at edges if multiple).
|
||||||
|
/// </summary>
|
||||||
|
SpaceBetween
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AlignItems
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Align to the top or left (cross-axis)
|
||||||
|
/// </summary>
|
||||||
|
Start,
|
||||||
|
/// <summary>
|
||||||
|
/// Center along the cross-axis.
|
||||||
|
/// </summary>
|
||||||
|
Center,
|
||||||
|
/// <summary>
|
||||||
|
/// Align to the bottom or right.
|
||||||
|
/// </summary>
|
||||||
|
End
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A flexible layout container that arranges its child elements in rows or columns, similar to CSS flexbox. Supports wrapping, alignment, spacing, and space distribution.
|
||||||
|
/// </summary>
|
||||||
|
public class FlexContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Layout direction to use with this <see cref="FlexContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public FlexDirection Direction { get; set; } = FlexDirection.Row;
|
||||||
|
/// <summary>
|
||||||
|
/// Controls main-axis alignment of child elements. By default, children will be positioned at the start of a line.
|
||||||
|
/// </summary>
|
||||||
|
public JustifyContent Justify { get; set; } = JustifyContent.Start;
|
||||||
|
/// <summary>
|
||||||
|
/// Controls cross-axis alignment (start, center, end).
|
||||||
|
/// </summary>
|
||||||
|
public AlignItems Align { get; set; } = AlignItems.Start;
|
||||||
|
/// <summary>
|
||||||
|
/// If true, children wrap onto multiple lines (rows or columns).
|
||||||
|
/// </summary>
|
||||||
|
public bool Wrap { get; set; } = false;
|
||||||
|
/// <summary>
|
||||||
|
/// Space between child elements.
|
||||||
|
/// </summary>
|
||||||
|
public float Gap { get; set; } = 16f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// (Reserved) If true, distributes extra space among children (not implemented)
|
||||||
|
/// </summary>
|
||||||
|
public bool DistributeRemainingSpace { get; set; } = false;
|
||||||
|
|
||||||
|
public FlexContainer() : base() { }
|
||||||
|
|
||||||
|
public FlexContainer(Rect minimumSize, List<UIElement> children) : base(minimumSize, children) { }
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
float containerMainSize = (Direction == FlexDirection.Row) ? Size.Width : Size.Height;
|
||||||
|
float mainPos = 0.0f;
|
||||||
|
float crossPos = 0.0f;
|
||||||
|
|
||||||
|
List<List<UIElement>> lines = new();
|
||||||
|
List<UIElement> currentLine = new();
|
||||||
|
float lineMainSum = 0f;
|
||||||
|
float maxCross = 0f;
|
||||||
|
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
Vector2 childSize = GetChildSize(child);
|
||||||
|
float mainSize = GetMainSize(childSize);
|
||||||
|
float crossSize = GetCrossSize(childSize);
|
||||||
|
|
||||||
|
bool wrapLine = Wrap && currentLine.Count > 0 && (lineMainSum + mainSize + Gap > containerMainSize);
|
||||||
|
|
||||||
|
if (wrapLine)
|
||||||
|
{
|
||||||
|
lines.Add(currentLine);
|
||||||
|
currentLine = new();
|
||||||
|
lineMainSum = 0f;
|
||||||
|
maxCross += crossSize + Gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLine.Add(child);
|
||||||
|
lineMainSum += mainSize + (currentLine.Count > 1 ? Gap : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLine.Count > 0)
|
||||||
|
{
|
||||||
|
lines.Add(currentLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
float currentCross = crossPos;
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
float lineMainLength = line.Sum(child => GetMainSize(GetChildSize(child))) + Gap * (line.Count - 1);
|
||||||
|
float justifyOffset = GetJustifyOffset(containerMainSize, lineMainLength, line.Count);
|
||||||
|
|
||||||
|
float currentMain = mainPos + justifyOffset;
|
||||||
|
float maxLineCross = line.Select(child => GetCrossSize(GetChildSize(child))).Max();
|
||||||
|
|
||||||
|
foreach (var child in line)
|
||||||
|
{
|
||||||
|
Vector2 childSize = GetChildSize(child);
|
||||||
|
|
||||||
|
float alignedCross = currentCross + GetAlignOffset(maxLineCross, GetCrossSize(childSize));
|
||||||
|
|
||||||
|
Vector2 childPos = (Direction == FlexDirection.Row)
|
||||||
|
? new Vector2(currentMain, alignedCross)
|
||||||
|
: new Vector2(alignedCross, currentMain);
|
||||||
|
|
||||||
|
child.LocalPosition = childPos;
|
||||||
|
|
||||||
|
currentMain += GetMainSize(childSize) + Gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCross += maxLineCross + Gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 GetChildSize(IElement child)
|
||||||
|
{
|
||||||
|
if (child is IResizeableElement resizeable)
|
||||||
|
return new Vector2(resizeable.MinimumSize.Width, resizeable.MinimumSize.Height);
|
||||||
|
return new Vector2(child.Size.Width, child.Size.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetMainSize(Vector2 size)
|
||||||
|
{
|
||||||
|
return Direction == FlexDirection.Row ? size.X : size.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetCrossSize(Vector2 size)
|
||||||
|
{
|
||||||
|
return Direction == FlexDirection.Row ? size.Y : size.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetJustifyOffset(float containerSize, float totalMain, int itemCount)
|
||||||
|
{
|
||||||
|
return Justify switch
|
||||||
|
{
|
||||||
|
JustifyContent.Center => (containerSize - totalMain) / 2f,
|
||||||
|
JustifyContent.End => containerSize - totalMain,
|
||||||
|
JustifyContent.SpaceBetween => (itemCount > 1) ? 0 : (containerSize - totalMain) / 2f,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetAlignOffset(float maxCross, float childCross)
|
||||||
|
{
|
||||||
|
return Align switch
|
||||||
|
{
|
||||||
|
AlignItems.Center => (maxCross - childCross) / 2f,
|
||||||
|
AlignItems.End => maxCross - childCross,
|
||||||
|
_ => 0f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Voile/Source/UI/Containers/Frame.cs
Normal file
19
Voile/Source/UI/Containers/Frame.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
public class Frame : Container
|
||||||
|
{
|
||||||
|
public Frame()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Frame(Rect minimumSize) : base(minimumSize)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Voile/Source/UI/Containers/GridContainer.cs
Normal file
69
Voile/Source/UI/Containers/GridContainer.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A grid container arranges its children in a grid layout with a given column count, and column and row spacing between child elements.
|
||||||
|
/// </summary>
|
||||||
|
public class GridContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of columns this grid has.
|
||||||
|
/// </summary>
|
||||||
|
public int Columns { get; set; } = 2;
|
||||||
|
/// <summary>
|
||||||
|
/// Spacing between each grid column.
|
||||||
|
/// </summary>
|
||||||
|
public float ColumnSpacing { get; set; } = 16.0f;
|
||||||
|
/// <summary>
|
||||||
|
/// Spacing between each grid row.
|
||||||
|
/// </summary>
|
||||||
|
public float RowSpacing { get; set; } = 16.0f;
|
||||||
|
|
||||||
|
public GridContainer(Rect minimumSize, List<UIElement> children, int columns = 2, float colSpacing = 16.0f, float rowSpacing = 16.0f)
|
||||||
|
: base(minimumSize, children)
|
||||||
|
{
|
||||||
|
Columns = columns;
|
||||||
|
ColumnSpacing = colSpacing;
|
||||||
|
RowSpacing = rowSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridContainer(int columns = 2, float colSpacing = 16.0f, float rowSpacing = 16.0f)
|
||||||
|
{
|
||||||
|
Columns = columns;
|
||||||
|
ColumnSpacing = colSpacing;
|
||||||
|
RowSpacing = rowSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
float currentX = 0.0f;
|
||||||
|
float currentY = 0.0f;
|
||||||
|
int colIndex = 0;
|
||||||
|
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.LocalPosition = new Vector2(currentX, currentY);
|
||||||
|
|
||||||
|
float childWidth = 0.0f;
|
||||||
|
float childHeight = 0.0f;
|
||||||
|
|
||||||
|
childWidth = child.Size.Width;
|
||||||
|
childHeight = child.Size.Height;
|
||||||
|
|
||||||
|
colIndex++;
|
||||||
|
|
||||||
|
if (colIndex >= Columns)
|
||||||
|
{
|
||||||
|
colIndex = 0;
|
||||||
|
currentX = 0.0f;
|
||||||
|
currentY += childHeight + RowSpacing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentX += childWidth + ColumnSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Voile/Source/UI/Containers/HorizontalContainer.cs
Normal file
44
Voile/Source/UI/Containers/HorizontalContainer.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A horizontal container arranges its children horizontally, with a given horizontal spacing between child elements.
|
||||||
|
/// </summary>
|
||||||
|
public class HorizontalContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Horizontal spacing between child elements.
|
||||||
|
/// </summary>
|
||||||
|
public float Spacing { get; set; } = 16.0f;
|
||||||
|
|
||||||
|
public HorizontalContainer(Rect minimumSize, List<UIElement> children, float spacing = 16.0f) : base(minimumSize, children)
|
||||||
|
{
|
||||||
|
Spacing = spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HorizontalContainer(float spacing = 16.0f)
|
||||||
|
{
|
||||||
|
Spacing = spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
float currentX = 0.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < Children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = Children[i];
|
||||||
|
var pos = new Vector2(currentX, 0.0f);
|
||||||
|
child.LocalPosition = pos;
|
||||||
|
|
||||||
|
currentX += child.Size.Width;
|
||||||
|
|
||||||
|
if (i < Children.Count - 1)
|
||||||
|
{
|
||||||
|
currentX += Spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Voile/Source/UI/Containers/VerticalContainer.cs
Normal file
44
Voile/Source/UI/Containers/VerticalContainer.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Containers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A vertical container arranges its children vertically, with a given vertical spacing between child elements.
|
||||||
|
/// </summary>
|
||||||
|
public class VerticalContainer : Container
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Vertical spacing between child elements.
|
||||||
|
/// </summary>
|
||||||
|
public float Spacing { get; set; } = 16.0f;
|
||||||
|
|
||||||
|
public VerticalContainer(Rect minimumSize, List<UIElement> children, float spacing = 16.0f) : base(minimumSize, children)
|
||||||
|
{
|
||||||
|
Spacing = spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VerticalContainer(float spacing = 16.0f)
|
||||||
|
{
|
||||||
|
Spacing = spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Arrange()
|
||||||
|
{
|
||||||
|
float currentY = 0.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < Children.Count; i++)
|
||||||
|
{
|
||||||
|
var child = Children[i];
|
||||||
|
var pos = new Vector2(0.0f, currentY);
|
||||||
|
child.LocalPosition = pos;
|
||||||
|
|
||||||
|
currentY += child.Size.Height;
|
||||||
|
|
||||||
|
if (i < Children.Count - 1)
|
||||||
|
{
|
||||||
|
currentY += Spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Voile/Source/UI/IElement.cs
Normal file
98
Voile/Source/UI/IElement.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public interface IElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This element's position in pixels relative to the viewport top-left edge.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 GlobalPosition { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The size of this element.
|
||||||
|
/// </summary>
|
||||||
|
public Rect Size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IParentableElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This parentable element's children.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<UIElement> Children { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Add a child element to this element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="child">Child <see cref="UIElement"/>.</param>
|
||||||
|
public void AddChild(UIElement child);
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a child element from this element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="child">Child <see cref="UIElement"/> to remove.</param>
|
||||||
|
public void RemoveChild(UIElement child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IResizeableElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get a minimum rectangle size for this element.
|
||||||
|
/// </summary>
|
||||||
|
public abstract Rect MinimumSize { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IUpdatableElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this element's properties have changed, making it necessary to update it.
|
||||||
|
/// </summary>
|
||||||
|
public bool Dirty { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Update this element.
|
||||||
|
/// </summary>
|
||||||
|
void Update();
|
||||||
|
/// <summary>
|
||||||
|
/// Marks this element as changed, requiring an update.
|
||||||
|
/// </summary>
|
||||||
|
void MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IRenderableElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this element should be drawn.
|
||||||
|
/// </summary>
|
||||||
|
public bool Visible { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Render this element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Renderer to call draw operations on.</param>
|
||||||
|
/// <param name="style">A style to use to draw this element.</param>
|
||||||
|
public void Render(RenderSystem renderer, Style style);
|
||||||
|
/// <summary>
|
||||||
|
/// Draws this element's size bounds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Renderer to use.</param>
|
||||||
|
public void DrawSize(RenderSystem renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IInputElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this element should ignore inputs.
|
||||||
|
/// </summary>
|
||||||
|
public bool IgnoreInput { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Send an input action to this element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">Input action to send.</param>
|
||||||
|
void Input(UIInputContext action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAnchorableElement
|
||||||
|
{
|
||||||
|
public Anchor Anchor { get; set; }
|
||||||
|
public Vector2 AnchorOffset { get; set; }
|
||||||
|
public void ApplyAnchor(Vector2 parentPosition, Rect parentRect);
|
||||||
|
}
|
||||||
23
Voile/Source/UI/Rect.cs
Normal file
23
Voile/Source/UI/Rect.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a rectangle. Used to determine widget confines for UI layout.
|
||||||
|
/// </summary>
|
||||||
|
public record Rect(float Width = 0.0f, float Height = 0.0f)
|
||||||
|
{
|
||||||
|
public float Width { get; set; } = Width;
|
||||||
|
public float Height { get; set; } = Height;
|
||||||
|
public static Rect Zero => new Rect(0.0f, 0.0f);
|
||||||
|
public float Area => Width * Height;
|
||||||
|
|
||||||
|
public int CompareTo(Rect? other)
|
||||||
|
{
|
||||||
|
if (other is null) return 1;
|
||||||
|
return Area.CompareTo(other.Area);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
public static bool operator <=(Rect left, Rect right) => left.CompareTo(right) <= 0;
|
||||||
|
}
|
||||||
13
Voile/Source/UI/Style.cs
Normal file
13
Voile/Source/UI/Style.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Voile.Resources;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A resource containing UI style settings.
|
||||||
|
/// </summary>
|
||||||
|
public class Style : TextDataResource
|
||||||
|
{
|
||||||
|
public Style(string path) : base(path)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
95
Voile/Source/UI/UIElement.cs
Normal file
95
Voile/Source/UI/UIElement.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public abstract class UIElement : IElement, IRenderableElement, IResizeableElement, IUpdatableElement, IAnchorableElement
|
||||||
|
{
|
||||||
|
public bool Visible { get; set; } = true;
|
||||||
|
public bool IgnoreInput { get; set; } = false;
|
||||||
|
public Vector2 LocalPosition { get; set; } = Vector2.Zero;
|
||||||
|
public Vector2 GlobalPosition => _parent?.GlobalPosition + LocalPosition ?? LocalPosition;
|
||||||
|
|
||||||
|
public Rect Size
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_size = value;
|
||||||
|
|
||||||
|
if (value.Width < MinimumSize.Width)
|
||||||
|
{
|
||||||
|
_size.Width = MinimumSize.Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Height < MinimumSize.Height)
|
||||||
|
{
|
||||||
|
_size.Height = MinimumSize.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Vector2 AnchorOffset { get; set; } = Vector2.Zero;
|
||||||
|
public Anchor Anchor { get; set; } = Anchor.TopLeft;
|
||||||
|
|
||||||
|
public abstract Rect MinimumSize { get; }
|
||||||
|
public bool Dirty => _dirty;
|
||||||
|
|
||||||
|
public virtual void MarkDirty() => _dirty = true;
|
||||||
|
|
||||||
|
public void SetParent(UIElement parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!_dirty) return;
|
||||||
|
_dirty = false;
|
||||||
|
|
||||||
|
if (Size == Rect.Zero)
|
||||||
|
Size = MinimumSize;
|
||||||
|
|
||||||
|
OnUpdate();
|
||||||
|
|
||||||
|
if (_parent is not null && _parent.Size != Rect.Zero)
|
||||||
|
{
|
||||||
|
ApplyAnchor(_parent.GlobalPosition, _parent.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Render(RenderSystem renderer, Style style);
|
||||||
|
protected abstract void OnUpdate();
|
||||||
|
|
||||||
|
public void DrawSize(RenderSystem renderer)
|
||||||
|
{
|
||||||
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||||
|
renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if this <see cref="UIElement"/> contains a point within its confines.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pointPosition">A global position of the point.</param>
|
||||||
|
/// <returns>True if the point is inside the widget; otherwise, false.</returns>
|
||||||
|
public bool ContainsPoint(Vector2 point)
|
||||||
|
{
|
||||||
|
return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
|
||||||
|
point.X <= GlobalPosition.X + Size.Width &&
|
||||||
|
point.Y <= GlobalPosition.Y + Size.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies this <see cref="UIElement"/> anchor.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
|
||||||
|
{
|
||||||
|
LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _dirty = true;
|
||||||
|
private Rect _size = Rect.Zero;
|
||||||
|
|
||||||
|
private UIElement? _parent;
|
||||||
|
}
|
||||||
35
Voile/Source/UI/UIInputContext.cs
Normal file
35
Voile/Source/UI/UIInputContext.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public class UIInputContext
|
||||||
|
{
|
||||||
|
public IInputAction Action { get; }
|
||||||
|
public Vector2 MousePosition { get; }
|
||||||
|
public bool MousePressed { get; set; }
|
||||||
|
public bool MouseReleased { get; set; }
|
||||||
|
public bool MouseDown { get; set; }
|
||||||
|
|
||||||
|
public string ActionName { get; }
|
||||||
|
|
||||||
|
public int CharPressed { get; }
|
||||||
|
public bool HasCharInput => CharPressed != 0;
|
||||||
|
|
||||||
|
public bool Handled => _handled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks this context as handled, meaning next elements that will receive it should discard it.
|
||||||
|
/// </summary>
|
||||||
|
public void SetHandled() => _handled = true;
|
||||||
|
|
||||||
|
public UIInputContext(IInputAction action, Vector2 mousePosition, string actionName, int charPressed = 0)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
MousePosition = mousePosition;
|
||||||
|
ActionName = actionName;
|
||||||
|
CharPressed = charPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _handled;
|
||||||
|
}
|
||||||
147
Voile/Source/UI/UISystem.cs
Normal file
147
Voile/Source/UI/UISystem.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI;
|
||||||
|
|
||||||
|
public class UISystem : IUpdatableSystem, IRenderableSystem
|
||||||
|
{
|
||||||
|
public IReadOnlyList<IElement> Elements => _elements;
|
||||||
|
|
||||||
|
public bool RenderDebugRects { get; set; }
|
||||||
|
|
||||||
|
public UISystem(InputSystem inputSystem)
|
||||||
|
{
|
||||||
|
_style = ResourceRef<Style>.Empty();
|
||||||
|
_input = inputSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UISystem(InputSystem inputSystem, ResourceRef<Style> style)
|
||||||
|
{
|
||||||
|
_input = inputSystem;
|
||||||
|
_style = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UISystem(InputSystem inputSystem, ResourceRef<Style> style, List<UIElement> elements)
|
||||||
|
{
|
||||||
|
_input = inputSystem;
|
||||||
|
_style = style;
|
||||||
|
_elements = elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddElement(UIElement element) => _elements.Add(element);
|
||||||
|
public void RemoveElement(UIElement element) => _elements.Remove(element);
|
||||||
|
|
||||||
|
public void Update(double deltaTime)
|
||||||
|
{
|
||||||
|
HandleInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render(RenderSystem renderer)
|
||||||
|
{
|
||||||
|
// Update elements each time UI system is rendered.
|
||||||
|
foreach (var element in _elements)
|
||||||
|
{
|
||||||
|
if (element is not IUpdatableElement updatable) continue;
|
||||||
|
updatable.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var element in _elements)
|
||||||
|
{
|
||||||
|
if (element is IRenderableElement renderable)
|
||||||
|
{
|
||||||
|
renderable.Render(renderer, _style.Value);
|
||||||
|
|
||||||
|
if (!RenderDebugRects) return;
|
||||||
|
renderable.DrawSize(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is IParentableElement parentable)
|
||||||
|
{
|
||||||
|
foreach (var child in parentable.Children)
|
||||||
|
{
|
||||||
|
if (child is not IRenderableElement renderableChild) continue;
|
||||||
|
|
||||||
|
if (!RenderDebugRects) return;
|
||||||
|
renderableChild.DrawSize(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleInput()
|
||||||
|
{
|
||||||
|
int charPressed = _input.GetCharPressed();
|
||||||
|
Vector2 mousePos = _input.GetMousePosition();
|
||||||
|
|
||||||
|
Vector2 currentMousePosition = _input.GetMousePosition();
|
||||||
|
|
||||||
|
foreach (var (actionName, mappings) in InputSystem.InputMappings)
|
||||||
|
{
|
||||||
|
foreach (var action in mappings)
|
||||||
|
{
|
||||||
|
if (action.IsPressed(_input))
|
||||||
|
{
|
||||||
|
// TODO: specify which mouse button is used in the context.
|
||||||
|
var context = new UIInputContext(action, mousePos, actionName, charPressed)
|
||||||
|
{
|
||||||
|
MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
|
||||||
|
MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
MousePressed = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (PropagateInput(_elements, context))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charPressed != 0)
|
||||||
|
{
|
||||||
|
var context = new UIInputContext(new KeyInputAction(KeyboardKey.Null), mousePos, "", charPressed);
|
||||||
|
PropagateInput(_elements, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (currentMousePosition != _lastMousePosition)
|
||||||
|
{
|
||||||
|
// TODO: specify which mouse button is used in the context.
|
||||||
|
var context = new UIInputContext(new MouseInputAction(MouseButton.Left), mousePos, "", charPressed)
|
||||||
|
{
|
||||||
|
MouseDown = _input.IsMouseButtonDown(MouseButton.Left),
|
||||||
|
MouseReleased = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
MousePressed = _input.IsMouseButtonReleased(MouseButton.Left),
|
||||||
|
};
|
||||||
|
PropagateInput(_elements, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PropagateInput(List<UIElement> elements, UIInputContext context)
|
||||||
|
{
|
||||||
|
for (int i = elements.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var element = elements[i];
|
||||||
|
|
||||||
|
if (element is IInputElement inputElement && !inputElement.IgnoreInput)
|
||||||
|
{
|
||||||
|
inputElement.Input(context);
|
||||||
|
_input.SetAsHandled();
|
||||||
|
// return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is IParentableElement parent)
|
||||||
|
{
|
||||||
|
if (PropagateInput(parent.Children.ToList(), context))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceRef<Style> _style;
|
||||||
|
private List<UIElement> _elements = new();
|
||||||
|
private InputSystem _input;
|
||||||
|
|
||||||
|
private Vector2 _lastMousePosition = Vector2.Zero;
|
||||||
|
}
|
||||||
47
Voile/Source/UI/Widgets/Button.cs
Normal file
47
Voile/Source/UI/Widgets/Button.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Widgets;
|
||||||
|
|
||||||
|
public enum ButtonState
|
||||||
|
{
|
||||||
|
Disabled,
|
||||||
|
Hovered,
|
||||||
|
Pressed,
|
||||||
|
Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A clickable button with a label.
|
||||||
|
/// </summary>
|
||||||
|
public class Button : Widget
|
||||||
|
{
|
||||||
|
public string Label { get; set; } = "Button";
|
||||||
|
public override Rect MinimumSize => new Rect(Width: 128.0f, Height: 64.0f);
|
||||||
|
|
||||||
|
public Button(string label, Action pressedAction)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
_pressedAction = pressedAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
|
{
|
||||||
|
// TODO: use a button color from style.
|
||||||
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||||
|
renderer.DrawRectangle(new Vector2(MinimumSize.Width, MinimumSize.Height), new Color(0.25f, 0.25f, 0.25f));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInput(UIInputContext action)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action _pressedAction;
|
||||||
|
}
|
||||||
43
Voile/Source/UI/Widgets/Label.cs
Normal file
43
Voile/Source/UI/Widgets/Label.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Widgets;
|
||||||
|
|
||||||
|
public class Label : Widget
|
||||||
|
{
|
||||||
|
public override Rect MinimumSize => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public string Text { get; set; } = "Hello World!";
|
||||||
|
|
||||||
|
public Label(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label(string text, ResourceRef<Font> fontOverride)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
_fontOverride = fontOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInput(UIInputContext action)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
|
{
|
||||||
|
// TODO: use style here.
|
||||||
|
if (!_fontOverride.HasValue) return;
|
||||||
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||||
|
renderer.DrawText(_fontOverride, Text, Color.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceRef<Font> _fontOverride = ResourceRef<Font>.Empty();
|
||||||
|
}
|
||||||
78
Voile/Source/UI/Widgets/RectangleWidget.cs
Normal file
78
Voile/Source/UI/Widgets/RectangleWidget.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Widgets;
|
||||||
|
|
||||||
|
public class RectangleWidget : Widget
|
||||||
|
{
|
||||||
|
public override Rect MinimumSize { get; }
|
||||||
|
public Color Color { get; set; } = Color.White;
|
||||||
|
public RectangleWidget(Rect minimumRect, Color color) : base()
|
||||||
|
{
|
||||||
|
MinimumSize = minimumRect;
|
||||||
|
Color = color;
|
||||||
|
|
||||||
|
_defaultColor = color;
|
||||||
|
|
||||||
|
_defaultSize = Size;
|
||||||
|
|
||||||
|
_hoverColor = color.Lightened(0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(RenderSystem renderer, Style style)
|
||||||
|
{
|
||||||
|
renderer.SetTransform(GlobalPosition, Vector2.Zero);
|
||||||
|
renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInput(UIInputContext inputContext)
|
||||||
|
{
|
||||||
|
if (_defaultSize == Rect.Zero)
|
||||||
|
{
|
||||||
|
_defaultSize = Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_downSize == Rect.Zero)
|
||||||
|
{
|
||||||
|
_downSize = new Rect(Size.Width * 0.75f, Size.Height * 0.75f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mouseInside = ContainsPoint(inputContext.MousePosition);
|
||||||
|
|
||||||
|
if (mouseInside)
|
||||||
|
{
|
||||||
|
Color = _hoverColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Color = _defaultColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (mouseInside && inputContext.MouseDown)
|
||||||
|
// {
|
||||||
|
// Size = _downSize;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (mouseInside && inputContext.MouseReleased)
|
||||||
|
// {
|
||||||
|
// Size = _defaultSize;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (mouseInside && inputContext.MousePressed)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Hello, I was clicked!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color _defaultColor;
|
||||||
|
private Color _hoverColor;
|
||||||
|
|
||||||
|
private Rect _defaultSize = Rect.Zero;
|
||||||
|
private Rect _downSize = Rect.Zero;
|
||||||
|
}
|
||||||
43
Voile/Source/UI/Widgets/Widget.cs
Normal file
43
Voile/Source/UI/Widgets/Widget.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Voile.Input;
|
||||||
|
using Voile.Rendering;
|
||||||
|
|
||||||
|
namespace Voile.UI.Widgets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A base class for all UI widgets.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Widget : UIElement, IInputElement
|
||||||
|
{
|
||||||
|
public Widget()
|
||||||
|
{
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Widget(Rect size)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Widget(Vector2 position)
|
||||||
|
{
|
||||||
|
LocalPosition = position;
|
||||||
|
MarkDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Input(UIInputContext context)
|
||||||
|
{
|
||||||
|
if (context.Handled || IgnoreInput) return;
|
||||||
|
if (ContainsPoint(context.MousePosition))
|
||||||
|
{
|
||||||
|
OnInput(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when this widget receives input.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">An input action this widget received.</param>
|
||||||
|
protected abstract void OnInput(UIInputContext context);
|
||||||
|
}
|
||||||
@@ -35,6 +35,15 @@ namespace Voile
|
|||||||
return new Vector2((float)x, (float)y);
|
return new Vector2((float)x, (float)y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Color RandomColor()
|
||||||
|
{
|
||||||
|
var r = _random.NextDouble(0.0f, 1.0f);
|
||||||
|
var g = _random.NextDouble(0.0f, 1.0f);
|
||||||
|
var b = _random.NextDouble(0.0f, 1.0f);
|
||||||
|
|
||||||
|
return new Color((float)r, (float)g, (float)b);
|
||||||
|
}
|
||||||
|
|
||||||
public static float EaseOutBack(float x)
|
public static float EaseOutBack(float x)
|
||||||
{
|
{
|
||||||
var c1 = 1.70158f;
|
var c1 = 1.70158f;
|
||||||
|
|||||||
69
Voile/Source/VFS/FileSystemMountPoint.cs
Normal file
69
Voile/Source/VFS/FileSystemMountPoint.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using Voile.Utils;
|
||||||
|
|
||||||
|
namespace Voile.VFS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A file in the OS file system.
|
||||||
|
/// </summary>
|
||||||
|
public class FileSystemFile : VirtualFile
|
||||||
|
{
|
||||||
|
public FileSystemFile(string path)
|
||||||
|
{
|
||||||
|
_fsPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Stream GetStream()
|
||||||
|
{
|
||||||
|
return new FileStream(_fsPath, FileMode.Open, FileAccess.Read);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _fsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="IVirtualMountPoint"/> implementation for an OS file system.
|
||||||
|
/// </summary>
|
||||||
|
public class FileSystemMountPoint : IVirtualMountPoint
|
||||||
|
{
|
||||||
|
public int Order => int.MaxValue;
|
||||||
|
|
||||||
|
public FileSystemMountPoint(string path)
|
||||||
|
{
|
||||||
|
_fsPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Mount()
|
||||||
|
{
|
||||||
|
_logger.Info($"Mounting from file system path \"{_fsPath}\".");
|
||||||
|
|
||||||
|
int rootLength = _fsPath.Length;
|
||||||
|
var files =
|
||||||
|
Directory.GetFiles(_fsPath, "*", SearchOption.AllDirectories)
|
||||||
|
.Select(p => p.Remove(0, rootLength))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
var relativePath = NormalizePath(file);
|
||||||
|
var fullPath = NormalizePath(Path.Combine(_fsPath, file));
|
||||||
|
_files[relativePath] = new FileSystemFile(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualFile GetFile(string path) => _files[path];
|
||||||
|
|
||||||
|
public IDictionary<string, VirtualFile> GetFiles() => _files;
|
||||||
|
|
||||||
|
public bool HasFile(string path) => _files.ContainsKey(path);
|
||||||
|
|
||||||
|
private string NormalizePath(string path)
|
||||||
|
{
|
||||||
|
return path
|
||||||
|
.Replace(@"\\", @"\")
|
||||||
|
.Replace(@"\", @"/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Logger _logger = new(nameof(FileSystemMountPoint));
|
||||||
|
private string _fsPath;
|
||||||
|
private Dictionary<string, VirtualFile> _files = new();
|
||||||
|
}
|
||||||
6
Voile/Source/VFS/IVirtualFile.cs
Normal file
6
Voile/Source/VFS/IVirtualFile.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Voile.VFS;
|
||||||
|
|
||||||
|
public abstract class VirtualFile
|
||||||
|
{
|
||||||
|
public abstract Stream GetStream();
|
||||||
|
}
|
||||||
33
Voile/Source/VFS/IVirtualMountPoint.cs
Normal file
33
Voile/Source/VFS/IVirtualMountPoint.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Voile.VFS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A virtual mounting point.
|
||||||
|
/// </summary>
|
||||||
|
public interface IVirtualMountPoint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Order of mounting for this mount point. Lower values indicate higher priority for lookup.
|
||||||
|
/// </summary>
|
||||||
|
int Order { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Mounts this <see cref="IVirtualMountPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
void Mount();
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Relative path to the file.</param>
|
||||||
|
/// <returns>An instance of <see cref="VirtualFile"/> if the file exists; otherwise, an exception is thrown.</returns>
|
||||||
|
VirtualFile GetFile(string path);
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all files available at this <see cref="IVirtualMountPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A dictionary mapping a relative path to an instance of a <see cref="VirtualFile"/>.</returns>
|
||||||
|
IDictionary<string, VirtualFile> GetFiles();
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a file exists at the given relative path within this mount point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The relative path of the file to check.</param>
|
||||||
|
/// <returns><c>true</c> if the file exists; otherwise, <c>false</c>.</returns>
|
||||||
|
bool HasFile(string path);
|
||||||
|
}
|
||||||
59
Voile/Source/VFS/VirtualFileSystem.cs
Normal file
59
Voile/Source/VFS/VirtualFileSystem.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
namespace Voile.VFS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A virtual file system that provides an abstract interface for manipulating files from various sources.
|
||||||
|
/// </summary>
|
||||||
|
public static class VirtualFileSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mounts a <see cref="IVirtualMountPoint"/>.
|
||||||
|
/// This will make files available for this mount point accessible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mountPoint"></param>
|
||||||
|
public static void Mount(IVirtualMountPoint mountPoint)
|
||||||
|
{
|
||||||
|
mountPoint.Mount();
|
||||||
|
_mounts.Add(mountPoint);
|
||||||
|
|
||||||
|
_mounts = _mounts.OrderBy(mount => mount.Order).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if file exists or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Relative path to the file.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool FileExists(string path)
|
||||||
|
{
|
||||||
|
return _mounts.Any(mount => mount.HasFile(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="VirtualFile"/> from path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Relative path to the file.</param>
|
||||||
|
/// <returns>A virtual file at the path.</returns>
|
||||||
|
public static VirtualFile GetFile(string path)
|
||||||
|
{
|
||||||
|
var mount = _mounts.FirstOrDefault(mount => mount.HasFile(path));
|
||||||
|
if (mount == null)
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"File \"{path}\" was not found in any mounted point.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mount.GetFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Relative path to the file.</param>
|
||||||
|
/// <returns>A readable stream.</returns>
|
||||||
|
public static Stream Read(string path)
|
||||||
|
{
|
||||||
|
var file = GetFile(path);
|
||||||
|
return file.GetStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<IVirtualMountPoint> _mounts = new();
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
@@ -12,18 +12,14 @@
|
|||||||
<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" />
|
||||||
|
<PackageReference Include="SoLoud.NET" Version="2020.2.7.1" />
|
||||||
<PackageReference Include="Tommy" Version="3.1.2" />
|
<PackageReference Include="Tommy" Version="3.1.2" />
|
||||||
<PackageReference Include="Voile.Fmod" Version="0.2.2.8" />
|
|
||||||
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
||||||
<PackageReference Include="Raylib-cs" Version="4.2.0.1" />
|
<PackageReference Include="Raylib-cs" Version="7.0.1" />
|
||||||
<PackageReference Include="SharpFont" Version="4.0.1" />
|
<PackageReference Include="SharpFont" Version="4.0.1" />
|
||||||
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta19" />
|
<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>
|
||||||
|
|
||||||
<Target Name="BuildFmod" BeforeTargets="BeforeBuild">
|
|
||||||
<MSBuild Projects="../Voile.Fmod/Voile.Fmod.csproj" Targets="Restore;Build" />
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
Reference in New Issue
Block a user