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