mirror of
https://github.com/ZuneDev/MicrosoftIris.git
synced 2026-07-27 13:13:29 -07:00
Add Silk.NET.Input handling and hit-testing
This commit is contained in:
@@ -1,13 +1,22 @@
|
|||||||
|
using Microsoft.Iris.Input;
|
||||||
|
|
||||||
namespace Microsoft.Iris.Render.OpenGL
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the current raw-input callback sink. The window (see <see cref="GLRenderWindow"/>)
|
/// Holds the current raw-input callback sink and mouse-capture site. The input
|
||||||
/// translates Silk.NET input events and dispatches them here.
|
/// translator (see <see cref="GLInputTranslator"/>) reads <see cref="Callbacks"/>
|
||||||
|
/// each event and dispatches translated Silk.NET input to it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GLInputSystem : IInputSystem
|
public sealed class GLInputSystem : IInputSystem
|
||||||
{
|
{
|
||||||
public IRawInputCallbacks? Callbacks { get; private set; }
|
public IRawInputCallbacks? Callbacks { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The visual that has grabbed the mouse via <see cref="IRenderWindow.SetCapture"/>,
|
||||||
|
/// or null. When set, mouse events are routed to it regardless of hit-testing.
|
||||||
|
/// </summary>
|
||||||
|
public IRawInputSite? CaptureSite { get; set; }
|
||||||
|
|
||||||
public void RegisterRawInputCallbacks(IRawInputCallbacks handlers) => Callbacks = handlers;
|
public void RegisterRawInputCallbacks(IRawInputCallbacks handlers) => Callbacks = handlers;
|
||||||
|
|
||||||
public void UnregisterRawInputCallbacks() => Callbacks = null;
|
public void UnregisterRawInputCallbacks() => Callbacks = null;
|
||||||
|
|||||||
@@ -0,0 +1,326 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Iris.Input;
|
||||||
|
using Silk.NET.Input;
|
||||||
|
using SilkKey = Silk.NET.Input.Key;
|
||||||
|
using SilkMouseButton = Silk.NET.Input.MouseButton;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Render.OpenGL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Translates Silk.NET.Input keyboard/mouse events into the render API's raw-input
|
||||||
|
/// protocol (<see cref="IRawInputCallbacks"/>). Message identifiers match what the
|
||||||
|
/// UIX <c>InputManager</c> expects: keyboard uses the <see cref="KeyboardMessageId"/>
|
||||||
|
/// ordinals, mouse uses Win32 <c>WM_*</c> codes.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GLInputTranslator : IDisposable
|
||||||
|
{
|
||||||
|
// Win32 mouse messages (the values MouseDevice.OnRawInput switches on).
|
||||||
|
private const uint WM_MOUSEMOVE = 0x0200;
|
||||||
|
private const uint WM_LBUTTONDOWN = 0x0201, WM_LBUTTONUP = 0x0202, WM_LBUTTONDBLCLK = 0x0203;
|
||||||
|
private const uint WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205, WM_RBUTTONDBLCLK = 0x0206;
|
||||||
|
private const uint WM_MBUTTONDOWN = 0x0207, WM_MBUTTONUP = 0x0208, WM_MBUTTONDBLCLK = 0x0209;
|
||||||
|
private const uint WM_MOUSEWHEEL = 0x020A;
|
||||||
|
private const uint WM_XBUTTONDOWN = 0x020B, WM_XBUTTONUP = 0x020C, WM_XBUTTONDBLCLK = 0x020D;
|
||||||
|
|
||||||
|
private readonly IInputContext m_context;
|
||||||
|
private readonly GLInputSystem m_input;
|
||||||
|
private readonly GLRenderWindow m_window;
|
||||||
|
|
||||||
|
// Per-key repeat counters so RawKeyboardData._repCount reflects auto-repeat,
|
||||||
|
// which the InputManager uses for key coalescing.
|
||||||
|
private readonly Dictionary<SilkKey, uint> m_repeat = new Dictionary<SilkKey, uint>();
|
||||||
|
|
||||||
|
public GLInputTranslator(IInputContext context, GLInputSystem input, GLRenderWindow window)
|
||||||
|
{
|
||||||
|
m_context = context;
|
||||||
|
m_input = input;
|
||||||
|
m_window = window;
|
||||||
|
|
||||||
|
foreach (IKeyboard keyboard in context.Keyboards)
|
||||||
|
Hook(keyboard);
|
||||||
|
foreach (IMouse mouse in context.Mice)
|
||||||
|
Hook(mouse);
|
||||||
|
|
||||||
|
context.ConnectionChanged += OnConnectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConnectionChanged(IInputDevice device, bool connected)
|
||||||
|
{
|
||||||
|
if (!connected)
|
||||||
|
return;
|
||||||
|
if (device is IKeyboard keyboard)
|
||||||
|
Hook(keyboard);
|
||||||
|
else if (device is IMouse mouse)
|
||||||
|
Hook(mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Hook(IKeyboard keyboard)
|
||||||
|
{
|
||||||
|
keyboard.KeyDown += OnKeyDown;
|
||||||
|
keyboard.KeyUp += OnKeyUp;
|
||||||
|
keyboard.KeyChar += OnKeyChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Hook(IMouse mouse)
|
||||||
|
{
|
||||||
|
mouse.MouseMove += OnMouseMove;
|
||||||
|
mouse.MouseDown += OnMouseDown;
|
||||||
|
mouse.MouseUp += OnMouseUp;
|
||||||
|
mouse.Scroll += OnScroll;
|
||||||
|
mouse.DoubleClick += OnDoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Keyboard ------------------------------------------------------------
|
||||||
|
private void OnKeyDown(IKeyboard keyboard, SilkKey key, int scanCode)
|
||||||
|
{
|
||||||
|
IRawInputCallbacks? cb = m_input.Callbacks;
|
||||||
|
if (cb == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint repeat = m_repeat.TryGetValue(key, out uint prev) ? prev + 1 : 1;
|
||||||
|
m_repeat[key] = repeat;
|
||||||
|
|
||||||
|
bool system = IsAltDown();
|
||||||
|
uint message = (uint)(system ? KeyboardMessageId.SysDown : KeyboardMessageId.Down);
|
||||||
|
var data = new RawKeyboardData(MapKey(key), scanCode, repeat, 0, InputDeviceType.Keyboard);
|
||||||
|
cb.HandleRawKeyboardInput(message, ComputeModifiers(), ref data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyUp(IKeyboard keyboard, SilkKey key, int scanCode)
|
||||||
|
{
|
||||||
|
m_repeat.Remove(key);
|
||||||
|
|
||||||
|
IRawInputCallbacks? cb = m_input.Callbacks;
|
||||||
|
if (cb == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool system = IsAltDown();
|
||||||
|
uint message = (uint)(system ? KeyboardMessageId.SysUp : KeyboardMessageId.Up);
|
||||||
|
var data = new RawKeyboardData(MapKey(key), scanCode, 1, 0, InputDeviceType.Keyboard);
|
||||||
|
cb.HandleRawKeyboardInput(message, ComputeModifiers(), ref data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyChar(IKeyboard keyboard, char character)
|
||||||
|
{
|
||||||
|
IRawInputCallbacks? cb = m_input.Callbacks;
|
||||||
|
if (cb == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool system = IsAltDown();
|
||||||
|
uint message = (uint)(system ? KeyboardMessageId.SysChar : KeyboardMessageId.Char);
|
||||||
|
// Character messages carry the char in _virtualKey (see KeyboardDevice.OnRawKeyCharacter).
|
||||||
|
var data = new RawKeyboardData((Keys)character, 0, 1, 0, InputDeviceType.Keyboard);
|
||||||
|
cb.HandleRawKeyboardInput(message, ComputeModifiers(), ref data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Mouse ---------------------------------------------------------------
|
||||||
|
private void OnMouseMove(IMouse mouse, System.Numerics.Vector2 position)
|
||||||
|
=> DispatchMouse(mouse, WM_MOUSEMOVE, MouseButtons.None, 0);
|
||||||
|
|
||||||
|
private void OnMouseDown(IMouse mouse, SilkMouseButton button)
|
||||||
|
{
|
||||||
|
(uint message, MouseButtons iris) = button switch
|
||||||
|
{
|
||||||
|
SilkMouseButton.Left => (WM_LBUTTONDOWN, MouseButtons.Left),
|
||||||
|
SilkMouseButton.Right => (WM_RBUTTONDOWN, MouseButtons.Right),
|
||||||
|
SilkMouseButton.Middle => (WM_MBUTTONDOWN, MouseButtons.Middle),
|
||||||
|
SilkMouseButton.Button4 => (WM_XBUTTONDOWN, MouseButtons.XButton1),
|
||||||
|
SilkMouseButton.Button5 => (WM_XBUTTONDOWN, MouseButtons.XButton2),
|
||||||
|
_ => (0u, MouseButtons.None),
|
||||||
|
};
|
||||||
|
if (message != 0)
|
||||||
|
DispatchMouse(mouse, message, iris, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseUp(IMouse mouse, SilkMouseButton button)
|
||||||
|
{
|
||||||
|
(uint message, MouseButtons iris) = button switch
|
||||||
|
{
|
||||||
|
SilkMouseButton.Left => (WM_LBUTTONUP, MouseButtons.Left),
|
||||||
|
SilkMouseButton.Right => (WM_RBUTTONUP, MouseButtons.Right),
|
||||||
|
SilkMouseButton.Middle => (WM_MBUTTONUP, MouseButtons.Middle),
|
||||||
|
SilkMouseButton.Button4 => (WM_XBUTTONUP, MouseButtons.XButton1),
|
||||||
|
SilkMouseButton.Button5 => (WM_XBUTTONUP, MouseButtons.XButton2),
|
||||||
|
_ => (0u, MouseButtons.None),
|
||||||
|
};
|
||||||
|
if (message != 0)
|
||||||
|
DispatchMouse(mouse, message, iris, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDoubleClick(IMouse mouse, SilkMouseButton button, System.Numerics.Vector2 position)
|
||||||
|
{
|
||||||
|
(uint message, MouseButtons iris) = button switch
|
||||||
|
{
|
||||||
|
SilkMouseButton.Left => (WM_LBUTTONDBLCLK, MouseButtons.Left),
|
||||||
|
SilkMouseButton.Right => (WM_RBUTTONDBLCLK, MouseButtons.Right),
|
||||||
|
SilkMouseButton.Middle => (WM_MBUTTONDBLCLK, MouseButtons.Middle),
|
||||||
|
SilkMouseButton.Button4 => (WM_XBUTTONDBLCLK, MouseButtons.XButton1),
|
||||||
|
SilkMouseButton.Button5 => (WM_XBUTTONDBLCLK, MouseButtons.XButton2),
|
||||||
|
_ => (0u, MouseButtons.None),
|
||||||
|
};
|
||||||
|
if (message != 0)
|
||||||
|
DispatchMouse(mouse, message, iris, 0, doubleClick: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScroll(IMouse mouse, ScrollWheel wheel)
|
||||||
|
=> DispatchMouse(mouse, WM_MOUSEWHEEL, MouseButtons.None, (int)(wheel.Y * 120f));
|
||||||
|
|
||||||
|
private void DispatchMouse(IMouse mouse, uint message, MouseButtons button, int wheelDelta, bool doubleClick = false)
|
||||||
|
{
|
||||||
|
IRawInputCallbacks? cb = m_input.Callbacks;
|
||||||
|
if (cb == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
System.Numerics.Vector2 pos = mouse.Position;
|
||||||
|
int clientX = (int)pos.X;
|
||||||
|
int clientY = (int)pos.Y;
|
||||||
|
|
||||||
|
IVisual? natural = m_window.HitTest(new Silk.NET.Maths.Vector2D<float>(pos.X, pos.Y));
|
||||||
|
IVisual? capture = m_input.CaptureSite as IVisual ?? natural;
|
||||||
|
|
||||||
|
var data = new RawMouseData(
|
||||||
|
visCapture: capture!,
|
||||||
|
visNatural: natural!,
|
||||||
|
positionX: clientX,
|
||||||
|
positionY: clientY,
|
||||||
|
naturalX: clientX,
|
||||||
|
naturalY: clientY,
|
||||||
|
physicalX: clientX,
|
||||||
|
physicalY: clientY,
|
||||||
|
screenX: clientX + m_window.Left,
|
||||||
|
screenY: clientY + m_window.Top,
|
||||||
|
button: button,
|
||||||
|
wheelDelta: wheelDelta);
|
||||||
|
|
||||||
|
InputModifiers modifiers = ComputeModifiers(mouse);
|
||||||
|
if (doubleClick)
|
||||||
|
modifiers |= InputModifiers.DoubleClick;
|
||||||
|
|
||||||
|
cb.HandleRawMouseInput(message, modifiers, ref data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Modifiers -----------------------------------------------------------
|
||||||
|
private bool IsAltDown()
|
||||||
|
{
|
||||||
|
foreach (IKeyboard k in m_context.Keyboards)
|
||||||
|
{
|
||||||
|
if (k.IsKeyPressed(SilkKey.AltLeft) || k.IsKeyPressed(SilkKey.AltRight))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputModifiers ComputeModifiers(IMouse? mouse = null)
|
||||||
|
{
|
||||||
|
InputModifiers m = InputModifiers.None;
|
||||||
|
foreach (IKeyboard k in m_context.Keyboards)
|
||||||
|
{
|
||||||
|
if (k.IsKeyPressed(SilkKey.ControlLeft) || k.IsKeyPressed(SilkKey.ControlRight)) m |= InputModifiers.ControlKey;
|
||||||
|
if (k.IsKeyPressed(SilkKey.ShiftLeft) || k.IsKeyPressed(SilkKey.ShiftRight)) m |= InputModifiers.ShiftKey;
|
||||||
|
if (k.IsKeyPressed(SilkKey.AltLeft) || k.IsKeyPressed(SilkKey.AltRight)) m |= InputModifiers.AltKey;
|
||||||
|
if (k.IsKeyPressed(SilkKey.SuperLeft) || k.IsKeyPressed(SilkKey.SuperRight)) m |= InputModifiers.WindowsKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
mouse ??= FirstMouse();
|
||||||
|
if (mouse != null)
|
||||||
|
{
|
||||||
|
if (mouse.IsButtonPressed(SilkMouseButton.Left)) m |= InputModifiers.LeftMouse;
|
||||||
|
if (mouse.IsButtonPressed(SilkMouseButton.Right)) m |= InputModifiers.RightMouse;
|
||||||
|
if (mouse.IsButtonPressed(SilkMouseButton.Middle)) m |= InputModifiers.MiddleMouse;
|
||||||
|
if (mouse.IsButtonPressed(SilkMouseButton.Button4)) m |= InputModifiers.XMouse1;
|
||||||
|
if (mouse.IsButtonPressed(SilkMouseButton.Button5)) m |= InputModifiers.XMouse2;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IMouse? FirstMouse()
|
||||||
|
{
|
||||||
|
foreach (IMouse mouse in m_context.Mice)
|
||||||
|
return mouse;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
m_context.ConnectionChanged -= OnConnectionChanged;
|
||||||
|
foreach (IKeyboard keyboard in m_context.Keyboards)
|
||||||
|
{
|
||||||
|
keyboard.KeyDown -= OnKeyDown;
|
||||||
|
keyboard.KeyUp -= OnKeyUp;
|
||||||
|
keyboard.KeyChar -= OnKeyChar;
|
||||||
|
}
|
||||||
|
foreach (IMouse mouse in m_context.Mice)
|
||||||
|
{
|
||||||
|
mouse.MouseMove -= OnMouseMove;
|
||||||
|
mouse.MouseDown -= OnMouseDown;
|
||||||
|
mouse.MouseUp -= OnMouseUp;
|
||||||
|
mouse.Scroll -= OnScroll;
|
||||||
|
mouse.DoubleClick -= OnDoubleClick;
|
||||||
|
}
|
||||||
|
m_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Key mapping (Silk.NET.Input.Key -> Win32 virtual key codes) ---------
|
||||||
|
private static Keys MapKey(SilkKey key) => key switch
|
||||||
|
{
|
||||||
|
>= SilkKey.A and <= SilkKey.Z => Keys.A + (key - SilkKey.A),
|
||||||
|
>= SilkKey.Number0 and <= SilkKey.Number9 => Keys.D0 + (key - SilkKey.Number0),
|
||||||
|
>= SilkKey.Keypad0 and <= SilkKey.Keypad9 => Keys.NumPad0 + (key - SilkKey.Keypad0),
|
||||||
|
>= SilkKey.F1 and <= SilkKey.F24 => Keys.F1 + (key - SilkKey.F1),
|
||||||
|
|
||||||
|
SilkKey.Space => Keys.Space,
|
||||||
|
SilkKey.Enter => Keys.Return,
|
||||||
|
SilkKey.KeypadEnter => Keys.Return,
|
||||||
|
SilkKey.Escape => Keys.Escape,
|
||||||
|
SilkKey.Tab => Keys.Tab,
|
||||||
|
SilkKey.Backspace => Keys.Back,
|
||||||
|
SilkKey.Insert => Keys.Insert,
|
||||||
|
SilkKey.Delete => Keys.Delete,
|
||||||
|
SilkKey.Home => Keys.Home,
|
||||||
|
SilkKey.End => Keys.End,
|
||||||
|
SilkKey.PageUp => Keys.PageUp,
|
||||||
|
SilkKey.PageDown => Keys.PageDown,
|
||||||
|
SilkKey.Left => Keys.Left,
|
||||||
|
SilkKey.Right => Keys.Right,
|
||||||
|
SilkKey.Up => Keys.Up,
|
||||||
|
SilkKey.Down => Keys.Down,
|
||||||
|
|
||||||
|
SilkKey.CapsLock => Keys.CapsLock,
|
||||||
|
SilkKey.NumLock => Keys.NumLock,
|
||||||
|
SilkKey.ScrollLock => Keys.Scroll,
|
||||||
|
SilkKey.PrintScreen => Keys.PrintScreen,
|
||||||
|
SilkKey.Pause => Keys.Pause,
|
||||||
|
SilkKey.Menu => Keys.Apps,
|
||||||
|
|
||||||
|
SilkKey.ShiftLeft => Keys.LShiftKey,
|
||||||
|
SilkKey.ShiftRight => Keys.RShiftKey,
|
||||||
|
SilkKey.ControlLeft => Keys.LControlKey,
|
||||||
|
SilkKey.ControlRight => Keys.RControlKey,
|
||||||
|
SilkKey.AltLeft => Keys.LMenu,
|
||||||
|
SilkKey.AltRight => Keys.RMenu,
|
||||||
|
SilkKey.SuperLeft => Keys.LWin,
|
||||||
|
SilkKey.SuperRight => Keys.RWin,
|
||||||
|
|
||||||
|
SilkKey.KeypadDivide => Keys.Divide,
|
||||||
|
SilkKey.KeypadMultiply => Keys.Multiply,
|
||||||
|
SilkKey.KeypadSubtract => Keys.Subtract,
|
||||||
|
SilkKey.KeypadAdd => Keys.Add,
|
||||||
|
SilkKey.KeypadDecimal => Keys.Decimal,
|
||||||
|
|
||||||
|
SilkKey.Semicolon => Keys.OemSemicolon,
|
||||||
|
SilkKey.Equal => Keys.OemPlus,
|
||||||
|
SilkKey.Comma => Keys.OemComma,
|
||||||
|
SilkKey.Minus => Keys.OemMinus,
|
||||||
|
SilkKey.Period => Keys.OemPeriod,
|
||||||
|
SilkKey.Slash => Keys.OemQuestion,
|
||||||
|
SilkKey.GraveAccent => Keys.OemTilde,
|
||||||
|
SilkKey.LeftBracket => Keys.OemOpenBrackets,
|
||||||
|
SilkKey.BackSlash => Keys.OemPipe,
|
||||||
|
SilkKey.RightBracket => Keys.OemCloseBrackets,
|
||||||
|
SilkKey.Apostrophe => Keys.OemQuotes,
|
||||||
|
|
||||||
|
_ => Keys.None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Silk.NET.Input;
|
||||||
using Silk.NET.Maths;
|
using Silk.NET.Maths;
|
||||||
using Silk.NET.OpenGL;
|
using Silk.NET.OpenGL;
|
||||||
using Silk.NET.Windowing;
|
using Silk.NET.Windowing;
|
||||||
@@ -23,6 +24,8 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
private GL? m_gl;
|
private GL? m_gl;
|
||||||
private SceneRenderer? m_renderer;
|
private SceneRenderer? m_renderer;
|
||||||
private GLDisplayManager? m_displayManager;
|
private GLDisplayManager? m_displayManager;
|
||||||
|
private IInputContext? m_inputContext;
|
||||||
|
private GLInputTranslator? m_inputTranslator;
|
||||||
|
|
||||||
private GraphicsRenderingQuality m_quality;
|
private GraphicsRenderingQuality m_quality;
|
||||||
private SoundDeviceType m_soundType;
|
private SoundDeviceType m_soundType;
|
||||||
@@ -73,6 +76,10 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
m_displayManager = new GLDisplayManager(m_silkWindow);
|
m_displayManager = new GLDisplayManager(m_silkWindow);
|
||||||
m_session.GraphicsDevice = new GLGraphicsDevice(m_gl, m_quality, RenderNow);
|
m_session.GraphicsDevice = new GLGraphicsDevice(m_gl, m_quality, RenderNow);
|
||||||
m_session.SoundDevice = new GLSoundDevice(m_soundType);
|
m_session.SoundDevice = new GLSoundDevice(m_soundType);
|
||||||
|
|
||||||
|
m_inputContext = m_silkWindow.CreateInput();
|
||||||
|
m_inputTranslator = new GLInputTranslator(m_inputContext, (GLInputSystem)m_session.InputSystem, m_window);
|
||||||
|
|
||||||
m_window.RaiseLoad();
|
m_window.RaiseLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +128,7 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
m_inputTranslator?.Dispose();
|
||||||
m_renderer?.Dispose();
|
m_renderer?.Dispose();
|
||||||
m_session.Dispose();
|
m_session.Dispose();
|
||||||
if (!m_silkWindow.IsClosing)
|
if (!m_silkWindow.IsClosing)
|
||||||
|
|||||||
@@ -13,18 +13,24 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
public sealed class GLRenderWindow : IRenderWindow
|
public sealed class GLRenderWindow : IRenderWindow
|
||||||
{
|
{
|
||||||
private readonly SilkWindow m_window;
|
private readonly SilkWindow m_window;
|
||||||
|
private readonly GLRenderSession m_session;
|
||||||
private readonly GLVisualContainer m_root;
|
private readonly GLVisualContainer m_root;
|
||||||
private Size m_initialClientSize = new Size(1024, 768);
|
private Size m_initialClientSize = new Size(1024, 768);
|
||||||
|
|
||||||
public GLRenderWindow(SilkWindow window, GLRenderSession session)
|
public GLRenderWindow(SilkWindow window, GLRenderSession session)
|
||||||
{
|
{
|
||||||
m_window = window;
|
m_window = window;
|
||||||
|
m_session = session;
|
||||||
m_root = new GLVisualContainer(session, null!, isRoot: true);
|
m_root = new GLVisualContainer(session, null!, isRoot: true);
|
||||||
m_root.RegisterUsage(this);
|
m_root.RegisterUsage(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal GLVisualContainer Root => m_root;
|
internal GLVisualContainer Root => m_root;
|
||||||
|
|
||||||
|
/// <summary>Frontmost hittable visual under a client-space point, or null.</summary>
|
||||||
|
internal GLVisual? HitTest(Vector2D<float> clientPoint)
|
||||||
|
=> m_root.HitTest(new Vector2(clientPoint.X, clientPoint.Y), Matrix4X4<float>.Identity);
|
||||||
|
|
||||||
// ---- Geometry ------------------------------------------------------------
|
// ---- Geometry ------------------------------------------------------------
|
||||||
public int Left => m_window.Position.X;
|
public int Left => m_window.Position.X;
|
||||||
public int Top => m_window.Position.Y;
|
public int Top => m_window.Position.Y;
|
||||||
@@ -125,7 +131,8 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
public void SetEdgeImages(bool fActiveEdges, ShadowEdgePart[] edges) { /* TODO(stage 3): window shadow edges */ }
|
public void SetEdgeImages(bool fActiveEdges, ShadowEdgePart[] edges) { /* TODO(stage 3): window shadow edges */ }
|
||||||
public void SetWindowOptions(WindowOptions options, bool enable) { /* TODO(stage 3): map to Silk window flags */ }
|
public void SetWindowOptions(WindowOptions options, bool enable) { /* TODO(stage 3): map to Silk window flags */ }
|
||||||
public void SetMouseIdleOptions(Size sizeMouseIdleTolerance, uint nMouseIdleDelay) { }
|
public void SetMouseIdleOptions(Size sizeMouseIdleTolerance, uint nMouseIdleDelay) { }
|
||||||
public void SetCapture(IRawInputSite captureSite, bool state) { }
|
public void SetCapture(IRawInputSite captureSite, bool state)
|
||||||
|
=> ((GLInputSystem)m_session.InputSystem).CaptureSite = state ? captureSite : null;
|
||||||
public void SetDragDropResult(uint nDragOverResult, uint nDragDropResult) { }
|
public void SetDragDropResult(uint nDragOverResult, uint nDragDropResult) { }
|
||||||
|
|
||||||
public void ClientToScreen(ref Point point)
|
public void ClientToScreen(ref Point point)
|
||||||
|
|||||||
@@ -57,5 +57,13 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
renderer.DrawColoredQuad(matrix, Size.X, Size.Y, c, alpha);
|
renderer.DrawColoredQuad(matrix, Size.X, Size.Y, c, alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override GLVisual? HitTest(Vector2 screenPoint, Matrix4X4<float> parentMatrix)
|
||||||
|
{
|
||||||
|
if (!Visible || (MouseOptions & MouseOptions.Hittable) == 0)
|
||||||
|
return null;
|
||||||
|
Matrix4X4<float> world = LocalMatrix * parentMatrix;
|
||||||
|
return ContainsPoint(screenPoint, world) ? this : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,5 +116,25 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
|
|
||||||
/// <summary>Draw this visual (and its subtree) with the accumulated parent transform.</summary>
|
/// <summary>Draw this visual (and its subtree) with the accumulated parent transform.</summary>
|
||||||
internal abstract void Render(SceneRenderer renderer, Matrix4X4<float> parentMatrix, float inheritedAlpha);
|
internal abstract void Render(SceneRenderer renderer, Matrix4X4<float> parentMatrix, float inheritedAlpha);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the frontmost hittable visual under <paramref name="screenPoint"/>
|
||||||
|
/// (client pixel space), or null. <paramref name="parentMatrix"/> is the
|
||||||
|
/// accumulated transform of this visual's parent.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract GLVisual? HitTest(Vector2 screenPoint, Matrix4X4<float> parentMatrix);
|
||||||
|
|
||||||
|
/// <summary>Is the client-space point inside this visual's local quad?</summary>
|
||||||
|
private protected bool ContainsPoint(Vector2 screenPoint, Matrix4X4<float> worldMatrix)
|
||||||
|
{
|
||||||
|
if (Size.X <= 0f || Size.Y <= 0f)
|
||||||
|
return false;
|
||||||
|
if (!Matrix4X4.Invert(worldMatrix, out Matrix4X4<float> inverse))
|
||||||
|
return false;
|
||||||
|
// Our world matrix maps local -> screen as (local * world) under the
|
||||||
|
// renderer's convention, so the inverse maps screen -> local the same way.
|
||||||
|
Vector3D<float> local = Vector3D.Transform(new Vector3D<float>(screenPoint.X, screenPoint.Y, 0f), inverse);
|
||||||
|
return local.X >= 0f && local.X <= Size.X && local.Y >= 0f && local.Y <= Size.Y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,30 @@ namespace Microsoft.Iris.Render.OpenGL
|
|||||||
child.Render(renderer, matrix, alpha);
|
child.Render(renderer, matrix, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override GLVisual? HitTest(Vector2 screenPoint, Matrix4X4<float> parentMatrix)
|
||||||
|
{
|
||||||
|
if (!Visible)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Matrix4X4<float> world = LocalMatrix * parentMatrix;
|
||||||
|
|
||||||
|
// Children draw ascending by layer (back-to-front), so the frontmost hit is
|
||||||
|
// found by testing in reverse order.
|
||||||
|
m_children.Sort((a, b) => a.Layer.CompareTo(b.Layer));
|
||||||
|
for (int i = m_children.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
GLVisual? hit = m_children[i].HitTest(screenPoint, world);
|
||||||
|
if (hit != null)
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise the container itself, if it is hittable and has extent.
|
||||||
|
if ((MouseOptions & MouseOptions.Hittable) != 0 && ContainsPoint(screenPoint, world))
|
||||||
|
return this;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void DisposeCore() => RemoveAllChildren();
|
protected override void DisposeCore() => RemoveAllChildren();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,35 +8,20 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
|
|
||||||
<!-- The task requires targeting both modern .NET and .NET Framework 4.8.
|
|
||||||
net48 only builds on Windows in this repo (the projects we reference,
|
|
||||||
e.g. UIX.RenderApi, only produce their netfx output there — see the
|
|
||||||
MicrosoftIris Directory.Build.props and the note in ZuneDBApi/CLAUDE.md
|
|
||||||
that "on Linux only net8.0 TFMs build"). We mirror that so the project
|
|
||||||
stays buildable on both hosts. -->
|
|
||||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
|
||||||
<TargetFrameworks Condition=" $([MSBuild]::IsOsPlatform('Windows')) ">net8.0;net48</TargetFrameworks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- We only implement the interfaces defined in UIX.RenderApi; per the task we
|
|
||||||
must not modify that project, so this is a plain reference. -->
|
|
||||||
<ProjectReference Include="..\UIX.RenderApi\UIX.RenderApi.csproj" />
|
<ProjectReference Include="..\UIX.RenderApi\UIX.RenderApi.csproj" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- Silk.NET provides the windowing/GL/input abstractions and OpenGL bindings.
|
<!-- Silk.NET provides the windowing/GL/input abstractions and OpenGL bindings.
|
||||||
The meta-packages register the GLFW/SDL backends automatically. -->
|
The meta-packages register the GLFW/SDL backends automatically. -->
|
||||||
<PackageReference Include="Silk.NET.Windowing" Version="2.21.0" />
|
<PackageReference Include="Silk.NET.Windowing" Version="2.23.0" />
|
||||||
<PackageReference Include="Silk.NET.Input" Version="2.21.0" />
|
<PackageReference Include="Silk.NET.Input" Version="2.23.0" />
|
||||||
<PackageReference Include="Silk.NET.OpenGL" Version="2.21.0" />
|
<PackageReference Include="Silk.NET.OpenGL" Version="2.23.0" />
|
||||||
<PackageReference Include="Silk.NET.Maths" Version="2.21.0" />
|
<PackageReference Include="Silk.NET.Maths" Version="2.23.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('net4')) ">
|
<ItemGroup Condition=" $(TargetFramework.StartsWith('net4')) ">
|
||||||
<!-- PolySharp backfills modern C# language features (init accessors,
|
|
||||||
nullable attributes, records, etc.) for the netfx target. -->
|
|
||||||
<PackageReference Include="PolySharp" Version="1.16.0">
|
<PackageReference Include="PolySharp" Version="1.16.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
|||||||
@@ -2,6 +2,39 @@
|
|||||||
|
|
||||||
Reverse-chronological log (prepend new entries; never edit older ones).
|
Reverse-chronological log (prepend new entries; never edit older ones).
|
||||||
|
|
||||||
|
## 2026-07-25 — Input-event translation (Silk.NET.Input)
|
||||||
|
|
||||||
|
Wired real keyboard/mouse input via `GLInputTranslator`, created by the engine on
|
||||||
|
window Load from `IWindow.CreateInput()`. It hooks every keyboard/mouse (and new
|
||||||
|
devices via `ConnectionChanged`) and dispatches to the registered
|
||||||
|
`IRawInputCallbacks`.
|
||||||
|
|
||||||
|
Message-id conventions were confirmed by reading the UIX consumer, not guessed:
|
||||||
|
- Keyboard (`KeyboardDevice.OnRawInput`): the `message` is the `KeyboardMessageId`
|
||||||
|
ordinal — 0 Down, 1 Up, 2 Char, 3 SysDown, 4 SysUp, 5 SysChar. We emit Sys*
|
||||||
|
variants while Alt is held. Character events put the char in
|
||||||
|
`RawKeyboardData._virtualKey` (matches `OnRawKeyCharacter`'s `(char)_virtualKey`).
|
||||||
|
- Mouse (`MouseDevice.OnRawInput`): the `message` is the Win32 `WM_*` code
|
||||||
|
(0x200 move, 0x201/0x202 L down/up, 0x204/5 R, 0x207/8 M, 0x20A wheel,
|
||||||
|
0x20B-D X buttons, dblclk variants 0x203/6/9/20D). Wheel delta is `scroll.Y*120`.
|
||||||
|
|
||||||
|
`InputModifiers` is computed from live Silk key/button state each event. Silk
|
||||||
|
`Key` → Iris `Keys` (Win32 VK) mapping uses contiguous-range arithmetic for
|
||||||
|
A–Z / 0–9 / Keypad / F-keys plus an explicit table for the rest; unmapped → None.
|
||||||
|
|
||||||
|
Hit-testing: added `GLVisual.HitTest` (frontmost-first, honoring `Visible` and
|
||||||
|
`MouseOptions.Hittable`) so `RawMouseData._visNatural` is the visual under the
|
||||||
|
cursor. `_visCapture` is the `SetCapture` site when set (now stored on
|
||||||
|
`GLInputSystem.CaptureSite`), else the natural target. Screen<->local uses the
|
||||||
|
inverse of the visual's world matrix (same row-vector convention as rendering).
|
||||||
|
|
||||||
|
TODOs (still stage-3): HID/AppCommand (media/remote) and drag/drop translation are
|
||||||
|
not sourced from Silk yet; `_repCount` is a simple per-key press counter; keyboard
|
||||||
|
`_flags` is 0.
|
||||||
|
|
||||||
|
Validated the same way as before (compile against the prebuilt `UIX.RenderApi.dll`
|
||||||
|
+ Silk.NET, incl. Silk.NET.Input) — build succeeded.
|
||||||
|
|
||||||
## 2026-07-25 — Initial in-process OpenGL renderer
|
## 2026-07-25 — Initial in-process OpenGL renderer
|
||||||
|
|
||||||
### Goal
|
### Goal
|
||||||
|
|||||||
Reference in New Issue
Block a user