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;
/// 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);
}
/// 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;
/// Gets whether the window is currently being resized.
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;
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();
}
};
lock (uiElementsLock)
{
uiElements.Add(uiElementId, uiElement);
}
if (uiElement is Control control)
{
lock (controlsLock)
{
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.
///
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;
}
}
/// 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.
///
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, Size screenSize)
{
if (currentInteraction == InteractionMode.Drag)
{
int newX = pointerPosition.X - dragOffset.X;
int newY = pointerPosition.Y - dragOffset.Y;
// Clamp left and top edges
newX = Math.Max(0, newX);
newY = Math.Max(0, newY);
// Clamp right and bottom edges
newX = Math.Min(screenSize.Width - Size.Width, newX);
newY = Math.Min(screenSize.Height - Size.Height, 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);
newW = Math.Min(newW, screenSize.Width - newX);
}
if (currentInteraction is InteractionMode.ResizeBottom or InteractionMode.ResizeBottomRight or InteractionMode.ResizeBottomLeft)
{
newH = Math.Max(minHeight, interactionStartBounds.Height + dy);
newH = Math.Min(newH, screenSize.Height - newY);
}
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.X + interactionStartBounds.Width - newX;
}
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.Y + interactionStartBounds.Height - newY;
}
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 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
&& pointerPosition.X < Position.X + Size.Width
&& pointerPosition.Y >= Position.Y
&& pointerPosition.Y < Position.Y + 18;
}
private int GetNextUIElementId()
{
return nextUIElementId++;
}
}