mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-09 15:56:32 +02:00
Replace string element render types with RenderCommandType enum, decompose UI elements (Button, CheckBox, chrome) into drawing primitives via ToPrimitives(), emit per-element commands on flush, and move composite into IRenderSource.
This commit is contained in:
@@ -87,6 +87,24 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
|
|||||||
return Task.WhenAll(sendTasks);
|
return Task.WhenAll(sendTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Broadcasts raw binary payload to all connected clients. </summary>
|
||||||
|
public Task SendRawToAll(string type, byte[] payload)
|
||||||
|
{
|
||||||
|
List<Task> sendTasks = [];
|
||||||
|
|
||||||
|
foreach (TcpConnection conn in connections.Keys)
|
||||||
|
{
|
||||||
|
sendTasks.Add(SendRaw(conn, new TcpMessage
|
||||||
|
{
|
||||||
|
Type = type,
|
||||||
|
RequestId = Guid.NewGuid().ToString(),
|
||||||
|
Payload = payload
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.WhenAll(sendTasks);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnConnectionClosed(TcpConnection conn)
|
protected override void OnConnectionClosed(TcpConnection conn)
|
||||||
{
|
{
|
||||||
_ = connections.TryRemove(conn, out _);
|
_ = connections.TryRemove(conn, out _);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Cosmos.Kernel.System.Graphics;
|
using Cosmos.Kernel.System.Graphics;
|
||||||
using Cosmos.Kernel.System.Graphics.Fonts;
|
using Cosmos.Kernel.System.Graphics.Fonts;
|
||||||
|
using Cosmos.Kernel.System.Mouse;
|
||||||
|
|
||||||
|
using RemSox.UI.GUI.UIEelements;
|
||||||
using RemSox.Utils;
|
using RemSox.Utils;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -14,77 +16,97 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
private static readonly Dictionary<int, int> windowZIndices = [];
|
private static readonly Dictionary<int, int> windowZIndices = [];
|
||||||
|
|
||||||
// Sorted list keeps windows in Z-order without re-sorting.
|
// Sorted list keeps windows in Z-order without re-sorting.
|
||||||
// Key = (zIndex << 16 | windowId) so equal Z stays insertion-stable.
|
// Key = (zIndex << 32 | windowId) so equal Z stays insertion-stable.
|
||||||
private static readonly SortedList<long, int> zOrderedWindows = [];
|
private static readonly SortedList<long, int> zOrderedWindows = [];
|
||||||
|
|
||||||
private static bool isContentDirty = true; // pixel content changed
|
// Accumulated drawing primitives per window (in draw order).
|
||||||
private static bool isPositionDirty = true; // only layout changed
|
private static readonly Dictionary<int, List<(int ElementId, RenderCommand Command)>> windowPrimitives = [];
|
||||||
|
|
||||||
|
private static readonly HashSet<int> dirtyWindows = [];
|
||||||
|
private static bool isPositionDirty = true;
|
||||||
private static Point lastPointerPosition = new(-1, -1);
|
private static Point lastPointerPosition = new(-1, -1);
|
||||||
|
|
||||||
private static readonly Lock renderLock = new();
|
private static readonly Lock renderLock = new();
|
||||||
|
|
||||||
private static readonly Dictionary<string, Action<Canvas, RenderCommand>> elementRenderers = new()
|
|
||||||
{
|
|
||||||
["Circle"] = RenderCircle,
|
|
||||||
["Rectangle"] = RenderRectangle,
|
|
||||||
["Text"] = RenderText,
|
|
||||||
["Line"] = RenderLine,
|
|
||||||
["Button"] = RenderButton,
|
|
||||||
["CheckBox"] = RenderCheckBox,
|
|
||||||
};
|
|
||||||
|
|
||||||
public void Render(IEnumerable<RenderCommand> commands)
|
public void Render(IEnumerable<RenderCommand> commands)
|
||||||
{
|
{
|
||||||
lock (renderLock)
|
lock (renderLock)
|
||||||
{
|
{
|
||||||
foreach (RenderCommand command in commands)
|
foreach (RenderCommand command in commands)
|
||||||
{
|
{
|
||||||
switch (command.ElementType)
|
switch (command.Type)
|
||||||
{
|
{
|
||||||
case "WindowClose":
|
case RenderCommandType.CreateWindow:
|
||||||
RemoveWindow(command.WindowId);
|
CreateOrUpdateWindow(command);
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
case "WindowMove":
|
case RenderCommandType.DestroyWindow:
|
||||||
|
RemoveWindow(command.WindowId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.MoveWindow:
|
||||||
if (windowCanvases.ContainsKey(command.WindowId))
|
if (windowCanvases.ContainsKey(command.WindowId))
|
||||||
{
|
{
|
||||||
windowPositions[command.WindowId] = command.Position;
|
windowPositions[command.WindowId] = command.Position;
|
||||||
isPositionDirty = true;
|
isPositionDirty = true;
|
||||||
}
|
}
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
case "Window":
|
case RenderCommandType.RemovePrimitives:
|
||||||
ProcessWindowCommand(command);
|
RemovePrimitives(command.WindowId, command.ElementId);
|
||||||
isContentDirty = true;
|
break;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!windowCanvases.TryGetValue(command.WindowId, out Canvas? canvas))
|
default:
|
||||||
{
|
if (windowCanvases.ContainsKey(command.WindowId))
|
||||||
continue;
|
{
|
||||||
}
|
UpsertPrimitive(command);
|
||||||
|
}
|
||||||
if (elementRenderers.TryGetValue(command.ElementType, out Action<Canvas, RenderCommand>? renderer))
|
break;
|
||||||
{
|
|
||||||
renderer(canvas, command);
|
|
||||||
isContentDirty = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CompositeAndDisplay(Canvas screenCanvas, Point pointerPosition)
|
public void Composite()
|
||||||
{
|
{
|
||||||
|
Canvas screenCanvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||||
|
Point pointerPosition = new(MouseManager.X, MouseManager.Y);
|
||||||
|
|
||||||
lock (renderLock)
|
lock (renderLock)
|
||||||
{
|
{
|
||||||
bool pointerMoved = pointerPosition != lastPointerPosition;
|
bool pointerMoved = pointerPosition != lastPointerPosition;
|
||||||
if (!isContentDirty && !isPositionDirty && !pointerMoved)
|
bool hasDirty = dirtyWindows.Count > 0;
|
||||||
|
|
||||||
|
if (!hasDirty && !isPositionDirty && !pointerMoved)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
screenCanvas.Clear(Color.Black);
|
// Redraw dirty windows from accumulated primitives
|
||||||
|
if (hasDirty)
|
||||||
|
{
|
||||||
|
foreach (int winId in dirtyWindows)
|
||||||
|
{
|
||||||
|
if (!windowCanvases.TryGetValue(winId, out Canvas? canvas))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.Clear(Color.Black);
|
||||||
|
|
||||||
|
if (windowPrimitives.TryGetValue(winId, out var primitives))
|
||||||
|
{
|
||||||
|
foreach (var (_, cmd) in primitives)
|
||||||
|
{
|
||||||
|
DrawPrimitive(canvas, cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dirtyWindows.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Composite all windows to screen
|
||||||
|
screenCanvas.Clear(Color.Black);
|
||||||
foreach (int windowId in zOrderedWindows.Values)
|
foreach (int windowId in zOrderedWindows.Values)
|
||||||
{
|
{
|
||||||
if (windowPositions.TryGetValue(windowId, out Point pos) &&
|
if (windowPositions.TryGetValue(windowId, out Point pos) &&
|
||||||
@@ -98,17 +120,48 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
screenCanvas.Display();
|
screenCanvas.Display();
|
||||||
|
|
||||||
lastPointerPosition = pointerPosition;
|
lastPointerPosition = pointerPosition;
|
||||||
isContentDirty = false;
|
|
||||||
isPositionDirty = false;
|
isPositionDirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// --- Accumulated state management ---
|
||||||
|
|
||||||
|
private static void CreateOrUpdateWindow(RenderCommand cmd)
|
||||||
|
{
|
||||||
|
int id = cmd.WindowId;
|
||||||
|
Size size = Get(cmd.Properties, "Size", new Size(160, 120));
|
||||||
|
int zIndex = Get(cmd.Properties, "ZIndex", 0);
|
||||||
|
|
||||||
|
if (!windowCanvases.TryGetValue(id, out Canvas? existing) ||
|
||||||
|
existing.Mode.Width != size.Width ||
|
||||||
|
existing.Mode.Height != size.Height)
|
||||||
|
{
|
||||||
|
windowCanvases[id] = new Canvas(size.Width, size.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
windowPositions[id] = cmd.Position;
|
||||||
|
|
||||||
|
if (windowZIndices.TryGetValue(id, out int oldZ))
|
||||||
|
{
|
||||||
|
_ = zOrderedWindows.Remove(ZKey(oldZ, id));
|
||||||
|
}
|
||||||
|
windowZIndices[id] = zIndex;
|
||||||
|
zOrderedWindows[ZKey(zIndex, id)] = id;
|
||||||
|
|
||||||
|
if (!windowPrimitives.ContainsKey(id))
|
||||||
|
{
|
||||||
|
windowPrimitives[id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = dirtyWindows.Add(id);
|
||||||
|
isPositionDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void RemoveWindow(int windowId)
|
private static void RemoveWindow(int windowId)
|
||||||
{
|
{
|
||||||
_ = windowCanvases.Remove(windowId);
|
_ = windowCanvases.Remove(windowId);
|
||||||
_ = windowPositions.Remove(windowId);
|
_ = windowPositions.Remove(windowId);
|
||||||
|
_ = windowPrimitives.Remove(windowId);
|
||||||
|
|
||||||
if (windowZIndices.TryGetValue(windowId, out int z))
|
if (windowZIndices.TryGetValue(windowId, out int z))
|
||||||
{
|
{
|
||||||
@@ -119,38 +172,69 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
isPositionDirty = true;
|
isPositionDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessWindowCommand(RenderCommand command)
|
private static void UpsertPrimitive(RenderCommand cmd)
|
||||||
{
|
{
|
||||||
int id = command.WindowId;
|
if (!windowPrimitives.TryGetValue(cmd.WindowId, out var list))
|
||||||
|
|
||||||
if (command.Properties.TryGetValue("ZIndex", out object? rawZ) && rawZ is int newZ)
|
|
||||||
{
|
{
|
||||||
if (windowZIndices.TryGetValue(id, out int oldZ))
|
list = [];
|
||||||
{
|
windowPrimitives[cmd.WindowId] = list;
|
||||||
_ = zOrderedWindows.Remove(ZKey(oldZ, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
windowZIndices[id] = newZ;
|
|
||||||
zOrderedWindows[ZKey(newZ, id)] = id;
|
|
||||||
}
|
|
||||||
else if (!windowZIndices.ContainsKey(id))
|
|
||||||
{
|
|
||||||
windowZIndices[id] = 0;
|
|
||||||
zOrderedWindows[ZKey(0, id)] = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Size size = Get(command.Properties, "Size", new Size(160, 120));
|
int idx = list.FindIndex(p => p.ElementId == cmd.ElementId);
|
||||||
|
if (idx >= 0)
|
||||||
if (!windowCanvases.TryGetValue(id, out Canvas? current) ||
|
|
||||||
current.Mode.Width != size.Width ||
|
|
||||||
current.Mode.Height != size.Height)
|
|
||||||
{
|
{
|
||||||
windowCanvases[id] = new Canvas(size.Width, size.Height);
|
list[idx] = (cmd.ElementId, cmd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add((cmd.ElementId, cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
windowPositions[id] = command.Position;
|
_ = dirtyWindows.Add(cmd.WindowId);
|
||||||
isPositionDirty = true;
|
}
|
||||||
RenderWindow(windowCanvases[id], command);
|
|
||||||
|
private static void RemovePrimitives(int windowId, int baseElementId)
|
||||||
|
{
|
||||||
|
if (!windowPrimitives.TryGetValue(windowId, out var list))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.RemoveAll(p => p.ElementId >= 0
|
||||||
|
? (p.ElementId >> UIElement.PrimitiveIdShift) == baseElementId
|
||||||
|
: p.ElementId == baseElementId);
|
||||||
|
|
||||||
|
_ = dirtyWindows.Add(windowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Primitive drawing ---
|
||||||
|
|
||||||
|
private static void DrawPrimitive(Canvas canvas, RenderCommand cmd)
|
||||||
|
{
|
||||||
|
switch (cmd.Type)
|
||||||
|
{
|
||||||
|
case RenderCommandType.DrawFilledRect:
|
||||||
|
DrawFilledRect(canvas, cmd);
|
||||||
|
break;
|
||||||
|
case RenderCommandType.DrawRectBorder:
|
||||||
|
DrawRectBorder(canvas, cmd);
|
||||||
|
break;
|
||||||
|
case RenderCommandType.DrawFilledCircle:
|
||||||
|
DrawFilledCircle(canvas, cmd);
|
||||||
|
break;
|
||||||
|
case RenderCommandType.DrawCircle:
|
||||||
|
DrawCircle(canvas, cmd);
|
||||||
|
break;
|
||||||
|
case RenderCommandType.DrawPoint:
|
||||||
|
DrawPoint(canvas, cmd);
|
||||||
|
break;
|
||||||
|
case RenderCommandType.DrawText:
|
||||||
|
DrawText(canvas, cmd);
|
||||||
|
break;
|
||||||
|
case RenderCommandType.DrawLine:
|
||||||
|
DrawLine(canvas, cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds a stable sort key from Z-index and window ID.
|
// Builds a stable sort key from Z-index and window ID.
|
||||||
@@ -159,110 +243,61 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
return ((long)z << 32) | (uint)id;
|
return ((long)z << 32) | (uint)id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline generic property getter — eliminates repeated TryGetValue + pattern-match boilerplate.
|
|
||||||
private static T Get<T>(IReadOnlyDictionary<string, object?> props, string key, T fallback)
|
private static T Get<T>(IReadOnlyDictionary<string, object?> props, string key, T fallback)
|
||||||
{
|
{
|
||||||
return props.TryGetValue(key, out object? raw) && raw is T value ? value : fallback;
|
return props.TryGetValue(key, out object? raw) && raw is T value ? value : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
private static void DrawFilledRect(Canvas canvas, RenderCommand cmd)
|
||||||
{
|
{
|
||||||
Size size = Get(command.Properties, "Size", new Size(160, 120));
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
bool focused = Get(command.Properties, "IsFocused", false);
|
Size size = Get(cmd.Properties, "Size", new Size(10, 10));
|
||||||
string titleText = Get(command.Properties, "Title", string.Empty);
|
canvas.DrawFilledRectangle(color, cmd.Position.X, cmd.Position.Y, size.Width, size.Height);
|
||||||
|
|
||||||
Color border = focused ? Color.White : Color.DarkGray;
|
|
||||||
Color title = focused ? Color.FromArgb(0, 120, 215) : Color.FromArgb(80, 80, 80);
|
|
||||||
|
|
||||||
canvas.DrawFilledRectangle(Color.FromArgb(32, 32, 32), 0, 0, size.Width, size.Height);
|
|
||||||
canvas.DrawFilledRectangle(title, 0, 0, size.Width, 18);
|
|
||||||
canvas.DrawStringHeight(titleText, PCScreenFont.DefaultFont, Color.White, 4, 2, 18);
|
|
||||||
canvas.DrawRectangle(border, 0, 0, size.Width, size.Height - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderCircle(Canvas canvas, RenderCommand command)
|
private static void DrawRectBorder(Canvas canvas, RenderCommand cmd)
|
||||||
{
|
{
|
||||||
Color color = Get(command.Properties, "Color", Color.White);
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
int radius = Get(command.Properties, "Radius", 10);
|
Size size = Get(cmd.Properties, "Size", new Size(10, 10));
|
||||||
|
canvas.DrawRectangle(color, cmd.Position.X, cmd.Position.Y, size.Width, size.Height);
|
||||||
canvas.DrawFilledCircle(color, command.Position.X + radius, command.Position.Y + radius, radius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderRectangle(Canvas canvas, RenderCommand command)
|
private static void DrawFilledCircle(Canvas canvas, RenderCommand cmd)
|
||||||
{
|
{
|
||||||
Color color = Get(command.Properties, "Color", Color.White);
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
Size size = Get(command.Properties, "Size", new Size(10, 10));
|
int radius = Get(cmd.Properties, "Radius", 10);
|
||||||
bool isFilled = Get(command.Properties, "IsFilled", false);
|
canvas.DrawFilledCircle(color, cmd.Position.X + radius, cmd.Position.Y + radius, radius);
|
||||||
|
|
||||||
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)
|
private static void DrawCircle(Canvas canvas, RenderCommand cmd)
|
||||||
{
|
{
|
||||||
Color color = Get(command.Properties, "Color", Color.White);
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
string content = Get(command.Properties, "Content", string.Empty);
|
int radius = Get(cmd.Properties, "Radius", 10);
|
||||||
int fontSize = Get(command.Properties, "FontSize", 12);
|
canvas.DrawCircle(color, cmd.Position.X + radius, cmd.Position.Y + radius, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawPoint(Canvas canvas, RenderCommand cmd)
|
||||||
|
{
|
||||||
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
|
canvas.DrawPoint(color, cmd.Position.X, cmd.Position.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawText(Canvas canvas, RenderCommand cmd)
|
||||||
|
{
|
||||||
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
|
string content = Get(cmd.Properties, "Content", string.Empty);
|
||||||
|
int fontSize = Get(cmd.Properties, "FontSize", 12);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(content))
|
if (!string.IsNullOrEmpty(content))
|
||||||
{
|
{
|
||||||
canvas.DrawStringHeight(content, PCScreenFont.DefaultFont, color, command.Position.X, command.Position.Y, fontSize);
|
canvas.DrawStringHeight(content, PCScreenFont.DefaultFont, color, cmd.Position.X, cmd.Position.Y, fontSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderLine(Canvas canvas, RenderCommand command)
|
private static void DrawLine(Canvas canvas, RenderCommand cmd)
|
||||||
{
|
{
|
||||||
Color color = Get(command.Properties, "Color", Color.White);
|
Color color = Get(cmd.Properties, "Color", Color.White);
|
||||||
Point end = Get(command.Properties, "EndPosition", command.Position);
|
Point end = Get(cmd.Properties, "EndPosition", cmd.Position);
|
||||||
|
canvas.DrawLine(color, cmd.Position.X, cmd.Position.Y, end.X, end.Y);
|
||||||
canvas.DrawLine(color, command.Position.X, command.Position.Y, end.X, end.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RenderButton(Canvas canvas, RenderCommand command)
|
|
||||||
{
|
|
||||||
Color bg = Get(command.Properties, "BackgroundColor", Color.LightGray);
|
|
||||||
Color fg = Get(command.Properties, "TextColor", Color.Black);
|
|
||||||
Size size = Get(command.Properties, "Size", new Size(60, 20));
|
|
||||||
string text = Get(command.Properties, "Text", string.Empty);
|
|
||||||
|
|
||||||
canvas.DrawFilledRectangle(bg, command.Position.X, command.Position.Y, size.Width, size.Height);
|
|
||||||
canvas.DrawRectangle(Color.DarkGray, command.Position.X, command.Position.Y, size.Width, size.Height);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
int tx = command.Position.X + (size.Width / 2) - (text.Length * 4);
|
|
||||||
int ty = command.Position.Y + (size.Height / 2) - 8;
|
|
||||||
canvas.DrawStringHeight(text, PCScreenFont.DefaultFont, fg, tx, ty, size.Height - 8, size.Width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RenderCheckBox(Canvas canvas, RenderCommand command)
|
|
||||||
{
|
|
||||||
Color bg = Get(command.Properties, "BackgroundColor", Color.LightGray);
|
|
||||||
Color fg = Get(command.Properties, "TextColor", Color.White);
|
|
||||||
bool isChecked = Get(command.Properties, "IsChecked", false);
|
|
||||||
string text = Get(command.Properties, "Text", string.Empty);
|
|
||||||
Size size = Get(command.Properties, "Size", new Size(12, 12));
|
|
||||||
|
|
||||||
int boxSize = size.Height;
|
|
||||||
|
|
||||||
canvas.DrawFilledRectangle(bg, command.Position.X, command.Position.Y, boxSize, boxSize);
|
|
||||||
canvas.DrawRectangle(Color.DarkGray, command.Position.X, command.Position.Y, boxSize, boxSize);
|
|
||||||
|
|
||||||
if (isChecked)
|
|
||||||
{
|
|
||||||
canvas.DrawFilledRectangle(Color.Black, command.Position.X + 3, command.Position.Y + 3, boxSize - 6, boxSize - 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
canvas.DrawStringHeight(text, PCScreenFont.DefaultFont, fg, command.Position.X + boxSize + 5, command.Position.Y - 2, boxSize, size.Width);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,4 +3,5 @@ namespace RemSox.UI.GUI.Rendering;
|
|||||||
public interface IRenderSource
|
public interface IRenderSource
|
||||||
{
|
{
|
||||||
void Render(IEnumerable<RenderCommand> commands);
|
void Render(IEnumerable<RenderCommand> commands);
|
||||||
|
void Composite();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using RemSox.Networking;
|
||||||
|
|
||||||
|
namespace RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
|
public sealed class NetworkRenderSource(TcpRpcServer server) : IRenderSource
|
||||||
|
{
|
||||||
|
private const string MessageType = "RenderCmd";
|
||||||
|
|
||||||
|
public void Render(IEnumerable<RenderCommand> commands)
|
||||||
|
{
|
||||||
|
foreach (RenderCommand cmd in commands)
|
||||||
|
{
|
||||||
|
byte[] data = cmd.ToBytes();
|
||||||
|
_ = server.SendRawToAll(MessageType, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Composite()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.Rendering;
|
namespace RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
@@ -6,7 +7,230 @@ public class RenderCommand
|
|||||||
{
|
{
|
||||||
public required int WindowId { get; set; }
|
public required int WindowId { get; set; }
|
||||||
public required int ElementId { get; set; }
|
public required int ElementId { get; set; }
|
||||||
public required string ElementType { get; set; }
|
public required RenderCommandType Type { get; set; }
|
||||||
public required Point Position { get; set; }
|
public Point Position { get; set; }
|
||||||
public required IReadOnlyDictionary<string, object?> Properties { get; set; }
|
public Dictionary<string, object?> Properties { get; set; } = [];
|
||||||
|
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
using MemoryStream ms = new();
|
||||||
|
Write(ms);
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(Stream s)
|
||||||
|
{
|
||||||
|
s.WriteByte((byte)Type);
|
||||||
|
WriteVarint(s, WindowId);
|
||||||
|
WriteVarint(s, ElementId);
|
||||||
|
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case RenderCommandType.CreateWindow:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
Size size = GetProp("Size", new Size(160, 120));
|
||||||
|
WriteUInt16(s, size.Width);
|
||||||
|
WriteUInt16(s, size.Height);
|
||||||
|
WriteVarint(s, GetProp("ZIndex", 0));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DestroyWindow:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.MoveWindow:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawFilledRect:
|
||||||
|
case RenderCommandType.DrawRectBorder:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
Size rs = GetProp("Size", new Size(10, 10));
|
||||||
|
WriteUInt16(s, rs.Width);
|
||||||
|
WriteUInt16(s, rs.Height);
|
||||||
|
WriteColor(s, GetProp("Color", Color.White));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawFilledCircle:
|
||||||
|
case RenderCommandType.DrawCircle:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
WriteUInt16(s, GetProp("Radius", 10));
|
||||||
|
WriteColor(s, GetProp("Color", Color.White));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawPoint:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
WriteColor(s, GetProp("Color", Color.White));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawText:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
WriteColor(s, GetProp("Color", Color.White));
|
||||||
|
s.WriteByte((byte)GetProp("FontSize", 12));
|
||||||
|
string text = GetProp("Content", string.Empty);
|
||||||
|
byte[] utf8 = Encoding.UTF8.GetBytes(text);
|
||||||
|
WriteVarint(s, utf8.Length);
|
||||||
|
s.Write(utf8, 0, utf8.Length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawLine:
|
||||||
|
WriteInt16(s, Position.X);
|
||||||
|
WriteInt16(s, Position.Y);
|
||||||
|
Point end = GetProp("EndPosition", Position);
|
||||||
|
WriteInt16(s, end.X);
|
||||||
|
WriteInt16(s, end.Y);
|
||||||
|
WriteColor(s, GetProp("Color", Color.White));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.RemovePrimitives:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RenderCommand FromBytes(byte[] data)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
RenderCommandType type = (RenderCommandType)data[offset++];
|
||||||
|
int windowId = ReadVarint(data, ref offset);
|
||||||
|
int elementId = ReadVarint(data, ref offset);
|
||||||
|
Point pos = default;
|
||||||
|
Dictionary<string, object?> props = [];
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case RenderCommandType.CreateWindow:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["Size"] = new Size(ReadUInt16(data, ref offset), ReadUInt16(data, ref offset));
|
||||||
|
props["ZIndex"] = ReadVarint(data, ref offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DestroyWindow:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.MoveWindow:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawFilledRect:
|
||||||
|
case RenderCommandType.DrawRectBorder:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["Size"] = new Size(ReadUInt16(data, ref offset), ReadUInt16(data, ref offset));
|
||||||
|
props["Color"] = ReadColor(data, ref offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawFilledCircle:
|
||||||
|
case RenderCommandType.DrawCircle:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["Radius"] = ReadUInt16(data, ref offset);
|
||||||
|
props["Color"] = ReadColor(data, ref offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawPoint:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["Color"] = ReadColor(data, ref offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawText:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["Color"] = ReadColor(data, ref offset);
|
||||||
|
props["FontSize"] = data[offset++];
|
||||||
|
int textLen = ReadVarint(data, ref offset);
|
||||||
|
props["Content"] = Encoding.UTF8.GetString(data, offset, textLen);
|
||||||
|
offset += textLen;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.DrawLine:
|
||||||
|
pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["EndPosition"] = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset));
|
||||||
|
props["Color"] = ReadColor(data, ref offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RenderCommandType.RemovePrimitives:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RenderCommand
|
||||||
|
{
|
||||||
|
Type = type,
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = elementId,
|
||||||
|
Position = pos,
|
||||||
|
Properties = props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Serialization helpers ---
|
||||||
|
|
||||||
|
private T GetProp<T>(string key, T fallback) =>
|
||||||
|
Properties.TryGetValue(key, out object? raw) && raw is T val ? val : fallback;
|
||||||
|
|
||||||
|
private static void WriteVarint(Stream s, int value)
|
||||||
|
{
|
||||||
|
uint v = (uint)value;
|
||||||
|
while (v >= 0x80)
|
||||||
|
{
|
||||||
|
s.WriteByte((byte)(v | 0x80));
|
||||||
|
v >>= 7;
|
||||||
|
}
|
||||||
|
s.WriteByte((byte)v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteInt16(Stream s, int value)
|
||||||
|
{
|
||||||
|
s.WriteByte((byte)(value & 0xFF));
|
||||||
|
s.WriteByte((byte)((value >> 8) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteUInt16(Stream s, int value)
|
||||||
|
{
|
||||||
|
s.WriteByte((byte)(value & 0xFF));
|
||||||
|
s.WriteByte((byte)((value >> 8) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteColor(Stream s, Color c)
|
||||||
|
{
|
||||||
|
s.WriteByte(c.R);
|
||||||
|
s.WriteByte(c.G);
|
||||||
|
s.WriteByte(c.B);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadVarint(byte[] data, ref int offset)
|
||||||
|
{
|
||||||
|
uint result = 0;
|
||||||
|
int shift = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
byte b = data[offset++];
|
||||||
|
result |= (uint)(b & 0x7F) << shift;
|
||||||
|
if ((b & 0x80) == 0) return (int)result;
|
||||||
|
shift += 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadInt16(byte[] data, ref int offset)
|
||||||
|
{
|
||||||
|
int lo = data[offset++];
|
||||||
|
int hi = data[offset++];
|
||||||
|
return (short)(lo | (hi << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadUInt16(byte[] data, ref int offset)
|
||||||
|
{
|
||||||
|
int lo = data[offset++];
|
||||||
|
int hi = data[offset++];
|
||||||
|
return lo | (hi << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color ReadColor(byte[] data, ref int offset)
|
||||||
|
{
|
||||||
|
byte r = data[offset++];
|
||||||
|
byte g = data[offset++];
|
||||||
|
byte b = data[offset++];
|
||||||
|
return Color.FromArgb(r, g, b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
|
public enum RenderCommandType : byte
|
||||||
|
{
|
||||||
|
CreateWindow = 0x01,
|
||||||
|
DestroyWindow = 0x02,
|
||||||
|
MoveWindow = 0x03,
|
||||||
|
|
||||||
|
DrawFilledRect = 0x10,
|
||||||
|
DrawRectBorder = 0x11,
|
||||||
|
DrawFilledCircle = 0x12,
|
||||||
|
DrawCircle = 0x13,
|
||||||
|
DrawText = 0x14,
|
||||||
|
DrawLine = 0x15,
|
||||||
|
DrawPoint = 0x16,
|
||||||
|
|
||||||
|
RemovePrimitives = 0x20,
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.UIEelements.Controls;
|
namespace RemSox.UI.GUI.UIEelements.Controls;
|
||||||
@@ -18,6 +20,58 @@ public class Button() : Control("Button")
|
|||||||
set => SetProperty(nameof(TextColor), ref field, value);
|
set => SetProperty(nameof(TextColor), ref field, value);
|
||||||
} = Color.Black;
|
} = Color.Black;
|
||||||
|
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
// Background fill
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = RenderCommandType.DrawFilledRect,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = BackgroundColor,
|
||||||
|
["Size"] = Size,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Border
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(1),
|
||||||
|
Type = RenderCommandType.DrawRectBorder,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color.DarkGray,
|
||||||
|
["Size"] = Size,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Text (centered)
|
||||||
|
if (!string.IsNullOrEmpty(Text))
|
||||||
|
{
|
||||||
|
int tx = Position.X + (Size.Width / 2) - (Text.Length * 4);
|
||||||
|
int ty = Position.Y + (Size.Height / 2) - 8;
|
||||||
|
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(2),
|
||||||
|
Type = RenderCommandType.DrawText,
|
||||||
|
Position = new Point(tx, ty),
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = TextColor,
|
||||||
|
["Content"] = Text,
|
||||||
|
["FontSize"] = Size.Height - 8,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void HandleMouseEvent(MouseEvent mouseEvent)
|
public override void HandleMouseEvent(MouseEvent mouseEvent)
|
||||||
{
|
{
|
||||||
if (mouseEvent.Type == MouseEventType.ButtonDown && mouseEvent.Button == MouseButton.Left)
|
if (mouseEvent.Type == MouseEventType.ButtonDown && mouseEvent.Button == MouseButton.Left)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.UIEelements.Controls;
|
namespace RemSox.UI.GUI.UIEelements.Controls;
|
||||||
@@ -28,6 +30,74 @@ public class CheckBox() : Control("CheckBox")
|
|||||||
set => SetProperty(nameof(TextColor), ref field, value);
|
set => SetProperty(nameof(TextColor), ref field, value);
|
||||||
} = Color.White;
|
} = Color.White;
|
||||||
|
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
int boxSize = Size.Height;
|
||||||
|
|
||||||
|
// Checkbox box background
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = RenderCommandType.DrawFilledRect,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = BackgroundColor,
|
||||||
|
["Size"] = new Size(boxSize, boxSize),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Checkbox box border
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(1),
|
||||||
|
Type = RenderCommandType.DrawRectBorder,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color.DarkGray,
|
||||||
|
["Size"] = new Size(boxSize, boxSize),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check mark (filled inner rect)
|
||||||
|
if (IsChecked)
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(2),
|
||||||
|
Type = RenderCommandType.DrawFilledRect,
|
||||||
|
Position = new Point(Position.X + 3, Position.Y + 3),
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color.Black,
|
||||||
|
["Size"] = new Size(boxSize - 6, boxSize - 6),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label text
|
||||||
|
if (!string.IsNullOrEmpty(Text))
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(3),
|
||||||
|
Type = RenderCommandType.DrawText,
|
||||||
|
Position = new Point(Position.X + boxSize + 5, Position.Y - 2),
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = TextColor,
|
||||||
|
["Content"] = Text,
|
||||||
|
["FontSize"] = boxSize,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void HandleMouseEvent(MouseEvent mouseEvent)
|
public override void HandleMouseEvent(MouseEvent mouseEvent)
|
||||||
{
|
{
|
||||||
if (mouseEvent.Type == MouseEventType.ButtonDown && mouseEvent.Button == MouseButton.Left)
|
if (mouseEvent.Type == MouseEventType.ButtonDown && mouseEvent.Button == MouseButton.Left)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
||||||
|
|
||||||
public class Circle() : Shape("Circle")
|
public class Circle() : Shape("Circle")
|
||||||
@@ -7,4 +11,26 @@ public class Circle() : Shape("Circle")
|
|||||||
get;
|
get;
|
||||||
set => SetProperty(nameof(Radius), ref field, value);
|
set => SetProperty(nameof(Radius), ref field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsFilled
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetProperty(nameof(IsFilled), ref field, value);
|
||||||
|
} = true;
|
||||||
|
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = IsFilled ? RenderCommandType.DrawFilledCircle : RenderCommandType.DrawCircle,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color,
|
||||||
|
["Radius"] = Radius,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
||||||
@@ -9,4 +11,20 @@ public class Line() : Shape("Line")
|
|||||||
get;
|
get;
|
||||||
set => SetProperty(nameof(EndPosition), ref field, value);
|
set => SetProperty(nameof(EndPosition), ref field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = RenderCommandType.DrawLine,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color,
|
||||||
|
["EndPosition"] = EndPosition,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
||||||
|
|
||||||
|
public class Pixel() : Shape("Pixel")
|
||||||
|
{
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = RenderCommandType.DrawPoint,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
||||||
@@ -15,4 +17,20 @@ public class Rectangle() : Shape("Rectangle")
|
|||||||
get;
|
get;
|
||||||
set => SetProperty(nameof(IsFilled), ref field, value);
|
set => SetProperty(nameof(IsFilled), ref field, value);
|
||||||
} = true;
|
} = true;
|
||||||
|
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = IsFilled ? RenderCommandType.DrawFilledRect : RenderCommandType.DrawRectBorder,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color,
|
||||||
|
["Size"] = Size,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
namespace RemSox.UI.GUI.UIEelements.Shapes;
|
||||||
@@ -21,4 +23,21 @@ public class Text() : UIElement("Text")
|
|||||||
get;
|
get;
|
||||||
set => SetProperty(nameof(FontSize), ref field, value);
|
set => SetProperty(nameof(FontSize), ref field, value);
|
||||||
} = 12;
|
} = 12;
|
||||||
|
|
||||||
|
public override IEnumerable<RenderCommand> ToPrimitives(int windowId)
|
||||||
|
{
|
||||||
|
yield return new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = windowId,
|
||||||
|
ElementId = PrimitiveId(0),
|
||||||
|
Type = RenderCommandType.DrawText,
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color,
|
||||||
|
["Content"] = Content,
|
||||||
|
["FontSize"] = FontSize,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using RemSox.UI.GUI.Rendering;
|
||||||
using RemSox.Utils;
|
using RemSox.Utils;
|
||||||
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -6,6 +7,8 @@ namespace RemSox.UI.GUI.UIEelements;
|
|||||||
|
|
||||||
public abstract class UIElement(string type) : ChangedPropertiesTracker
|
public abstract class UIElement(string type) : ChangedPropertiesTracker
|
||||||
{
|
{
|
||||||
|
public const int PrimitiveIdShift = 6;
|
||||||
|
|
||||||
public int Id { get; init; }
|
public int Id { get; init; }
|
||||||
|
|
||||||
public string Type { get; set; } = type;
|
public string Type { get; set; } = type;
|
||||||
@@ -15,4 +18,10 @@ public abstract class UIElement(string type) : ChangedPropertiesTracker
|
|||||||
get;
|
get;
|
||||||
set => SetProperty(nameof(Position), ref field, value);
|
set => SetProperty(nameof(Position), ref field, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Expands this UI element into drawing primitives. </summary>
|
||||||
|
public abstract IEnumerable<RenderCommand> ToPrimitives(int windowId);
|
||||||
|
|
||||||
|
/// <summary> Builds a stable primitive ID from element ID and sub-index. </summary>
|
||||||
|
protected int PrimitiveId(int subIndex) => (Id << PrimitiveIdShift) | subIndex;
|
||||||
}
|
}
|
||||||
+130
-35
@@ -143,9 +143,19 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removed && AutoFlush)
|
if (removed)
|
||||||
{
|
{
|
||||||
Flush();
|
renderSource.Render([new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = elementId,
|
||||||
|
Type = RenderCommandType.RemovePrimitives,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if (AutoFlush)
|
||||||
|
{
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +168,13 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const int ChromeClientBg = -1;
|
||||||
|
private const int ChromeTitleBg = -2;
|
||||||
|
private const int ChromeTitleText = -3;
|
||||||
|
private const int ChromeBorder = -4;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends current window and element state to the renderer.
|
/// Sends current window and element state to the renderer (delta-only).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Flush()
|
public void Flush()
|
||||||
{
|
{
|
||||||
@@ -168,69 +183,83 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool anyChildChanged;
|
|
||||||
List<UIElement> elementsCopy;
|
List<UIElement> elementsCopy;
|
||||||
|
|
||||||
lock (uiElementsLock)
|
lock (uiElementsLock)
|
||||||
{
|
{
|
||||||
anyChildChanged = uiElements.Values.Any(e => e.AnyPropertyChanged);
|
elementsCopy = [.. uiElements.Values];
|
||||||
elementsCopy = uiElements.Values.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool windowStateChanged = isFirstRender || Size != lastRenderedSize || IsFocused != lastRenderedIsFocused || Title != lastRenderedTitle;
|
bool sizeChanged = Size != lastRenderedSize;
|
||||||
bool positionChanged = Position != lastRenderedPosition;
|
bool positionChanged = Position != lastRenderedPosition;
|
||||||
bool zIndexChanged = ZIndex != lastRenderedZIndex;
|
bool zIndexChanged = ZIndex != lastRenderedZIndex;
|
||||||
|
bool titleChanged = Title != lastRenderedTitle;
|
||||||
bool fullRedraw = windowStateChanged || anyChildChanged;
|
bool focusChanged = IsFocused != lastRenderedIsFocused;
|
||||||
|
bool chromeChanged = isFirstRender || sizeChanged || titleChanged || focusChanged;
|
||||||
|
|
||||||
List<RenderCommand> commands = [];
|
List<RenderCommand> commands = [];
|
||||||
|
|
||||||
if (fullRedraw)
|
// --- Structural commands ---
|
||||||
|
if (isFirstRender || sizeChanged || zIndexChanged)
|
||||||
{
|
{
|
||||||
commands.Add(new RenderCommand
|
commands.Add(new RenderCommand
|
||||||
{
|
{
|
||||||
WindowId = Id,
|
WindowId = Id,
|
||||||
ElementId = Id,
|
ElementId = Id,
|
||||||
ElementType = "Window",
|
Type = RenderCommandType.CreateWindow,
|
||||||
Position = Position,
|
Position = Position,
|
||||||
Properties = new Dictionary<string, object?>
|
Properties = new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
[nameof(Title)] = Title,
|
["Size"] = Size,
|
||||||
[nameof(Size)] = Size,
|
["ZIndex"] = ZIndex,
|
||||||
[nameof(IsFocused)] = IsFocused,
|
|
||||||
[nameof(IsResizable)] = IsResizable,
|
|
||||||
[nameof(IsDraggable)] = IsDraggable,
|
|
||||||
[nameof(ZIndex)] = ZIndex
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
else if (positionChanged)
|
||||||
|
{
|
||||||
|
commands.Add(new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = Id,
|
||||||
|
Type = RenderCommandType.MoveWindow,
|
||||||
|
Position = Position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Chrome primitives (title bar, border, background) ---
|
||||||
|
if (chromeChanged)
|
||||||
|
{
|
||||||
|
ChromePrimitives(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Child element primitives (delta) ---
|
||||||
|
if (isFirstRender)
|
||||||
|
{
|
||||||
foreach (UIElement element in elementsCopy)
|
foreach (UIElement element in elementsCopy)
|
||||||
{
|
{
|
||||||
|
commands.AddRange(element.ToPrimitives(Id));
|
||||||
|
element.ClearChangedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (UIElement element in elementsCopy)
|
||||||
|
{
|
||||||
|
if (!element.AnyPropertyChanged)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old primitives for this element, then emit new ones
|
||||||
commands.Add(new RenderCommand
|
commands.Add(new RenderCommand
|
||||||
{
|
{
|
||||||
WindowId = Id,
|
WindowId = Id,
|
||||||
ElementId = element.Id,
|
ElementId = element.Id,
|
||||||
ElementType = element.Type,
|
Type = RenderCommandType.RemovePrimitives,
|
||||||
Position = element.Position,
|
|
||||||
Properties = element.AllProperties
|
|
||||||
});
|
});
|
||||||
|
commands.AddRange(element.ToPrimitives(Id));
|
||||||
element.ClearChangedProperties();
|
element.ClearChangedProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (positionChanged || zIndexChanged)
|
|
||||||
{
|
|
||||||
commands.Add(new RenderCommand
|
|
||||||
{
|
|
||||||
WindowId = Id,
|
|
||||||
ElementId = Id,
|
|
||||||
ElementType = "WindowMove",
|
|
||||||
Position = Position,
|
|
||||||
Properties = new Dictionary<string, object?>
|
|
||||||
{
|
|
||||||
[nameof(ZIndex)] = ZIndex
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commands.Count > 0)
|
if (commands.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -244,6 +273,72 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ChromePrimitives(List<RenderCommand> commands)
|
||||||
|
{
|
||||||
|
Color border = IsFocused ? Color.White : Color.DarkGray;
|
||||||
|
Color title = IsFocused ? Color.FromArgb(0, 120, 215) : Color.FromArgb(80, 80, 80);
|
||||||
|
|
||||||
|
// Client area background
|
||||||
|
commands.Add(new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = ChromeClientBg,
|
||||||
|
Type = RenderCommandType.DrawFilledRect,
|
||||||
|
Position = Point.Empty,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color.FromArgb(32, 32, 32),
|
||||||
|
["Size"] = Size,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Title bar background
|
||||||
|
commands.Add(new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = ChromeTitleBg,
|
||||||
|
Type = RenderCommandType.DrawFilledRect,
|
||||||
|
Position = Point.Empty,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = title,
|
||||||
|
["Size"] = new Size(Size.Width, 18),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Title bar text
|
||||||
|
if (!string.IsNullOrEmpty(Title))
|
||||||
|
{
|
||||||
|
commands.Add(new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = ChromeTitleText,
|
||||||
|
Type = RenderCommandType.DrawText,
|
||||||
|
Position = new Point(4, 2),
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = Color.White,
|
||||||
|
["Content"] = Title,
|
||||||
|
["FontSize"] = 18,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window border
|
||||||
|
commands.Add(new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = ChromeBorder,
|
||||||
|
Type = RenderCommandType.DrawRectBorder,
|
||||||
|
Position = Point.Empty,
|
||||||
|
Properties = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Color"] = border,
|
||||||
|
["Size"] = Size,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Dispatches a key event to the window's registered event handlers. </summary>
|
/// <summary> Dispatches a key event to the window's registered event handlers. </summary>
|
||||||
public void HandleKeyEvent(Sys.Keyboard.KeyEvent keyEvent)
|
public void HandleKeyEvent(Sys.Keyboard.KeyEvent keyEvent)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public static class WindowManager
|
|||||||
wasRightButtonDown = rightButtonDown;
|
wasRightButtonDown = rightButtonDown;
|
||||||
wasMiddleButtonDown = middleButtonDown;
|
wasMiddleButtonDown = middleButtonDown;
|
||||||
|
|
||||||
CanvasRenderSource.CompositeAndDisplay(canvas, pointerPosition);
|
renderSource.Composite();
|
||||||
|
|
||||||
lastPointerPosition = pointerPosition;
|
lastPointerPosition = pointerPosition;
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ public static class WindowManager
|
|||||||
activeInteractWindow = null;
|
activeInteractWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSource.Render([new RenderCommand { WindowId = window.Id, ElementId = window.Id, ElementType = "WindowClose", Position = window.Position, Properties = new Dictionary<string, object?>() }]);
|
renderSource.Render([new RenderCommand { WindowId = window.Id, ElementId = window.Id, Type = RenderCommandType.DestroyWindow, Position = window.Position }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -300,7 +300,7 @@ public static class WindowManager
|
|||||||
List<RenderCommand> closeCommands = [];
|
List<RenderCommand> closeCommands = [];
|
||||||
foreach (Window window in windowsToClose)
|
foreach (Window window in windowsToClose)
|
||||||
{
|
{
|
||||||
closeCommands.Add(new RenderCommand { WindowId = window.Id, ElementId = window.Id, ElementType = "WindowClose", Position = window.Position, Properties = new Dictionary<string, object?>() });
|
closeCommands.Add(new RenderCommand { WindowId = window.Id, ElementId = window.Id, Type = RenderCommandType.DestroyWindow, Position = window.Position });
|
||||||
}
|
}
|
||||||
renderSource.Render(closeCommands);
|
renderSource.Render(closeCommands);
|
||||||
}
|
}
|
||||||
@@ -448,5 +448,19 @@ public static class WindowManager
|
|||||||
source.Render(commands);
|
source.Render(commands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Composite()
|
||||||
|
{
|
||||||
|
List<IRenderSource> sourcesCopy;
|
||||||
|
lock (sourcesLock)
|
||||||
|
{
|
||||||
|
sourcesCopy = sources.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (IRenderSource source in sourcesCopy)
|
||||||
|
{
|
||||||
|
source.Composite();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user