├── .clangd ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── examples ├── font.c ├── input.c ├── render.c └── simple.c └── src ├── murl.c └── murl.h /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: 3 | - "-I./vendor" 4 | - "-I./../vendor" 5 | - "-I./src" 6 | - "-I./../src" 7 | - "-I/opt/homebrew/opt/raylib/include/" 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: marionauta 2 | custom: ['https://mario.nachbaur.dev/tips/?app=microui-raylib'] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fonts/ 2 | vendor/ 3 | examples/* 4 | !examples/*.c 5 | *.o 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mario Nachbaur 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -std=c99 -Wall -Wextra 2 | COMMON_SOURCES = src/murl.c vendor/microui.c 3 | EXAMPLES = $(wildcard examples/*.c) 4 | 5 | .PHONY: all 6 | all: $(basename $(EXAMPLES)) 7 | 8 | examples/%: examples/%.c $(COMMON_SOURCES) 9 | cc $(CFLAGS) -o $@ $^ -I./vendor -I./src `pkg-config --cflags --libs raylib` -lm 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microui-raylib 2 | 3 | [raylib][raylib] renderer for [microui][microui] in C99 4 | 5 | ## features 6 | 7 | - Tiny: under 200 sloc of C99. 8 | - Handle mouse & keyboard inputs. 9 | - Handle custom fonts. 10 | - Draw controls on screen. 11 | - All symbols are namespaced (prefixed) with either `MURL_` or `murl_`. 12 | 13 | ## examples 14 | 15 | There are some code examples in the [`examples`](examples) folder. 16 | 17 | > [!NOTE] 18 | > To compile the examples with the given `Makefile`, make sure you have the 19 | > `microui.c` and `microui.h` files in the `vendor` folder. 20 | > Then simply run `make`. 21 | 22 | All available functions are listed in [`src/murl.h`](src/murl.h). 23 | 24 | ## contributing 25 | 26 | New features are not guaranteed to be merged, since I want to keep the 27 | codebase small. Issues, bug reports and bug fixes are welcome. 28 | 29 | All contributions will be licensed under [`LICENSE`](LICENSE). 30 | 31 | [microui]: https://github.com/rxi/microui 32 | [raylib]: https://www.raylib.com 33 | -------------------------------------------------------------------------------- /examples/font.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "microui.h" 5 | #include "murl.h" 6 | 7 | int main(void) { 8 | InitWindow(800, 600, ""); 9 | SetTargetFPS(60); 10 | 11 | mu_Context *ctx = malloc(sizeof(mu_Context)); 12 | mu_init(ctx); 13 | Font font = LoadFontEx("./fonts/Roboto-Regular.ttf", 20, NULL, 0); 14 | murl_setup_font_ex(ctx, &font); 15 | 16 | char textbox_buffer[512] = {0}; 17 | 18 | while (!WindowShouldClose()) { 19 | murl_handle_input(ctx); 20 | mu_begin(ctx); 21 | if (mu_begin_window(ctx, "font", mu_rect(10, 10, 400, 300))) { 22 | mu_layout_row(ctx, 1, (int[]){-1}, 0); 23 | mu_label(ctx, "Hello, World"); 24 | mu_textbox(ctx, textbox_buffer, 512); 25 | mu_end_window(ctx); 26 | } 27 | mu_end(ctx); 28 | 29 | BeginDrawing(); 30 | ClearBackground(BLACK); 31 | murl_render(ctx); 32 | EndDrawing(); 33 | } 34 | 35 | free(ctx); 36 | CloseWindow(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /examples/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "microui.h" 5 | #include "murl.h" 6 | 7 | int main(void) { 8 | InitWindow(800, 600, "Custom Input"); 9 | SetTargetFPS(60); 10 | 11 | mu_Context *ctx = malloc(sizeof(mu_Context)); 12 | mu_init(ctx); 13 | murl_setup_font(ctx); 14 | 15 | while (!WindowShouldClose()) { 16 | murl_handle_mouse_move(ctx); 17 | murl_handle_mouse_scroll(ctx); 18 | murl_handle_mouse_buttons_input(ctx); 19 | murl_handle_keyboard_input(ctx); 20 | murl_handle_text_input(ctx); 21 | 22 | mu_begin(ctx); 23 | if (mu_begin_window(ctx, "Custom Input", mu_rect(0, 0, 800, 600))) { 24 | mu_label(ctx, "This is a label"); 25 | mu_end_window(ctx); 26 | } 27 | mu_end(ctx); 28 | 29 | BeginDrawing(); 30 | ClearBackground(BLACK); 31 | murl_render(ctx); 32 | EndDrawing(); 33 | } 34 | 35 | free(ctx); 36 | CloseWindow(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /examples/render.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "microui.h" 5 | #include "murl.h" 6 | 7 | #define WIDTH 600 8 | #define HEIGHT 800 9 | 10 | int main(void) { 11 | InitWindow(WIDTH, HEIGHT, "Render Textures"); 12 | SetTargetFPS(60); 13 | 14 | RenderTexture texture = LoadRenderTexture(WIDTH, HEIGHT / 2); 15 | 16 | mu_Context *ctx = malloc(sizeof(mu_Context)); 17 | mu_init(ctx); 18 | murl_setup_font(ctx); 19 | 20 | while (!WindowShouldClose()) { 21 | murl_handle_input(ctx); 22 | 23 | mu_begin(ctx); 24 | if (mu_begin_window(ctx, "Render Texture", mu_rect(150, 50, 300, 300))) { 25 | mu_layout_row(ctx, 1, (int[]){-1}, 0); 26 | mu_label(ctx, "Only the top window responds to events."); 27 | mu_label(ctx, "The bottom one is a mirror."); 28 | mu_end_window(ctx); 29 | } 30 | mu_end(ctx); 31 | 32 | BeginTextureMode(texture); 33 | ClearBackground(BLACK); 34 | murl_render(ctx); 35 | EndTextureMode(); 36 | 37 | BeginDrawing(); 38 | for (size_t index = 0; index < 2; index++) { 39 | Vector2 position = {.x = 0, .y = index * texture.texture.height}; 40 | Rectangle rectangle = { 41 | .x = 0, 42 | .y = 0, 43 | .width = texture.texture.width, 44 | .height = -1 * texture.texture.height, 45 | }; 46 | DrawTextureRec(texture.texture, rectangle, position, WHITE); 47 | } 48 | EndDrawing(); 49 | } 50 | 51 | free(ctx); 52 | CloseWindow(); 53 | return 0; 54 | } -------------------------------------------------------------------------------- /examples/simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "microui.h" 6 | #include "murl.h" 7 | 8 | int main(void) { 9 | InitWindow(800, 600, "microui + raylib"); 10 | SetTargetFPS(60); 11 | 12 | mu_Context *ctx = malloc(sizeof(mu_Context)); 13 | mu_init(ctx); 14 | murl_setup_font(ctx); 15 | 16 | while (!WindowShouldClose()) { 17 | murl_handle_input(ctx); 18 | 19 | mu_begin(ctx); 20 | 21 | if (mu_begin_window(ctx, "Hello", mu_rect(20, 20, 200, 150))) { 22 | mu_label(ctx, "Hello, raylib"); 23 | 24 | if (mu_button(ctx, "The button")) { 25 | mu_open_popup(ctx, "popup"); 26 | } 27 | 28 | if (mu_begin_popup(ctx, "popup")) { 29 | mu_label(ctx, "This is a popup"); 30 | mu_end_popup(ctx); 31 | } 32 | 33 | mu_end_window(ctx); 34 | } 35 | 36 | mu_end(ctx); 37 | 38 | BeginDrawing(); 39 | ClearBackground(BLACK); 40 | murl_render(ctx); 41 | EndDrawing(); 42 | } 43 | 44 | free(ctx); 45 | CloseWindow(); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /src/murl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "microui.h" 6 | #include "murl.h" 7 | 8 | #define array_count(array) (sizeof(array)/sizeof(array[0])) 9 | #define END_CLIP_SIZE (0x1000000) 10 | 11 | void murl_setup_font_ex(mu_Context *ctx, const Font *font) { 12 | ctx->style->font = (mu_Font)font; 13 | ctx->text_width = murl_text_width; 14 | ctx->text_height = murl_text_height; 15 | ctx->style->spacing = MURL_TEXT_SPACING; 16 | } 17 | 18 | int murl_text_width(mu_Font font, const char *str, int len) { 19 | (void)len; 20 | Font rlfont = MURL_FONT_FROM_MU(font); 21 | Vector2 size = MeasureTextEx(rlfont, str, rlfont.baseSize, MURL_TEXT_SPACING); 22 | return size.x; 23 | } 24 | 25 | int murl_text_height(mu_Font font) { 26 | Font rlfont = MURL_FONT_FROM_MU(font); 27 | return rlfont.baseSize; 28 | } 29 | 30 | void murl_handle_mouse_scroll(mu_Context *ctx) { 31 | const Vector2 mouse_wheel_scroll = GetMouseWheelMoveV(); 32 | mu_input_scroll(ctx, (int)mouse_wheel_scroll.x * -30, 33 | (int)mouse_wheel_scroll.y * -30); 34 | } 35 | 36 | struct murl__MouseButtonMap { 37 | MouseButton rl; 38 | int mu; 39 | }; 40 | 41 | static struct murl__MouseButtonMap murl__mouse_buttons[] = { 42 | {MOUSE_BUTTON_LEFT, MU_MOUSE_LEFT}, 43 | {MOUSE_BUTTON_RIGHT, MU_MOUSE_RIGHT}, 44 | {MOUSE_BUTTON_MIDDLE, MU_MOUSE_MIDDLE}, 45 | }; 46 | 47 | void murl_handle_mouse_buttons_input_ex(mu_Context *ctx, int x, int y) { 48 | size_t buttons_count = array_count(murl__mouse_buttons); 49 | for (size_t index = 0; index < buttons_count; index++) { 50 | struct murl__MouseButtonMap button = murl__mouse_buttons[index]; 51 | if (IsMouseButtonPressed(button.rl)) { 52 | mu_input_mousedown(ctx, x, y, button.mu); 53 | } else if (IsMouseButtonReleased(button.rl)) { 54 | mu_input_mouseup(ctx, x, y, button.mu); 55 | } 56 | } 57 | } 58 | 59 | struct murl__KeyboardKeyMap { 60 | KeyboardKey rl; 61 | int mu; 62 | }; 63 | 64 | static struct murl__KeyboardKeyMap murl__keyboard_keys[] = { 65 | {KEY_LEFT_SHIFT, MU_KEY_SHIFT}, {KEY_RIGHT_SHIFT, MU_KEY_SHIFT}, 66 | {KEY_LEFT_CONTROL, MU_KEY_CTRL}, {KEY_RIGHT_CONTROL, MU_KEY_CTRL}, 67 | {KEY_LEFT_ALT, MU_KEY_ALT}, {KEY_RIGHT_ALT, MU_KEY_ALT}, 68 | {KEY_ENTER, MU_KEY_RETURN}, {KEY_KP_ENTER, MU_KEY_RETURN}, 69 | {KEY_BACKSPACE, MU_KEY_BACKSPACE}, 70 | }; 71 | 72 | void murl_handle_keyboard_input(mu_Context *ctx) { 73 | size_t keys_count = array_count(murl__keyboard_keys); 74 | for (size_t index = 0; index < keys_count; index++) { 75 | struct murl__KeyboardKeyMap key = murl__keyboard_keys[index]; 76 | if (IsKeyPressed(key.rl) || IsKeyPressedRepeat(key.rl)) { 77 | mu_input_keydown(ctx, key.mu); 78 | } else if (IsKeyReleased(key.rl)) { 79 | mu_input_keyup(ctx, key.mu); 80 | } 81 | } 82 | } 83 | 84 | void murl_handle_text_input(mu_Context *ctx) { 85 | char buffer[512]; 86 | for (size_t index = 0; index < 512; index++) { 87 | char c = GetCharPressed(); 88 | buffer[index] = c; 89 | if (c == '\0') { 90 | break; 91 | } 92 | } 93 | mu_input_text(ctx, buffer); 94 | } 95 | 96 | void murl_handle_input(mu_Context *ctx) { 97 | const int mouse_position_x = GetMouseX(); 98 | const int mouse_position_y = GetMouseY(); 99 | mu_input_mousemove(ctx, mouse_position_x, mouse_position_y); 100 | murl_handle_mouse_scroll(ctx); 101 | murl_handle_mouse_buttons_input_ex(ctx, mouse_position_x, mouse_position_y); 102 | murl_handle_keyboard_input(ctx); 103 | murl_handle_text_input(ctx); 104 | } 105 | 106 | void murl_render(mu_Context *ctx) { 107 | mu_Command *cmd = NULL; 108 | while (mu_next_command(ctx, &cmd)) { 109 | switch (cmd->type) { 110 | case MU_COMMAND_TEXT: { 111 | Font font = MURL_FONT_FROM_MU(cmd->text.font); 112 | Vector2 text_position = MURL_VECTOR2_FROM_MU(cmd->text.pos); 113 | int font_size = ctx->text_height(&font); 114 | Color text_color = MURL_COLOR_FROM_MU(cmd->text.color); 115 | DrawTextEx(font, cmd->text.str, text_position, font_size, 116 | ctx->style->spacing, text_color); 117 | } break; 118 | 119 | case MU_COMMAND_RECT: { 120 | Rectangle rect = MURL_RECTANGLE_FROM_MU(cmd->rect.rect); 121 | Color rect_color = MURL_COLOR_FROM_MU(cmd->rect.color); 122 | DrawRectangleRec(rect, rect_color); 123 | } break; 124 | 125 | case MU_COMMAND_ICON: { 126 | Color icon_color = MURL_COLOR_FROM_MU(cmd->icon.color); 127 | char *icon = "?"; 128 | switch (cmd->icon.id) { 129 | case MU_ICON_CLOSE: { 130 | icon = "x"; 131 | } break; 132 | case MU_ICON_CHECK: { 133 | icon = "*"; 134 | } break; 135 | case MU_ICON_COLLAPSED: { 136 | icon = "+"; 137 | } break; 138 | case MU_ICON_EXPANDED: { 139 | icon = "-"; 140 | } break; 141 | default: 142 | assert(0 && "unreachable"); 143 | } 144 | DrawText(icon, cmd->icon.rect.x, cmd->icon.rect.y, cmd->icon.rect.h, 145 | icon_color); 146 | } break; 147 | 148 | case MU_COMMAND_CLIP: { 149 | mu_Rect r = cmd->clip.rect; 150 | if (r.x == 0 && r.y == 0 && r.w == END_CLIP_SIZE && r.h == END_CLIP_SIZE) { 151 | EndScissorMode(); 152 | } else { 153 | BeginScissorMode(r.x, r.y, r.w, r.h); 154 | } 155 | } break; 156 | 157 | default: 158 | assert(0 && "unreachable"); 159 | break; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/murl.h: -------------------------------------------------------------------------------- 1 | #ifndef MURL_H 2 | #define MURL_H 3 | 4 | #include 5 | 6 | #include "microui.h" 7 | 8 | #define MURL_TEXT_SPACING 1 9 | 10 | // Create a raylib Color from a microui mu_Color. 11 | #define MURL_COLOR_FROM_MU(c) ((Color){c.r, c.g, c.b, c.a}) 12 | 13 | // Create a raylib Font from a microui mu_Font. 14 | // If `f` is `NULL` get the default raylib font. 15 | #define MURL_FONT_FROM_MU(f) ((f == NULL) ? GetFontDefault() : *(Font *)f) 16 | 17 | // Create a raylib Rectangle from a microui mu_Rect. 18 | #define MURL_RECTANGLE_FROM_MU(r) ((Rectangle){r.x, r.y, r.w, r.h}) 19 | 20 | // Create a raylib Vector2 from a microui mu_Vec2. 21 | #define MURL_VECTOR2_FROM_MU(v) ((Vector2){v.x, v.y}) 22 | 23 | // Set the text height/width callbacks and the font. 24 | void murl_setup_font_ex(mu_Context *ctx, const Font *font); 25 | #define murl_setup_font(ctx) murl_setup_font_ex(ctx, NULL) 26 | 27 | // `mu_Context.text_width` callback. See `murl_setup_font`. 28 | int murl_text_width(mu_Font font, const char *str, int len); 29 | 30 | // `mu_Context.text_height` callback. See `murl_setup_font`. 31 | int murl_text_height(mu_Font font); 32 | 33 | // Handle all keyboard & mouse events. 34 | void murl_handle_input(mu_Context *ctx); 35 | 36 | // Handle mouse cursor position update. 37 | #define murl_handle_mouse_move(ctx) \ 38 | mu_input_mousemove(ctx, GetMouseX(), GetMouseY()) 39 | 40 | // Handle mouse wheel scroll. 41 | void murl_handle_mouse_scroll(mu_Context *ctx); 42 | 43 | // Handle right, left & middle clicks. 44 | void murl_handle_mouse_buttons_input_ex(mu_Context *ctx, int x, int y); 45 | #define murl_handle_mouse_buttons_input(ctx) \ 46 | murl_handle_mouse_buttons_input_ex(ctx, GetMouseX(), GetMouseY()) 47 | 48 | // Handle shift, control, alt, enter & backspace presses. 49 | void murl_handle_keyboard_input(mu_Context *ctx); 50 | 51 | // Handle text input. 52 | void murl_handle_text_input(mu_Context *ctx); 53 | 54 | // Draw controls, text & icons using raylib. 55 | void murl_render(mu_Context *ctx); 56 | 57 | #endif // MURL_H 58 | --------------------------------------------------------------------------------