Input overhaul + ImGui

wip/source-generators
copygirl 1 year ago
parent c559c40243
commit dca2275c4d
  1. 2
      src/FastNoiseLite
  2. 16
      src/Immersion/Program.cs
  3. 2
      src/flecs-cs
  4. 26
      src/gaemstone.Client/Color.cs
  5. 90
      src/gaemstone.Client/Components/InputComponents.cs
  6. 70
      src/gaemstone.Client/Systems/FreeCameraController.cs
  7. 193
      src/gaemstone.Client/Systems/ImGuiInputDebug.cs
  8. 120
      src/gaemstone.Client/Systems/ImGuiManager.cs
  9. 83
      src/gaemstone.Client/Systems/Input.cs
  10. 141
      src/gaemstone.Client/Systems/InputManager.cs
  11. 10
      src/gaemstone.Client/Systems/Renderer.cs
  12. 8
      src/gaemstone.Client/Systems/TextureManager.cs
  13. 5
      src/gaemstone.Client/Systems/Windowing.cs
  14. 1
      src/gaemstone.Client/gaemstone.Client.csproj
  15. 2
      src/gaemstone/ECS/System.cs
  16. 10
      src/gaemstone/ECS/Universe+Modules.cs

@ -1 +1 @@
Subproject commit 5923df5d822f7610100d0e77f629c607ed64934a
Subproject commit 95900f7372d9aad1691cfeabf45103a132a4664f

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
@ -13,7 +13,6 @@ using static gaemstone.Client.Components.CameraComponents;
using static gaemstone.Client.Components.RenderingComponents;
using static gaemstone.Client.Components.ResourceComponents;
using static gaemstone.Client.Systems.FreeCameraController;
using static gaemstone.Client.Systems.Input;
using static gaemstone.Client.Systems.Windowing;
using static gaemstone.Components.TransformComponents;
@ -39,23 +38,26 @@ window.Center();
// universe.Modules.Register<ObserverTest>();
universe.Modules.Register<gaemstone.Client.Systems.Windowing>();
game.Set(new Canvas(Silk.NET.OpenGL.ContextSourceExtensions.CreateOpenGL(window)));
game.Set(new GameWindow(window));
universe.Modules.Register<gaemstone.Components.TransformComponents>();
universe.Modules.Register<gaemstone.Client.Components.RenderingComponents>();
universe.Modules.Register<gaemstone.Client.Systems.Renderer>();
universe.Modules.Register<gaemstone.Client.Systems.ImGuiManager>();
universe.Modules.Register<gaemstone.Client.Components.ResourceComponents>();
universe.Modules.Register<gaemstone.Client.Systems.TextureManager>();
universe.Modules.Register<gaemstone.Client.Systems.MeshManager>();
universe.Modules.Register<gaemstone.Client.Systems.Input>();
game.Set(new RawInput());
universe.Modules.Register<gaemstone.Client.Components.InputComponents>();
universe.Modules.Register<gaemstone.Client.Systems.InputManager>();
universe.Modules.Register<gaemstone.Client.Systems.ImGuiInputDebug>();
universe.Modules.Register<gaemstone.Client.Components.CameraComponents>();
universe.Modules.Register<gaemstone.Client.Systems.FreeCameraController>();
game.Set(new Canvas(Silk.NET.OpenGL.ContextSourceExtensions.CreateOpenGL(window)));
game.Set(new GameWindow(window));
universe.New("MainCamera")
.Set(Camera.Default3D)
.Set((GlobalTransform) Matrix4X4.CreateTranslation(0.0F, 2.0F, 0.0F))

@ -1 +1 @@
Subproject commit 3f9cf9c3793337eabf8647db6a4ac44017f20cc3
Subproject commit 2e82db165948e073b813ac712bedd00c70627d03

