Add mouse event handling and improve key event handling for controls and windows

This commit is contained in:
Stone_Red
2026-06-12 23:04:38 +02:00
parent c6a9bc1870
commit 0bc9ca6cf2
6 changed files with 218 additions and 24 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ using Cosmos.Kernel.System.Keyboard;
using RemSox.Processing;
using RemSox.UI.CLI;
using RemSox.UI.GUI.UIEelements;
using RemSox.UI.GUI.UIEelements.Shapes;
using RemSox.UI.GUI.Windows;
using System.Drawing;
+10
View File
@@ -1,3 +1,5 @@
using Cosmos.Kernel.System.Keyboard;
using System.Drawing;
namespace RemSox.UI.GUI.UIEelements;
@@ -15,4 +17,12 @@ public abstract class Control(string type) : UIElement(type)
get;
set => SetProperty(nameof(Size), ref field, value);
}
public virtual void HandleMouseEvent(MouseEvent mouseEvent)
{
}
public virtual void HandleKeyEvent(KeyEvent keyEvent)
{
}
}
@@ -1,6 +1,6 @@
using System.Drawing;
namespace RemSox.UI.GUI.UIEelements;
namespace RemSox.UI.GUI.UIEelements.Shapes;
public class Text() : UIElement("Text")
{
+106 -11
View File
@@ -28,16 +28,14 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
/// <summary> Event raised when a keyboard event is handled by this window. </summary>
public event Action<Sys.Keyboard.KeyEvent>? OnKeyEvent;
/// <summary> Dispatches a key event to the window's registered event handlers. </summary>
public void HandleKeyEvent(Sys.Keyboard.KeyEvent keyEvent)
{
OnKeyEvent?.Invoke(keyEvent);
}
/// <summary> Event raised when a mouse event is handled by this window. </summary>
public event Action<MouseEvent>? OnMouseEvent;
/// <summary> Gets or sets whether this window is currently focused. </summary>
public bool IsFocused
{
get => WindowManager.IsWindowFocused(this); set => WindowManager.FocusWindow(value ? this : null);
get => WindowManager.IsWindowFocused(this);
set => WindowManager.FocusWindow(value ? this : null);
}
/// <summary> Gets or sets whether the window is visible. </summary>
@@ -62,7 +60,10 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
public bool IsResizing => currentInteraction is InteractionMode.ResizeTop or InteractionMode.ResizeBottom or InteractionMode.ResizeLeft or InteractionMode.ResizeRight or InteractionMode.ResizeTopLeft or InteractionMode.ResizeTopRight or InteractionMode.ResizeBottomLeft or InteractionMode.ResizeBottomRight;
private readonly Lock uiElementsLock = new();
private readonly Lock controlsLock = new();
private readonly Dictionary<int, UIElement> uiElements = [];
private readonly Dictionary<int, Control> controls = [];
private Control? focusedControl = null;
private int nextUIElementId = 1;
@@ -101,19 +102,53 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
}
};
if (AutoFlush)
{
Flush();
}
lock (uiElementsLock)
{
uiElements.Add(uiElementId, uiElement);
}
lock (controlsLock)
{
if (uiElement is Control control)
{
controls.Add(uiElementId, control);
}
}
if (AutoFlush)
{
Flush();
}
return uiElement;
}
public void RemoveUIElement(int elementId)
{
bool removed = false;
lock (uiElementsLock)
{
if (uiElements.Remove(elementId))
{
removed = true;
}
}
lock (controlsLock)
{
if (controls.Remove(elementId))
{
removed = true;
}
}
if (removed && AutoFlush)
{
Flush();
}
}
/// <summary>
/// Invalidates the window state, forcing a full redraw on the next flush.
/// </summary>
@@ -209,6 +244,42 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
}
}
/// <summary> Dispatches a key event to the window's registered event handlers. </summary>
public void HandleKeyEvent(Sys.Keyboard.KeyEvent keyEvent)
{
OnKeyEvent?.Invoke(keyEvent);
focusedControl?.HandleKeyEvent(keyEvent);
}
/// <summary> Dispatches a mouse event to the window's registered event handlers. </summary>
public void HandleMouseEvent(MouseEvent mouseEvent)
{
OnMouseEvent?.Invoke(mouseEvent);
Point local = new(mouseEvent.X - Position.X, mouseEvent.Y - Position.Y);
Control? target = HitTestControl(local);
if (mouseEvent.Type == MouseEventType.ButtonDown && mouseEvent.Button == MouseButton.Left)
{
focusedControl = target;
}
if (target is null)
{
return;
}
MouseEvent localEvent = mouseEvent with
{
X = local.X,
Y = local.Y
};
target.HandleMouseEvent(localEvent);
}
/// <summary>
/// Checks if a pointer position starts an interaction (drag/resize) and handles focus.
/// </summary>
@@ -361,6 +432,30 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
currentInteraction = InteractionMode.None;
}
private Control? HitTestControl(Point windowRelativePosition)
{
lock (controlsLock)
{
// top-most last (simple Z-order assumption: insertion order)
foreach (Control control in controls.Values.Reverse())
{
Rectangle bounds = new(
control.Position.X,
control.Position.Y,
control.Size.Width,
control.Size.Height
);
if (bounds.Contains(windowRelativePosition))
{
return control;
}
}
}
return null;
}
private bool IsPointInTitleBar(Point pointerPosition)
{
return pointerPosition.X >= Position.X
+61 -11
View File
@@ -26,6 +26,8 @@ public static class WindowManager
private static Window? activeInteractWindow = null;
private static Point lastPointerPosition = Point.Empty;
private static bool wasLeftButtonDown = false;
private static bool wasRightButtonDown = false;
private static bool wasMiddleButtonDown = false;
private static readonly MuliRenderSource renderSource = new([]);
@@ -36,6 +38,8 @@ public static class WindowManager
{
Point pointerPosition = new(MouseManager.X, MouseManager.Y);
bool leftButtonDown = MouseManager.LeftButton;
bool rightButtonDown = MouseManager.RightButton;
bool middleButtonDown = MouseManager.MiddleButton;
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
@@ -54,12 +58,19 @@ public static class WindowManager
}
wasLeftButtonDown = leftButtonDown;
wasRightButtonDown = rightButtonDown;
wasMiddleButtonDown = middleButtonDown;
while (KeyboardManager.TryReadKey(out KeyEvent? keyEvent) && keyEvent is not null)
{
focusedWindow?.HandleKeyEvent(keyEvent);
}
if (focusedWindow is not null && activeInteractWindow is null)
{
DispatchMouseEvents(focusedWindow, pointerPosition);
}
CanvasRenderSource.CompositeAndDisplay(canvas, pointerPosition);
lastPointerPosition = pointerPosition;
@@ -323,9 +334,23 @@ public static class WindowManager
}
/// <summary>
/// Attempts to begin interaction (drag/resize) with a window at the specified pointer position.
/// Forces a full redraw of all windows in the system.
/// </summary>
public static Window? TryBeginInteract(Point pointerPosition)
public static void InvalidateAll()
{
List<Window> allWindows;
lock (windowsLock)
{
allWindows = windows.Values.SelectMany(w => w).ToList();
}
foreach (Window window in allWindows)
{
window.Invalidate();
}
}
private static Window? TryBeginInteract(Point pointerPosition)
{
List<Window> allWindows;
lock (windowsLock)
@@ -343,20 +368,45 @@ public static class WindowManager
return null;
}
/// <summary>
/// Forces a full redraw of all windows in the system.
/// </summary>
public static void InvalidateAll()
private static void DispatchMouseEvents(Window window, Point pos)
{
List<Window> allWindows;
lock (windowsLock)
int delta = MouseManager.ScrollDelta;
if (pos != lastPointerPosition)
{
allWindows = windows.Values.SelectMany(w => w).ToList();
window.HandleMouseEvent(new MouseEvent(MouseEventType.Move, pos.X, pos.Y, MouseButton.None, 0));
}
foreach (Window window in allWindows)
if (MouseManager.LeftButton && !wasLeftButtonDown)
{
window.Invalidate();
window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonDown, pos.X, pos.Y, MouseButton.Left, 0));
}
else if (!MouseManager.LeftButton && wasLeftButtonDown)
{
window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonUp, pos.X, pos.Y, MouseButton.Left, 0));
}
if (MouseManager.RightButton && !wasRightButtonDown)
{
window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonDown, pos.X, pos.Y, MouseButton.Right, 0));
}
else if (!MouseManager.RightButton && wasRightButtonDown)
{
window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonUp, pos.X, pos.Y, MouseButton.Right, 0));
}
if (MouseManager.MiddleButton && !wasMiddleButtonDown)
{
window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonDown, pos.X, pos.Y, MouseButton.Middle, 0));
}
else if (!MouseManager.MiddleButton && wasMiddleButtonDown)
{
window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonUp, pos.X, pos.Y, MouseButton.Middle, 0));
}
if (delta != 0)
{
window.HandleMouseEvent(new MouseEvent(MouseEventType.Wheel, pos.X, pos.Y, MouseButton.None, delta));
}
}
+39
View File
@@ -0,0 +1,39 @@
namespace RemSox.UI;
public record MouseEvent(MouseEventType Type, int X = 0, int Y = 0, MouseButton Button = MouseButton.None, int Delta = 0)
{
public static MouseEvent Move(int x, int y)
{
return new(MouseEventType.Move, x, y);
}
public static MouseEvent ButtonDown(int x, int y, MouseButton button)
{
return new(MouseEventType.ButtonDown, x, y, button);
}
public static MouseEvent ButtonUp(int x, int y, MouseButton button)
{
return new(MouseEventType.ButtonUp, x, y, button);
}
public static MouseEvent Wheel(int x, int y, int delta)
{
return new(MouseEventType.Wheel, x, y, Delta: delta);
}
}
public enum MouseEventType
{
Move,
ButtonDown,
ButtonUp,
Wheel
}
public enum MouseButton
{
Left,
Right,
Middle,
None
}