Add automatic window positioning to avoid overlap and maximize spaciousness

This commit is contained in:
Stone_Red
2026-06-10 04:02:54 +02:00
parent 3a635e85df
commit fad05a3e9a
5 changed files with 124 additions and 23 deletions
+1 -9
View File
@@ -1,5 +1,3 @@
using Cosmos.Kernel.System.Graphics;
using RemSox.Processing; using RemSox.Processing;
using RemSox.UI.GUI.Rendering; using RemSox.UI.GUI.Rendering;
using RemSox.UI.GUI.Windows; using RemSox.UI.GUI.Windows;
@@ -18,17 +16,11 @@ internal class DesktopProcess() : Process("Desktop Manager")
isGraphicsInitialized = true; isGraphicsInitialized = true;
} }
// Trigger Canvas initialization
_ = FullScreenCanvas.GetFullScreenCanvas();
// Force existing windows to redraw onto the new canvas renderer // Force existing windows to redraw onto the new canvas renderer
WindowManager.InvalidateAll(); WindowManager.InvalidateAll();
// Start the terminal process within the desktop environment
_ = ProcessManager.SpawnProcess<TerminalProcess>();
// Create a test window for new UI controls // Create a test window for new UI controls
Window testWindow = WindowManager.CreateWindow(this, "UI Controls Test", new System.Drawing.Point(500, 50), new System.Drawing.Size(200, 180)); Window testWindow = WindowManager.CreateWindow(this, "UI Controls Test", new System.Drawing.Size(200, 180));
_ = testWindow.CreateUIElement<UI.GUI.UIEelements.Controls.Button>(b => _ = testWindow.CreateUIElement<UI.GUI.UIEelements.Controls.Button>(b =>
{ {
+11 -7
View File
@@ -9,7 +9,7 @@ using System.Drawing;
namespace RemSox.Processes; namespace RemSox.Processes;
public class TerminalProcess : Process public class TerminalProcess() : Process("Terminal")
{ {
private Window window = null!; private Window window = null!;
private readonly List<string> history = []; private readonly List<string> history = [];
@@ -19,13 +19,9 @@ public class TerminalProcess : Process
private const int LineHeight = 30; private const int LineHeight = 30;
private Size lastSize = new(-1, -1); private Size lastSize = new(-1, -1);
public TerminalProcess() : base("Terminal")
{
}
internal override void Start(string[] args) internal override void Start(string[] args)
{ {
window = WindowManager.CreateWindow(this, "Terminal", new Point(50, 50), new Size(400, 300)); window = WindowManager.CreateWindow(this, "Terminal", new Size(400, 300));
PrintLine("RemSox GUI Terminal v1.0"); PrintLine("RemSox GUI Terminal v1.0");
PrintLine("Type 'help' for commands."); PrintLine("Type 'help' for commands.");
@@ -58,10 +54,18 @@ public class TerminalProcess : Process
if (!string.IsNullOrWhiteSpace(cmd)) if (!string.IsNullOrWhiteSpace(cmd))
{ {
if (cmd == "exit") if (cmd.Trim() == "exit")
{ {
RequestStop(); RequestStop();
} }
else if (cmd.Trim() == "clear")
{
lock (textLinesLock)
{
history.Clear();
UpdateDisplay();
}
}
else else
{ {
bool found = CommandManager.TryExecute(cmd, PrintLine); bool found = CommandManager.TryExecute(cmd, PrintLine);
+1 -1
View File
@@ -11,7 +11,7 @@ public class TestProcess() : Process("Test Process")
{ {
internal override void Start(string[] args) internal override void Start(string[] args)
{ {
Window window = WindowManager.CreateWindow(this, "Test Window", Point.Empty, new Size(200, 150)); Window window = WindowManager.CreateWindow(this, "Test Window", new Size(200, 150));
window.AutoFlush = true; window.AutoFlush = true;
Circle circle = window.CreateUIElement<Circle>(rect => Circle circle = window.CreateUIElement<Circle>(rect =>
+5 -5
View File
@@ -288,7 +288,7 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
/// <summary> /// <summary>
/// Updates the drag or resize interaction based on the current pointer position. /// Updates the drag or resize interaction based on the current pointer position.
/// </summary> /// </summary>
public void UpdateInteraction(Point pointerPosition, Point screenSize) public void UpdateInteraction(Point pointerPosition, Size screenSize)
{ {
if (currentInteraction == InteractionMode.Drag) if (currentInteraction == InteractionMode.Drag)
{ {
@@ -300,8 +300,8 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
newY = Math.Max(0, newY); newY = Math.Max(0, newY);
// Clamp right and bottom edges // Clamp right and bottom edges
newX = Math.Min(screenSize.X - Size.Width, newX); newX = Math.Min(screenSize.Width - Size.Width, newX);
newY = Math.Min(screenSize.Y - Size.Height, newY); newY = Math.Min(screenSize.Height - Size.Height, newY);
Position = new Point(newX, newY); Position = new Point(newX, newY);
Flush(); Flush();
@@ -322,12 +322,12 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
if (currentInteraction is InteractionMode.ResizeRight or InteractionMode.ResizeBottomRight or InteractionMode.ResizeTopRight) if (currentInteraction is InteractionMode.ResizeRight or InteractionMode.ResizeBottomRight or InteractionMode.ResizeTopRight)
{ {
newW = Math.Max(minWidth, interactionStartBounds.Width + dx); newW = Math.Max(minWidth, interactionStartBounds.Width + dx);
newW = Math.Min(newW, screenSize.X - newX); newW = Math.Min(newW, screenSize.Width - newX);
} }
if (currentInteraction is InteractionMode.ResizeBottom or InteractionMode.ResizeBottomRight or InteractionMode.ResizeBottomLeft) if (currentInteraction is InteractionMode.ResizeBottom or InteractionMode.ResizeBottomRight or InteractionMode.ResizeBottomLeft)
{ {
newH = Math.Max(minHeight, interactionStartBounds.Height + dy); newH = Math.Max(minHeight, interactionStartBounds.Height + dy);
newH = Math.Min(newH, screenSize.Y - newY); newH = Math.Min(newH, screenSize.Height - newY);
} }
if (currentInteraction is InteractionMode.ResizeLeft or InteractionMode.ResizeBottomLeft or InteractionMode.ResizeTopLeft) if (currentInteraction is InteractionMode.ResizeLeft or InteractionMode.ResizeBottomLeft or InteractionMode.ResizeTopLeft)
{ {
+106 -1
View File
@@ -45,7 +45,7 @@ public static class WindowManager
} }
else if (leftButtonDown && activeInteractWindow is not null) else if (leftButtonDown && activeInteractWindow is not null)
{ {
activeInteractWindow.UpdateInteraction(pointerPosition, new Point((int)canvas.Mode.Width, (int)canvas.Mode.Height)); activeInteractWindow.UpdateInteraction(pointerPosition, new Size((int)canvas.Mode.Width, (int)canvas.Mode.Height));
} }
else if (!leftButtonDown && activeInteractWindow is not null) else if (!leftButtonDown && activeInteractWindow is not null)
{ {
@@ -106,6 +106,111 @@ public static class WindowManager
return window; return window;
} }
/// <summary>
/// Creates and registers a new window, automatically finding the most spacious, least overlapping position.
/// </summary>
public static Window CreateWindow(Process process, string title, Size size)
{
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
int screenWidth = (int)canvas.Mode.Width;
int screenHeight = (int)canvas.Mode.Height;
List<Window> existingWindows;
lock (windowsLock)
{
existingWindows = windows.Values.SelectMany(w => w).ToList();
}
int bestX = 50;
int bestY = 50;
int minOverlap = int.MaxValue;
int maxSpaciousness = -1;
const int step = 40;
int maxX = Math.Max(0, screenWidth - size.Width);
int maxY = Math.Max(0, screenHeight - size.Height);
for (int y = 0; y <= maxY; y += step)
{
for (int x = 0; x <= maxX; x += step)
{
int currentOverlap = 0;
int minWindowDist = int.MaxValue;
// Edges of the proposed window
int rectLeft = x;
int rectTop = y;
int rectRight = x + size.Width;
int rectBottom = y + size.Height;
foreach (Window win in existingWindows)
{
int winLeft = win.Position.X;
int winTop = win.Position.Y;
int winRight = win.Position.X + win.Size.Width;
int winBottom = win.Position.Y + win.Size.Height;
// Calculate Overlap (AABB)
int intersectLeft = Math.Max(rectLeft, winLeft);
int intersectTop = Math.Max(rectTop, winTop);
int intersectRight = Math.Min(rectRight, winRight);
int intersectBottom = Math.Min(rectBottom, winBottom);
if (intersectRight > intersectLeft && intersectBottom > intersectTop)
{
currentOverlap += (intersectRight - intersectLeft) * (intersectBottom - intersectTop);
}
// Calculate True Edge-to-Edge Distance (Manhattan)
// If the windows overlap, dx and dy will be 0.
int dx = Math.Max(0, Math.Max(winLeft - rectRight, rectLeft - winRight));
int dy = Math.Max(0, Math.Max(winTop - rectBottom, rectTop - winBottom));
int dist = dx + dy;
if (dist < minWindowDist)
{
minWindowDist = dist;
}
}
// Calculate Distance to Screen Borders
int borderDistLeft = x;
int borderDistTop = y;
int borderDistRight = screenWidth - rectRight;
int borderDistBottom = screenHeight - rectBottom;
int minBorderDist = Math.Min(Math.Min(borderDistLeft, borderDistTop), Math.Min(borderDistRight, borderDistBottom));
// Spaciousness evaluates the closest boundary (either a screen edge or another window edge)
int currentSpaciousness = existingWindows.Count == 0
? minBorderDist
: Math.Min(minBorderDist, minWindowDist);
// Score Evaluation
if (currentOverlap < minOverlap)
{
minOverlap = currentOverlap;
maxSpaciousness = currentSpaciousness;
bestX = x;
bestY = y;
}
else if (currentOverlap == minOverlap)
{
// If overlap is tied (e.g., both are 0), pick the most spacious position
if (currentSpaciousness > maxSpaciousness)
{
maxSpaciousness = currentSpaciousness;
bestX = x;
bestY = y;
}
}
}
}
return CreateWindow(process, title, new Point(bestX, bestY), size);
}
/// <summary> /// <summary>
/// Closes a specific window and notifies the renderer. /// Closes a specific window and notifies the renderer.
/// </summary> /// </summary>