From a911d5310cffc0e37341ee79e996ed4be6ae7d67 Mon Sep 17 00:00:00 2001 From: Bobby Lucero Date: Tue, 1 Jul 2025 19:24:43 -0400 Subject: [PATCH] Initial work in progress --- .gitignore | 3 + MicroUI.cs.sln | 16 + MicroUI.cs/MicroUI.cs | 808 +++++++++++++++++++++++++++++++++++ MicroUI.cs/MicroUI.cs.csproj | 14 + MicroUI.cs/Program.cs | 25 ++ 5 files changed, 866 insertions(+) create mode 100644 .gitignore create mode 100644 MicroUI.cs.sln create mode 100644 MicroUI.cs/MicroUI.cs create mode 100644 MicroUI.cs/MicroUI.cs.csproj create mode 100644 MicroUI.cs/Program.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df9cef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +MicroUI.cs/obj +MicroUI.cs/bin +.idea diff --git a/MicroUI.cs.sln b/MicroUI.cs.sln new file mode 100644 index 0000000..1019bac --- /dev/null +++ b/MicroUI.cs.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroUI.cs", "MicroUI.cs\MicroUI.cs.csproj", "{7668C69C-9286-4EB9-BA74-5F9CC2CF953C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7668C69C-9286-4EB9-BA74-5F9CC2CF953C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7668C69C-9286-4EB9-BA74-5F9CC2CF953C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7668C69C-9286-4EB9-BA74-5F9CC2CF953C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7668C69C-9286-4EB9-BA74-5F9CC2CF953C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/MicroUI.cs/MicroUI.cs b/MicroUI.cs/MicroUI.cs new file mode 100644 index 0000000..98119f6 --- /dev/null +++ b/MicroUI.cs/MicroUI.cs @@ -0,0 +1,808 @@ +using System.Runtime.CompilerServices; + +namespace MicroUI +{ + using mu_Id = System.UInt32; + + + public static class Constants + { + public const string Version = "1.0.0"; + public const int CommandListSize = 256 * 1024; + public const int RootListSize = 32; + public const int ContainerStackSize = 32; + public const int ClipStackSize = 32; + public const int IdStackSize = 32; + public const int LayoutStackSize = 16; + public const int ContainerPoolSize = 48; + public const int TreeNodePoolSize = 48; + public const int MaxWidths = 16; + public const int MaxFormatLength = 127; + } + + public static class Format + { + public static string Real(float val) => val.ToString("G3"); + public static string Slider(float val) => val.ToString("F2"); + } + + public static class MathUtil + { + public static int Min(int a, int b) => a < b ? a : b; + public static int Max(int a, int b) => a > b ? a : b; + public static int Clamp(int x, int a, int b) => Min(b, Max(a, x)); + + public static float Min(float a, float b) => a < b ? a : b; + public static float Max(float a, float b) => a > b ? a : b; + public static float Clamp(float x, float a, float b) => Min(b, Max(a, x)); + } + + public struct FixedStack + { + public int Index; + public T[] Items; + + public FixedStack(int capacity) + { + Items = new T[capacity]; + Index = 0; + } + + public void Push(T val) + { + if (Index >= Items.Length) + { + MuAssert.Expect(false); + return; + } + + Items[Index] = val; + Index++; + } + + public T Pop() + { + MuAssert.Expect(Index > 0); + Index--; + return Items[Index]; + } + + public T Peek() + { + MuAssert.Expect(Index > 0); + return Items[Index - 1]; + } + + public void Clear() => Index = 0; + } + + public enum ClipMode + { + Part = 1, + All + } + + public enum CommandType + { + Jump = 1, + Clip, + Rect, + Text, + Icon, + Max + } + + public enum ColorType + { + Text, + Border, + WindowBg, + TitleBg, + TitleText, + PanelBg, + Button, + ButtonHover, + ButtonFocus, + Base, + BaseHover, + BaseFocus, + ScrollBase, + ScrollThumb, + Max + } + + public enum IconType + { + Close = 1, + Check = 2, + Collapsed = 3, + Expanded = 4, + Max = 5 + } + + [Flags] + public enum ResultFlags + { + Active = 1 << 0, // 1 + Submit = 1 << 1, // 2 + Change = 1 << 2 // 4 + } + + [System.Flags] + public enum Options + { + AlignCenter = 1 << 0, // 1 + AlignRight = 1 << 1, // 2 + NoInteract = 1 << 2, // 4 + NoFrame = 1 << 3, // 8 + NoResize = 1 << 4, // 16 + NoScroll = 1 << 5, // 32 + NoClose = 1 << 6, // 64 + NoTitle = 1 << 7, // 128 + HoldFocus = 1 << 8, // 256 + AutoSize = 1 << 9, // 512 + Popup = 1 << 10, // 1024 + Closed = 1 << 11, // 2048 + Expanded = 1 << 12 // 4096 + } + + [System.Flags] + public enum MouseButton + { + Left = 1 << 0, // 1 + Right = 1 << 1, // 2 + Middle = 1 << 2 // 4 + } + + [System.Flags] + public enum KeyModifiers + { + Shift = 1 << 0, // 1 + Ctrl = 1 << 1, // 2 + Alt = 1 << 2, // 4 + Backspace = 1 << 3, // 8 + Return = 1 << 4 // 16 + } + + public struct MuVec2 + { + public int X; + public int Y; + + public MuVec2(int x, int y) + { + X = x; + Y = y; + } + } + + public struct MuRect + { + public int X; + public int Y; + public int W; + public int H; + + public MuRect(int x, int y, int w, int h) + { + X = x; + Y = y; + W = w; + H = h; + } + } + + public struct MuColor + { + public byte R; + public byte G; + public byte B; + public byte A; + + public MuColor(byte r, byte g, byte b, byte a) + { + R = r; + G = g; + B = b; + A = a; + } + } + + public struct MuPoolItem + { + public mu_Id Id; + public int LastUpdate; + + public MuPoolItem(uint id, int lastUpdate) + { + Id = id; + LastUpdate = lastUpdate; + } + } + + public abstract class MuCommand + { + public abstract CommandType Type { get; } + } + + public class MuJumpCommand : MuCommand + { + public int DestinationIndex { get; set; } + public override CommandType Type => CommandType.Jump; + } + + public class MuClipCommand : MuCommand + { + public MuRect Rect { get; } + + public MuClipCommand(MuRect rect) + { + Rect = rect; + } + + public override CommandType Type => CommandType.Clip; + } + + public class MuRectCommand : MuCommand + { + public MuRect Rect { get; } + public MuColor Color { get; } + + public MuRectCommand(MuRect rect, MuColor color) + { + Rect = rect; + Color = color; + } + + public override CommandType Type => CommandType.Rect; + } + + public class MuTextCommand : MuCommand + { + public object? Font { get; } // TODO: Idk how this works yet + public MuVec2 Position { get; } + public MuColor Color { get; } + public string Text { get; } + + public MuTextCommand(object? font, MuVec2 position, MuColor color, string text) + { + Font = font; + Position = position; + Color = color; + Text = text; + } + + public override CommandType Type => CommandType.Text; + } + + public class MuIconCommand : MuCommand + { + public MuRect Rect { get; } + public int IconId { get; } + public MuColor Color { get; } + + public MuIconCommand(MuRect rect, int iconId, MuColor color) + { + Rect = rect; + IconId = iconId; + Color = color; + } + + public override CommandType Type => CommandType.Icon; + } + + public class MuCommandBuffer + { + public List Commands { get; } = new(); + + public void Add(MuCommand command) + { + Commands.Add(command); + } + + public void Clear() + { + Commands.Clear(); + } + } + + public struct MuLayout + { + public MuRect Body { get; set; } + public MuRect Next { get; set; } + public MuVec2 Position { get; set; } + public MuVec2 Size { get; set; } + public MuVec2 Max { get; set; } + public int[] Widths { get; } + public int Items { get; set; } + public int ItemIndex { get; set; } + public int NextRow { get; set; } + public int NextType { get; set; } + public int Indent { get; set; } + + public MuLayout() + { + Widths = new int[Constants.MaxWidths]; + } + } + + public class MuContainer + { + public int HeadIndex { get; set; } = -1; + public int TailIndex { get; set; } = -1; + + public MuRect Rect { get; set; } + public MuRect Body { get; set; } + public MuVec2 ContentSize { get; set; } + public MuVec2 Scroll { get; set; } + public int ZIndex { get; set; } + public bool Open { get; set; } + + public MuContainer() + { + Open = false; + } + } + + public class MuStyle + { + public object? Font { get; set; } // Replace 'object' with actual font type as needed + public MuVec2 Size { get; set; } + public int Padding { get; set; } + public int Spacing { get; set; } + public int Indent { get; set; } + public int TitleHeight { get; set; } + public int ScrollbarSize { get; set; } + public int ThumbSize { get; set; } + public MuColor[] Colors { get; set; } + + public MuStyle() + { + Colors = new MuColor[(int)ColorType.Max]; + } + } + + public delegate int TextWidthDelegate(object font, string str, int len); + public delegate int TextHeightDelegate(object font); + public delegate void DrawFrameDelegate(MuContext ctx, MuRect rect, ColorType colorId); + + public class MuContext + { + // Callbacks + public TextWidthDelegate? TextWidth; + public TextHeightDelegate? TextHeight; + public DrawFrameDelegate? DrawFrame; + + // Styles + public MuStyle Style { get; set; } = new MuStyle(); + + // IDs and last widget info + public mu_Id Hover { get; set; } // assuming mu_Id is uint + public mu_Id Focus { get; set; } + public mu_Id LastId { get; set; } + public MuRect LastRect { get; set; } + public int LastZIndex { get; set; } + public int UpdatedFocus { get; set; } + public int Frame { get; set; } + + // Containers + public MuContainer? HoverRoot { get; set; } + public MuContainer? NextHoverRoot { get; set; } + public MuContainer? ScrollTarget { get; set; } + + // Number edit buffer + private char[] numberEditBuf = new char[Constants.MaxFormatLength]; // e.g. 127 or 128 + public mu_Id NumberEdit { get; set; } + + // Stacks + public FixedStack CommandList { get; } = new FixedStack(Constants.CommandListSize); + public FixedStack RootList { get; } = new FixedStack(Constants.RootListSize); + public FixedStack ContainerStack { get; } = new FixedStack(Constants.ContainerStackSize); + public FixedStack ClipStack { get; } = new FixedStack(Constants.ClipStackSize); + public FixedStack IdStack { get; } = new FixedStack(Constants.IdStackSize); + public FixedStack LayoutStack { get; } = new FixedStack(Constants.LayoutStackSize); + + // Retained State Pools + public MuPoolItem[] ContainerPool { get; } = new MuPoolItem[Constants.ContainerPoolSize]; + public MuContainer[] Containers { get; } = new MuContainer[Constants.ContainerPoolSize]; + public MuPoolItem[] TreeNodePool { get; } = new MuPoolItem[Constants.TreeNodePoolSize]; + + // Input State + public MuVec2 MousePos { get; set; } + public MuVec2 LastMousePos { get; set; } + public MuVec2 MouseDelta { get; set; } + public MuVec2 ScrollDelta { get; set; } + public int MouseDown { get; set; } + public int MousePressed { get; set; } + public int KeyDown { get; set; } + public int KeyPressed { get; set; } + public char[] inputText = new char[32]; + + + + } + + public static class MuAssert + { + public static void Expect(bool condition, + [CallerFilePath] string file = "", + [CallerLineNumber] int line = 0, + [CallerMemberName] string member = "") + { + if (!condition) + { + Console.Error.WriteLine($"Fatal error at {file}:{line} in {member}: assertion failed."); + Environment.FailFast("Expectation failed."); + } + } + } + + public class MicroUI + { + public static readonly MuRect UnclippedRect = new MuRect(0,0,0x1000000,0x1000000); + + public static readonly MuStyle DefaultStyle = new MuStyle + { + Font = null, + Size = new MuVec2(68, 10), + Padding = 5, + Spacing = 4, + Indent = 24, + TitleHeight = 24, + ScrollbarSize = 12, + ThumbSize = 8, + Colors = new MuColor[] + { + new MuColor(230, 230, 230, 255), // MU_COLOR_TEXT + new MuColor(25, 25, 25, 255), // MU_COLOR_BORDER + new MuColor(50, 50, 50, 255), // MU_COLOR_WINDOWBG + new MuColor(25, 25, 25, 255), // MU_COLOR_TITLEBG + new MuColor(240, 240, 240, 255), // MU_COLOR_TITLETEXT + new MuColor(0, 0, 0, 0), // MU_COLOR_PANELBG + new MuColor(75, 75, 75, 255), // MU_COLOR_BUTTON + new MuColor(95, 95, 95, 255), // MU_COLOR_BUTTONHOVER + new MuColor(115, 115, 115, 255), // MU_COLOR_BUTTONFOCUS + new MuColor(30, 30, 30, 255), // MU_COLOR_BASE + new MuColor(35, 35, 35, 255), // MU_COLOR_BASEHOVER + new MuColor(40, 40, 40, 255), // MU_COLOR_BASEFOCUS + new MuColor(43, 43, 43, 255), // MU_COLOR_SCROLLBASE + new MuColor(30, 30, 30, 255) // MU_COLOR_SCROLLTHUMB + } + }; + + public static MuRect ExpandRect(MuRect rect, int n) + { + return new MuRect( + rect.X - n, + rect.Y - n, + rect.W + 2 * n, + rect.H + 2 * n + ); + } + + public static MuRect IntersectRects(MuRect r1, MuRect r2) + { + int x1 = MathUtil.Max(r1.X, r2.X); + int y1 = MathUtil.Max(r1.Y, r2.Y); + int x2 = MathUtil.Min(r1.X + r1.W, r2.X + r2.W); + int y2 = MathUtil.Min(r1.Y + r1.H, r2.Y + r2.H); + + if (x2 < x1) x2 = x1; + if (y2 < y1) y2 = y1; + + return new MuRect(x1, y1, x2 - x1, y2 - y1); + } + + public static bool RectOverlapsVec2(MuRect r, MuVec2 p) + { + return p.X >= r.X && p.X < r.X + r.W && + p.Y >= r.Y && p.Y < r.Y + r.H; + } + + public static void DrawFrame(MuContext ctx, MuRect rect, ColorType colorId) + { + // Draw filled rectangle with given color + DrawRect(ctx, rect, ctx.Style.Colors[(int)colorId]); + + // Early return for certain color IDs (skip border) + if (colorId == ColorType.ScrollBase || + colorId == ColorType.ScrollThumb || + colorId == ColorType.TitleBg) + { + return; + } + + // Draw border if alpha > 0 + if (ctx.Style.Colors[(int)ColorType.Border].A > 0) + { + DrawBox(ctx, ExpandRect(rect, 1), ctx.Style.Colors[(int)ColorType.Border]); + } + } + + public static void Init(MuContext ctx) + { + // Zeroing is not needed — C# classes are automatically zeroed/null-initialized. + + // Assign the draw function delegate + ctx.DrawFrame = DrawFrame; + + // Copy the default style (make sure it's cloned, not shared!) + ctx.Style = DefaultStyle; + } + + public static void Begin(MuContext ctx) + { + MuAssert.Expect(ctx.TextWidth != null && ctx.TextHeight != null); + + ctx.CommandList.Clear(); + ctx.RootList.Clear(); + ctx.ScrollTarget = null; + + ctx.HoverRoot = ctx.NextHoverRoot; + ctx.NextHoverRoot = null; + + ctx.MouseDelta = new MuVec2(ctx.MousePos.X - ctx.LastMousePos.X, ctx.MousePos.Y - ctx.LastMousePos.Y); + + ctx.Frame++; + } + + public static int CompareZIndex(MuContainer a, MuContainer b) + { + return a.ZIndex.CompareTo(b.ZIndex); + } + + private static readonly IComparer ZIndexComparerInstance = Comparer.Create(CompareZIndex); + + public static void End(MuContext ctx){ + MuAssert.Expect(ctx.ContainerStack.Index == 0); + MuAssert.Expect(ctx.ClipStack.Index == 0); + MuAssert.Expect(ctx.IdStack.Index == 0); + MuAssert.Expect(ctx.LayoutStack.Index == 0); + + if (ctx.ScrollTarget != null) + { + ctx.ScrollTarget.Scroll = new MuVec2( + ctx.ScrollTarget.Scroll.X + ctx.ScrollDelta.X, + ctx.ScrollTarget.Scroll.Y + ctx.ScrollDelta.Y + ); + } + + if (ctx.UpdatedFocus == 0) + { + ctx.Focus = 0; + } + ctx.UpdatedFocus = 0; + + if (ctx.MousePressed != 0 && ctx.NextHoverRoot != null && + ctx.NextHoverRoot.ZIndex < ctx.LastZIndex && + ctx.NextHoverRoot.ZIndex >= 0) + { + BringToFront(ctx.NextHoverRoot); + } + + ctx.KeyPressed = 0; + ctx.inputText = new char[32]; + ctx.MousePressed = 0; + ctx.ScrollDelta = new MuVec2(0, 0); + ctx.LastMousePos = ctx.MousePos; + + int n = ctx.RootList.Index; + Array.Sort(ctx.RootList.Items, 0, n, ZIndexComparerInstance); + + + for (int i = 0; i < n; i++) + { + var cnt = ctx.RootList.Items[i]; + + if (i == 0) + { + ((MuJumpCommand)ctx.CommandList.Items[0]).DestinationIndex = cnt.HeadIndex + 1; + } + else + { + var prev = ctx.RootList.Items[i - 1]; + // Set previous container's tail jump destination to current container's head + 1 + ((MuJumpCommand)ctx.CommandList.Items[prev.TailIndex]).DestinationIndex = cnt.HeadIndex + 1; + + } + + if (i == n - 1) + { + // Last container tail jump destination points to end of command list (beyond last command) + ((MuJumpCommand)ctx.CommandList.Items[cnt.TailIndex]).DestinationIndex = cnt.HeadIndex + 1; + + } + } + } + + public static void SetFocus(MuContext ctx, mu_Id id) + { + ctx.Focus = id; + ctx.UpdatedFocus = 1; + } + + public const uint HashInitial = 2166136261; + + public static void Hash(ref uint hash, ReadOnlySpan data) + { + const uint fnvPrime = 16777619; + foreach (byte b in data) + { + hash = (hash ^ b) * fnvPrime; + } + } + + public static uint GetId(MuContext ctx, ReadOnlySpan data) + { + uint seed = ctx.IdStack.Index > 0 ? ctx.IdStack.Items[ctx.IdStack.Index - 1] : HashInitial; + Hash(ref seed, data); + ctx.LastId = seed; + return seed; + } + + public static void PushId(MuContext ctx, ReadOnlySpan data) + { + ctx.IdStack.Push(GetId(ctx, data)); + } + + public static void PopId(MuContext ctx) + { + ctx.IdStack.Pop(); + } + + public static void PushClipRect(MuContext ctx, MuRect rect) { + MuRect last = GetClipRect(ctx); + ctx.ClipStack.Push( IntersectRects(rect, last)); + } + + public static void PopClipRect(MuContext ctx) + { + ctx.ClipStack.Pop(); + } + + public static MuRect GetClipRect(MuContext ctx) + { + MuAssert.Expect(ctx.ClipStack.Index > 0); + return ctx.ClipStack.Items[ctx.ClipStack.Index - 1]; + } + + public static ClipMode CheckClip(MuContext ctx, MuRect r) + { + MuRect cr = GetClipRect(ctx); + + // Completely outside clip rect + if (r.X > cr.X + cr.W || r.X + r.W < cr.X || + r.Y > cr.Y + cr.H || r.Y + r.H < cr.Y) + { + return ClipMode.All; // MU_CLIP_ALL + } + + // Completely inside clip rect + if (r.X >= cr.X && r.X + r.W <= cr.X + cr.W && + r.Y >= cr.Y && r.Y + r.H <= cr.Y + cr.H) + { + return 0; // no clipping + } + + // Partially clipped + return ClipMode.Part; // MU_CLIP_PART + } + + public static void PushLayout(MuContext ctx, MuRect body, MuVec2 scroll) + { + MuLayout layout = new MuLayout(); + + // Adjust body position by subtracting scroll offset + layout.Body = new MuRect( + body.X - scroll.X, + body.Y - scroll.Y, + body.W, + body.H); + + // Initialize max to very negative values (like C's -0x1000000) + layout.Max = new MuVec2(-0x1000000, -0x1000000); + + // Push the layout struct onto the layout stack + ctx.LayoutStack.Push(layout); + + // Call mu_layout_row with count=1, widths pointer to width var, height=0 + int[] widths = { 0 }; + LayoutRow(ctx, 1, widths, 0); + } + + static ref MuLayout GetLayout(MuContext ctx) + { + if (ctx.LayoutStack.Index == 0) + throw new InvalidOperationException("Layout stack is empty."); + + return ref ctx.LayoutStack.Items[ctx.LayoutStack.Index - 1]; + } + + static void PopContainer(MuContext ctx) + { + MuContainer cnt = GetCurrentContainer(ctx); + MuLayout layout = GetLayout(ctx); + + cnt.ContentSize = new MuVec2( + layout.Max.X - layout.Body.X, + layout.Max.Y - layout.Body.Y + ); + + // Pop container, layout, and id + ctx.ContainerStack.Pop(); + ctx.LayoutStack.Pop(); + PopId(ctx); + } + + static MuContainer GetCurrentContainer(MuContext ctx) + { + MuAssert.Expect(ctx.ContainerStack.Index > 0); + return ctx.ContainerStack.Items[ctx.ContainerStack.Index - 1]; + } + + /*============================================================================ + ** layout + **============================================================================*/ + + + public static void LayoutRow(MuContext ctx, int items, int[] widths, int height) + { + var layout = GetLayout(ctx); // get current layout (implement accordingly) + + if (items > Constants.MaxWidths) + throw new ArgumentOutOfRangeException(nameof(items), $"Items cannot be more than {Constants.MaxWidths}."); + + // Copy widths into layout.Widths + Array.Copy(widths, layout.Widths, items); + + layout.Items = items; + layout.Position = new MuVec2(layout.Indent, layout.NextRow); + layout.Size = new MuVec2(layout.Size.X, height); // assuming Size is MuVec2, updating only Y component + layout.ItemIndex = 0; + } + + static MuContainer? GetContainer(MuContext ctx, uint id, int opt) + { + // Try to get an existing container from the pool + int idx = MuPool.Get(ctx, ctx.ContainerPool, ctx.Containers.Length, id); + if (idx >= 0) + { + if (ctx.Containers[idx].Open || (opt & (int)Option.Closed) == 0) + { + MuPool.Update(ctx, ctx.ContainerPool, idx); + } + return ctx.Containers[idx]; + } + + // If container is closed, return null + if ((opt & (int)Option.Closed) != 0) + { + return null; + } + + // Create a new container + idx = MuPool.Init(ctx, ctx.ContainerPool, ctx.Containers.Length, id); + ref MuContainer cnt = ref ctx.Containers[idx]; + cnt = new MuContainer(); // Reset all fields + cnt.Open = true; + BringToFront(ctx, ref cnt); + return cnt; + } + + public static int Get(MuContext ctx, MuPoolItem[] items, int length, uint id) + { + for (int i = 0; i < length; i++) + { + if (items[i].Id == id) + { + return i; + } + } + return -1; + } + + } + + + +} \ No newline at end of file diff --git a/MicroUI.cs/MicroUI.cs.csproj b/MicroUI.cs/MicroUI.cs.csproj new file mode 100644 index 0000000..e6b18b1 --- /dev/null +++ b/MicroUI.cs/MicroUI.cs.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/MicroUI.cs/Program.cs b/MicroUI.cs/Program.cs new file mode 100644 index 0000000..9439d66 --- /dev/null +++ b/MicroUI.cs/Program.cs @@ -0,0 +1,25 @@ +using Raylib_cs; + +namespace HelloWorld; + +class Program +{ + // STAThread is required if you deploy using NativeAOT on Windows - See https://github.com/raylib-cs/raylib-cs/issues/301 + [STAThread] + public static void Main() + { + Raylib.InitWindow(800, 480, "Hello World"); + + while (!Raylib.WindowShouldClose()) + { + Raylib.BeginDrawing(); + Raylib.ClearBackground(Color.White); + + Raylib.DrawText("Hello, world!", 12, 12, 20, Color.Black); + + Raylib.EndDrawing(); + } + + Raylib.CloseWindow(); + } +} \ No newline at end of file