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)
|
||||
{
|
||||
// Suspend the CLI while the GUI is active to prevent blocking and console corruption.
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(1000);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,20 +29,23 @@ public class DesktopProcess : Process
|
||||
|
||||
// Trigger Canvas initialization
|
||||
FullScreenCanvas.GetFullScreenCanvas();
|
||||
|
||||
|
||||
// Force existing windows to redraw onto the new canvas renderer
|
||||
WindowManager.InvalidateAll();
|
||||
|
||||
// Start the terminal process within the desktop environment
|
||||
ProcessManager.SpawnProcess<TerminalProcess>();
|
||||
|
||||
while (!StopRequested)
|
||||
{
|
||||
WindowManager.Update();
|
||||
|
||||
|
||||
// Sleep slightly to yield CPU to the main CLI thread (approx 60 FPS)
|
||||
Thread.Sleep(16);
|
||||
//Thread.Sleep(16);
|
||||
}
|
||||
|
||||
|
||||
IsRunning = false;
|
||||
|
||||
|
||||
// Cosmos doesn't robustly support switching back to text mode yet.
|
||||
// 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.Linq;
|
||||
using Cosmos.Kernel.System.Graphics;
|
||||
using Cosmos.Kernel.System.Graphics.Fonts;
|
||||
|
||||
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, 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)
|
||||
{
|
||||
bool changed = false;
|
||||
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.Properties.TryGetValue("ZIndex", out object? rawZIndex) && rawZIndex is int z)
|
||||
{
|
||||
windowZIndices[command.WindowId] = z;
|
||||
isZOrderDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +48,14 @@ public sealed class CanvasRenderSource : IRenderSource
|
||||
? windowSize
|
||||
: new Size(160, 120);
|
||||
|
||||
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? currentCanvas) ||
|
||||
currentCanvas.Mode.Width != size.Width ||
|
||||
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? currentCanvas) ||
|
||||
currentCanvas.Mode.Width != size.Width ||
|
||||
currentCanvas.Mode.Height != size.Height)
|
||||
{
|
||||
windowCanvases[command.WindowId] = new Canvas(size.Width, size.Height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!windowCanvases.ContainsKey(command.WindowId))
|
||||
{
|
||||
windowCanvases[command.WindowId] = new Canvas(160, 120);
|
||||
@@ -62,16 +80,40 @@ public sealed class CanvasRenderSource : IRenderSource
|
||||
{
|
||||
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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
@@ -81,6 +123,9 @@ public sealed class CanvasRenderSource : IRenderSource
|
||||
|
||||
screenCanvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
||||
screenCanvas.Display();
|
||||
|
||||
lastPointerPosition = pointerPosition;
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
||||
@@ -115,4 +160,42 @@ public sealed class CanvasRenderSource : IRenderSource
|
||||
|
||||
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 event Action<Cosmos.Kernel.System.Keyboard.KeyEvent>? OnKeyEvent;
|
||||
|
||||
public void HandleKeyEvent(Cosmos.Kernel.System.Keyboard.KeyEvent keyEvent)
|
||||
{
|
||||
OnKeyEvent?.Invoke(keyEvent);
|
||||
}
|
||||
|
||||
public bool IsFocused
|
||||
{
|
||||
get => WindowManager.IsWindowFocused(this);
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Drawing;
|
||||
using System.Linq;
|
||||
using Cosmos.Kernel.System.Graphics;
|
||||
using Cosmos.Kernel.System.Mouse;
|
||||
using Cosmos.Kernel.System.Keyboard;
|
||||
using RemSox.Processing;
|
||||
using RemSox.UI.GUI.Rendering;
|
||||
|
||||
@@ -51,6 +52,11 @@ public static class WindowManager
|
||||
|
||||
wasLeftButtonDown = leftButtonDown;
|
||||
|
||||
if (KeyboardManager.TryReadKey(out KeyEvent keyEvent))
|
||||
{
|
||||
focusedWindow?.HandleKeyEvent(keyEvent);
|
||||
}
|
||||
|
||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||
CanvasRenderSource.CompositeAndDisplay(canvas, pointerPosition);
|
||||
|
||||
@@ -86,6 +92,8 @@ public static class WindowManager
|
||||
{
|
||||
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)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user