mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-04 00:56:19 +02:00
Add mouse event handling and improve key event handling for controls and windows
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user