From 0bc9ca6cf2e7284d20c546220b2bdd9e83532456 Mon Sep 17 00:00:00 2001
From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com>
Date: Fri, 12 Jun 2026 23:03:57 +0200
Subject: [PATCH] Add mouse event handling and improve key event handling for
controls and windows
---
Processes/TerminalProcess.cs | 2 +-
UI/GUI/UIEelements/Control.cs | 10 ++
UI/GUI/UIEelements/{ => Shapes}/Text.cs | 2 +-
UI/GUI/Windows/Window.cs | 117 +++++++++++++++++++++---
UI/GUI/Windows/WindowManager.cs | 72 ++++++++++++---
UI/MouseEvent.cs | 39 ++++++++
6 files changed, 218 insertions(+), 24 deletions(-)
rename UI/GUI/UIEelements/{ => Shapes}/Text.cs (88%)
create mode 100644 UI/MouseEvent.cs
diff --git a/Processes/TerminalProcess.cs b/Processes/TerminalProcess.cs
index 5bddea7..20640ff 100644
--- a/Processes/TerminalProcess.cs
+++ b/Processes/TerminalProcess.cs
@@ -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;
diff --git a/UI/GUI/UIEelements/Control.cs b/UI/GUI/UIEelements/Control.cs
index fd9e1c9..e4f7846 100644
--- a/UI/GUI/UIEelements/Control.cs
+++ b/UI/GUI/UIEelements/Control.cs
@@ -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)
+ {
+ }
}
diff --git a/UI/GUI/UIEelements/Text.cs b/UI/GUI/UIEelements/Shapes/Text.cs
similarity index 88%
rename from UI/GUI/UIEelements/Text.cs
rename to UI/GUI/UIEelements/Shapes/Text.cs
index de0e15b..9c87ca6 100644
--- a/UI/GUI/UIEelements/Text.cs
+++ b/UI/GUI/UIEelements/Shapes/Text.cs
@@ -1,6 +1,6 @@
using System.Drawing;
-namespace RemSox.UI.GUI.UIEelements;
+namespace RemSox.UI.GUI.UIEelements.Shapes;
public class Text() : UIElement("Text")
{
diff --git a/UI/GUI/Windows/Window.cs b/UI/GUI/Windows/Window.cs
index 9f32e0c..d6c05f6 100644
--- a/UI/GUI/Windows/Window.cs
+++ b/UI/GUI/Windows/Window.cs
@@ -28,16 +28,14 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
/// Event raised when a keyboard event is handled by this window.
public event Action? OnKeyEvent;
- /// Dispatches a key event to the window's registered event handlers.
- public void HandleKeyEvent(Sys.Keyboard.KeyEvent keyEvent)
- {
- OnKeyEvent?.Invoke(keyEvent);
- }
+ /// Event raised when a mouse event is handled by this window.
+ public event Action? OnMouseEvent;
/// Gets or sets whether this window is currently focused.
public bool IsFocused
{
- get => WindowManager.IsWindowFocused(this); set => WindowManager.FocusWindow(value ? this : null);
+ get => WindowManager.IsWindowFocused(this);
+ set => WindowManager.FocusWindow(value ? this : null);
}
/// Gets or sets whether the window is visible.
@@ -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 uiElements = [];
+ private readonly Dictionary 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();
+ }
+ }
+
///
/// Invalidates the window state, forcing a full redraw on the next flush.
///
@@ -209,6 +244,42 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
}
}
+ /// Dispatches a key event to the window's registered event handlers.
+ public void HandleKeyEvent(Sys.Keyboard.KeyEvent keyEvent)
+ {
+ OnKeyEvent?.Invoke(keyEvent);
+
+ focusedControl?.HandleKeyEvent(keyEvent);
+ }
+
+ /// Dispatches a mouse event to the window's registered event handlers.
+ 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);
+ }
+
///
/// Checks if a pointer position starts an interaction (drag/resize) and handles focus.
///
@@ -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
diff --git a/UI/GUI/Windows/WindowManager.cs b/UI/GUI/Windows/WindowManager.cs
index e4533b4..3bbba9f 100644
--- a/UI/GUI/Windows/WindowManager.cs
+++ b/UI/GUI/Windows/WindowManager.cs
@@ -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
}
///
- /// Attempts to begin interaction (drag/resize) with a window at the specified pointer position.
+ /// Forces a full redraw of all windows in the system.
///
- public static Window? TryBeginInteract(Point pointerPosition)
+ public static void InvalidateAll()
+ {
+ List allWindows;
+ lock (windowsLock)
+ {
+ allWindows = windows.Values.SelectMany(w => w).ToList();
+ }
+
+ foreach (Window window in allWindows)
+ {
+ window.Invalidate();
+ }
+ }
+
+ private static Window? TryBeginInteract(Point pointerPosition)
{
List allWindows;
lock (windowsLock)
@@ -343,20 +368,45 @@ public static class WindowManager
return null;
}
- ///
- /// Forces a full redraw of all windows in the system.
- ///
- public static void InvalidateAll()
+ private static void DispatchMouseEvents(Window window, Point pos)
{
- List 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));
}
}
diff --git a/UI/MouseEvent.cs b/UI/MouseEvent.cs
new file mode 100644
index 0000000..3983be1
--- /dev/null
+++ b/UI/MouseEvent.cs
@@ -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
+}
\ No newline at end of file