mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-04 00:56:19 +02:00
Implement full canvas compositing and redraw only on state change
This commit is contained in:
@@ -85,17 +85,6 @@ public class Kernel : Sys.Kernel
|
|||||||
Point pointerPosition = new(Sys.Mouse.MouseManager.X, Sys.Mouse.MouseManager.Y);
|
Point pointerPosition = new(Sys.Mouse.MouseManager.X, Sys.Mouse.MouseManager.Y);
|
||||||
bool leftButtonDown = Sys.Mouse.MouseManager.LeftButton;
|
bool leftButtonDown = Sys.Mouse.MouseManager.LeftButton;
|
||||||
|
|
||||||
// Draw Cursor
|
|
||||||
|
|
||||||
if (pointerPosition.X != lastPointerPosition.X || pointerPosition.Y != lastPointerPosition.Y)
|
|
||||||
{
|
|
||||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
|
||||||
canvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
|
||||||
canvas.Display();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPointerPosition = pointerPosition;
|
|
||||||
|
|
||||||
if (leftButtonDown && !wasLeftButtonDown)
|
if (leftButtonDown && !wasLeftButtonDown)
|
||||||
{
|
{
|
||||||
activeDragWindow = TryBeginDrag(pointerPosition);
|
activeDragWindow = TryBeginDrag(pointerPosition);
|
||||||
@@ -111,6 +100,12 @@ public class Kernel : Sys.Kernel
|
|||||||
}
|
}
|
||||||
|
|
||||||
wasLeftButtonDown = leftButtonDown;
|
wasLeftButtonDown = leftButtonDown;
|
||||||
|
|
||||||
|
// Render all canvases and cursor every tick, or if changed
|
||||||
|
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||||
|
CanvasRenderSource.CompositeAndDisplay(canvas, pointerPosition);
|
||||||
|
|
||||||
|
lastPointerPosition = pointerPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Window? TryBeginDrag(Point pointerPosition)
|
private static Window? TryBeginDrag(Point pointerPosition)
|
||||||
|
|||||||
@@ -1,57 +1,102 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Cosmos.Kernel.System.Graphics;
|
using Cosmos.Kernel.System.Graphics;
|
||||||
|
|
||||||
namespace RemSox.UI.GUI.Rendering;
|
namespace RemSox.UI.GUI.Rendering;
|
||||||
|
|
||||||
public sealed class CanvasRenderSource : IRenderSource
|
public sealed class CanvasRenderSource : IRenderSource
|
||||||
{
|
{
|
||||||
|
private static readonly Dictionary<int, Canvas> windowCanvases = new();
|
||||||
|
private static readonly Dictionary<int, Point> windowPositions = new();
|
||||||
|
|
||||||
public void Render(IEnumerable<RenderCommand> commands)
|
public void Render(IEnumerable<RenderCommand> commands)
|
||||||
{
|
{
|
||||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
|
||||||
|
|
||||||
foreach (RenderCommand command in commands)
|
foreach (RenderCommand command in commands)
|
||||||
{
|
{
|
||||||
if (command.ElementType == "Window")
|
if (command.ElementType == "Window")
|
||||||
{
|
{
|
||||||
RenderWindow(canvas, command);
|
Size size = command.Properties.TryGetValue("Size", out object? rawSize) && rawSize is Size windowSize
|
||||||
|
? windowSize
|
||||||
|
: new Size(160, 120);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas windowCanvas = windowCanvases[command.WindowId];
|
||||||
|
|
||||||
|
if (command.ElementType == "Window")
|
||||||
|
{
|
||||||
|
RenderWindow(windowCanvas, command);
|
||||||
|
windowPositions[command.WindowId] = command.Position;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.ElementType == "WindowMove")
|
||||||
|
{
|
||||||
|
windowPositions[command.WindowId] = command.Position;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.ElementType == "Circle")
|
if (command.ElementType == "Circle")
|
||||||
{
|
{
|
||||||
RenderCircle(canvas, command);
|
RenderCircle(windowCanvas, command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void CompositeAndDisplay(Canvas screenCanvas, Point pointerPosition)
|
||||||
|
{
|
||||||
|
screenCanvas.Clear(Color.Black);
|
||||||
|
|
||||||
|
foreach (var kvp in windowPositions)
|
||||||
|
{
|
||||||
|
int windowId = kvp.Key;
|
||||||
|
Point position = kvp.Value;
|
||||||
|
if (windowCanvases.TryGetValue(windowId, out Canvas? windowCanvas))
|
||||||
|
{
|
||||||
|
screenCanvas.DrawCanvas(windowCanvas, position.X, position.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenCanvas.DrawFilledCircle(Color.White, pointerPosition.X, pointerPosition.Y, 5);
|
||||||
|
screenCanvas.Display();
|
||||||
|
}
|
||||||
|
|
||||||
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
private static void RenderWindow(Canvas canvas, RenderCommand command)
|
||||||
{
|
{
|
||||||
int x = command.Position.X;
|
Size size = command.Properties.TryGetValue("Size", out object? rawSize) && rawSize is Size windowSize
|
||||||
int y = command.Position.Y;
|
|
||||||
|
|
||||||
Size size = command.Properties.TryGetValue(nameof(Size), out object? rawSize) && rawSize is Size windowSize
|
|
||||||
? windowSize
|
? windowSize
|
||||||
: new Size(160, 120);
|
: new Size(160, 120);
|
||||||
|
|
||||||
bool isFocused = command.Properties.TryGetValue(nameof(Windows.Window.IsFocused), out object? rawFocused) && rawFocused is bool focused && focused;
|
bool isFocused = command.Properties.TryGetValue("IsFocused", out object? rawFocused) && rawFocused is bool focused && focused;
|
||||||
|
|
||||||
Color borderColor = isFocused ? Color.White : Color.DarkGray;
|
Color borderColor = isFocused ? Color.White : Color.DarkGray;
|
||||||
Color bodyColor = Color.FromArgb(32, 32, 32);
|
Color bodyColor = Color.FromArgb(32, 32, 32);
|
||||||
Color titleColor = isFocused ? Color.FromArgb(0, 120, 215) : Color.FromArgb(80, 80, 80);
|
Color titleColor = isFocused ? Color.FromArgb(0, 120, 215) : Color.FromArgb(80, 80, 80);
|
||||||
|
|
||||||
canvas.DrawFilledRectangle(bodyColor, x, y, size.Width, size.Height);
|
canvas.DrawFilledRectangle(bodyColor, 0, 0, size.Width, size.Height);
|
||||||
canvas.DrawFilledRectangle(titleColor, x, y, size.Width, 18);
|
canvas.DrawFilledRectangle(titleColor, 0, 0, size.Width, 18);
|
||||||
canvas.DrawRectangle(borderColor, x, y, size.Width, size.Height);
|
canvas.DrawRectangle(borderColor, 0, 0, size.Width, size.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RenderCircle(Canvas canvas, RenderCommand command)
|
private static void RenderCircle(Canvas canvas, RenderCommand command)
|
||||||
{
|
{
|
||||||
Color color = command.Properties.TryGetValue(nameof(Color), out object? rawColor) && rawColor is Color circleColor
|
Color color = command.Properties.TryGetValue("Color", out object? rawColor) && rawColor is Color circleColor
|
||||||
? circleColor
|
? circleColor
|
||||||
: Color.White;
|
: Color.White;
|
||||||
|
|
||||||
int radius = command.Properties.TryGetValue(nameof(Radius), out object? rawRadius) && rawRadius is int circleRadius
|
int radius = command.Properties.TryGetValue("Radius", out object? rawRadius) && rawRadius is int circleRadius
|
||||||
? circleRadius
|
? circleRadius
|
||||||
: 10;
|
: 10;
|
||||||
|
|
||||||
@@ -60,6 +105,4 @@ public sealed class CanvasRenderSource : IRenderSource
|
|||||||
|
|
||||||
canvas.DrawFilledCircle(color, centerX, centerY, radius);
|
canvas.DrawFilledCircle(color, centerX, centerY, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Radius => nameof(Radius);
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace RemSox.UI.GUI.Rendering;
|
|||||||
|
|
||||||
public class RenderCommand
|
public class RenderCommand
|
||||||
{
|
{
|
||||||
|
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 string ElementType { get; set; }
|
||||||
public required Point Position { get; set; }
|
public required Point Position { get; set; }
|
||||||
|
|||||||
+47
-10
@@ -45,6 +45,12 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
|
|
||||||
private Point dragOffset;
|
private Point dragOffset;
|
||||||
|
|
||||||
|
private Point lastRenderedPosition = new Point(-1, -1);
|
||||||
|
private Size lastRenderedSize = new Size(-1, -1);
|
||||||
|
private bool lastRenderedIsFocused = false;
|
||||||
|
private string lastRenderedTitle = string.Empty;
|
||||||
|
private bool isFirstRender = true;
|
||||||
|
|
||||||
public T CreateUIElement<T>(Action<T>? options = null) where T : UIElement, new()
|
public T CreateUIElement<T>(Action<T>? options = null) where T : UIElement, new()
|
||||||
{
|
{
|
||||||
int uiElementId = GetNextUIElementId();
|
int uiElementId = GetNextUIElementId();
|
||||||
@@ -76,12 +82,24 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
|
|
||||||
public void Flush()
|
public void Flush()
|
||||||
{
|
{
|
||||||
|
if (!IsVisible)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool windowStateChanged = isFirstRender || Size != lastRenderedSize || IsFocused != lastRenderedIsFocused || Title != lastRenderedTitle;
|
||||||
|
bool anyChildChanged = uiElements.Values.Any(e => e.AnyPropertyChanged);
|
||||||
|
bool positionChanged = Position != lastRenderedPosition;
|
||||||
|
|
||||||
|
bool fullRedraw = windowStateChanged || anyChildChanged;
|
||||||
|
|
||||||
List<RenderCommand> commands = [];
|
List<RenderCommand> commands = [];
|
||||||
|
|
||||||
if (IsVisible)
|
if (fullRedraw)
|
||||||
{
|
{
|
||||||
commands.Add(new RenderCommand
|
commands.Add(new RenderCommand
|
||||||
{
|
{
|
||||||
|
WindowId = Id,
|
||||||
ElementId = Id,
|
ElementId = Id,
|
||||||
ElementType = "Window",
|
ElementType = "Window",
|
||||||
Position = Position,
|
Position = Position,
|
||||||
@@ -94,29 +112,40 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
[nameof(IsDraggable)] = IsDraggable
|
[nameof(IsDraggable)] = IsDraggable
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
foreach (UIElement element in uiElements.Values)
|
foreach (UIElement element in uiElements.Values)
|
||||||
{
|
|
||||||
if (element.AnyPropertyChanged)
|
|
||||||
{
|
{
|
||||||
IReadOnlyDictionary<string, object?> changes = element.ChangedProperties;
|
|
||||||
|
|
||||||
commands.Add(new RenderCommand
|
commands.Add(new RenderCommand
|
||||||
{
|
{
|
||||||
|
WindowId = Id,
|
||||||
ElementId = element.Id,
|
ElementId = element.Id,
|
||||||
ElementType = element.Type,
|
ElementType = element.Type,
|
||||||
Position = element.Position,
|
Position = element.Position,
|
||||||
Properties = changes
|
Properties = element.AllProperties
|
||||||
});
|
});
|
||||||
|
|
||||||
element.ClearChangedProperties();
|
element.ClearChangedProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (positionChanged)
|
||||||
|
{
|
||||||
|
commands.Add(new RenderCommand
|
||||||
|
{
|
||||||
|
WindowId = Id,
|
||||||
|
ElementId = Id,
|
||||||
|
ElementType = "WindowMove",
|
||||||
|
Position = Position,
|
||||||
|
Properties = new Dictionary<string, object?>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (commands.Count > 0)
|
if (commands.Count > 0)
|
||||||
{
|
{
|
||||||
renderSource.Render(commands);
|
renderSource.Render(commands);
|
||||||
|
lastRenderedPosition = Position;
|
||||||
|
lastRenderedSize = Size;
|
||||||
|
lastRenderedIsFocused = IsFocused;
|
||||||
|
lastRenderedTitle = Title;
|
||||||
|
isFirstRender = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +171,15 @@ public sealed class Window(string title, int processId, int id, IRenderSource re
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Position = new Point(pointerPosition.X - dragOffset.X, pointerPosition.Y - dragOffset.Y);
|
int newX = pointerPosition.X - dragOffset.X;
|
||||||
|
int newY = pointerPosition.Y - dragOffset.Y;
|
||||||
|
|
||||||
|
// Cosmos DrawCanvas fails to render if coordinates are negative.
|
||||||
|
// Clamp to 0,0 to prevent the window from disappearing.
|
||||||
|
newX = Math.Max(0, newX);
|
||||||
|
newY = Math.Max(0, newY);
|
||||||
|
|
||||||
|
Position = new Point(newX, newY);
|
||||||
|
|
||||||
Flush();
|
Flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,49 @@
|
|||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace RemSox.Utils;
|
namespace RemSox.Utils;
|
||||||
|
|
||||||
public abstract class ChangedPropertiesTracker : INotifyPropertyChanged
|
public abstract class ChangedPropertiesTracker : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, object?> changedProperties = [];
|
private readonly Dictionary<string, object?> properties = [];
|
||||||
|
private readonly HashSet<string> changedPropertyNames = [];
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, object?> ChangedProperties => changedProperties;
|
public IReadOnlyDictionary<string, object?> ChangedProperties
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var changes = new Dictionary<string, object?>();
|
||||||
|
foreach (var name in changedPropertyNames)
|
||||||
|
{
|
||||||
|
if (properties.TryGetValue(name, out var value))
|
||||||
|
{
|
||||||
|
changes[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, object?> AllProperties => properties;
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
public bool AnyPropertyChanged => changedProperties.Count > 0;
|
public bool AnyPropertyChanged => changedPropertyNames.Count > 0;
|
||||||
|
|
||||||
protected void SetProperty<T>(string name, ref T field, T value)
|
protected void SetProperty<T>(string name, ref T field, T value)
|
||||||
{
|
{
|
||||||
if (!EqualityComparer<T>.Default.Equals(field, value))
|
if (!EqualityComparer<T>.Default.Equals(field, value))
|
||||||
{
|
{
|
||||||
field = value;
|
field = value;
|
||||||
changedProperties[name] = value;
|
properties[name] = value;
|
||||||
|
changedPropertyNames.Add(name);
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearChangedProperties() => changedProperties.Clear();
|
public void ClearChangedProperties() => changedPropertyNames.Clear();
|
||||||
|
|
||||||
public bool IsPropertyChanged(string name) => changedProperties.ContainsKey(name);
|
public bool IsPropertyChanged(string name) => changedPropertyNames.Contains(name);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user