parent
c559c40243
commit
dca2275c4d
16 changed files with 633 additions and 146 deletions
@ -1 +1 @@ |
||||
Subproject commit 5923df5d822f7610100d0e77f629c607ed64934a |
||||
Subproject commit 95900f7372d9aad1691cfeabf45103a132a4664f |
@ -1 +1 @@ |
||||
Subproject commit 3f9cf9c3793337eabf8647db6a4ac44017f20cc3 |
||||
Subproject commit 2e82db165948e073b813ac712bedd00c70627d03 |
@ -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); |
||||
} |
||||
} |
@ -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>(); |
||||
} |
Loading…
Reference in new issue