mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-04 09:06:23 +02:00
Add windowed terminal, basic text, rectangle rendering, and keyboard events
This commit is contained in:
@@ -53,7 +53,7 @@ public class Kernel : Sys.Kernel
|
|||||||
if (Processes.DesktopProcess.IsRunning)
|
if (Processes.DesktopProcess.IsRunning)
|
||||||
{
|
{
|
||||||
// Suspend the CLI while the GUI is active to prevent blocking and console corruption.
|
// Suspend the CLI while the GUI is active to prevent blocking and console corruption.
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,20 +29,23 @@ public class DesktopProcess : Process
|
|||||||
|
|
||||||
// Trigger Canvas initialization
|
// Trigger Canvas initialization
|
||||||
FullScreenCanvas.GetFullScreenCanvas();
|
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>();
|
||||||
|
|
||||||
while (!StopRequested)
|
while (!StopRequested)
|
||||||
{
|
{
|
||||||
WindowManager.Update();
|
WindowManager.Update();
|
||||||
|
|
||||||
// Sleep slightly to yield CPU to the main CLI thread (approx 60 FPS)
|
// Sleep slightly to yield CPU to the main CLI thread (approx 60 FPS)
|
||||||
Thread.Sleep(16);
|
//Thread.Sleep(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
// Cosmos doesn't robustly support switching back to text mode yet.
|
// Cosmos doesn't robustly support switching back to text mode yet.
|
||||||
// We will stop updating, but the screen will remain in graphics mode.
|
// We will stop updating, but the screen will remain in graphics mode.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using Cosmos.Kernel.System;
|
||||||
|
using Cosmos.Kernel.System.Keyboard;
|
||||||
|
using RemSox.Processing;
|
||||||
|
using RemSox.UI.GUI.UIEelements;
|
||||||
|
using RemSox.UI.GUI.Windows;
|
||||||
|
using RemSox.UI.GUI.CLI;
|
||||||
|
|
||||||
|
namespace RemSox.Processes;
|
||||||
|
|
||||||
|
public class TerminalProcess : Process
|
||||||
|
{
|
||||||
|
private Window? window;
|
||||||
|
private readonly List<string> history = new();
|
||||||
|
private string currentInput = "";
|
||||||
|
private readonly List<Text> textLines = new();
|
||||||
|
private const int MaxLines = 15;
|
||||||
|
private const int LineHeight = 16;
|
||||||
|
|
||||||
|
public TerminalProcess() : base("Terminal")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Run()
|
||||||
|
{
|
||||||
|
window = WindowManager.CreateWindow(this, "Terminal", new Point(50, 50), new Size(400, 300));
|
||||||
|
|
||||||
|
for (int i = 0; i < MaxLines + 1; i++) // +1 for the input line
|
||||||
|
{
|
||||||
|
var textElement = window.CreateUIElement<Text>(t =>
|
||||||
|
{
|
||||||
|
t.Position = new Point(5, 20 + (i * LineHeight));
|
||||||
|
t.Color = Color.LightGreen;
|
||||||
|
t.Content = "";
|
||||||
|
});
|
||||||
|
textLines.Add(textElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.AutoFlush = true;
|
||||||
|
|
||||||
|
PrintLine("RemSox GUI Terminal v1.0");
|
||||||
|
PrintLine("Type 'help' for commands.");
|
||||||
|
|
||||||
|
window.OnKeyEvent += HandleKey;
|
||||||
|
|
||||||
|
while (!StopRequested)
|
||||||
|
{
|
||||||
|
System.Threading.Thread.Sleep(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.OnKeyEvent -= HandleKey;
|
||||||
|
WindowManager.CloseWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleKey(KeyEvent keyEvent)
|
||||||
|
{
|
||||||
|
if (keyEvent.Key == ConsoleKeyEx.Enter)
|
||||||
|
{
|
||||||
|
string cmd = currentInput;
|
||||||
|
PrintLine("> " + cmd);
|
||||||
|
currentInput = "";
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(cmd))
|
||||||
|
{
|
||||||
|
// Temporarily capture console output
|
||||||
|
// Wait, CommandManager uses Console.WriteLine.
|
||||||
|
// Redirecting Console output in Cosmos might be tricky.
|
||||||
|
// For now we will just execute it. Note: CommandManager commands write to standard Console,
|
||||||
|
// which might write over the VGA buffer or just be invisible.
|
||||||
|
// To make a proper terminal, commands should return strings or we need a custom ICommand context.
|
||||||
|
|
||||||
|
if (cmd == "exit")
|
||||||
|
{
|
||||||
|
RequestStop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool found = CommandManager.TryExecute(cmd);
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
PrintLine($"\"{cmd}\" is not a command");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintLine("Command executed. (Output sent to background console)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateDisplay();
|
||||||
|
}
|
||||||
|
else if (keyEvent.Key == ConsoleKeyEx.Backspace)
|
||||||
|
{
|
||||||
|
if (currentInput.Length > 0)
|
||||||
|
{
|
||||||
|
currentInput = currentInput.Substring(0, currentInput.Length - 1);
|
||||||
|
UpdateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (keyEvent.KeyChar >= 32 && keyEvent.KeyChar <= 126) // Printable chars
|
||||||
|
{
|
||||||
|
currentInput += keyEvent.KeyChar;
|
||||||
|
UpdateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintLine(string text)
|
||||||
|
{
|
||||||
|
history.Add(text);
|
||||||
|
if (history.Count > MaxLines)
|
||||||
|
{
|
||||||
|
history.RemoveAt(0);
|
||||||
|
}
|
||||||
|
UpdateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDisplay()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < MaxLines; i++)
|
||||||
|
{
|
||||||
|
if (i < history.Count)
|
||||||
|
{
|
||||||
|
textLines[i].Content = history[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
textLines[i].Content = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last line is the input line
|
||||||
|
textLines[MaxLines].Content = "> " + currentInput + "_";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Drawing;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Cosmos.Kernel.System.Graphics;
|
using Cosmos.Kernel.System.Graphics;
|
||||||
|
using Cosmos.Kernel.System.Graphics.Fonts;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.Rendering;
|
namespace RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
@@ -12,15 +13,32 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
private static readonly Dictionary<int, Point> windowPositions = new();
|
private static readonly Dictionary<int, Point> windowPositions = new();
|
||||||
private static readonly Dictionary<int, int> windowZIndices = new();
|
private static readonly Dictionary<int, int> windowZIndices = new();
|
||||||
|
|
||||||
|
private static bool isDirty = true;
|
||||||
|
private static Point lastPointerPosition = new Point(-1, -1);
|
||||||
|
private static List<int> orderedWindowsCache = new();
|
||||||
|
private static bool isZOrderDirty = true;
|
||||||
|
|
||||||
public void Render(IEnumerable<RenderCommand> commands)
|
public void Render(IEnumerable<RenderCommand> commands)
|
||||||
{
|
{
|
||||||
|
bool changed = false;
|
||||||
foreach (RenderCommand command in commands)
|
foreach (RenderCommand command in commands)
|
||||||
{
|
{
|
||||||
|
changed = true;
|
||||||
|
if (command.ElementType == "WindowClose")
|
||||||
|
{
|
||||||
|
windowCanvases.Remove(command.WindowId);
|
||||||
|
windowPositions.Remove(command.WindowId);
|
||||||
|
windowZIndices.Remove(command.WindowId);
|
||||||
|
isZOrderDirty = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (command.ElementType == "Window" || command.ElementType == "WindowMove")
|
if (command.ElementType == "Window" || command.ElementType == "WindowMove")
|
||||||
{
|
{
|
||||||
if (command.Properties.TryGetValue("ZIndex", out object? rawZIndex) && rawZIndex is int z)
|
if (command.Properties.TryGetValue("ZIndex", out object? rawZIndex) && rawZIndex is int z)
|
||||||
{
|
{
|
||||||
windowZIndices[command.WindowId] = z;
|
windowZIndices[command.WindowId] = z;
|
||||||
|
isZOrderDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,14 +48,14 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
? windowSize
|
? windowSize
|
||||||
: new Size(160, 120);
|
: new Size(160, 120);
|
||||||
|
|
||||||
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? currentCanvas) ||
|
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? currentCanvas) ||
|
||||||
currentCanvas.Mode.Width != size.Width ||
|
currentCanvas.Mode.Width != size.Width ||
|
||||||
currentCanvas.Mode.Height != size.Height)
|
currentCanvas.Mode.Height != size.Height)
|
||||||
{
|
{
|
||||||
windowCanvases[command.WindowId] = new Canvas(size.Width, size.Height);
|
windowCanvases[command.WindowId] = new Canvas(size.Width, size.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!windowCanvases.ContainsKey(command.WindowId))
|
if (!windowCanvases.ContainsKey(command.WindowId))
|
||||||
{
|
{
|
||||||
windowCanvases[command.WindowId] = new Canvas(160, 120);
|
windowCanvases[command.WindowId] = new Canvas(160, 120);
|
||||||
@@ -62,16 +80,40 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
{
|
{
|
||||||
RenderCircle(windowCanvas, command);
|
RenderCircle(windowCanvas, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Rectangle")
|
||||||
|
{
|
||||||
|
RenderRectangle(windowCanvas, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "Text")
|
||||||
|
{
|
||||||
|
RenderText(windowCanvas, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
isDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CompositeAndDisplay(Canvas screenCanvas, Point pointerPosition)
|
public static void CompositeAndDisplay(Canvas screenCanvas, Point pointerPosition)
|
||||||
{
|
{
|
||||||
screenCanvas.Clear(Color.Black);
|
if (!isDirty && pointerPosition == lastPointerPosition)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var orderedWindows = windowPositions.Keys.OrderBy(id => windowZIndices.TryGetValue(id, out int z) ? z : 0);
|
if (isZOrderDirty)
|
||||||
|
{
|
||||||
|
orderedWindowsCache = windowPositions.Keys.OrderBy(id => windowZIndices.TryGetValue(id, out int z) ? z : 0).ToList();
|
||||||
|
isZOrderDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var windowId in orderedWindows)
|
//screenCanvas.Clear(Color.Black);
|
||||||
|
|
||||||
|
foreach (var windowId in orderedWindowsCache)
|
||||||
{
|
{
|
||||||
if (windowPositions.TryGetValue(windowId, out Point position) && windowCanvases.TryGetValue(windowId, out Canvas? windowCanvas))
|
if (windowPositions.TryGetValue(windowId, out Point position) && windowCanvases.TryGetValue(windowId, out Canvas? windowCanvas))
|
||||||
{
|
{
|
||||||
@@ -81,6 +123,9 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
|
|
||||||
screenCanvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
screenCanvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
||||||
screenCanvas.Display();
|
screenCanvas.Display();
|
||||||
|
|
||||||
|
lastPointerPosition = pointerPosition;
|
||||||
|
isDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
||||||
@@ -115,4 +160,42 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
|
|
||||||
canvas.DrawFilledCircle(color, centerX, centerY, radius);
|
canvas.DrawFilledCircle(color, centerX, centerY, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RenderRectangle(Canvas canvas, RenderCommand command)
|
||||||
|
{
|
||||||
|
Color color = command.Properties.TryGetValue("Color", out object? rawColor) && rawColor is Color rectColor
|
||||||
|
? rectColor
|
||||||
|
: Color.White;
|
||||||
|
|
||||||
|
Size size = command.Properties.TryGetValue("Size", out object? rawSize) && rawSize is Size rectSize
|
||||||
|
? rectSize
|
||||||
|
: new Size(10, 10);
|
||||||
|
|
||||||
|
bool isFilled = command.Properties.TryGetValue("IsFilled", out object? rawFilled) && rawFilled is bool filled && filled;
|
||||||
|
|
||||||
|
if (isFilled)
|
||||||
|
{
|
||||||
|
canvas.DrawFilledRectangle(color, command.Position.X, command.Position.Y, size.Width, size.Height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
canvas.DrawRectangle(color, command.Position.X, command.Position.Y, size.Width, size.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderText(Canvas canvas, RenderCommand command)
|
||||||
|
{
|
||||||
|
Color color = command.Properties.TryGetValue("Color", out object? rawColor) && rawColor is Color textColor
|
||||||
|
? textColor
|
||||||
|
: Color.White;
|
||||||
|
|
||||||
|
string content = command.Properties.TryGetValue("Content", out object? rawContent) && rawContent is string textContent
|
||||||
|
? textContent
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(content))
|
||||||
|
{
|
||||||
|
canvas.DrawString(content, Cosmos.Kernel.System.Graphics.Fonts.PCScreenFont.DefaultFont, color, command.Position.X, command.Position.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
||||||
|
|
||||||
|
public class Rectangle() : Shape("Rectangle")
|
||||||
|
{
|
||||||
|
public Size Size
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetProperty(nameof(Size), ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFilled
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetProperty(nameof(IsFilled), ref field, value);
|
||||||
|
} = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace RemSox.UI.GUI.UIEelements;
|
||||||
|
|
||||||
|
public class Text() : UIElement("Text")
|
||||||
|
{
|
||||||
|
public string Content
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetProperty(nameof(Content), ref field, value);
|
||||||
|
} = string.Empty;
|
||||||
|
|
||||||
|
public Color Color
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetProperty(nameof(Color), ref field, value);
|
||||||
|
} = Color.White;
|
||||||
|
}
|
||||||
@@ -18,6 +18,13 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
|
|
||||||
public bool AutoFlush { get; set; } = false;
|
public bool AutoFlush { get; set; } = false;
|
||||||
|
|
||||||
|
public event Action<Cosmos.Kernel.System.Keyboard.KeyEvent>? OnKeyEvent;
|
||||||
|
|
||||||
|
public void HandleKeyEvent(Cosmos.Kernel.System.Keyboard.KeyEvent keyEvent)
|
||||||
|
{
|
||||||
|
OnKeyEvent?.Invoke(keyEvent);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsFocused
|
public bool IsFocused
|
||||||
{
|
{
|
||||||
get => WindowManager.IsWindowFocused(this);
|
get => WindowManager.IsWindowFocused(this);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Cosmos.Kernel.System.Graphics;
|
using Cosmos.Kernel.System.Graphics;
|
||||||
using Cosmos.Kernel.System.Mouse;
|
using Cosmos.Kernel.System.Mouse;
|
||||||
|
using Cosmos.Kernel.System.Keyboard;
|
||||||
using RemSox.Processing;
|
using RemSox.Processing;
|
||||||
using RemSox.UI.GUI.Rendering;
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
@@ -51,6 +52,11 @@ public static class WindowManager
|
|||||||
|
|
||||||
wasLeftButtonDown = leftButtonDown;
|
wasLeftButtonDown = leftButtonDown;
|
||||||
|
|
||||||
|
if (KeyboardManager.TryReadKey(out KeyEvent keyEvent))
|
||||||
|
{
|
||||||
|
focusedWindow?.HandleKeyEvent(keyEvent);
|
||||||
|
}
|
||||||
|
|
||||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||||
CanvasRenderSource.CompositeAndDisplay(canvas, pointerPosition);
|
CanvasRenderSource.CompositeAndDisplay(canvas, pointerPosition);
|
||||||
|
|
||||||
@@ -86,6 +92,8 @@ public static class WindowManager
|
|||||||
{
|
{
|
||||||
processWindows.Remove(window);
|
processWindows.Remove(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderSource.Render(new[] { new RenderCommand { WindowId = window.Id, ElementId = window.Id, ElementType = "WindowClose", Position = window.Position, Properties = new Dictionary<string, object?>() } });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Window> GetWindowsForProcess(Process process)
|
public static List<Window> GetWindowsForProcess(Process process)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user