using RemSox.UI.GUI.Rendering; using RemSox.UI.GUI.UIEelements; using System.Drawing; namespace RemSox.UI.GUI.Windows; /// /// Represents a window within the GUI system, managing its state, UI elements, and interactions. /// public sealed class Window(string title, int processId, int id, IRenderSource renderSource) { /// Gets the unique identifier for this window. public int Id { get; } = id; /// Gets the ID of the process that owns this window. public int ProcessId { get; } = processId; /// Gets or sets the title of the window. public string Title { get; set; } = title; /// Gets or sets the Z-order index of the window (higher means more foreground). public int ZIndex { get; set; } /// Gets or sets a value indicating whether changes should automatically trigger a redraw. public bool AutoFlush { get; set; } = false; /// 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(Cosmos.Kernel.System.Keyboard.KeyEvent keyEvent) { OnKeyEvent?.Invoke(keyEvent); } /// Gets or sets whether this window is currently focused. public bool IsFocused { get => WindowManager.IsWindowFocused(this); set => WindowManager.FocusWindow(value ? this : null); } /// Gets or sets whether the window is visible. public bool IsVisible { get; set; } = true; /// Gets or sets whether the window is resizable by the user. public bool IsResizable { get; set; } = true; /// Gets or sets whether the window can be dragged by the user. public bool IsDraggable { get; set; } = true; /// Gets or sets the position of the window. public Point Position { get; set; } /// Gets or sets the size of the window. public Size Size { get; set; } /// Gets whether the window is currently being dragged. public bool IsDragging => currentInteraction == InteractionMode.Drag; private readonly Lock uiElementsLock = new(); private readonly Dictionary uiElements = []; private int nextUIElementId = 1; private enum InteractionMode { None, Drag, ResizeTop, ResizeBottom, ResizeLeft, ResizeRight, ResizeTopLeft, ResizeTopRight, ResizeBottomLeft, ResizeBottomRight } private InteractionMode currentInteraction = InteractionMode.None; private Rectangle interactionStartBounds; private Point interactionStartPointer; private Point dragOffset; private Point lastRenderedPosition = new(-1, -1); private Size lastRenderedSize = new(-1, -1); private bool lastRenderedIsFocused = false; private string lastRenderedTitle = string.Empty; private int lastRenderedZIndex = -1; private bool isFirstRender = true; /// /// Creates and registers a new UI element within this window. /// public T CreateUIElement(Action? options = null) where T : UIElement, new() { int uiElementId = GetNextUIElementId(); T uiElement = new() { Id = uiElementId }; options?.Invoke(uiElement); uiElement.PropertyChanged += (sender, args) => { if (AutoFlush) { Flush(); } }; if (AutoFlush) { Flush(); } lock (uiElementsLock) { uiElements.Add(uiElementId, uiElement); } return uiElement; } /// /// Invalidates the window state, forcing a full redraw on the next flush. /// public void Invalidate() { isFirstRender = true; Flush(); } /// /// Sends current window and element state to the renderer. /// public void Flush() { if (!IsVisible) { return; } bool anyChildChanged; List elementsCopy; lock (uiElementsLock) { anyChildChanged = uiElements.Values.Any(e => e.AnyPropertyChanged); elementsCopy = uiElements.Values.ToList(); } bool windowStateChanged = isFirstRender || Size != lastRenderedSize || IsFocused != lastRenderedIsFocused || Title != lastRenderedTitle; bool positionChanged = Position != lastRenderedPosition; bool zIndexChanged = ZIndex != lastRenderedZIndex; bool fullRedraw = windowStateChanged || anyChildChanged; List commands = []; if (fullRedraw) { commands.Add(new RenderCommand { WindowId = Id, ElementId = Id, ElementType = "Window", Position = Position, Properties = new Dictionary { [nameof(Title)] = Title, [nameof(Size)] = Size, [nameof(IsFocused)] = IsFocused, [nameof(IsResizable)] = IsResizable, [nameof(IsDraggable)] = IsDraggable, [nameof(ZIndex)] = ZIndex } }); foreach (UIElement element in elementsCopy) { commands.Add(new RenderCommand { WindowId = Id, ElementId = element.Id, ElementType = element.Type, Position = element.Position, Properties = element.AllProperties }); element.ClearChangedProperties(); } } else if (positionChanged || zIndexChanged) { commands.Add(new RenderCommand { WindowId = Id, ElementId = Id, ElementType = "WindowMove", Position = Position, Properties = new Dictionary { [nameof(ZIndex)] = ZIndex } }); } if (commands.Count > 0) { renderSource.Render(commands); lastRenderedPosition = Position; lastRenderedSize = Size; lastRenderedIsFocused = IsFocused; lastRenderedTitle = Title; lastRenderedZIndex = ZIndex; isFirstRender = false; } } /// /// Checks if a pointer position starts an interaction (drag/resize) and handles focus. /// public bool TryBeginInteract(Point pointerPosition) { if (!IsVisible) { return false; } const int resizeMargin = 5; bool onLeft = pointerPosition.X >= Position.X && pointerPosition.X <= Position.X + resizeMargin; bool onRight = pointerPosition.X >= Position.X + Size.Width - resizeMargin && pointerPosition.X <= Position.X + Size.Width; bool onTop = pointerPosition.Y >= Position.Y && pointerPosition.Y <= Position.Y + resizeMargin; bool onBottom = pointerPosition.Y >= Position.Y + Size.Height - resizeMargin && pointerPosition.Y <= Position.Y + Size.Height; bool inBounds = pointerPosition.X >= Position.X && pointerPosition.X <= Position.X + Size.Width && pointerPosition.Y >= Position.Y && pointerPosition.Y <= Position.Y + Size.Height; currentInteraction = InteractionMode.None; if (IsResizable) { if (onTop && onLeft) { currentInteraction = InteractionMode.ResizeTopLeft; } else if (onTop && onRight) { currentInteraction = InteractionMode.ResizeTopRight; } else if (onBottom && onLeft) { currentInteraction = InteractionMode.ResizeBottomLeft; } else if (onBottom && onRight) { currentInteraction = InteractionMode.ResizeBottomRight; } else if (onLeft && inBounds) { currentInteraction = InteractionMode.ResizeLeft; } else if (onRight && inBounds) { currentInteraction = InteractionMode.ResizeRight; } else if (onTop && inBounds) { currentInteraction = InteractionMode.ResizeTop; } else if (onBottom && inBounds) { currentInteraction = InteractionMode.ResizeBottom; } } if (currentInteraction == InteractionMode.None && IsDraggable && IsPointInTitleBar(pointerPosition)) { currentInteraction = InteractionMode.Drag; dragOffset = new Point(pointerPosition.X - Position.X, pointerPosition.Y - Position.Y); } if (currentInteraction != InteractionMode.None || inBounds) { if (currentInteraction != InteractionMode.None) { interactionStartBounds = new Rectangle(Position, Size); interactionStartPointer = pointerPosition; } WindowManager.FocusWindow(this); return true; } return false; } /// /// Updates the drag or resize interaction based on the current pointer position. /// public void UpdateInteraction(Point pointerPosition) { if (currentInteraction == InteractionMode.Drag) { int newX = pointerPosition.X - dragOffset.X; int newY = pointerPosition.Y - dragOffset.Y; // Cosmos DrawCanvas fails to render if coordinates are negative. // Clamp to 0,0 to prevent the window from disappearing. newX = Math.Max(0, newX); newY = Math.Max(0, newY); Position = new Point(newX, newY); Flush(); } else if (currentInteraction != InteractionMode.None) { int dx = pointerPosition.X - interactionStartPointer.X; int dy = pointerPosition.Y - interactionStartPointer.Y; int newX = interactionStartBounds.X; int newY = interactionStartBounds.Y; int newW = interactionStartBounds.Width; int newH = interactionStartBounds.Height; const int minWidth = 100; const int minHeight = 50; if (currentInteraction is InteractionMode.ResizeRight or InteractionMode.ResizeBottomRight or InteractionMode.ResizeTopRight) { newW = Math.Max(minWidth, interactionStartBounds.Width + dx); } if (currentInteraction is InteractionMode.ResizeBottom or InteractionMode.ResizeBottomRight or InteractionMode.ResizeBottomLeft) { newH = Math.Max(minHeight, interactionStartBounds.Height + dy); } if (currentInteraction is InteractionMode.ResizeLeft or InteractionMode.ResizeBottomLeft or InteractionMode.ResizeTopLeft) { int maxDx = interactionStartBounds.Width - minWidth; int clampedDx = Math.Min(dx, maxDx); newX = Math.Max(0, interactionStartBounds.X + clampedDx); newW = interactionStartBounds.Width - clampedDx; } if (currentInteraction is InteractionMode.ResizeTop or InteractionMode.ResizeTopLeft or InteractionMode.ResizeTopRight) { int maxDy = interactionStartBounds.Height - minHeight; int clampedDy = Math.Min(dy, maxDy); newY = Math.Max(0, interactionStartBounds.Y + clampedDy); newH = interactionStartBounds.Height - clampedDy; } Position = new Point(newX, newY); Size = new Size(newW, newH); Flush(); } } /// /// Ends the current drag or resize interaction. /// public void EndInteraction() { currentInteraction = InteractionMode.None; } private bool IsPointInTitleBar(Point pointerPosition) { return pointerPosition.X >= Position.X && pointerPosition.X < Position.X + Size.Width && pointerPosition.Y >= Position.Y && pointerPosition.Y < Position.Y + 18; } private int GetNextUIElementId() { return nextUIElementId++; } }