@ -10,13 +10,12 @@ namespace gaemstone.Client;
public readonly struct Color
: IEquatable<Color>
{
public static Color Transparent { get; } = default;
public static Color Black { get; } = FromRGB(0x000000);
public static Color White { get; } = FromRGB(0xFFFFFF);
public static readonly Color Transparent = default;
public static readonly Color Black = FromRGB(0x000000);
public static readonly Color White = FromRGB(0xFFFFFF);
[FieldOffset(0)]
public readonly uint Value;
public readonly uint RGBA;
[FieldOffset(0)]
public readonly byte R;
@ -27,25 +26,28 @@ public readonly struct Color
[FieldOffset(3)]
public readonly byte A;
private Color(uint value)
{ Unsafe.SkipInit(out this); Value = value; }
private Color(uint rgba)
{ Unsafe.SkipInit(out this); RGBA = rgba; }
private Color(byte r, byte g, byte b, byte a)
{ Unsafe.SkipInit(out this); R = r; G = g; B = b; A = a; }
public static Color FromRGBA(uint rgba) => new(rgba);
public static Color FromRGB(uint rgb) => new(rgb | 0xFF000000);
public static Color FromRGBA(byte r, byte g, byte b, byte a) => new(r, g, b, a);
public static Color FromRGB(uint rgb) => new(rgb | 0xFF000000);
public static Color FromRGB(byte r, byte g, byte b) => new(r, g, b, 0xFF);
public static Color FromGrayscale(byte gray) => new(gray, gray, gray, 0xFF);
public static Color FromGrayscale(byte gray, byte alpha) => new(gray, gray, gray, alpha);
public bool Equals(Color other)
=> Value == other.Value;
=> RGBA == other.RGBA;
public override bool Equals([NotNullWhen(true)] object? obj)
=> (obj is Color color) && Equals(color);
public override int GetHashCode()
=> Value.GetHashCode();
=> RGBA.GetHashCode();
public override string? ToString()
=> $"Color(0x{Value:X8})";
=> $"Color(0x{RGBA:X8})";
public static bool operator ==(Color left, Color right) => left.Equals(right);
public static bool operator !=(Color left, Color right) => !left.Equals(right);

@ -0,0 +1,90 @@
using System;
using System.Numerics;
using gaemstone.ECS;
namespace gaemstone.Client.Components;
[Module]
public class InputComponents
{
[Entity(Global = true)]
[Add<Input>]
public struct Input { }
[Entity("Input", "Mouse", Global = true)]
[Add<Mouse>]
public struct Mouse { }
[Entity("Input", "Keyboard", Global = true)]
[Add<Keyboard>]
public struct Keyboard { }
[Tag]
public struct Gamepad { }
/// <summary> Present on inputs / actions that are currently active. </summary>
[Component] public struct Active { public TimeSpan Duration; }
/// <summary> Present on inputs / actions were activated this frame. </summary>
[Tag] public struct Activated { }
/// <summary> Present on inputs / actions were deactivated this frame. </summary>
[Tag] public struct Deactivated { }
/// <summary>
/// Relationship on <see cref="Input"/> which indicates keyboard
/// and gamepad input is currently captured by the target.
/// </summary>
/// <remarks>
/// This is set if a UI element is focused that captures
/// navigational or text input.
/// </remarks>
[Tag, Relation, Exclusive]
public struct InputCapturedBy { }
/// <summary>
/// Relationship on <see cref="Input"/> which indicates that mouse
/// input (buttons and wheel) is currently captured by the target.
/// </summary>
/// <remarks>
/// This could for example include the mouse currently being over
/// a UI element, preventing the game from handling mouse input.
/// </remarks>
[Tag, Relation, Exclusive]
public struct MouseInputCapturedBy { }
/// <summary>
/// Relationship on <see cref="Input"/> which indicates that the
/// cursor is currently captured by the target, and thus hidden.
/// </summary>
/// <remarks>
/// This is set when a camera controller assumes control of the mouse.
/// </remarks>
[Tag, Relation, Exclusive]
[With<InputCapturedBy>]
[With<MouseInputCapturedBy>]
public struct CursorCapturedBy { }
[Private, Component]
public readonly struct RawValue1D
{
private readonly float _value;
private RawValue1D(float value) => _value = value;
public static implicit operator float(RawValue1D value) => value._value;
public static implicit operator RawValue1D(float value) => new(value);
}
[Private, Component]
public readonly struct RawValue2D
{
private readonly Vector2 _value;
private RawValue2D(Vector2 value) => _value = value;
public static implicit operator Vector2(RawValue2D value) => value._value;
public static implicit operator RawValue2D(Vector2 value) => new(value);
}
}

@ -1,16 +1,16 @@
using System;
using System.Numerics;
using gaemstone.ECS;
using Silk.NET.Input;
using Silk.NET.Maths;
using static gaemstone.Client.Components.CameraComponents;
using static gaemstone.Client.Systems.Input;
using static gaemstone.Client.Components.InputComponents;
using static gaemstone.Components.TransformComponents;
namespace gaemstone.Client.Systems;
[Module]
[DependsOn<gaemstone.Client.Components.CameraComponents>]
[DependsOn<gaemstone.Client.Systems.Input>]
[DependsOn<gaemstone.Client.Components.InputComponents>]
[DependsOn<gaemstone.Components.TransformComponents>]
public class FreeCameraController
{
@ -18,41 +18,59 @@ public class FreeCameraController
public struct CameraController
{
public float MouseSensitivity { get; set; }
public Vector2D<float>? MouseGrabbedAt { get; set; }
}
[System]
public static void UpdateCamera(TimeSpan delta, in Camera camera,
ref GlobalTransform transform, ref CameraController controller,
[Game] RawInput input)
public static void UpdateCamera(
Universe universe, TimeSpan delta,
in Camera camera, ref GlobalTransform transform, ref CameraController controller)
{
var isMouseDown = input.IsDown(MouseButton.Right);
var isMouseGrabbed = controller.MouseGrabbedAt != null;
if (isMouseDown != isMouseGrabbed) {
if (isMouseDown) controller.MouseGrabbedAt = input.MousePosition;
else controller.MouseGrabbedAt = null;
}
var input = universe.Lookup<Input>();
var mouse = universe.Lookup<Mouse>();
var keyboard = universe.Lookup<Keyboard>();
if ((input == null) || (mouse == null) || (keyboard == null)) return;
var module = universe.LookupOrThrow<FreeCameraController>();
var capturedBy = input.GetTarget<CursorCapturedBy>();
var inputCapturedBy = input.GetTarget<MouseInputCapturedBy>();
var isCaptured = (capturedBy != null);
// If another system has the mouse captured, don't do anything here.
if (isCaptured && (capturedBy != module)) return;
var mouseMoved = Vector2D<float>.Zero;
if (controller.MouseGrabbedAt is Vector2D<float> pos) {
mouseMoved = input.MousePosition - pos;
input.Context!.Mice[0].Position = pos.ToSystem();
var isMouseDown = ((inputCapturedBy == null) || (inputCapturedBy == module))
&& mouse.Lookup("Buttons/Right")?.Has<Active>() == true;
if (isMouseDown != isCaptured) {
if (isMouseDown)
input.Add<CursorCapturedBy>(module);
else {
input.Remove<CursorCapturedBy>(module);
input.Remove<InputCapturedBy>(module);
input.Remove<MouseInputCapturedBy>(module);
}
}
var dt = (float)delta.TotalSeconds;
var xMovement = mouseMoved.X * dt * controller.MouseSensitivity;
var yMovement = mouseMoved.Y * dt * controller.MouseSensitivity;
var mouseMovement = Vector2.Zero;
if (isCaptured) {
var raw = (Vector2?)mouse.Lookup("Delta")?.Get<RawValue2D>() ?? default;
mouseMovement = raw * controller.MouseSensitivity * (float)delta.TotalSeconds;
}
if (camera.IsOrthographic) {
transform *= Matrix4X4.CreateTranslation(-xMovement, -yMovement, 0);
transform *= Matrix4X4.CreateTranslation(-mouseMovement.X, -mouseMovement.Y, 0);
} else {
var speed = dt * (input.IsDown(Key.ShiftLeft) ? 12 : 4);
var forwardMovement = ((input.IsDown(Key.W) ? -1 : 0) + (input.IsDown(Key.S) ? 1 : 0)) * speed;
var sideMovement = ((input.IsDown(Key.A) ? -1 : 0) + (input.IsDown(Key.D) ? 1 : 0)) * speed;
var shift = keyboard.Lookup("ShiftLeft")?.Has<Active>() == true;
var w = keyboard.Lookup("W")?.Has<Active>() == true;
var a = keyboard.Lookup("A")?.Has<Active>() == true;
var s = keyboard.Lookup("S")?.Has<Active>() == true;
var d = keyboard.Lookup("D")?.Has<Active>() == true;
var speed = (shift ? 12 : 4) * (float)delta.TotalSeconds;
var forwardMovement = ((w ? -1 : 0) + (s ? 1 : 0)) * speed;
var sideMovement = ((a ? -1 : 0) + (d ? 1 : 0)) * speed;
var curTranslation = new Vector3D<float>(transform.Value.M41, transform.Value.M42, transform.Value.M43);
var yawRotation = Matrix4X4.CreateRotationY(-xMovement / 100, curTranslation);
var pitchRotation = Matrix4X4.CreateRotationX(-yMovement / 100);
var yawRotation = Matrix4X4.CreateRotationY(-mouseMovement.X / 100, curTranslation);
var pitchRotation = Matrix4X4.CreateRotationX(-mouseMovement.Y / 100);
var translation = Matrix4X4.CreateTranslation(sideMovement, 0, forwardMovement);
transform = translation * pitchRotation * transform * yawRotation;

@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using gaemstone.ECS;
using ImGuiNET;
using Silk.NET.GLFW;
using Silk.NET.Input;
using static gaemstone.Client.Components.InputComponents;
using static gaemstone.Client.Systems.ImGuiManager;
namespace gaemstone.Client.Systems;
[Module]
[DependsOn<gaemstone.Client.Components.InputComponents>]
[DependsOn<gaemstone.Client.Systems.ImGuiManager>]
public class ImGuiInputDebug
{
[System]
public static void ShowInputDebugWindow(Universe universe, ImGuiData _)
{
var input = universe.Lookup<Input>();
if (input == null) return;
ImGui.Begin("Input Information", ImGuiWindowFlags.NoResize);
if (universe.Lookup<Keyboard>() is EntityRef keyboard)
DrawKeyboard(keyboard);
if (universe.Lookup<Mouse>() is EntityRef mouse) {
ImGui.BeginChild("Mouse Info", new(160, 180), true);
ImGui.Text("Position: " + (Vector2?)mouse.Lookup("Position")?.MaybeGet<RawValue2D>());
ImGui.Text("Delta: " + (Vector2?)mouse.Lookup("Delta" )?.MaybeGet<RawValue2D>());
ImGui.Text("Wheel: " + (float?) mouse.Lookup("Wheel" )?.MaybeGet<RawValue1D>());
ImGui.Spacing();
var buttons = mouse.Lookup("Buttons")?.GetChildren().ToArray() ?? Array.Empty<EntityRef>();
ImGui.Text("Buttons: " + string.Join(" ", buttons
.Where (button => button.Has<Active>())
.Select(button => $"{button.Name} ({button.Get<Active>().Duration.TotalSeconds:f2}s)")));
ImGui.Text(" Pressed: " + string.Join(" ", buttons
.Where (button => button.Has<Activated>())
.Select(button => button.Name)));
ImGui.Text(" Released: " + string.Join(" ", buttons
.Where (button => button.Has<Deactivated>())
.Select(button => button.Name)));
ImGui.EndChild();
}
for (var index = 0; input.Lookup("Gamepad" + index) is EntityRef gamepad; index++) {
ImGui.SameLine();
ImGui.BeginChild($"{gamepad.Name} Info", new(160, 180), true);
var buttons = gamepad.Lookup("Buttons")?.GetChildren().ToArray() ?? Array.Empty<EntityRef>();
ImGui.Text("Buttons: " + string.Join(" ", buttons.Where(b => b.Has<Active>())
.Select(b => $"{b.Name} ({b.Get<Active>().Duration.TotalSeconds:f2}s)")));
ImGui.Text(" Pressed: " + string.Join(" ", buttons.Where(b => b.Has<Activated>()).Select(b => b.Name)));
ImGui.Text(" Released: " + string.Join(" ", buttons.Where(b => b.Has<Deactivated>()).Select(b => b.Name)));
ImGui.Spacing();
ImGui.Text("Triggers:");
for (var i = 0; gamepad.Lookup("Trigger" + i) is EntityRef trigger; i++) {
var text = $" {i}: {(float?)trigger.MaybeGet<RawValue1D>() ?? default:f2}";
if (trigger.Has<Activated>()) text += " pressed!";
else if (trigger.Has<Deactivated>()) text += " released!";
else if (trigger.MaybeGet<Active>() is Active active)
text += $" ({active.Duration.TotalSeconds:f2}s)";
ImGui.Text(text);
}
ImGui.Text("Thumbsticks:");
for (var i = 0; gamepad.Lookup("Thumbstick" + i) is EntityRef thumbstick; i++)
ImGui.Text($" {i}: {(Vector2?)thumbstick.MaybeGet<RawValue2D>() ?? default:f2}");
ImGui.EndChild();
}
ImGui.End();
}
private const float U = 1.00F;
private const float SM = 1.25F;
// Spacing (invisible)
private const float I = -0.50F;
private const float _ = -1.00F;
private const float ER = -1.25F;
// Special
private const float T = -11.00F;
private const float ENT = -11.50F;
private static readonly float[][] KeyboardLayout = {
new[] { U, _, U, U, U, U,I,U, U, U, U,I, U, U, U, U, I,U, U, U },
new[] { U, U, U, U, U, U, U, U, U, U, U, U, U, 2.0F, I,U, U, U,I, U, U, U, U },
new[] { 1.5F, U, U, U, U, U, U, U, U, U, U, U, U, ENT, I,U, U, U,I, U, U, U, T },
new[] { 1.75F, U, U, U, U, U, U, U, U, U, U, U, U, ER, I,_, _, _,I, U, U, U, _ },
new[] { SM, U, U, U, U, U, U, U, U, U, U, U, 2.75F, I,_, U, _,I, U, U, U, T },
new[] { SM, SM, SM, 6.25F, SM, SM, SM, SM, I,U, U, U,I, 2.0F, U, _ },
};
private static readonly Key?[][] KeyboardKeys = {
new Key?[] { Key.Escape, null, Key.F1, Key.F2, Key.F3, Key.F4, null, Key.F5, Key.F6, Key.F7, Key.F8, null, Key.F9, Key.F10, Key.F11, Key.F12, null, Key.PrintScreen, Key.ScrollLock, Key.Pause },
new Key?[] { Key.GraveAccent, Key.Number1, Key.Number2, Key.Number3, Key.Number4, Key.Number5, Key.Number6, Key.Number7, Key.Number8, Key.Number9, Key.Number0, Key.Minus, Key.Equal, Key.Backspace, null, Key.Insert, Key.Home, Key.PageUp, null, Key.NumLock, Key.KeypadDivide, Key.KeypadMultiply, Key.KeypadSubtract },
new Key?[] { Key.Tab, Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P, Key.LeftBracket, Key.RightBracket, Key.Enter, null, Key.Delete, Key.End, Key.PageDown, null, Key.Keypad7, Key.Keypad8, Key.Keypad9, Key.KeypadAdd },
new Key?[] { Key.CapsLock, Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L, Key.Semicolon, Key.Apostrophe, Key.BackSlash, null, null, null, null, null, null, Key.Keypad4, Key.Keypad5, Key.Keypad6, null },
new Key?[] { Key.ShiftLeft, Key.World1, Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M, Key.Comma, Key.Period, Key.Slash, Key.ShiftRight, null, null, Key.Up, null, null, Key.Keypad1, Key.Keypad2, Key.Keypad3, Key.KeypadEnter },
new Key?[] { Key.ControlLeft, Key.SuperLeft, Key.AltLeft, Key.Space, Key.AltRight, Key.SuperRight, Key.Menu, Key.ControlRight, null, Key.Left, Key.Down, Key.Right, null, Key.Keypad0, Key.KeypadDecimal, null },
};
private static readonly Dictionary<Key, string> KeyToNameMapping = new() {
[Key.Escape] = "Ecs", [Key.PrintScreen] = "Prn\nScr", [Key.ScrollLock] = "Scr\nLck", [Key.Pause] = "Pause",
[Key.F1] = "F1", [Key.F2] = "F2", [Key.F3] = "F3", [Key.F4] = "F4", [Key.F5] = "F5", [Key.F6] = "F6",
[Key.F7] = "F7", [Key.F8] = "F8", [Key.F9] = "F9", [Key.F10] = "F10", [Key.F11] = "F11", [Key.F12] = "F12",
[Key.Tab] = "Tab", [Key.CapsLock] = "Caps\nLock", [Key.Menu] = "Menu",
[Key.Backspace] = "Backspace", [Key.Enter] = "Enter",
[Key.ControlLeft] = "Ctrl", [Key.ControlRight] = "Ctrl",
[Key.ShiftLeft] = "Shift", [Key.ShiftRight] = "Shift",
[Key.AltLeft] = "Alt", [Key.AltRight] = "Alt",
[Key.SuperLeft] = "Super", [Key.SuperRight] = "Super",
[Key.Insert] = "Ins", [Key.Delete] = "Del",
[Key.Home] = "Home", [Key.End] = "End",
[Key.PageUp] = "PgUp", [Key.PageDown] = "PgDn",
[Key.NumLock] = "Num\nLck", [Key.KeypadEnter] = "Enter",
};
public static void DrawKeyboard(EntityRef keyboard)
{
var GLFW = Glfw.GetApi();
const float UnitKeySize = 32.0F;
Vector2 Size = new Vector2(23, 6.5F) * UnitKeySize;
Vector2 Border = new(1, 1);
Vector2 LabelOffset = new(7, 3);
Vector2 FaceStartOffset = new(5, 3);
Vector2 FaceEndOffset = new(5, 6);
uint BorderColor = Color.FromGrayscale(24).RGBA;
uint LabelColor = Color.FromGrayscale(64).RGBA;
var draw = ImGui.GetWindowDrawList();
var offset = ImGui.GetCursorScreenPos();
var current = Vector2.Zero;
foreach (var (widths, keys) in KeyboardLayout.Zip(KeyboardKeys)) {
foreach (var (width, key) in widths.Zip(keys)) {
uint KeyColor(byte lightness)
=> (key != null) && (keyboard.Lookup(key.Value.ToString())?.Has<Active>() == true)
? Color.FromRGB(lightness, (byte)(lightness / 2), (byte)(lightness / 2)).RGBA
: Color.FromGrayscale(lightness).RGBA;
var start = offset + current;
var keySize = new Vector2(width, 1.0F);
if (width == T) keySize = new Vector2((-keySize.X - 10), 2.0F);
else if (width < -10) keySize = new Vector2((-keySize.X - 10), 1.0F);
else if (width < 0) { current += new Vector2(-keySize.X * UnitKeySize, 0); continue; }
var label = (key != null) ? KeyToNameMapping.GetValueOrDefault(key.Value)
?? GLFW.GetKeyName((int)key.Value, 0)?.ToUpper() : null;
var end = start + keySize * UnitKeySize;
if (width == ENT) {
var start2 = start + new Vector2(0.25F * UnitKeySize, 0);
var end2 = end + new Vector2(0, 1 * UnitKeySize);
draw.AddRectFilled(start , end , BorderColor, 3);
draw.AddRectFilled(start2 , end2 , BorderColor, 3);
draw.AddRectFilled(start + Border, end - Border, KeyColor(204), 3);
draw.AddRectFilled(start2 + Border, end2 - Border, KeyColor(204), 3);
var faceStart = start + FaceStartOffset;
var faceEnd = end - FaceEndOffset;
draw.AddRectFilled(faceStart , faceEnd , KeyColor(252), 2);
var faceStart2 = start2 + FaceStartOffset;
var faceEnd2 = end2 - FaceEndOffset;
draw.AddRectFilled(faceStart2, faceEnd2, KeyColor(252), 2);
if (label != null) draw.AddText(start + LabelOffset, LabelColor, label);
} else {
draw.AddRectFilled(start , end , BorderColor, 3);
draw.AddRectFilled(start + Border, end - Border, KeyColor(204), 3);
var faceStart = start + FaceStartOffset;
var faceEnd = end - FaceEndOffset;
draw.AddRectFilled(faceStart, faceEnd, KeyColor(252), 2);
if (label != null) draw.AddText(start + LabelOffset, LabelColor, label);
}
current += new Vector2(keySize.X * UnitKeySize, 0);
}
current = new Vector2(0, current.Y + UnitKeySize);
if (widths == KeyboardLayout[0]) current += new Vector2(0, UnitKeySize / 2);
}
ImGui.Dummy(Size);
}
}

@ -0,0 +1,120 @@
using System;
using gaemstone.ECS;
using gaemstone.Flecs;
using ImGuiNET;
using Silk.NET.Input;
using Silk.NET.OpenGL.Extensions.ImGui;
using static gaemstone.Client.Components.InputComponents;
using static gaemstone.Client.Systems.Windowing;
namespace gaemstone.Client.Systems;
[Module]
[DependsOn<gaemstone.Client.Components.InputComponents>]
[DependsOn<gaemstone.Client.Systems.InputManager>]
[DependsOn<gaemstone.Client.Systems.Windowing>]
public class ImGuiManager
{
[Entity, Add<Pipeline.Phase>]
[DependsOn<SystemPhase.OnLoad>]
public struct ImGuiUpdatePhase { }
[Entity, Add<Pipeline.Phase>]
[DependsOn<SystemPhase.OnStore>]
public struct ImGuiRenderPhase { }
[Component, Singleton]
public class ImGuiData
{
public ImGuiController Controller { get; }
internal ImGuiData(ImGuiController controller) => Controller = controller;
}
[System<SystemPhase.OnLoad>]
public unsafe void Initialize(Universe universe, GameWindow window, Canvas canvas,
[Source<Input>] IInputContext inputContext, [Not] ImGuiData _)
=> universe.LookupOrThrow<ImGuiData>().Set(new ImGuiData(
new(canvas.GL, window.Handle, inputContext, () => {
var IO = ImGui.GetIO();
// Do not save a settings or log file.
IO.NativePtr->IniFilename = null;
IO.NativePtr->LogFilename = null;
IO.BackendFlags |= ImGuiBackendFlags.HasMouseCursors
| ImGuiBackendFlags.HasSetMousePos;
IO.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard
| ImGuiConfigFlags.NavEnableSetMousePos;
IO.ConfigWindowsResizeFromEdges = true;
// Set up key mappings.
foreach (var imguiKey in Enum.GetValues<ImGuiKey>()) {
var name = imguiKey.ToString();
// Adjust ImGuiKey enum names to match Silk.NET.Input.Key enum.
if (name.StartsWith("_")) name = name.Replace("_", "Number");
if (name.EndsWith("Arrow")) name = name[..^"Arrow".Length];
if (name.EndsWith("Ctrl" )) name = name.Replace("Ctrl", "Control");
if (!name.EndsWith("Bracket")) { // Leave "LeftBracket" and "RightBracket" alone.
if (name.StartsWith("Left" )) name = name["Left" .Length..] + "Left";
if (name.StartsWith("Right")) name = name["Right".Length..] + "Right";
}
if (Enum.TryParse<Key>(name, true, out var silkKey))
IO.KeyMap[(int)imguiKey] = (int)silkKey;
}
})));
[System<SystemPhase.OnLoad>]
public static void UpdateMouse(Universe universe,
[Source<Mouse>] IMouse impl, ImGuiData _)
{
var input = universe.LookupOrThrow<Input>();
var module = universe.LookupOrThrow<ImGuiManager>();
var capturedBy = input.GetTarget<MouseInputCapturedBy>();
var isCaptured = (capturedBy != null);
// If another system has the mouse captured, don't do anything here.
if (isCaptured && (capturedBy != module)) return;
var IO = ImGui.GetIO();
// Set the mouse position if ImGui wants to move it.
if (IO.WantSetMousePos) impl.Position = IO.MousePos;
// Capture the mouse input it is above GUI elements.
if (IO.WantCaptureMouse != isCaptured) {
if (IO.WantCaptureMouse) input.Add <MouseInputCapturedBy>(module);
else input.Remove<MouseInputCapturedBy>(module);
}
var cursor = ImGui.GetMouseCursor();
impl.Cursor.CursorMode = (cursor == ImGuiMouseCursor.None)
? CursorMode.Hidden : CursorMode.Normal;
// TODO: Use additional cursors once Silk.NET supports GLFW 3.4.
impl.Cursor.StandardCursor = cursor switch {
ImGuiMouseCursor.Arrow => StandardCursor.Arrow,
ImGuiMouseCursor.TextInput => StandardCursor.IBeam,
// ImGuiMouseCursor.ResizeAll => StandardCursor.,
ImGuiMouseCursor.ResizeNS => StandardCursor.VResize,
ImGuiMouseCursor.ResizeEW => StandardCursor.HResize,
// ImGuiMouseCursor.ResizeNESW => StandardCursor.,
// ImGuiMouseCursor.ResizeNWSE => StandardCursor.,
ImGuiMouseCursor.Hand => StandardCursor.Hand,
// ImGuiMouseCursor.NotAllowed => StandardCursor.,
_ => StandardCursor.Default,
};
}
[System<ImGuiUpdatePhase>]
public static void Update(TimeSpan delta, ImGuiData imgui)
=> imgui.Controller.Update((float)delta.TotalSeconds);
[System]
public static void ShowDemoWindow(ImGuiData _)
=> ImGui.ShowDemoWindow();
[System<ImGuiRenderPhase>]
public static void Render(ImGuiData imgui)
=> imgui.Controller.Render();
}

@ -1,83 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.Input;
using Silk.NET.Maths;
using static gaemstone.Client.Systems.Windowing;
namespace gaemstone.Client.Systems;
[Module]
[DependsOn<gaemstone.Client.Systems.Windowing>]
public class Input
{
[Component]
public class RawInput
{
internal IInputContext? Context { get; set; }
public Dictionary<Key, ButtonState> Keyboard { get; } = new();
public Dictionary<MouseButton, ButtonState> MouseButtons { get; } = new();
public Vector2D<float> MousePosition { get; set; }
public float MouseWheel { get; set; }
public float MouseWheelDelta { get; set; }
public bool IsDown(Key key) => Keyboard.GetValueOrDefault(key)?.IsDown == true;
public bool IsDown(MouseButton button) => MouseButtons.GetValueOrDefault(button)?.IsDown == true;
}
public class ButtonState
{
public TimeSpan TimePressed;
public bool IsDown;
public bool Pressed;
public bool Released;
}
[System<SystemPhase.OnLoad>]
public static void ProcessInput(TimeSpan delta,
GameWindow window, RawInput input)
{
window.Handle.DoEvents();
input.Context ??= window.Handle.CreateInput();
foreach (var state in input.Keyboard.Values.Concat(input.MouseButtons.Values)) {
if (state.IsDown) state.TimePressed += delta;
state.Pressed = state.Released = false;
}
var keyboard = input.Context.Keyboards[0];
foreach (var key in keyboard.SupportedKeys) {
var state = input.Keyboard.GetValueOrDefault(key);
if (keyboard.IsKeyPressed(key)) {
if (state == null) input.Keyboard.Add(key, state = new());
if (!state.IsDown) state.Pressed = true;
state.IsDown = true;
} else if (state != null) {
if (state.IsDown) state.Released = true;
state.IsDown = false;
}
}
var mouse = input.Context.Mice[0];
foreach (var button in mouse.SupportedButtons) {
var state = input.MouseButtons.GetValueOrDefault(button);
if (mouse.IsButtonPressed(button)) {
if (state == null) input.MouseButtons.Add(button, state = new());
if (!state.IsDown) state.Pressed = true;
state.IsDown = true;
} else if (state != null) {
if (state.IsDown) state.Released = true;
state.IsDown = false;
}
}
input.MousePosition = mouse.Position.ToGeneric();
input.MouseWheelDelta += mouse.ScrollWheels[0].Y - input.MouseWheel;
input.MouseWheel = mouse.ScrollWheels[0].Y;
}
}

@ -0,0 +1,141 @@
using System;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.Input;
using static gaemstone.Client.Components.InputComponents;
using static gaemstone.Client.Systems.Windowing;
namespace gaemstone.Client.Systems;
[Module]
[DependsOn<gaemstone.Client.Components.InputComponents>]
[DependsOn<gaemstone.Client.Systems.Windowing>]
public class InputManager
{
[Component("InputContext"), Proxy<IInputContext>] public struct ContextProxy { }
[Component("MouseImpl" ), Proxy<IMouse >] public struct MouseProxy { }
[Component("KeyboardImpl"), Proxy<IKeyboard>] public struct KeyboardProxy { }
[Component("GamepadImpl" ), Proxy<IGamepad >] public struct GamepadProxy { }
[System<SystemPhase.OnLoad>]
public static void Initialize(Universe universe,
[Game] GameWindow window, [Source<Input>, Not] IInputContext _)
{
var input = universe.LookupOrThrow<Input>();
var context = window.Handle.CreateInput();
input.Set(context);
// TODO: Add device names as documentation names to these entities.
foreach (var impl in context.Mice.Take(1))
input.LookupOrThrow("Mouse").Set(impl);
foreach (var impl in context.Keyboards.Take(1))
input.LookupOrThrow("Keyboard").Set(impl);
foreach (var impl in context.Gamepads)
input.NewChild("Gamepad" + impl.Index).Add<Gamepad>().Set(impl).Build();
// TODO: Should we even support joysticks?
}
[Observer<ObserverEvent.OnAdd>]
[Expression("CursorCapturedBy(Input, *)")]
public static void OnCursorCaptured(Universe universe)
=> universe.LookupOrThrow<Mouse>().Get<IMouse>()
.Cursor.CursorMode = CursorMode.Raw;
[Observer<ObserverEvent.OnRemove>]
[Expression("CursorCapturedBy(Input, *)")]
public static void OnCursorReleased(Universe universe)
=> universe.LookupOrThrow<Mouse>().Get<IMouse>()
.Cursor.CursorMode = CursorMode.Normal;
[System<SystemPhase.OnLoad>]
public static void ProcessMouse(TimeSpan delta, EntityRef mouse, IMouse impl)
{
var isCaptured = mouse.Parent!.Has<CursorCapturedBy, Core.Any>();
ref var position = ref mouse.NewChild("Position").Build().GetMut<RawValue2D>();
ref var posDelta = ref mouse.NewChild("Delta" ).Build().GetMut<RawValue2D>();
posDelta = impl.Position - position;
if (isCaptured) impl.Position = position;
else position = impl.Position;
Update1D(delta, mouse.NewChild("Wheel").Build(), impl.ScrollWheels[0].Y);
var buttons = mouse.NewChild("Buttons").Build();
foreach (var button in impl.SupportedButtons)
Update1D(delta, buttons.NewChild(button.ToString()).Build(),
impl.IsButtonPressed(button) ? 1 : 0);
}
[System<SystemPhase.OnLoad>]
public static void ProcessKeyboard(TimeSpan delta, EntityRef keyboard, IKeyboard impl)
{
foreach (var key in impl.SupportedKeys) {
var entity = keyboard.NewChild(key.ToString()).Build();
Update1D(delta, entity, impl.IsKeyPressed(key) ? 1 : 0);
}
}
[System<SystemPhase.OnLoad>]
public static void ProcessGamepad(TimeSpan delta, EntityRef gamepad, IGamepad impl)
{
var buttons = gamepad.NewChild("Buttons").Build();
foreach (var button in impl.Buttons)
Update1D(delta, buttons.NewChild(button.Name.ToString()).Build(), button.Pressed ? 1 : 0);
foreach (var trigger in impl.Triggers)
Update1D(delta, gamepad.NewChild("Trigger" + trigger.Index).Build(), trigger.Position);
foreach (var thumbstick in impl.Thumbsticks)
Update2D(delta, gamepad.NewChild("Thumbstick" + thumbstick.Index).Build(), new(thumbstick.X, thumbstick.Y));
}
private const float ActivationThreshold = 0.90F;
private const float DeactivationThreshold = 0.75F;
private static void Update1D(TimeSpan delta, EntityRef entity, float current)
{
entity.GetMut<RawValue1D>() = current;
if (current >= ActivationThreshold) {
ref var active = ref entity.GetRefOrNull<Active>();
if (Unsafe.IsNullRef(ref active)) {
entity.Set(new Active());
entity.Add<Activated>();
} else active.Duration += delta;
} else if (current <= DeactivationThreshold)
entity.Remove<Active>();
}
private static void Update2D(TimeSpan delta, EntityRef entity, Vector2 current)
{
entity.GetMut<RawValue2D>() = current;
var magnitude = current.Length();
if (magnitude >= ActivationThreshold) {
ref var active = ref entity.GetRefOrNull<Active>();
if (Unsafe.IsNullRef(ref active)) {
entity.Set(new Active());
entity.Add<Activated>();
} else active.Duration += delta;
} else if (magnitude <= DeactivationThreshold)
entity.Remove<Active>();
}
// TODO: flecs calls OnAdd observers repeatedly as the entity has other things added to it.
// So at least for now we need to add Activated manually, until this changes.
// [Observer<ObserverEvent.OnAdd>]
// public static void OnActiveAdded(EntityRef entity, Active _)
// => entity.Add<Activated>();
[Observer<ObserverEvent.OnRemove>]
public static void OnActiveRemoved(EntityRef entity, Active _)
=> entity.Add<Deactivated>();
[System<SystemPhase.PostFrame>]
// [Expression("Activated || Deactivated")]
public static void ClearDeActivated(EntityRef entity, [Or] Activated _1, [Or] Deactivated _2)
=> entity.Remove<Activated>().Remove<Deactivated>();
}

@ -20,16 +20,16 @@ namespace gaemstone.Client.Systems;
[DependsOn<gaemstone.Client.Systems.Windowing>]
[DependsOn<gaemstone.Components.TransformComponents>]
public class Renderer
: IModuleInitializer
{
private uint _program;
private int _cameraMatrixUniform;
private int _modelMatrixUniform;
private Rule? _renderEntityRule;
public void Initialize(EntityRef module)
[Observer<ObserverEvent.OnSet>]
public void OnCanvasSet(Canvas canvas)
{
var GL = module.Universe.LookupOrThrow<Game>().Get<Canvas>().GL;
var GL = canvas.GL;
GL.Enable(EnableCap.DebugOutputSynchronous);
GL.DebugMessageCallback(DebugCallback, 0);
@ -59,8 +59,8 @@ public class Renderer
{
var GL = canvas.GL;
GL.UseProgram(_program);
GL.Viewport(default, canvas.Size);
GL.ClearColor(new Vector4D<float>(0, 0, 0, 255));
GL.Viewport(canvas.Size);
GL.ClearColor(Color.Black);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
}

@ -1,6 +1,7 @@
using System;
using System.IO;
using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.OpenGL;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@ -15,12 +16,11 @@ namespace gaemstone.Client.Systems;
[DependsOn<gaemstone.Client.Components.ResourceComponents>]
[DependsOn<gaemstone.Client.Systems.Windowing>]
public class TextureManager
: IModuleInitializer
{
public void Initialize(EntityRef module)
[Observer<ObserverEvent.OnSet>]
public static void OnCanvasSet(Canvas canvas)
{
var GL = module.Universe.LookupOrThrow<Game>().Get<Canvas>().GL;
var GL = canvas.GL;
// Upload single-pixel white texture into texture slot 0, so when
// "no" texture is bound, we can still use the texture sampler.
GL.BindTexture(TextureTarget.Texture2D, 0);

@ -28,5 +28,8 @@ public class Windowing
[System<SystemPhase.PreFrame>]
public static void ProcessWindow(GameWindow window, Canvas canvas)
=> canvas.Size = window.Handle.Size;
{
canvas.Size = window.Handle.Size;
window.Handle.DoEvents();
}
}

@ -20,6 +20,7 @@
<ItemGroup>
<PackageReference Include="SharpGLTF.Toolkit" Version="1.0.0-alpha0026" />
<PackageReference Include="Silk.NET" Version="2.16.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
</ItemGroup>

@ -107,9 +107,9 @@ public static class SystemExtensions
[UnmanagedCallersOnly]
private static unsafe void Run(ecs_iter_t* flecsIter)
{
// This is what flecs does, so I guess we'll do it too!
var callback = CallbackContextHelper.Get<CallbackContext>((nint)flecsIter->binding_ctx);
// This is what flecs does, so I guess we'll do it too!
var type = (&flecsIter->next == (delegate*<ecs_iter_t*, Runtime.CBool>)&ecs_query_next)
? IteratorType.Query : (IteratorType?)null;
using var iter = new Iterator(callback.Universe, type, *flecsIter);

@ -74,6 +74,8 @@ public class ModuleManager
private void TryEnableModule(ModuleInfo module)
{
if (module.UnmetDependencies.Count > 0) return;
Console.WriteLine($"Enabling module {module.Path} ...");
module.Enable();
// Find other modules that might be missing this module as a dependency.
@ -87,8 +89,6 @@ public class ModuleManager
TryEnableModule(other);
}
Console.WriteLine("Enabled module " + module.Path);
}
public static EntityPath GetModulePath(Type type)
@ -102,7 +102,7 @@ public class ModuleManager
// If global or path are specified in the attribute, return an absolute path.
if (global || attr.Path != null)
return new(global, attr.Path ?? new[] { type.Name });
return new(true, attr.Path ?? new[] { type.Name });
// Otherwise, create it based on the type's assembly, namespace and name.
var assemblyName = type.Assembly.GetName().Name!;
@ -140,7 +140,7 @@ internal class ModuleInfo
if (Type.GetConstructor(Type.EmptyTypes) == null) throw new Exception(
$"Module {Type} must define public parameterless constructor");
var module = Universe.New(path).Add<Module>();
var module = Universe.New(Path).Add<Module>();
// Add module dependencies from [DependsOn<>] attributes.
foreach (var dependsAttr in Type.GetMultiple<AddRelationAttribute>().Where(attr =>
@ -156,7 +156,7 @@ internal class ModuleInfo
module.Add<DependsOn>(dependency);
}
Entity = module.Build().CreateLookup(type);
Entity = module.Build().CreateLookup(Type);
}
public void Enable()

Loading…
Cancel
Save