diff --git a/.gitignore b/.gitignore index c1b0839..484a9dd 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ C/microui_test .DS_Store Thumbs.db .vscode/ + +build/ \ No newline at end of file diff --git a/MicroUI.cs/MicroUI.cs b/MicroUI.cs/MicroUI.cs index f7e7c94..47bb042 100644 --- a/MicroUI.cs/MicroUI.cs +++ b/MicroUI.cs/MicroUI.cs @@ -616,7 +616,7 @@ namespace MicroUI } ctx.KeyPressed = 0; - ctx.inputText = new char[32]; + ctx.inputText[0] = '\0'; ctx.MousePressed = 0; ctx.ScrollDelta = new MuVec2(0, 0); ctx.LastMousePos = ctx.MousePos; @@ -904,15 +904,21 @@ namespace MicroUI public static void InputText(MuContext ctx, string text) { - int len = ctx.inputText.Length; + // Find the current length of text in the buffer (up to first null terminator) + int len = 0; + while (len < ctx.inputText.Length && ctx.inputText[len] != '\0') + len++; + int size = text.Length; - if (len + size > ctx.inputText.Length) + if (len + size + 1 > ctx.inputText.Length) // +1 for null terminator throw new Exception("Input text buffer overflow"); - // Copy text into the buffer (simple version) - for (int i = 0; i < size && i < ctx.inputText.Length; i++) - { - ctx.inputText[i] = text[i]; - } + + // Copy text into the buffer at the current position + for (int i = 0; i < size && (len + i) < ctx.inputText.Length - 1; i++) + ctx.inputText[len + i] = text[i]; + + // Null-terminate + ctx.inputText[len + size] = '\0'; } } @@ -1275,6 +1281,16 @@ namespace MicroUI uint resizeId = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes("!resize")); var r = new MuRect(rect.X + rect.W - sz, rect.Y + rect.H - sz, sz, sz); MuControl.UpdateControl(ctx, resizeId, r, opt); + // Chevron/arrow design for resize handle + int pad = 4; + var baseColor = ctx.Style.Colors[(int)ColorType.Base]; + // Horizontal bar (bottom edge) + MuCommandList.Push(ctx, new MuRectCommand( + new MuRect(r.X + r.W - pad - 8, r.Y + r.H - pad - 2, 8, 2), baseColor)); + + // Vertical bar (right edge) + MuCommandList.Push(ctx, new MuRectCommand( + new MuRect(r.X + r.W - pad - 2, r.Y + r.H - pad - 8, 2, 8), baseColor)); if (resizeId == ctx.Focus && (ctx.MouseDown & (int)MouseButton.Left) != 0) { cnt.Rect = new MuRect( @@ -1487,7 +1503,7 @@ namespace MicroUI return new MuResult(res); } - + public static void Text(MuContext ctx, string text) { if (ctx.TextWidth == null || ctx.TextHeight == null) return; @@ -1529,5 +1545,192 @@ namespace MicroUI MuRect r = MuLayoutUtil.LayoutNext(ctx); DrawControlText(ctx, text, r, ColorType.Text, 0); } + + // Helper method for number textbox functionality + private static bool NumberTextbox(MuContext ctx, ref float value, MuRect r, uint id) + { + // Check if we should enter text input mode (Shift+Click) + if ((ctx.MousePressed & (int)MouseButton.Left) != 0 && + (ctx.KeyDown & (int)KeyModifiers.Shift) != 0 && + ctx.Hover == id) + { + ctx.NumberEdit = id; + // Convert number to string for editing using the same format as C version + var numberStr = value.ToString("G3"); // MU_REAL_FMT is "%.3g" + // Copy to input buffer (simplified - in real implementation you'd need a proper buffer) + for (int i = 0; i < Math.Min(numberStr.Length, ctx.inputText.Length); i++) + { + ctx.inputText[i] = numberStr[i]; + } + } + + if (ctx.NumberEdit == id) + { + // For now, we'll implement a simplified textbox + // In a full implementation, you'd use TextboxRaw here + var result = TextboxRaw(ctx, ctx.inputText, ctx.inputText.Length, id, r, 0); + if ((result & ResultFlags.Submit) != 0 || ctx.Focus != id) + { + // Parse the number back + if (float.TryParse(new string(ctx.inputText).Trim('\0'), out float newValue)) + { + value = newValue; + } + ctx.NumberEdit = 0; + } + else + { + return true; // Still in text input mode + } + } + return false; + } + + public static MuResult TextboxRaw(MuContext ctx, char[] buf, int bufSize, uint id, MuRect r, int opt) + { + ResultFlags res = 0; + UpdateControl(ctx, id, r, opt | (int)Options.HoldFocus); + + if (ctx.Focus == id) + { + // Handle text input + int len = 0; + while (len < bufSize && buf[len] != '\0') len++; + + // Get actual length of text in input buffer (up to first null terminator) + int inputLen = 0; + while (inputLen < ctx.inputText.Length && ctx.inputText[inputLen] != '\0') inputLen++; + + int n = Math.Min(bufSize - len - 1, inputLen); + if (n > 0) + { + for (int i = 0; i < n; i++) + { + if (len + i < bufSize) + buf[len + i] = ctx.inputText[i]; + } + len += n; + if (len < bufSize) buf[len] = '\0'; + res |= ResultFlags.Change; + } + + // Handle backspace + if ((ctx.KeyPressed & (int)KeyModifiers.Backspace) != 0 && len > 0) + { + // Skip UTF-8 continuation bytes (simplified for C#) + while (len > 0 && (buf[len - 1] & 0xC0) == 0x80) len--; + if (len > 0) len--; + buf[len] = '\0'; + res |= ResultFlags.Change; + } + + // Handle return + if ((ctx.KeyPressed & (int)KeyModifiers.Return) != 0) + { + MicroUI.SetFocus(ctx, 0); + res |= ResultFlags.Submit; + } + } + + // Draw + DrawControlFrame(ctx, id, r, ColorType.Base, opt); + string displayText = new string(buf).Trim('\0'); + DrawControlText(ctx, displayText, r, ColorType.Text, opt); + + return new MuResult(res); + } + + public static MuResult SliderEx(MuContext ctx, string name, ref float value, float low, float high, float step, string format, int opt) + { + char[] buf = new char[Constants.MaxFormatLength + 1]; + MuRect thumb; + int x, w; + ResultFlags res = 0; + float last = value, v = last; + + // Generate ID from the name, just like BeginWindowEx does + uint id = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes(name)); + + MuRect baseRect = MuLayoutUtil.LayoutNext(ctx); + + // Handle text input mode + if (NumberTextbox(ctx, ref v, baseRect, id)) + { + return new MuResult(res); + } + + // Handle normal mode + UpdateControl(ctx, id, baseRect, opt); + + // Handle input - EXACTLY match C version logic + if (ctx.Focus == id && (ctx.MouseDown | ctx.MousePressed) == (int)MouseButton.Left) + { + v = low + (ctx.MousePos.X - baseRect.X) * (high - low) / baseRect.W; + if (step != 0) + { + v = (long)((v + step / 2) / step) * step; + } + } + + // Clamp and store value, update res + value = v = MathUtil.Clamp(v, low, high); + if (last != v) { res |= ResultFlags.Change; } + + // Draw base + DrawControlFrame(ctx, id, baseRect, ColorType.Base, opt); + + // Draw thumb + w = ctx.Style.ThumbSize; + x = (int)((v - low) * (baseRect.W - w) / (high - low)); + thumb = new MuRect(baseRect.X + x, baseRect.Y, w, baseRect.H); + DrawControlFrame(ctx, id, thumb, ColorType.Button, opt); + + // Draw text + string text = v.ToString(format); + DrawControlText(ctx, text, baseRect, ColorType.Text, opt); + + return new MuResult(res); + } + + // Remove the caller information overload since we're using explicit names now + + public static MuResult NumberEx(MuContext ctx, string name, ref float value, float step, string format, int opt) + { + char[] buf = new char[Constants.MaxFormatLength + 1]; + ResultFlags res = 0; + + // Generate ID from the name, just like BeginWindowEx does + uint id = MicroUI.GetId(ctx, System.Text.Encoding.UTF8.GetBytes(name)); + + MuRect baseRect = MuLayoutUtil.LayoutNext(ctx); + float last = value; + + // Handle text input mode + if (NumberTextbox(ctx, ref value, baseRect, id)) + { + return new MuResult(res); + } + + // Handle normal mode + UpdateControl(ctx, id, baseRect, opt); + + // Handle input - EXACTLY match C version logic + if (ctx.Focus == id && ctx.MouseDown == (int)MouseButton.Left) + { + value += ctx.MouseDelta.X * step; + } + + // Set flag if value changed + if (value != last) { res |= ResultFlags.Change; } + + // Draw base + DrawControlFrame(ctx, id, baseRect, ColorType.Base, opt); + + // Draw text + string text = value.ToString(format); + DrawControlText(ctx, text, baseRect, ColorType.Text, opt); + + return new MuResult(res); + } } } \ No newline at end of file diff --git a/MicroUI.cs/MicroUI.cs.csproj b/MicroUI.cs/MicroUI.cs.csproj index b31bb32..e6b18b1 100644 --- a/MicroUI.cs/MicroUI.cs.csproj +++ b/MicroUI.cs/MicroUI.cs.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable diff --git a/MicroUI.cs/Program.cs b/MicroUI.cs/Program.cs index 177d9b8..2de702e 100644 --- a/MicroUI.cs/Program.cs +++ b/MicroUI.cs/Program.cs @@ -6,6 +6,14 @@ namespace MicroUI; class Program { + // Key state tracking for key up events + private static HashSet pressedKeys = new HashSet(); + // Persistent textbox buffer + private static char[] textboxBuf = new char[32]; + private static bool textboxBufInitialized = false; + // Track focus changes + private static uint lastFocus = 0; + // 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() @@ -18,17 +26,23 @@ class Program // Set up required callbacks ctx.TextWidth = (object font, string str, int len) => { - return str.Length * 8; // Simple approximation + // Use Raylib's MeasureText to get actual text width + // If font is null, use default font, otherwise cast to Font + Font textFont = font as Font? ?? Raylib.GetFontDefault(); + return Raylib.MeasureText(str, 10); // 16 is font size }; ctx.TextHeight = (object font) => { - return 16; // Simple approximation + // Use Raylib's font height measurement + Font textFont = font as Font? ?? Raylib.GetFontDefault(); + return 10; // Return a fixed height for now, or use textFont.baseSize if available }; // Initialize microui MicroUI.Init(ctx); bool isChecked = false; + float sliderValue = 50.0f; // Remove persistent slider value while (!Raylib.WindowShouldClose()) @@ -37,28 +51,88 @@ class Program var mousePos = Raylib.GetMousePosition(); MuInput.MouseMove(ctx, (int)mousePos.X, (int)mousePos.Y); + // Track focus changes in main loop + if (ctx.Focus != lastFocus) + { + lastFocus = ctx.Focus; + } + if (Raylib.IsMouseButtonPressed(Raylib_cs.MouseButton.Left)) { - Console.WriteLine("Mouse down"); MuInput.MouseDown(ctx, (int)mousePos.X, (int)mousePos.Y, 1); } + else if (Raylib.IsMouseButtonDown(Raylib_cs.MouseButton.Left)) + { + // Keep MouseDown state active while button is held + ctx.MouseDown = 1; + } if (Raylib.IsMouseButtonReleased(Raylib_cs.MouseButton.Left)) { MuInput.MouseUp(ctx, (int)mousePos.X, (int)mousePos.Y, 1); } + // Handle keyboard input - only for modifier keys + int key = Raylib.GetKeyPressed(); + if (key != 0) + { + // Only handle modifier keys here, not regular characters + // Regular characters are handled in the text input section below + if (key == (int)KeyboardKey.Enter || key == (int)KeyboardKey.Backspace) + { + // Convert to MicroUI key modifiers + int muKey = 0; + if (key == (int)KeyboardKey.Enter) muKey = (int)KeyModifiers.Return; + if (key == (int)KeyboardKey.Backspace) muKey = (int)KeyModifiers.Backspace; + + MuInput.KeyDown(ctx, muKey); + pressedKeys.Add(muKey); + } + } + + // Handle key up events for modifier keys + var keysToRemove = new List(); + foreach (int pressedKey in pressedKeys) + { + // Convert back from MicroUI key modifiers to Raylib keys for checking + KeyboardKey raylibKey = KeyboardKey.Null; + if (pressedKey == (int)KeyModifiers.Return) raylibKey = KeyboardKey.Enter; + if (pressedKey == (int)KeyModifiers.Backspace) raylibKey = KeyboardKey.Backspace; + + if (raylibKey != KeyboardKey.Null && !Raylib.IsKeyDown(raylibKey)) + { + MuInput.KeyUp(ctx, pressedKey); + keysToRemove.Add(pressedKey); + } + } + foreach (int keyToRemove in keysToRemove) + { + pressedKeys.Remove(keyToRemove); + } + + // Handle text input + int keyChar; + while ((keyChar = Raylib.GetCharPressed()) != 0) + { + char typedChar = (char)keyChar; + MuInput.InputText(ctx, typedChar.ToString()); + } + // Debug print for KeyPressed (commented out to reduce noise) + // Console.WriteLine($"DEBUG: KeyPressed after input: {ctx.KeyPressed}"); + Raylib.BeginDrawing(); Raylib.ClearBackground(Color.Black); // Begin frame MicroUI.Begin(ctx); - for(int i = 0; i < 10; i++){ - Window(ctx, i * 15, i * 15, i, ref isChecked); + for(int i = 0; i < 1; i++){ + Window(ctx, i * 15, i * 15, i, ref isChecked, ref sliderValue); // Remove ref sliderValue } // End frame MicroUI.End(ctx); + + // Print all commands in the command list int cmdIndex = 0; MuCommand? cmd = null; @@ -79,7 +153,7 @@ class Program //Console.Write($" | Jump to: {jumpCmd.DestinationIndex}"); break; case MuTextCommand textCmd: - Raylib.DrawText(textCmd.Text, textCmd.Position.X, textCmd.Position.Y, 16, new Color(textCmd.Color.R, textCmd.Color.G, textCmd.Color.B, textCmd.Color.A)); + Raylib.DrawText(textCmd.Text, textCmd.Position.X, textCmd.Position.Y, 10, new Color(textCmd.Color.R, textCmd.Color.G, textCmd.Color.B, textCmd.Color.A)); //Console.Write($" | Text: '{textCmd.Text}' at ({textCmd.Position.X},{textCmd.Position.Y}) Color: {textCmd.Color.R},{textCmd.Color.G},{textCmd.Color.B},{textCmd.Color.A}"); break; case MuIconCommand iconCmd: @@ -170,47 +244,60 @@ class Program } - public static void Window(MuContext ctx, int x, int y, int index, ref bool isChecked){ - if(MuControl.BeginWindowEx(ctx, $"Test Window {index}", new MuRect(x, y, 300, 200), 0)){ - - MuLayoutUtil.LayoutBeginColumn(ctx); - - // Test Text component with multi-line content - MuControl.Text(ctx, "This is a test of the Text component with word wrapping. It should automatically wrap long lines and handle multiple paragraphs."); - - // Test Label component - // MuControl.Label(ctx, "This is a simple label"); - - // Test button - // if (MuControl.ButtonEx(ctx, "Click Me!", 0, 0)) - // { - // Console.WriteLine("Button clicked!"); - // } - // MuLayoutUtil.LayoutRow(ctx, 2, new int[]{200, 50}, 0); - // if(MuControl.Checkbox(ctx, "Checkbox1", ref isChecked)){ - // Console.WriteLine("Checkbox clicked!"); - // } - // if(MuControl.ButtonEx(ctx, null, (int)IconType.Check, 0)){ - // Console.WriteLine("Button clicked!"); - // } - - // Test more text with explicit line breaks - //MuControl.Text(ctx, "This text has\nexplicit line breaks\nand should display\non separate lines."); - - // if(MuControl.Checkbox(ctx, "Checkbox2", ref isChecked)){ - // Console.WriteLine("Checkbox clicked!"); - // } - // if(MuControl.Checkbox(ctx, "Checkbox3", ref isChecked)){ - // Console.WriteLine("Checkbox clicked!"); - // } - // if(MuControl.Checkbox(ctx, "Checkbox4", ref isChecked)){ - // Console.WriteLine("Checkbox clicked!"); - // } - - MuLayoutUtil.LayoutEndColumn(ctx); - MuControl.EndWindow(ctx); - } + public static void Window(MuContext ctx, int x, int y, int index, ref bool isChecked, ref float sliderValue){ + if(MuControl.BeginWindowEx(ctx, $"Test Window", new MuRect(x, y, 300, 200), 0)){ + // Manual two-column layout + MuLayoutUtil.LayoutRow(ctx, 2, new int[] { 120, 160 }, 0); // Adjust widths as needed + // Left column (sliders/text) + { + MuLayoutUtil.LayoutRow(ctx, 1, new int[] { 120 }, 0); + float test = 0; + var sliderResult = MuControl.SliderEx(ctx, "main_slider", ref sliderValue, 0.0f, 100.0f, 1.0f, "F1", 0); + MuControl.SliderEx(ctx, "test_slider", ref test, 0.0f, 100.0f, 1.0f, "F1", 0); + if ((sliderResult & ResultFlags.Change) != 0) + { + Console.WriteLine($"Slider value changed to: {sliderValue}"); + } + MuControl.Text(ctx, "Other controls here..."); + + // Test TextboxRaw + if (!textboxBufInitialized) { + string initial = "Edit me!"; + for (int i = 0; i < initial.Length && i < textboxBuf.Length; i++) textboxBuf[i] = initial[i]; + textboxBufInitialized = true; + } + var textboxRect = MuLayoutUtil.LayoutNext(ctx); + var textboxResult = MuControl.TextboxRaw(ctx, textboxBuf, textboxBuf.Length, 12345, textboxRect, 0); + if ((textboxResult & ResultFlags.Change) != 0) + { + string value = new string(textboxBuf).TrimEnd('\0'); + Console.WriteLine($"Textbox changed: {value}"); + } + } + + // Right column (button grid) + { + MuLayoutUtil.LayoutRow(ctx, 1, new int[] { 160 }, 0); + MuControl.Text(ctx, "4x4 Grid of Buttons with 'x' labels:"); + for (int row = 0; row < 4; row++) + { + MuLayoutUtil.LayoutRow(ctx, 4, new int[] { 25, 25, 25, 25 }, 0); + for (int col = 0; col < 4; col++) + { + MicroUI.PushId(ctx, System.Text.Encoding.UTF8.GetBytes($"grid_{row}_{col}")); + if (MuControl.ButtonEx(ctx, "x", 0, (int)Options.AlignCenter)) + { + Console.WriteLine($"Button at ({row},{col}) clicked!"); + } + MicroUI.PopId(ctx); + } + } + } + + + MuControl.EndWindow(ctx); + } } } \ No newline at end of file diff --git a/MicroUICVersion/CMakeLists.txt b/MicroUICVersion/CMakeLists.txt new file mode 100644 index 0000000..06acce8 --- /dev/null +++ b/MicroUICVersion/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.11) # FetchContent is available in 3.11+ +project(example) + +# Generate compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Dependencies +set(RAYLIB_VERSION 5.5) +find_package(raylib ${RAYLIB_VERSION} QUIET) # QUIET or REQUIRED +if (NOT raylib_FOUND) # If there's none, fetch and build raylib + include(FetchContent) + FetchContent_Declare( + raylib + DOWNLOAD_EXTRACT_TIMESTAMP OFF + URL https://github.com/raysan5/raylib/archive/refs/tags/${RAYLIB_VERSION}.tar.gz + ) + FetchContent_GetProperties(raylib) + if (NOT raylib_POPULATED) # Have we downloaded raylib yet? + set(FETCHCONTENT_QUIET NO) + FetchContent_MakeAvailable(raylib) + endif() +endif() + +# Our Project + +add_executable(${PROJECT_NAME} main.c + microui.c) +#set(raylib_VERBOSE 1) +target_link_libraries(${PROJECT_NAME} raylib) + +# Web Configurations +if (${PLATFORM} STREQUAL "Web") + set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html") # Tell Emscripten to build an example.html file. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s ASYNCIFY -s GL_ENABLE_GET_PROC_ADDRESS=1") +endif() + +# Checks if OSX and links appropriate frameworks (Only required on MacOS) +if (APPLE) + target_link_libraries(${PROJECT_NAME} "-framework IOKit") + target_link_libraries(${PROJECT_NAME} "-framework Cocoa") + target_link_libraries(${PROJECT_NAME} "-framework OpenGL") +endif() \ No newline at end of file diff --git a/MicroUICVersion/cmake_install.cmake b/MicroUICVersion/cmake_install.cmake new file mode 100644 index 0000000..1fe2775 --- /dev/null +++ b/MicroUICVersion/cmake_install.cmake @@ -0,0 +1,67 @@ +# Install script for directory: /Users/bobbylucero/CLionProjects/MicroUITest + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set path to fallback-tool for dependency-resolution. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for each subdirectory. + include("/Users/bobbylucero/CLionProjects/MicroUITest/_deps/raylib-build/cmake_install.cmake") + +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +if(CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/bobbylucero/CLionProjects/MicroUITest/install_local_manifest.txt" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() +if(CMAKE_INSTALL_COMPONENT) + if(CMAKE_INSTALL_COMPONENT MATCHES "^[a-zA-Z0-9_.+-]+$") + set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") + else() + string(MD5 CMAKE_INST_COMP_HASH "${CMAKE_INSTALL_COMPONENT}") + set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INST_COMP_HASH}.txt") + unset(CMAKE_INST_COMP_HASH) + endif() +else() + set(CMAKE_INSTALL_MANIFEST "install_manifest.txt") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/bobbylucero/CLionProjects/MicroUITest/${CMAKE_INSTALL_MANIFEST}" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() diff --git a/MicroUICVersion/main.c b/MicroUICVersion/main.c new file mode 100644 index 0000000..2e7b6ff --- /dev/null +++ b/MicroUICVersion/main.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include + +#include "microui.h" + +static char logbuf[64000]; +static int logbuf_updated = 0; +static float bg[3] = { 90, 95, 100 }; + + +static float global_scale = 1; + +static int font_size = 10; + + +static void write_log(const char *text) { + if (logbuf[0]) { strcat(logbuf, "\n"); } + strcat(logbuf, text); + logbuf_updated = 1; +} + + +static void test_window(mu_Context *ctx) { + /* do window */ + if (mu_begin_window(ctx, "Demo Window", mu_rect(40, 40, 300, 450))) { + mu_Container *win = mu_get_current_container(ctx); + win->rect.w = mu_max(win->rect.w, 240); + win->rect.h = mu_max(win->rect.h, 300); + + /* window info */ + if (mu_header(ctx, "Window Info")) { + mu_Container *win = mu_get_current_container(ctx); + char buf[64]; + mu_layout_row(ctx, 2, (int[]) { 54, -1 }, 0); + mu_label(ctx,"Position:"); + sprintf(buf, "%d, %d", win->rect.x, win->rect.y); mu_label(ctx, buf); + mu_label(ctx, "Size:"); + sprintf(buf, "%d, %d", win->rect.w, win->rect.h); mu_label(ctx, buf); + } + + /* labels + buttons */ + if (mu_header_ex(ctx, "Test Buttons", MU_OPT_EXPANDED)) { + mu_layout_row(ctx, 3, (int[]) { 86, -110, -1 }, 0); + mu_label(ctx, "Test buttons 1:"); + if (mu_button(ctx, "Button 1")) { write_log("Pressed button 1"); } + if (mu_button(ctx, "Button 2")) { write_log("Pressed button 2"); } + mu_label(ctx, "Test buttons 2:"); + if (mu_button(ctx, "Button 3")) { write_log("Pressed button 3"); } + if (mu_button(ctx, "Popup")) { mu_open_popup(ctx, "Test Popup"); } + if (mu_begin_popup(ctx, "Test Popup")) { + mu_button(ctx, "Hello"); + mu_button(ctx, "World"); + mu_end_popup(ctx); + } + } + + /* tree */ + if (mu_header_ex(ctx, "Tree and Text", MU_OPT_EXPANDED)) { + mu_layout_row(ctx, 2, (int[]) { 140, -1 }, 0); + mu_layout_begin_column(ctx); + if (mu_begin_treenode(ctx, "Test 1")) { + if (mu_begin_treenode(ctx, "Test 1a")) { + mu_label(ctx, "Hello"); + mu_label(ctx, "world"); + mu_end_treenode(ctx); + } + if (mu_begin_treenode(ctx, "Test 1b")) { + if (mu_button(ctx, "Button 1")) { write_log("Pressed button 1"); } + if (mu_button(ctx, "Button 2")) { write_log("Pressed button 2"); } + mu_end_treenode(ctx); + } + mu_end_treenode(ctx); + } + if (mu_begin_treenode(ctx, "Test 2")) { + mu_layout_row(ctx, 2, (int[]) { 54, 54 }, 0); + if (mu_button(ctx, "Button 3")) { write_log("Pressed button 3"); } + if (mu_button(ctx, "Button 4")) { write_log("Pressed button 4"); } + if (mu_button(ctx, "Button 5")) { write_log("Pressed button 5"); } + if (mu_button(ctx, "Button 6")) { write_log("Pressed button 6"); } + mu_end_treenode(ctx); + } + if (mu_begin_treenode(ctx, "Test 3")) { + static int checks[3] = { 1, 0, 1 }; + mu_checkbox(ctx, "Checkbox 1", &checks[0]); + mu_checkbox(ctx, "Checkbox 2", &checks[1]); + mu_checkbox(ctx, "Checkbox 3", &checks[2]); + mu_end_treenode(ctx); + } + mu_layout_end_column(ctx); + + mu_layout_begin_column(ctx); + mu_layout_row(ctx, 1, (int[]) { -1 }, 0); + mu_text(ctx, "Lorem ipsum dolor sit amet, consectetur adipiscing " + "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus " + "ipsum, eu varius magna felis a nulla."); + mu_layout_end_column(ctx); + } + + /* background color sliders */ + if (mu_header_ex(ctx, "Background Color", MU_OPT_EXPANDED)) { + mu_layout_row(ctx, 2, (int[]) { -78, -1 }, 74); + /* sliders */ + mu_layout_begin_column(ctx); + mu_layout_row(ctx, 2, (int[]) { 46, -1 }, 0); + mu_label(ctx, "Red:"); mu_slider(ctx, &bg[0], 0, 255); + mu_label(ctx, "Green:"); mu_slider(ctx, &bg[1], 0, 255); + mu_label(ctx, "Blue:"); mu_slider(ctx, &bg[2], 0, 255); + mu_layout_end_column(ctx); + /* color preview */ + mu_Rect r = mu_layout_next(ctx); + mu_draw_rect(ctx, r, mu_color(bg[0], bg[1], bg[2], 255)); + char buf[32]; + sprintf(buf, "#%02X%02X%02X", (int) bg[0], (int) bg[1], (int) bg[2]); + mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER); + } + + mu_end_window(ctx); + } +} + + +static void log_window(mu_Context *ctx) { + if (mu_begin_window(ctx, "Log Window", mu_rect(350, 40, 300, 200))) { + /* output text panel */ + mu_layout_row(ctx, 1, (int[]) { -1 }, -25); + mu_begin_panel(ctx, "Log Output"); + mu_Container *panel = mu_get_current_container(ctx); + mu_layout_row(ctx, 1, (int[]) { -1 }, -1); + mu_text(ctx, logbuf); + mu_end_panel(ctx); + if (logbuf_updated) { + panel->scroll.y = panel->content_size.y; + logbuf_updated = 0; + } + + /* input textbox + submit button */ + static char buf[128]; + int submitted = 0; + mu_layout_row(ctx, 2, (int[]) { -70, -1 }, 0); + if (mu_textbox(ctx, buf, sizeof(buf)) & MU_RES_SUBMIT) { + mu_set_focus(ctx, ctx->last_id); + submitted = 1; + } + if (mu_button(ctx, "Submit")) { submitted = 1; } + if (submitted) { + write_log(buf); + buf[0] = '\0'; + } + + mu_end_window(ctx); + } +} + +static int uint8_slider(mu_Context *ctx, unsigned char *value, int low, int high) { + static float tmp; + mu_push_id(ctx, &value, sizeof(value)); + tmp = *value; + int res = mu_slider_ex(ctx, &tmp, low, high, 0, "%.0f", MU_OPT_ALIGNCENTER); + *value = tmp; + mu_pop_id(ctx); + return res; +} + +static void style_window(mu_Context *ctx) { + static struct { const char *label; int idx; } colors[] = { + { "text:", MU_COLOR_TEXT }, + { "border:", MU_COLOR_BORDER }, + { "windowbg:", MU_COLOR_WINDOWBG }, + { "titlebg:", MU_COLOR_TITLEBG }, + { "titletext:", MU_COLOR_TITLETEXT }, + { "panelbg:", MU_COLOR_PANELBG }, + { "button:", MU_COLOR_BUTTON }, + { "buttonhover:", MU_COLOR_BUTTONHOVER }, + { "buttonfocus:", MU_COLOR_BUTTONFOCUS }, + { "base:", MU_COLOR_BASE }, + { "basehover:", MU_COLOR_BASEHOVER }, + { "basefocus:", MU_COLOR_BASEFOCUS }, + { "scrollbase:", MU_COLOR_SCROLLBASE }, + { "scrollthumb:", MU_COLOR_SCROLLTHUMB }, + { NULL } + }; + + if (mu_begin_window(ctx, "Style Editor", mu_rect(350, 250, 300, 240))) { + int sw = mu_get_current_container(ctx)->body.w * 0.14; + mu_layout_row(ctx, 6, (int[]) { 80, sw, sw, sw, sw, -1 }, 0); + for (int i = 0; colors[i].label; i++) { + mu_label(ctx, colors[i].label); + uint8_slider(ctx, &ctx->style->colors[i].r, 0, 255); + uint8_slider(ctx, &ctx->style->colors[i].g, 0, 255); + uint8_slider(ctx, &ctx->style->colors[i].b, 0, 255); + uint8_slider(ctx, &ctx->style->colors[i].a, 0, 255); + mu_draw_rect(ctx, mu_layout_next(ctx), ctx->style->colors[i]); + } + mu_end_window(ctx); + } +} + +static void process_frame(mu_Context *ctx) { + mu_begin(ctx); + style_window(ctx); + log_window(ctx); + test_window(ctx); + mu_end(ctx); +} + + + +static int text_width(mu_Font font, const char *text, int len) { + //if (len == -1) { len = strlen(text); } + return MeasureText(text, font_size) * global_scale; +} + +static int text_height(mu_Font font) { + return font_size * global_scale; +} + +// Raylib uses MOUSE_LEFT_BUTTON, etc., which are ints from 0-2. +static const int button_map[3] = { + [MOUSE_LEFT_BUTTON] = MU_MOUSE_LEFT, + [MOUSE_RIGHT_BUTTON] = MU_MOUSE_RIGHT, + [MOUSE_MIDDLE_BUTTON] = MU_MOUSE_MIDDLE +}; + +static const char key_map[512] = { + [KEY_LEFT_SHIFT] = MU_KEY_SHIFT, + [KEY_RIGHT_SHIFT] = MU_KEY_SHIFT, + [KEY_LEFT_CONTROL] = MU_KEY_CTRL, + [KEY_RIGHT_CONTROL]= MU_KEY_CTRL, + [KEY_LEFT_ALT] = MU_KEY_ALT, + [KEY_RIGHT_ALT] = MU_KEY_ALT, + [KEY_ENTER] = MU_KEY_RETURN, + [KEY_BACKSPACE] = MU_KEY_BACKSPACE +}; + +void window(); + +int main(void) { + const int screenWidth = 800; + const int screenHeight = 400; + + + + InitWindow(screenWidth, screenHeight, "raylib [core] example - basic window"); + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + + + + mu_Context *ctx = malloc(sizeof(mu_Context)); + mu_init(ctx); + + ctx->text_height = text_height; + ctx->text_width = text_width; + + float sliderValue = 0; + + while (!WindowShouldClose()) // Detect window close button or ESC key + { + + + // Inside your frame loop + Vector2 mouse = GetMousePosition(); + mu_input_mousemove(ctx, (int)mouse.x / global_scale, (int)mouse.y / global_scale); + // Mouse button handling + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + mu_input_mousedown(ctx, mouse.x / global_scale, mouse.y / global_scale, MU_MOUSE_LEFT); + } + else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { + // Keep MouseDown state active while button is held + ctx->mouse_down |= MU_MOUSE_LEFT; + } + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { + mu_input_mouseup(ctx, mouse.x / global_scale, mouse.y / global_scale, MU_MOUSE_LEFT); + } + if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { + mu_input_mousedown(ctx, mouse.x / global_scale, mouse.y / global_scale, MU_MOUSE_RIGHT); + } + if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) { + mu_input_mouseup(ctx, mouse.x / global_scale, mouse.y / global_scale, MU_MOUSE_RIGHT); + } + if (IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) { + mu_input_mousedown(ctx, mouse.x / global_scale, mouse.y / global_scale, MU_MOUSE_MIDDLE); + } + if (IsMouseButtonReleased(MOUSE_MIDDLE_BUTTON)) { + mu_input_mouseup(ctx, mouse.x / global_scale, mouse.y / global_scale, MU_MOUSE_MIDDLE); + } + + // Keyboard key handling + for (int key = 0; key < 512; key++) { + if (IsKeyPressed(key)) { + int c = key_map[key & 0xff]; + if (c) mu_input_keydown(ctx, c); + } + if (IsKeyReleased(key)) { + int c = key_map[key & 0xff]; + if (c) mu_input_keyup(ctx, c); + } + } + + int ch; + while ((ch = GetCharPressed()) != 0) { + if (ch >= 32 && ch <= 126) { // Printable ASCII range + char c = (char)ch; + char str[2] = { c, 0 }; // Create null-terminated string + mu_input_text(ctx, str); + } + } + + + // process_frame(ctx); + mu_begin(ctx); + window(ctx, &sliderValue); + mu_end(ctx); + + BeginDrawing(); + + ClearBackground((Color){0,0,0,1}); + + mu_Command *cmd = NULL; + + while (mu_next_command(ctx, &cmd)) { + switch (cmd->type) { + case MU_COMMAND_TEXT: DrawText(cmd->text.str, cmd->text.pos.x * global_scale, cmd->text.pos.y * global_scale, font_size * global_scale, (Color){cmd->text.color.r, cmd->text.color.g, cmd->text.color.b, cmd->text.color.a}); + break; + case MU_COMMAND_RECT: DrawRectangle(cmd->rect.rect.x * global_scale, cmd->rect.rect.y * global_scale,cmd->rect.rect.w * global_scale, cmd->rect.rect.h * global_scale, (Color){cmd->rect.color.r, cmd->rect.color.g, cmd->rect.color.b, cmd->rect.color.a}); + break; + case MU_COMMAND_ICON: + //DrawRectangle(cmd->icon.rect.x, cmd->icon.rect.y,cmd->icon.rect.w, cmd->icon.rect.h, (Color){cmd->icon.color.r, cmd->icon.color.g, cmd->icon.color.b, cmd->icon.color.a}); + break; + case MU_COMMAND_CLIP: BeginScissorMode(cmd->rect.rect.x * global_scale, cmd->rect.rect.y * global_scale,cmd->rect.rect.w * global_scale, cmd->rect.rect.h * global_scale); break; + } + } + + EndDrawing(); + } + CloseWindow(); + return 0; +} + +void window(mu_Context* ctx, float* value) { + if (mu_begin_window_ex(ctx, "Test Window", mu_rect(50, 50, 300, 200), 0)) { + // Set up a row with 2 columns: left for sliders/text, right for grid + int widths[] = {120, 160}; + mu_layout_row(ctx, 2, widths, 0); + + // --- Left column --- + { + mu_layout_row(ctx, 1, (int[]){120}, 0); + static float slider1 = 50.0f, slider2 = 0.0f; + mu_slider_ex(ctx, &slider1, 0.0f, 100.0f, 1.0f, "%.1f", MU_OPT_ALIGNCENTER); + mu_slider_ex(ctx, &slider2, 0.0f, 100.0f, 1.0f, "%.1f", MU_OPT_ALIGNCENTER); + mu_text(ctx, "Other controls here..."); + + // Test TextboxRaw + static char textbox_buf[32] = "Edit me!"; + mu_Rect textbox_rect = mu_layout_next(ctx); + if (mu_textbox_raw(ctx, textbox_buf, sizeof(textbox_buf), 12345, textbox_rect, 0)) { + printf("Textbox changed: %s\n", textbox_buf); + } + } + + // --- Right column --- + { + mu_layout_row(ctx, 1, (int[]){160}, 0); + mu_text(ctx, "4x4 Grid of Buttons with 'x' labels:"); + for (int row = 0; row < 4; row++) { + int grid_widths[4] = {25, 25, 25, 25}; + mu_layout_row(ctx, 4, grid_widths, 0); + for (int col = 0; col < 4; col++) { + char id[16]; + snprintf(id, sizeof(id), "grid_%d_%d", row, col); + mu_push_id(ctx, id, strlen(id)); + if (mu_button_ex(ctx, "x", 0, MU_OPT_ALIGNCENTER)) { + // handle click + } + mu_pop_id(ctx); + } + } + } + + mu_end_window(ctx); +} +} + diff --git a/MicroUICVersion/microui.c b/MicroUICVersion/microui.c new file mode 100644 index 0000000..218dad7 --- /dev/null +++ b/MicroUICVersion/microui.c @@ -0,0 +1,1209 @@ +/* +** Copyright (c) 2024 rxi +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to +** deal in the Software without restriction, including without limitation the +** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +** sell copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +** IN THE SOFTWARE. +*/ + +#include +#include +#include +#include "microui.h" + +#define unused(x) ((void) (x)) + +#define expect(x) do { \ + if (!(x)) { \ + fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \ + __FILE__, __LINE__, #x); \ + abort(); \ + } \ + } while (0) + +#define push(stk, val) do { \ + expect((stk).idx < (int) (sizeof((stk).items) / sizeof(*(stk).items))); \ + (stk).items[(stk).idx] = (val); \ + (stk).idx++; /* incremented after incase `val` uses this value */ \ + } while (0) + +#define pop(stk) do { \ + expect((stk).idx > 0); \ + (stk).idx--; \ + } while (0) + + +static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 }; + +static mu_Style default_style = { + /* font | size | padding | spacing | indent */ + NULL, { 68, 10 }, 5, 4, 24, + /* title_height | scrollbar_size | thumb_size */ + 24, 12, 8, + { + { 230, 230, 230, 255 }, /* MU_COLOR_TEXT */ + { 25, 25, 25, 255 }, /* MU_COLOR_BORDER */ + { 50, 50, 50, 255 }, /* MU_COLOR_WINDOWBG */ + { 25, 25, 25, 255 }, /* MU_COLOR_TITLEBG */ + { 240, 240, 240, 255 }, /* MU_COLOR_TITLETEXT */ + { 0, 0, 0, 0 }, /* MU_COLOR_PANELBG */ + { 75, 75, 75, 255 }, /* MU_COLOR_BUTTON */ + { 95, 95, 95, 255 }, /* MU_COLOR_BUTTONHOVER */ + { 115, 115, 115, 255 }, /* MU_COLOR_BUTTONFOCUS */ + { 30, 30, 30, 255 }, /* MU_COLOR_BASE */ + { 35, 35, 35, 255 }, /* MU_COLOR_BASEHOVER */ + { 40, 40, 40, 255 }, /* MU_COLOR_BASEFOCUS */ + { 43, 43, 43, 255 }, /* MU_COLOR_SCROLLBASE */ + { 30, 30, 30, 255 } /* MU_COLOR_SCROLLTHUMB */ + } +}; + + +mu_Vec2 mu_vec2(int x, int y) { + mu_Vec2 res; + res.x = x; res.y = y; + return res; +} + + +mu_Rect mu_rect(int x, int y, int w, int h) { + mu_Rect res; + res.x = x; res.y = y; res.w = w; res.h = h; + return res; +} + + +mu_Color mu_color(int r, int g, int b, int a) { + mu_Color res; + res.r = r; res.g = g; res.b = b; res.a = a; + return res; +} + + +static mu_Rect expand_rect(mu_Rect rect, int n) { + return mu_rect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2); +} + + +static mu_Rect intersect_rects(mu_Rect r1, mu_Rect r2) { + int x1 = mu_max(r1.x, r2.x); + int y1 = mu_max(r1.y, r2.y); + int x2 = mu_min(r1.x + r1.w, r2.x + r2.w); + int y2 = mu_min(r1.y + r1.h, r2.y + r2.h); + if (x2 < x1) { x2 = x1; } + if (y2 < y1) { y2 = y1; } + return mu_rect(x1, y1, x2 - x1, y2 - y1); +} + + +static int rect_overlaps_vec2(mu_Rect r, mu_Vec2 p) { + return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h; +} + + +static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid) { + mu_draw_rect(ctx, rect, ctx->style->colors[colorid]); + if (colorid == MU_COLOR_SCROLLBASE || + colorid == MU_COLOR_SCROLLTHUMB || + colorid == MU_COLOR_TITLEBG) { return; } + /* draw border */ + if (ctx->style->colors[MU_COLOR_BORDER].a) { + mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_BORDER]); + } +} + + +void mu_init(mu_Context *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->draw_frame = draw_frame; + ctx->_style = default_style; + ctx->style = &ctx->_style; +} + + +void mu_begin(mu_Context *ctx) { + expect(ctx->text_width && ctx->text_height); + ctx->command_list.idx = 0; + ctx->root_list.idx = 0; + ctx->scroll_target = NULL; + ctx->hover_root = ctx->next_hover_root; + ctx->next_hover_root = NULL; + ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x; + ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y; + ctx->frame++; +} + + +static int compare_zindex(const void *a, const void *b) { + return (*(mu_Container**) a)->zindex - (*(mu_Container**) b)->zindex; +} + + +void mu_end(mu_Context *ctx) { + int i, n; + /* check stacks */ + expect(ctx->container_stack.idx == 0); + expect(ctx->clip_stack.idx == 0); + expect(ctx->id_stack.idx == 0); + expect(ctx->layout_stack.idx == 0); + + /* handle scroll input */ + if (ctx->scroll_target) { + ctx->scroll_target->scroll.x += ctx->scroll_delta.x; + ctx->scroll_target->scroll.y += ctx->scroll_delta.y; + } + + /* unset focus if focus id was not touched this frame */ + if (!ctx->updated_focus) { ctx->focus = 0; } + ctx->updated_focus = 0; + + /* bring hover root to front if mouse was pressed */ + if (ctx->mouse_pressed && ctx->next_hover_root && + ctx->next_hover_root->zindex < ctx->last_zindex && + ctx->next_hover_root->zindex >= 0 + ) { + mu_bring_to_front(ctx, ctx->next_hover_root); + } + + /* reset input state */ + ctx->key_pressed = 0; + ctx->input_text[0] = '\0'; + ctx->mouse_pressed = 0; + ctx->scroll_delta = mu_vec2(0, 0); + ctx->last_mouse_pos = ctx->mouse_pos; + + /* sort root containers by zindex */ + n = ctx->root_list.idx; + qsort(ctx->root_list.items, n, sizeof(mu_Container*), compare_zindex); + + /* set root container jump commands */ + for (i = 0; i < n; i++) { + mu_Container *cnt = ctx->root_list.items[i]; + /* if this is the first container then make the first command jump to it. + ** otherwise set the previous container's tail to jump to this one */ + if (i == 0) { + mu_Command *cmd = (mu_Command*) ctx->command_list.items; + cmd->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand); + } else { + mu_Container *prev = ctx->root_list.items[i - 1]; + prev->tail->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand); + } + /* make the last container's tail jump to the end of command list */ + if (i == n - 1) { + cnt->tail->jump.dst = ctx->command_list.items + ctx->command_list.idx; + } + } +} + + +void mu_set_focus(mu_Context *ctx, mu_Id id) { + ctx->focus = id; + ctx->updated_focus = 1; +} + + +/* 32bit fnv-1a hash */ +#define HASH_INITIAL 2166136261 + +static void hash(mu_Id *hash, const void *data, int size) { + const unsigned char *p = data; + while (size--) { + *hash = (*hash ^ *p++) * 16777619; + } +} + + +mu_Id mu_get_id(mu_Context *ctx, const void *data, int size) { + int idx = ctx->id_stack.idx; + mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL; + hash(&res, data, size); + ctx->last_id = res; + return res; +} + + +void mu_push_id(mu_Context *ctx, const void *data, int size) { + push(ctx->id_stack, mu_get_id(ctx, data, size)); +} + + +void mu_pop_id(mu_Context *ctx) { + pop(ctx->id_stack); +} + + +void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect) { + mu_Rect last = mu_get_clip_rect(ctx); + push(ctx->clip_stack, intersect_rects(rect, last)); +} + + +void mu_pop_clip_rect(mu_Context *ctx) { + pop(ctx->clip_stack); +} + + +mu_Rect mu_get_clip_rect(mu_Context *ctx) { + expect(ctx->clip_stack.idx > 0); + return ctx->clip_stack.items[ctx->clip_stack.idx - 1]; +} + + +int mu_check_clip(mu_Context *ctx, mu_Rect r) { + mu_Rect cr = mu_get_clip_rect(ctx); + 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 MU_CLIP_ALL; } + 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; } + return MU_CLIP_PART; +} + + +static void push_layout(mu_Context *ctx, mu_Rect body, mu_Vec2 scroll) { + mu_Layout layout; + int width = 0; + memset(&layout, 0, sizeof(layout)); + layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h); + layout.max = mu_vec2(-0x1000000, -0x1000000); + push(ctx->layout_stack, layout); + mu_layout_row(ctx, 1, &width, 0); +} + + +static mu_Layout* get_layout(mu_Context *ctx) { + return &ctx->layout_stack.items[ctx->layout_stack.idx - 1]; +} + + +static void pop_container(mu_Context *ctx) { + mu_Container *cnt = mu_get_current_container(ctx); + mu_Layout *layout = get_layout(ctx); + cnt->content_size.x = layout->max.x - layout->body.x; + cnt->content_size.y = layout->max.y - layout->body.y; + /* pop container, layout and id */ + pop(ctx->container_stack); + pop(ctx->layout_stack); + mu_pop_id(ctx); +} + + +mu_Container* mu_get_current_container(mu_Context *ctx) { + expect(ctx->container_stack.idx > 0); + return ctx->container_stack.items[ ctx->container_stack.idx - 1 ]; +} + + +static mu_Container* get_container(mu_Context *ctx, mu_Id id, int opt) { + mu_Container *cnt; + /* try to get existing container from pool */ + int idx = mu_pool_get(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id); + if (idx >= 0) { + if (ctx->containers[idx].open || ~opt & MU_OPT_CLOSED) { + mu_pool_update(ctx, ctx->container_pool, idx); + } + return &ctx->containers[idx]; + } + if (opt & MU_OPT_CLOSED) { return NULL; } + /* container not found in pool: init new container */ + idx = mu_pool_init(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id); + cnt = &ctx->containers[idx]; + memset(cnt, 0, sizeof(*cnt)); + cnt->open = 1; + mu_bring_to_front(ctx, cnt); + return cnt; +} + + +mu_Container* mu_get_container(mu_Context *ctx, const char *name) { + mu_Id id = mu_get_id(ctx, name, strlen(name)); + return get_container(ctx, id, 0); +} + + +void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt) { + cnt->zindex = ++ctx->last_zindex; +} + + +/*============================================================================ +** pool +**============================================================================*/ + +int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) { + int i, n = -1, f = ctx->frame; + for (i = 0; i < len; i++) { + if (items[i].last_update < f) { + f = items[i].last_update; + n = i; + } + } + expect(n > -1); + items[n].id = id; + mu_pool_update(ctx, items, n); + return n; +} + + +int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) { + int i; + unused(ctx); + for (i = 0; i < len; i++) { + if (items[i].id == id) { return i; } + } + return -1; +} + + +void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx) { + items[idx].last_update = ctx->frame; +} + + +/*============================================================================ +** input handlers +**============================================================================*/ + +void mu_input_mousemove(mu_Context *ctx, int x, int y) { + ctx->mouse_pos = mu_vec2(x, y); +} + + +void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn) { + mu_input_mousemove(ctx, x, y); + ctx->mouse_down |= btn; + ctx->mouse_pressed |= btn; +} + + +void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn) { + mu_input_mousemove(ctx, x, y); + ctx->mouse_down &= ~btn; +} + + +void mu_input_scroll(mu_Context *ctx, int x, int y) { + ctx->scroll_delta.x += x; + ctx->scroll_delta.y += y; +} + + +void mu_input_keydown(mu_Context *ctx, int key) { + ctx->key_pressed |= key; + ctx->key_down |= key; +} + + +void mu_input_keyup(mu_Context *ctx, int key) { + ctx->key_down &= ~key; +} + + +void mu_input_text(mu_Context *ctx, const char *text) { + int len = strlen(ctx->input_text); + int size = strlen(text) + 1; + expect(len + size <= (int) sizeof(ctx->input_text)); + memcpy(ctx->input_text + len, text, size); +} + + +/*============================================================================ +** commandlist +**============================================================================*/ + +mu_Command* mu_push_command(mu_Context *ctx, int type, int size) { + mu_Command *cmd = (mu_Command*) (ctx->command_list.items + ctx->command_list.idx); + expect(ctx->command_list.idx + size < MU_COMMANDLIST_SIZE); + cmd->base.type = type; + cmd->base.size = size; + ctx->command_list.idx += size; + return cmd; +} + + +int mu_next_command(mu_Context *ctx, mu_Command **cmd) { + if (*cmd) { + *cmd = (mu_Command*) (((char*) *cmd) + (*cmd)->base.size); + } else { + *cmd = (mu_Command*) ctx->command_list.items; + } + while ((char*) *cmd != ctx->command_list.items + ctx->command_list.idx) { + if ((*cmd)->type != MU_COMMAND_JUMP) { return 1; } + *cmd = (*cmd)->jump.dst; + } + return 0; +} + + +static mu_Command* push_jump(mu_Context *ctx, mu_Command *dst) { + mu_Command *cmd; + cmd = mu_push_command(ctx, MU_COMMAND_JUMP, sizeof(mu_JumpCommand)); + cmd->jump.dst = dst; + return cmd; +} + + +void mu_set_clip(mu_Context *ctx, mu_Rect rect) { + mu_Command *cmd; + cmd = mu_push_command(ctx, MU_COMMAND_CLIP, sizeof(mu_ClipCommand)); + cmd->clip.rect = rect; +} + + +void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color) { + mu_Command *cmd; + rect = intersect_rects(rect, mu_get_clip_rect(ctx)); + if (rect.w > 0 && rect.h > 0) { + cmd = mu_push_command(ctx, MU_COMMAND_RECT, sizeof(mu_RectCommand)); + cmd->rect.rect = rect; + cmd->rect.color = color; + } +} + + +void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color) { + mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color); + mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color); + mu_draw_rect(ctx, mu_rect(rect.x, rect.y, 1, rect.h), color); + mu_draw_rect(ctx, mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color); +} + + +void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len, + mu_Vec2 pos, mu_Color color) +{ + mu_Command *cmd; + mu_Rect rect = mu_rect( + pos.x, pos.y, ctx->text_width(font, str, len), ctx->text_height(font)); + int clipped = mu_check_clip(ctx, rect); + if (clipped == MU_CLIP_ALL ) { return; } + if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); } + /* add command */ + if (len < 0) { len = strlen(str); } + cmd = mu_push_command(ctx, MU_COMMAND_TEXT, sizeof(mu_TextCommand) + len); + memcpy(cmd->text.str, str, len); + cmd->text.str[len] = '\0'; + cmd->text.pos = pos; + cmd->text.color = color; + cmd->text.font = font; + /* reset clipping if it was set */ + if (clipped) { mu_set_clip(ctx, unclipped_rect); } +} + + +void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color) { + mu_Command *cmd; + /* do clip command if the rect isn't fully contained within the cliprect */ + int clipped = mu_check_clip(ctx, rect); + if (clipped == MU_CLIP_ALL ) { return; } + if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); } + /* do icon command */ + cmd = mu_push_command(ctx, MU_COMMAND_ICON, sizeof(mu_IconCommand)); + cmd->icon.id = id; + cmd->icon.rect = rect; + cmd->icon.color = color; + /* reset clipping if it was set */ + if (clipped) { mu_set_clip(ctx, unclipped_rect); } +} + + +/*============================================================================ +** layout +**============================================================================*/ + +enum { RELATIVE = 1, ABSOLUTE = 2 }; + + +void mu_layout_begin_column(mu_Context *ctx) { + push_layout(ctx, mu_layout_next(ctx), mu_vec2(0, 0)); +} + + +void mu_layout_end_column(mu_Context *ctx) { + mu_Layout *a, *b; + b = get_layout(ctx); + pop(ctx->layout_stack); + /* inherit position/next_row/max from child layout if they are greater */ + a = get_layout(ctx); + a->position.x = mu_max(a->position.x, b->position.x + b->body.x - a->body.x); + a->next_row = mu_max(a->next_row, b->next_row + b->body.y - a->body.y); + a->max.x = mu_max(a->max.x, b->max.x); + a->max.y = mu_max(a->max.y, b->max.y); +} + + +void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height) { + mu_Layout *layout = get_layout(ctx); + if (widths) { + expect(items <= MU_MAX_WIDTHS); + memcpy(layout->widths, widths, items * sizeof(widths[0])); + } + layout->items = items; + layout->position = mu_vec2(layout->indent, layout->next_row); + layout->size.y = height; + layout->item_index = 0; +} + + +void mu_layout_width(mu_Context *ctx, int width) { + get_layout(ctx)->size.x = width; +} + + +void mu_layout_height(mu_Context *ctx, int height) { + get_layout(ctx)->size.y = height; +} + + +void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative) { + mu_Layout *layout = get_layout(ctx); + layout->next = r; + layout->next_type = relative ? RELATIVE : ABSOLUTE; +} + + +mu_Rect mu_layout_next(mu_Context *ctx) { + mu_Layout *layout = get_layout(ctx); + mu_Style *style = ctx->style; + mu_Rect res; + + if (layout->next_type) { + /* handle rect set by `mu_layout_set_next` */ + int type = layout->next_type; + layout->next_type = 0; + res = layout->next; + if (type == ABSOLUTE) { return (ctx->last_rect = res); } + + } else { + /* handle next row */ + if (layout->item_index == layout->items) { + mu_layout_row(ctx, layout->items, NULL, layout->size.y); + } + + /* position */ + res.x = layout->position.x; + res.y = layout->position.y; + + /* size */ + res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x; + res.h = layout->size.y; + if (res.w == 0) { res.w = style->size.x + style->padding * 2; } + if (res.h == 0) { res.h = style->size.y + style->padding * 2; } + if (res.w < 0) { res.w += layout->body.w - res.x + 1; } + if (res.h < 0) { res.h += layout->body.h - res.y + 1; } + + layout->item_index++; + } + + /* update position */ + layout->position.x += res.w + style->spacing; + layout->next_row = mu_max(layout->next_row, res.y + res.h + style->spacing); + + /* apply body offset */ + res.x += layout->body.x; + res.y += layout->body.y; + + /* update max position */ + layout->max.x = mu_max(layout->max.x, res.x + res.w); + layout->max.y = mu_max(layout->max.y, res.y + res.h); + + return (ctx->last_rect = res); +} + + +/*============================================================================ +** controls +**============================================================================*/ + +static int in_hover_root(mu_Context *ctx) { + int i = ctx->container_stack.idx; + while (i--) { + if (ctx->container_stack.items[i] == ctx->hover_root) { return 1; } + /* only root containers have their `head` field set; stop searching if we've + ** reached the current root container */ + if (ctx->container_stack.items[i]->head) { break; } + } + return 0; +} + + +void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect, + int colorid, int opt) +{ + if (opt & MU_OPT_NOFRAME) { return; } + colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0; + ctx->draw_frame(ctx, rect, colorid); +} + + +void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, + int colorid, int opt) +{ + mu_Vec2 pos; + mu_Font font = ctx->style->font; + int tw = ctx->text_width(font, str, -1); + mu_push_clip_rect(ctx, rect); + pos.y = rect.y + (rect.h - ctx->text_height(font)) / 2; + if (opt & MU_OPT_ALIGNCENTER) { + pos.x = rect.x + (rect.w - tw) / 2; + } else if (opt & MU_OPT_ALIGNRIGHT) { + pos.x = rect.x + rect.w - tw - ctx->style->padding; + } else { + pos.x = rect.x + ctx->style->padding; + } + mu_draw_text(ctx, font, str, -1, pos, ctx->style->colors[colorid]); + mu_pop_clip_rect(ctx); +} + + +int mu_mouse_over(mu_Context *ctx, mu_Rect rect) { + return rect_overlaps_vec2(rect, ctx->mouse_pos) && + rect_overlaps_vec2(mu_get_clip_rect(ctx), ctx->mouse_pos) && + in_hover_root(ctx); +} + + +void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) { + int mouseover = mu_mouse_over(ctx, rect); + + if (ctx->focus == id) { ctx->updated_focus = 1; } + if (opt & MU_OPT_NOINTERACT) { return; } + if (mouseover && !ctx->mouse_down) { ctx->hover = id; } + + if (ctx->focus == id) { + if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); } + if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); } + } + + if (ctx->hover == id) { + if (ctx->mouse_pressed) { + mu_set_focus(ctx, id); + } else if (!mouseover) { + ctx->hover = 0; + } + } +} + + +void mu_text(mu_Context *ctx, const char *text) { + const char *start, *end, *p = text; + int width = -1; + mu_Font font = ctx->style->font; + mu_Color color = ctx->style->colors[MU_COLOR_TEXT]; + mu_layout_begin_column(ctx); + mu_layout_row(ctx, 1, &width, ctx->text_height(font)); + do { + mu_Rect r = mu_layout_next(ctx); + int w = 0; + start = end = p; + do { + const char* word = p; + while (*p && *p != ' ' && *p != '\n') { p++; } + w += ctx->text_width(font, word, p - word); + if (w > r.w && end != start) { break; } + w += ctx->text_width(font, p, 1); + end = p++; + } while (*end && *end != '\n'); + mu_draw_text(ctx, font, start, end - start, mu_vec2(r.x, r.y), color); + p = end + 1; + } while (*end); + mu_layout_end_column(ctx); +} + + +void mu_label(mu_Context *ctx, const char *text) { + mu_draw_control_text(ctx, text, mu_layout_next(ctx), MU_COLOR_TEXT, 0); +} + + +int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) { + int res = 0; + mu_Id id = label ? mu_get_id(ctx, label, strlen(label)) + : mu_get_id(ctx, &icon, sizeof(icon)); + mu_Rect r = mu_layout_next(ctx); + mu_update_control(ctx, id, r, opt); + /* handle click */ + if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { + res |= MU_RES_SUBMIT; + } + /* draw */ + mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, opt); + if (label) { mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, opt); } + if (icon) { mu_draw_icon(ctx, icon, r, ctx->style->colors[MU_COLOR_TEXT]); } + return res; +} + + +int mu_checkbox(mu_Context *ctx, const char *label, int *state) { + int res = 0; + mu_Id id = mu_get_id(ctx, &state, sizeof(state)); + mu_Rect r = mu_layout_next(ctx); + mu_Rect box = mu_rect(r.x, r.y, r.h, r.h); + mu_update_control(ctx, id, r, 0); + /* handle click */ + if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { + res |= MU_RES_CHANGE; + *state = !*state; + } + /* draw */ + mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0); + if (*state) { + mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]); + } + r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h); + mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); + return res; +} + + +int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, + int opt) +{ + int res = 0; + mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS); + + if (ctx->focus == id) { + /* handle text input */ + int len = strlen(buf); + int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text)); + if (n > 0) { + memcpy(buf + len, ctx->input_text, n); + len += n; + buf[len] = '\0'; + res |= MU_RES_CHANGE; + } + /* handle backspace */ + if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) { + /* skip utf-8 continuation bytes */ + while ((buf[--len] & 0xc0) == 0x80 && len > 0); + buf[len] = '\0'; + res |= MU_RES_CHANGE; + } + /* handle return */ + if (ctx->key_pressed & MU_KEY_RETURN) { + mu_set_focus(ctx, 0); + res |= MU_RES_SUBMIT; + } + } + + /* draw */ + mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt); + if (ctx->focus == id) { + mu_Color color = ctx->style->colors[MU_COLOR_TEXT]; + mu_Font font = ctx->style->font; + int textw = ctx->text_width(font, buf, -1); + int texth = ctx->text_height(font); + int ofx = r.w - ctx->style->padding - textw - 1; + int textx = r.x + mu_min(ofx, ctx->style->padding); + int texty = r.y + (r.h - texth) / 2; + mu_push_clip_rect(ctx, r); + mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color); + mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color); + mu_pop_clip_rect(ctx); + } else { + mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt); + } + + return res; +} + + +static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id) { + if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->key_down & MU_KEY_SHIFT && + ctx->hover == id + ) { + ctx->number_edit = id; + sprintf(ctx->number_edit_buf, MU_REAL_FMT, *value); + } + if (ctx->number_edit == id) { + int res = mu_textbox_raw( + ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0); + if (res & MU_RES_SUBMIT || ctx->focus != id) { + *value = strtod(ctx->number_edit_buf, NULL); + ctx->number_edit = 0; + } else { + return 1; + } + } + return 0; +} + + +int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) { + mu_Id id = mu_get_id(ctx, &buf, sizeof(buf)); + mu_Rect r = mu_layout_next(ctx); + return mu_textbox_raw(ctx, buf, bufsz, id, r, opt); +} + + +int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, + mu_Real step, const char *fmt, int opt) +{ + char buf[MU_MAX_FMT + 1]; + mu_Rect thumb; + int x, w, res = 0; + mu_Real last = *value, v = last; + mu_Id id = mu_get_id(ctx, &value, sizeof(value)); + mu_Rect base = mu_layout_next(ctx); + + /* handle text input mode */ + if (number_textbox(ctx, &v, base, id)) { return res; } + + /* handle normal mode */ + mu_update_control(ctx, id, base, opt); + + /* handle input */ + if (ctx->focus == id && + (ctx->mouse_down | ctx->mouse_pressed) == MU_MOUSE_LEFT) + { + printf("C Slider dragging - Focus: %d, MouseDown: %d, MousePressed: %d\n", ctx->focus, ctx->mouse_down, ctx->mouse_pressed); + v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w; + if (step) { v = ((long long)((v + step / 2) / step)) * step; } + } + /* clamp and store value, update res */ + *value = v = mu_clamp(v, low, high); + if (last != v) { res |= MU_RES_CHANGE; } + + /* draw base */ + mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt); + /* draw thumb */ + w = ctx->style->thumb_size; + x = (v - low) * (base.w - w) / (high - low); + thumb = mu_rect(base.x + x, base.y, w, base.h); + mu_draw_control_frame(ctx, id, thumb, MU_COLOR_BUTTON, opt); + /* draw text */ + sprintf(buf, fmt, v); + mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt); + + return res; +} + + +int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, + const char *fmt, int opt) +{ + char buf[MU_MAX_FMT + 1]; + int res = 0; + mu_Id id = mu_get_id(ctx, &value, sizeof(value)); + mu_Rect base = mu_layout_next(ctx); + mu_Real last = *value; + + /* handle text input mode */ + if (number_textbox(ctx, value, base, id)) { return res; } + + /* handle normal mode */ + mu_update_control(ctx, id, base, opt); + + /* handle input */ + if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { + *value += ctx->mouse_delta.x * step; + } + /* set flag if value changed */ + if (*value != last) { res |= MU_RES_CHANGE; } + + /* draw base */ + mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt); + /* draw text */ + sprintf(buf, fmt, *value); + mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt); + + return res; +} + + +static int header(mu_Context *ctx, const char *label, int istreenode, int opt) { + mu_Rect r; + int active, expanded; + mu_Id id = mu_get_id(ctx, label, strlen(label)); + int idx = mu_pool_get(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id); + int width = -1; + mu_layout_row(ctx, 1, &width, 0); + + active = (idx >= 0); + expanded = (opt & MU_OPT_EXPANDED) ? !active : active; + r = mu_layout_next(ctx); + mu_update_control(ctx, id, r, 0); + + /* handle click */ + active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id); + + /* update pool ref */ + if (idx >= 0) { + if (active) { mu_pool_update(ctx, ctx->treenode_pool, idx); } + else { memset(&ctx->treenode_pool[idx], 0, sizeof(mu_PoolItem)); } + } else if (active) { + mu_pool_init(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id); + } + + /* draw */ + if (istreenode) { + if (ctx->hover == id) { ctx->draw_frame(ctx, r, MU_COLOR_BUTTONHOVER); } + } else { + mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, 0); + } + mu_draw_icon( + ctx, expanded ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED, + mu_rect(r.x, r.y, r.h, r.h), ctx->style->colors[MU_COLOR_TEXT]); + r.x += r.h - ctx->style->padding; + r.w -= r.h - ctx->style->padding; + mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); + + return expanded ? MU_RES_ACTIVE : 0; +} + + +int mu_header_ex(mu_Context *ctx, const char *label, int opt) { + return header(ctx, label, 0, opt); +} + + +int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt) { + int res = header(ctx, label, 1, opt); + if (res & MU_RES_ACTIVE) { + get_layout(ctx)->indent += ctx->style->indent; + push(ctx->id_stack, ctx->last_id); + } + return res; +} + + +void mu_end_treenode(mu_Context *ctx) { + get_layout(ctx)->indent -= ctx->style->indent; + mu_pop_id(ctx); +} + + +#define scrollbar(ctx, cnt, b, cs, x, y, w, h) \ + do { \ + /* only add scrollbar if content size is larger than body */ \ + int maxscroll = cs.y - b->h; \ + \ + if (maxscroll > 0 && b->h > 0) { \ + mu_Rect base, thumb; \ + mu_Id id = mu_get_id(ctx, "!scrollbar" #y, 11); \ + \ + /* get sizing / positioning */ \ + base = *b; \ + base.x = b->x + b->w; \ + base.w = ctx->style->scrollbar_size; \ + \ + /* handle input */ \ + mu_update_control(ctx, id, base, 0); \ + if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { \ + cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h; \ + } \ + /* clamp scroll to limits */ \ + cnt->scroll.y = mu_clamp(cnt->scroll.y, 0, maxscroll); \ + \ + /* draw base and thumb */ \ + ctx->draw_frame(ctx, base, MU_COLOR_SCROLLBASE); \ + thumb = base; \ + thumb.h = mu_max(ctx->style->thumb_size, base.h * b->h / cs.y); \ + thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll; \ + ctx->draw_frame(ctx, thumb, MU_COLOR_SCROLLTHUMB); \ + \ + /* set this as the scroll_target (will get scrolled on mousewheel) */ \ + /* if the mouse is over it */ \ + if (mu_mouse_over(ctx, *b)) { ctx->scroll_target = cnt; } \ + } else { \ + cnt->scroll.y = 0; \ + } \ + } while (0) + + +static void scrollbars(mu_Context *ctx, mu_Container *cnt, mu_Rect *body) { + int sz = ctx->style->scrollbar_size; + mu_Vec2 cs = cnt->content_size; + cs.x += ctx->style->padding * 2; + cs.y += ctx->style->padding * 2; + mu_push_clip_rect(ctx, *body); + /* resize body to make room for scrollbars */ + if (cs.y > cnt->body.h) { body->w -= sz; } + if (cs.x > cnt->body.w) { body->h -= sz; } + /* to create a horizontal or vertical scrollbar almost-identical code is + ** used; only the references to `x|y` `w|h` need to be switched */ + scrollbar(ctx, cnt, body, cs, x, y, w, h); + scrollbar(ctx, cnt, body, cs, y, x, h, w); + mu_pop_clip_rect(ctx); +} + + +static void push_container_body( + mu_Context *ctx, mu_Container *cnt, mu_Rect body, int opt +) { + if (~opt & MU_OPT_NOSCROLL) { scrollbars(ctx, cnt, &body); } + push_layout(ctx, expand_rect(body, -ctx->style->padding), cnt->scroll); + cnt->body = body; +} + + +static void begin_root_container(mu_Context *ctx, mu_Container *cnt) { + push(ctx->container_stack, cnt); + /* push container to roots list and push head command */ + push(ctx->root_list, cnt); + cnt->head = push_jump(ctx, NULL); + /* set as hover root if the mouse is overlapping this container and it has a + ** higher zindex than the current hover root */ + if (rect_overlaps_vec2(cnt->rect, ctx->mouse_pos) && + (!ctx->next_hover_root || cnt->zindex > ctx->next_hover_root->zindex) + ) { + ctx->next_hover_root = cnt; + } + /* clipping is reset here in case a root-container is made within + ** another root-containers's begin/end block; this prevents the inner + ** root-container being clipped to the outer */ + push(ctx->clip_stack, unclipped_rect); +} + + +static void end_root_container(mu_Context *ctx) { + /* push tail 'goto' jump command and set head 'skip' command. the final steps + ** on initing these are done in mu_end() */ + mu_Container *cnt = mu_get_current_container(ctx); + cnt->tail = push_jump(ctx, NULL); + cnt->head->jump.dst = ctx->command_list.items + ctx->command_list.idx; + /* pop base clip rect and container */ + mu_pop_clip_rect(ctx); + pop_container(ctx); +} + + +int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt) { + mu_Rect body; + mu_Id id = mu_get_id(ctx, title, strlen(title)); + mu_Container *cnt = get_container(ctx, id, opt); + if (!cnt || !cnt->open) { return 0; } + push(ctx->id_stack, id); + + if (cnt->rect.w == 0) { cnt->rect = rect; } + begin_root_container(ctx, cnt); + rect = body = cnt->rect; + + /* draw frame */ + if (~opt & MU_OPT_NOFRAME) { + ctx->draw_frame(ctx, rect, MU_COLOR_WINDOWBG); + } + + /* do title bar */ + if (~opt & MU_OPT_NOTITLE) { + mu_Rect tr = rect; + tr.h = ctx->style->title_height; + ctx->draw_frame(ctx, tr, MU_COLOR_TITLEBG); + + /* do title text */ + if (~opt & MU_OPT_NOTITLE) { + mu_Id id = mu_get_id(ctx, "!title", 6); + mu_update_control(ctx, id, tr, opt); + mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt); + if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { + cnt->rect.x += ctx->mouse_delta.x; + cnt->rect.y += ctx->mouse_delta.y; + } + body.y += tr.h; + body.h -= tr.h; + } + + /* do `close` button */ + if (~opt & MU_OPT_NOCLOSE) { + mu_Id id = mu_get_id(ctx, "!close", 6); + mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h); + tr.w -= r.w; + mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]); + mu_update_control(ctx, id, r, opt); + if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) { + cnt->open = 0; + } + } + } + + push_container_body(ctx, cnt, body, opt); + + /* do `resize` handle */ + if (~opt & MU_OPT_NORESIZE) { + int sz = ctx->style->title_height; + mu_Id id = mu_get_id(ctx, "!resize", 7); + mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz); + mu_update_control(ctx, id, r, opt); + if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { + cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x); + cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y); + } + } + + /* resize to content size */ + if (opt & MU_OPT_AUTOSIZE) { + mu_Rect r = get_layout(ctx)->body; + cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w); + cnt->rect.h = cnt->content_size.y + (cnt->rect.h - r.h); + } + + /* close if this is a popup window and elsewhere was clicked */ + if (opt & MU_OPT_POPUP && ctx->mouse_pressed && ctx->hover_root != cnt) { + cnt->open = 0; + } + + mu_push_clip_rect(ctx, cnt->body); + return MU_RES_ACTIVE; +} + + +void mu_end_window(mu_Context *ctx) { + mu_pop_clip_rect(ctx); + end_root_container(ctx); +} + + +void mu_open_popup(mu_Context *ctx, const char *name) { + mu_Container *cnt = mu_get_container(ctx, name); + /* set as hover root so popup isn't closed in begin_window_ex() */ + ctx->hover_root = ctx->next_hover_root = cnt; + /* position at mouse cursor, open and bring-to-front */ + cnt->rect = mu_rect(ctx->mouse_pos.x, ctx->mouse_pos.y, 1, 1); + cnt->open = 1; + mu_bring_to_front(ctx, cnt); +} + + +int mu_begin_popup(mu_Context *ctx, const char *name) { + int opt = MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE | + MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED; + return mu_begin_window_ex(ctx, name, mu_rect(0, 0, 0, 0), opt); +} + + +void mu_end_popup(mu_Context *ctx) { + mu_end_window(ctx); +} + + +void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt) { + mu_Container *cnt; + mu_push_id(ctx, name, strlen(name)); + cnt = get_container(ctx, ctx->last_id, opt); + cnt->rect = mu_layout_next(ctx); + if (~opt & MU_OPT_NOFRAME) { + ctx->draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG); + } + push(ctx->container_stack, cnt); + push_container_body(ctx, cnt, cnt->rect, opt); + mu_push_clip_rect(ctx, cnt->body); +} + + +void mu_end_panel(mu_Context *ctx) { + mu_pop_clip_rect(ctx); + pop_container(ctx); +} diff --git a/MicroUICVersion/microui.h b/MicroUICVersion/microui.h new file mode 100644 index 0000000..6e18639 --- /dev/null +++ b/MicroUICVersion/microui.h @@ -0,0 +1,296 @@ +/* +** Copyright (c) 2024 rxi +** +** This library is free software; you can redistribute it and/or modify it +** under the terms of the MIT license. See `microui.c` for details. +*/ + +#ifndef MICROUI_H +#define MICROUI_H + +#define MU_VERSION "2.02" + +#define MU_COMMANDLIST_SIZE (256 * 1024) +#define MU_ROOTLIST_SIZE 32 +#define MU_CONTAINERSTACK_SIZE 32 +#define MU_CLIPSTACK_SIZE 32 +#define MU_IDSTACK_SIZE 32 +#define MU_LAYOUTSTACK_SIZE 16 +#define MU_CONTAINERPOOL_SIZE 48 +#define MU_TREENODEPOOL_SIZE 48 +#define MU_MAX_WIDTHS 16 +#define MU_REAL float +#define MU_REAL_FMT "%.3g" +#define MU_SLIDER_FMT "%.2f" +#define MU_MAX_FMT 127 + +#define mu_stack(T, n) struct { int idx; T items[n]; } +#define mu_min(a, b) ((a) < (b) ? (a) : (b)) +#define mu_max(a, b) ((a) > (b) ? (a) : (b)) +#define mu_clamp(x, a, b) mu_min(b, mu_max(a, x)) + +enum { + MU_CLIP_PART = 1, + MU_CLIP_ALL +}; + +enum { + MU_COMMAND_JUMP = 1, + MU_COMMAND_CLIP, + MU_COMMAND_RECT, + MU_COMMAND_TEXT, + MU_COMMAND_ICON, + MU_COMMAND_MAX +}; + +enum { + MU_COLOR_TEXT, + MU_COLOR_BORDER, + MU_COLOR_WINDOWBG, + MU_COLOR_TITLEBG, + MU_COLOR_TITLETEXT, + MU_COLOR_PANELBG, + MU_COLOR_BUTTON, + MU_COLOR_BUTTONHOVER, + MU_COLOR_BUTTONFOCUS, + MU_COLOR_BASE, + MU_COLOR_BASEHOVER, + MU_COLOR_BASEFOCUS, + MU_COLOR_SCROLLBASE, + MU_COLOR_SCROLLTHUMB, + MU_COLOR_MAX +}; + +enum { + MU_ICON_CLOSE = 1, + MU_ICON_CHECK, + MU_ICON_COLLAPSED, + MU_ICON_EXPANDED, + MU_ICON_MAX +}; + +enum { + MU_RES_ACTIVE = (1 << 0), + MU_RES_SUBMIT = (1 << 1), + MU_RES_CHANGE = (1 << 2) +}; + +enum { + MU_OPT_ALIGNCENTER = (1 << 0), + MU_OPT_ALIGNRIGHT = (1 << 1), + MU_OPT_NOINTERACT = (1 << 2), + MU_OPT_NOFRAME = (1 << 3), + MU_OPT_NORESIZE = (1 << 4), + MU_OPT_NOSCROLL = (1 << 5), + MU_OPT_NOCLOSE = (1 << 6), + MU_OPT_NOTITLE = (1 << 7), + MU_OPT_HOLDFOCUS = (1 << 8), + MU_OPT_AUTOSIZE = (1 << 9), + MU_OPT_POPUP = (1 << 10), + MU_OPT_CLOSED = (1 << 11), + MU_OPT_EXPANDED = (1 << 12) +}; + +enum { + MU_MOUSE_LEFT = (1 << 0), + MU_MOUSE_RIGHT = (1 << 1), + MU_MOUSE_MIDDLE = (1 << 2) +}; + +enum { + MU_KEY_SHIFT = (1 << 0), + MU_KEY_CTRL = (1 << 1), + MU_KEY_ALT = (1 << 2), + MU_KEY_BACKSPACE = (1 << 3), + MU_KEY_RETURN = (1 << 4) +}; + + +typedef struct mu_Context mu_Context; +typedef unsigned mu_Id; +typedef MU_REAL mu_Real; +typedef void* mu_Font; + +typedef struct { int x, y; } mu_Vec2; +typedef struct { int x, y, w, h; } mu_Rect; +typedef struct { unsigned char r, g, b, a; } mu_Color; +typedef struct { mu_Id id; int last_update; } mu_PoolItem; + +typedef struct { int type, size; } mu_BaseCommand; +typedef struct { mu_BaseCommand base; void *dst; } mu_JumpCommand; +typedef struct { mu_BaseCommand base; mu_Rect rect; } mu_ClipCommand; +typedef struct { mu_BaseCommand base; mu_Rect rect; mu_Color color; } mu_RectCommand; +typedef struct { mu_BaseCommand base; mu_Font font; mu_Vec2 pos; mu_Color color; char str[1]; } mu_TextCommand; +typedef struct { mu_BaseCommand base; mu_Rect rect; int id; mu_Color color; } mu_IconCommand; + +typedef union { + int type; + mu_BaseCommand base; + mu_JumpCommand jump; + mu_ClipCommand clip; + mu_RectCommand rect; + mu_TextCommand text; + mu_IconCommand icon; +} mu_Command; + +typedef struct { + mu_Rect body; + mu_Rect next; + mu_Vec2 position; + mu_Vec2 size; + mu_Vec2 max; + int widths[MU_MAX_WIDTHS]; + int items; + int item_index; + int next_row; + int next_type; + int indent; +} mu_Layout; + +typedef struct { + mu_Command *head, *tail; + mu_Rect rect; + mu_Rect body; + mu_Vec2 content_size; + mu_Vec2 scroll; + int zindex; + int open; +} mu_Container; + +typedef struct { + mu_Font font; + mu_Vec2 size; + int padding; + int spacing; + int indent; + int title_height; + int scrollbar_size; + int thumb_size; + mu_Color colors[MU_COLOR_MAX]; +} mu_Style; + +struct mu_Context { + /* callbacks */ + int (*text_width)(mu_Font font, const char *str, int len); + int (*text_height)(mu_Font font); + void (*draw_frame)(mu_Context *ctx, mu_Rect rect, int colorid); + /* core state */ + mu_Style _style; + mu_Style *style; + mu_Id hover; + mu_Id focus; + mu_Id last_id; + mu_Rect last_rect; + int last_zindex; + int updated_focus; + int frame; + mu_Container *hover_root; + mu_Container *next_hover_root; + mu_Container *scroll_target; + char number_edit_buf[MU_MAX_FMT]; + mu_Id number_edit; + /* stacks */ + mu_stack(char, MU_COMMANDLIST_SIZE) command_list; + mu_stack(mu_Container*, MU_ROOTLIST_SIZE) root_list; + mu_stack(mu_Container*, MU_CONTAINERSTACK_SIZE) container_stack; + mu_stack(mu_Rect, MU_CLIPSTACK_SIZE) clip_stack; + mu_stack(mu_Id, MU_IDSTACK_SIZE) id_stack; + mu_stack(mu_Layout, MU_LAYOUTSTACK_SIZE) layout_stack; + /* retained state pools */ + mu_PoolItem container_pool[MU_CONTAINERPOOL_SIZE]; + mu_Container containers[MU_CONTAINERPOOL_SIZE]; + mu_PoolItem treenode_pool[MU_TREENODEPOOL_SIZE]; + /* input state */ + mu_Vec2 mouse_pos; + mu_Vec2 last_mouse_pos; + mu_Vec2 mouse_delta; + mu_Vec2 scroll_delta; + int mouse_down; + int mouse_pressed; + int key_down; + int key_pressed; + char input_text[32]; +}; + + +mu_Vec2 mu_vec2(int x, int y); +mu_Rect mu_rect(int x, int y, int w, int h); +mu_Color mu_color(int r, int g, int b, int a); + +void mu_init(mu_Context *ctx); +void mu_begin(mu_Context *ctx); +void mu_end(mu_Context *ctx); +void mu_set_focus(mu_Context *ctx, mu_Id id); +mu_Id mu_get_id(mu_Context *ctx, const void *data, int size); +void mu_push_id(mu_Context *ctx, const void *data, int size); +void mu_pop_id(mu_Context *ctx); +void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect); +void mu_pop_clip_rect(mu_Context *ctx); +mu_Rect mu_get_clip_rect(mu_Context *ctx); +int mu_check_clip(mu_Context *ctx, mu_Rect r); +mu_Container* mu_get_current_container(mu_Context *ctx); +mu_Container* mu_get_container(mu_Context *ctx, const char *name); +void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt); + +int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id); +int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id); +void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx); + +void mu_input_mousemove(mu_Context *ctx, int x, int y); +void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn); +void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn); +void mu_input_scroll(mu_Context *ctx, int x, int y); +void mu_input_keydown(mu_Context *ctx, int key); +void mu_input_keyup(mu_Context *ctx, int key); +void mu_input_text(mu_Context *ctx, const char *text); + +mu_Command* mu_push_command(mu_Context *ctx, int type, int size); +int mu_next_command(mu_Context *ctx, mu_Command **cmd); +void mu_set_clip(mu_Context *ctx, mu_Rect rect); +void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color); +void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color); +void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len, mu_Vec2 pos, mu_Color color); +void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color); + +void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height); +void mu_layout_width(mu_Context *ctx, int width); +void mu_layout_height(mu_Context *ctx, int height); +void mu_layout_begin_column(mu_Context *ctx); +void mu_layout_end_column(mu_Context *ctx); +void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative); +mu_Rect mu_layout_next(mu_Context *ctx); + +void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect, int colorid, int opt); +void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, int colorid, int opt); +int mu_mouse_over(mu_Context *ctx, mu_Rect rect); +void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt); + +#define mu_button(ctx, label) mu_button_ex(ctx, label, 0, MU_OPT_ALIGNCENTER) +#define mu_textbox(ctx, buf, bufsz) mu_textbox_ex(ctx, buf, bufsz, 0) +#define mu_slider(ctx, value, lo, hi) mu_slider_ex(ctx, value, lo, hi, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER) +#define mu_number(ctx, value, step) mu_number_ex(ctx, value, step, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER) +#define mu_header(ctx, label) mu_header_ex(ctx, label, 0) +#define mu_begin_treenode(ctx, label) mu_begin_treenode_ex(ctx, label, 0) +#define mu_begin_window(ctx, title, rect) mu_begin_window_ex(ctx, title, rect, 0) +#define mu_begin_panel(ctx, name) mu_begin_panel_ex(ctx, name, 0) + +void mu_text(mu_Context *ctx, const char *text); +void mu_label(mu_Context *ctx, const char *text); +int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt); +int mu_checkbox(mu_Context *ctx, const char *label, int *state); +int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, int opt); +int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt); +int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, mu_Real step, const char *fmt, int opt); +int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, const char *fmt, int opt); +int mu_header_ex(mu_Context *ctx, const char *label, int opt); +int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt); +void mu_end_treenode(mu_Context *ctx); +int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt); +void mu_end_window(mu_Context *ctx); +void mu_open_popup(mu_Context *ctx, const char *name); +int mu_begin_popup(mu_Context *ctx, const char *name); +void mu_end_popup(mu_Context *ctx); +void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt); +void mu_end_panel(mu_Context *ctx); + +#endif