using System.Drawing; using System.Text; namespace RemSox.UI.GUI.Rendering; public class RenderCommand { public required int WindowId { get; set; } public required int ElementId { get; set; } public required RenderCommandType Type { get; set; } public Point Position { get; set; } public Dictionary 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); WriteVarint(s, GetProp("MaxWidth", int.MaxValue)); 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; case RenderCommandType.SetCursor: WriteInt16(s, Position.X); WriteInt16(s, Position.Y); 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 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; props["MaxWidth"] = ReadVarint(data, ref offset); 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; case RenderCommandType.SetCursor: pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset)); break; } return new RenderCommand { Type = type, WindowId = windowId, ElementId = elementId, Position = pos, Properties = props, }; } // --- Serialization helpers --- private T GetProp(string key, T fallback) { return 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); } }