ref: 0ac294decc984c7c3b0272db4de45728dadc38c5
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Fri Jan 17 06:20:17 EST 2020
Squashed 'microui/' content from commit 664a525 git-subtree-dir: microui git-subtree-split: 664a52591895e867ebdac2a56588cf3595b326e8
--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,2 @@
+[a0125678vqki].out
+*.[o0125678vqki]
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,20 @@
+Copyright (c) 2018-2019 rxi
+Copyright (c) 2020 ftrvxmtrx
+
+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.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,30 @@
+# microui
+![screenshot](doc/microui_9front.png)
+
+A tiny, portable, immediate-mode UI library written in ANSI C.
+This is a fork of [microui](https://github.com/rxi/microui)
+targetting Plan 9 specifically.
+
+## Usage
+
+* See [`doc/usage.md`](doc/usage.md) for usage instructions
+* See the [`demo`](demo) directory for a usage example
+
+## Notes
+
+* The library expects the user to provide input and handle the resultant
+ drawing commands, it does not do any drawing itself
+* In contrast to other immediate mode ui libraries microui does not store
+ window, panel, header or treenode state internally. It is up to the
+ user to manage this data themselves
+
+## Contributing
+
+The library is designed to be lightweight, providing a foundation to which
+you can easily add custom controls and UI elements; pull requests adding
+additional features will likely not be merged. Bug reports are welcome.
+
+## License
+
+This library is free software; you can redistribute it and/or modify it
+under the terms of the MIT license. See [LICENSE](LICENSE) for details.
--- /dev/null
+++ b/demo/frame.c
@@ -1,0 +1,237 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <microui.h>
+
+static char logbuf[64000];
+static int logbuf_updated = 0;
+
+static void
+write_log(const char *text)
+{
+ if (logbuf[0])
+ strcat(logbuf, "\n");
+ strcat(logbuf, text);
+ logbuf_updated = 1;
+}
+
+#define text_width(s) (stringwidth(mu_style.font, s) + 6)
+#define text_height() mu_style.font->height
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+static void
+test_window(void)
+{
+ static mu_Container window;
+
+ /* init window manually so we can set its position and size */
+ if (!window.inited) {
+ mu_init_window(&window, 0);
+ window.rect = mu_rect(40, 40, 320, 500);
+ }
+
+ /* limit window to minimum size */
+ window.rect.w = max(window.rect.w, 240);
+ window.rect.h = max(window.rect.h, 300);
+
+ /* do window */
+ if (mu_begin_window(&window, "Demo Window")) {
+
+ /* window info */
+ static int show_info = 0;
+ if (mu_header(&show_info, "Window Info")) {
+ char buf[64];
+ const int widths[] = { text_width("Position:"), -1 };
+ mu_layout_row(2, widths, 0);
+ mu_label("Position:");
+ sprint(buf, "%d, %d", window.rect.x, window.rect.y); mu_label(buf);
+ mu_label("Size:");
+ sprint(buf, "%d, %d", window.rect.w, window.rect.h); mu_label(buf);
+ }
+
+ /* labels + buttons */
+ static int show_buttons = 1;
+ if (mu_header(&show_buttons, "Test Buttons")) {
+ const int widths[] = { text_width("Test buttons 2:"), -text_width("Button 2 "), -1 };
+ mu_layout_row(3, widths, 0);
+ mu_label("Test buttons 1:");
+ if (mu_button("Button 1")) { write_log("Pressed button 1"); }
+ if (mu_button("Button 2")) { write_log("Pressed button 2"); }
+ mu_label("Test buttons 2:");
+ if (mu_button("Button 3")) { write_log("Pressed button 3"); }
+ if (mu_button("Button 4")) { write_log("Pressed button 4"); }
+ }
+
+ /* tree */
+ static int show_tree = 1;
+ if (mu_header(&show_tree, "Tree and Text")) {
+ int widths[] = { text_width("Test 1a")+text_height()*2+text_width("Button 3")+6, -1 };
+ mu_layout_row(2, widths, 0);
+ mu_layout_begin_column();
+ static int states[8];
+ if (mu_begin_treenode(&states[0], "Test 1")) {
+ if (mu_begin_treenode(&states[1], "Test 1a")) {
+ mu_label("Hello");
+ mu_label("world");
+ mu_end_treenode();
+ }
+ if (mu_begin_treenode(&states[2], "Test 1b")) {
+ if (mu_button("Button 1")) { write_log("Pressed button 1"); }
+ if (mu_button("Button 2")) { write_log("Pressed button 2"); }
+ mu_end_treenode();
+ }
+ mu_end_treenode();
+ }
+ if (mu_begin_treenode(&states[3], "Test 2")) {
+ int widths[2];
+ widths[0] = widths[1] = text_width("Button 3");
+ mu_layout_row(2, widths, 0);
+ if (mu_button("Button 3")) { write_log("Pressed button 3"); }
+ if (mu_button("Button 4")) { write_log("Pressed button 4"); }
+ if (mu_button("Button 5")) { write_log("Pressed button 5"); }
+ if (mu_button("Button 6")) { write_log("Pressed button 6"); }
+ mu_end_treenode();
+ }
+ if (mu_begin_treenode(&states[4], "Test 3")) {
+ static int checks[3] = { 1, 0, 1 };
+ mu_checkbox(&checks[0], "Checkbox 1");
+ mu_checkbox(&checks[1], "Checkbox 2");
+ mu_checkbox(&checks[2], "Checkbox 3");
+ mu_end_treenode();
+ }
+ mu_layout_end_column();
+
+ mu_layout_begin_column();
+ widths[0] = -1;
+ mu_layout_row(1, widths, 0);
+ mu_text("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();
+ }
+
+ mu_end_window();
+ }
+}
+
+
+static void
+log_window(void)
+{
+ static mu_Container window;
+
+ /* init window manually so we can set its position and size */
+ if (!window.inited) {
+ mu_init_window(&window, 0);
+ window.rect = mu_rect(370, 40, 340, 200);
+ }
+
+ if (mu_begin_window(&window, "Log Window")) {
+ int widths[] = { -1, -1 };
+
+ /* output text panel */
+ static mu_Container panel;
+ mu_layout_row(1, widths, -28);
+ mu_begin_panel(&panel);
+ mu_layout_row(1, widths, -1);
+ mu_text(logbuf);
+ mu_end_panel();
+ if (logbuf_updated) {
+ panel.scroll.y = panel.content_size.y;
+ logbuf_updated = 0;
+ }
+
+ /* input textbox + submit button */
+ static char buf[128];
+ int submitted = 0;
+ widths[0] = -text_width("Submit")-8;
+ mu_layout_row(2, widths, -1);
+ if (mu_textbox(buf, sizeof(buf)) & MU_RES_SUBMIT) {
+ mu_set_focus(mu_ctx.last_id);
+ submitted = 1;
+ }
+ if (mu_button("Submit")) { submitted = 1; }
+ if (submitted) {
+ write_log(buf);
+ buf[0] = '\0';
+ }
+
+ mu_end_window();
+ }
+}
+
+
+static int
+uint8_slider(unsigned char *value, int low, int high)
+{
+ static double tmp;
+ mu_push_id(&value, sizeof(value));
+ tmp = *value;
+ int res = mu_slider_ex(&tmp, low, high, 0, "%.0f", MU_OPT_ALIGNCENTER);
+ *value = tmp;
+ mu_pop_id();
+ return res;
+}
+
+static void
+style_window(void)
+{
+ static mu_Container window;
+ static u8int cur[MU_COLOR_MAX][4], old[MU_COLOR_MAX][4];
+ static struct { const char *label; int idx; } colors[] = {
+ { "background:", MU_COLOR_BG },
+ { "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 },
+ { nil }
+ };
+
+ /* init window manually so we can set its position and size */
+ if (!window.inited) {
+ mu_init_window(&window, 0);
+ window.rect = mu_rect(370, 250, 340, 290);
+ memmove(cur, defaultcolors, sizeof(cur));
+ memmove(old, defaultcolors, sizeof(old));
+ }
+
+ if (mu_begin_window(&window, "Style Editor")) {
+ int sw = max(text_width("255"), mu_get_container()->body.w * 0.14);
+ const int widths[] = { text_width("scrollthumb:"), sw, sw, sw, sw, -1 };
+ mu_layout_row(6, widths, 0);
+ for (int i = 0; colors[i].label; i++) {
+ mu_label(colors[i].label);
+ uint8_slider(&cur[i][0], 0, 255);
+ uint8_slider(&cur[i][1], 0, 255);
+ uint8_slider(&cur[i][2], 0, 255);
+ uint8_slider(&cur[i][3], 0, 255);
+ if (memcmp(cur[i], old[i], 4) != 0) {
+ freeimage(mu_style.colors[i]);
+ mu_style.colors[i] = mu_color(cur[i][0], cur[i][1], cur[i][2], cur[i][3]);
+ memmove(old[i], cur[i], 4);
+ }
+ mu_draw_rect(mu_layout_next(), mu_style.colors[i]);
+ }
+ mu_end_window();
+ }
+}
+
+void
+process_frame(void)
+{
+ mu_begin();
+ test_window();
+ log_window();
+ style_window();
+ mu_end();
+}
--- /dev/null
+++ b/demo/mkfile
@@ -1,0 +1,11 @@
+</$objtype/mkfile
+
+TARG=demo
+
+OFILES=\
+ frame.$O\
+ plan9.$O\
+
+default:V: all
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/demo/plan9.c
@@ -1,0 +1,120 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <thread.h>
+#include <bio.h>
+#include <microui.h>
+
+void process_frame(void);
+
+void
+threadmain(int argc, char **argv)
+{
+ Mousectl *mctl;
+ char *s;
+ Biobuf *snarf;
+ Keyboardctl *kctl;
+ Rune key;
+ Mouse m;
+ Alt a[] = {
+ { nil, &m, CHANRCV },
+ { nil, nil, CHANRCV },
+ { nil, &key, CHANRCV },
+ { nil, nil, CHANEND},
+ };
+ int oldbuttons, b, nkey, gotevent;
+ char text[5];
+
+ USED(argc); USED(argv);
+
+ if (initdraw(nil, nil, "microui demo") < 0)
+ sysfatal("initdraw: %r");
+ if ((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ if ((kctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+
+ a[0].c = mctl->c;
+ a[1].c = mctl->resizec;
+ a[2].c = kctl->c;
+
+ srand(time(0));
+ threadsetname("microui demo");
+
+ mu_init();
+ mu_style.font = font;
+ mu_style.size.y = font->height;
+ mu_style.title_height = mu_style.size.y + 6;
+ process_frame();
+
+ oldbuttons = 0;
+ for (;;) {
+ process_frame();
+ if (mu_render())
+ flushimage(display, 1);
+
+ gotevent = 1;
+ switch (alt(a)) {
+ case 0: /* mouse */
+ m.xy.x -= screen->r.min.x;
+ m.xy.y -= screen->r.min.y;
+ mu_input_mousemove(m.xy.x, m.xy.y);
+ if ((b = (m.buttons & 1)) != (oldbuttons & 1))
+ (b ? mu_input_mousedown : mu_input_mouseup)(m.xy.x, m.xy.y, MU_MOUSE_LEFT);
+ else if ((b = (m.buttons & 2)) != (oldbuttons & 2))
+ (b ? mu_input_mousedown : mu_input_mouseup)(m.xy.x, m.xy.y, MU_MOUSE_MIDDLE);
+ else if ((b = (m.buttons & 4)) != (oldbuttons & 4))
+ (b ? mu_input_mousedown : mu_input_mouseup)(m.xy.x, m.xy.y, MU_MOUSE_RIGHT);
+ if (m.buttons == 5 && (snarf = Bopen("/dev/snarf", OREAD)) != nil) {
+ if ((s = Brdstr(snarf, 0, 1)) != nil) {
+ mu_input_text(s);
+ free(s);
+ }
+ Bterm(snarf);
+ }
+ oldbuttons = m.buttons;
+ break;
+
+ case 1: /* resize */
+ getwindow(display, Refnone);
+ break;
+
+ case 2: /* keyboard */
+ nkey = -1;
+ switch (key) {
+ case Kdel: goto end;
+ case Kshift: nkey = MU_KEY_SHIFT; break;
+ case Kbs: nkey = MU_KEY_BACKSPACE; break;
+ case '\n': nkey = MU_KEY_RETURN; break;
+ case Knack: nkey = MU_KEY_NACK; break;
+ case Kleft: nkey = MU_KEY_LEFT; break;
+ case Kright: nkey = MU_KEY_RIGHT; break;
+ case Kesc: mu_set_focus(0); break;
+ default:
+ if (key < 0xf000 || key > 0xffff) {
+ memset(text, 0, sizeof(text));
+ if (runetochar(text, &key) > 0)
+ mu_input_text(text);
+ }
+ break;
+ }
+ if (nkey >= 0) {
+ mu_input_keydown(nkey);
+ mu_input_keyup(nkey);
+ }
+ break;
+
+ default:
+ gotevent = 0;
+ break;
+ }
+
+ if (gotevent)
+ process_frame();
+ }
+
+end:
+ threadexitsall(nil);
+}
binary files /dev/null b/doc/microui_9front.png differ
--- /dev/null
+++ b/doc/usage.md
@@ -1,0 +1,260 @@
+# Usage
+* **[Overview](#overview)**
+* **[Getting Started](#getting-started)**
+* **[Layout System](#layout-system)**
+* **[Style Customisation](#style-customisation)**
+* **[Custom Controls](#custom-controls)**
+
+## Overview
+The overall structure when using the library is as follows:
+```
+initialise `mu_Context`
+
+main loop:
+ call `mu_input_...` functions
+ call `mu_begin()`
+ process ui
+ call `mu_end()`
+ iterate commands using `mu_command_next()`
+```
+
+## Getting Started
+Before use a `mu_Context` should be initialised:
+```c
+mu_Context *ctx = malloc(sizeof(mu_Context));
+mu_init(ctx);
+```
+
+For font alignment and clipping to work correctly you should also set the
+context's `text_width` and `text_height` callback functions:
+```c
+ctx->text_width = text_width;
+ctx->text_height = text_height;
+```
+
+In your main loop you should first pass user input to microui using the
+`mu_input_...` functions. It is safe to call the input functions multiple
+times if the same input event occurs in a single frame.
+
+After handling the input the `mu_begin()` function must be called before
+processing your UI:
+```c
+mu_begin(ctx);
+```
+
+Before any controls can be used we must begin a window using one of the
+`mu_begin_window...` or `mu_begin_popup...` functions. The `mu_Container`
+for the window is expected to be either zeroed memory in which case
+it will be initialised automatically when used, or to have been
+initialised manually using the `mu_init_window()` function; once used,
+the `mu_Container`'s memory must remain valid until `mu_end()` is called
+at the end of the frame. The `mu_begin_...` window functions return a
+truthy value if the window is open, if this is not the case we should
+not process the window any further. When we are finished processing the
+window's ui the `mu_end_...` window function should be called.
+
+```c
+static mu_Container window;
+
+if (mu_begin_window(ctx, &window, "My Window")) {
+ /* process ui here... */
+ mu_end_window(ctx);
+}
+```
+
+It is safe to nest `mu_begin_window()` calls, this can be useful for
+things like context menus; the windows will still render separate from
+one another like normal.
+
+While inside a window block we can safely process controls. Controls
+that allow user interaction return a bitset of `MU_RES_...` values. Some
+controls — such as buttons — can only potentially return a single
+`MU_RES_...`, thus their return value can be treated as a boolean:
+```c
+if (mu_button(ctx, "My Button")) {
+ printf("'My Button' was pressed\n");
+}
+```
+
+The library generates unique IDs for controls internally to keep track
+of which are focused, hovered, etc. These are generated either from the
+pointer passed to the function (eg. for treenodes, checkboxes, textboxes
+and sliders), or the string/icon passed to the function (eg. buttons). An
+issue arises then if you have several buttons in a window or panel that
+use the same label. The `mu_push_id()` and `mu_pop_id()` functions are
+provided for such situations, allowing you to push additional data that
+will be mixed into the unique ID:
+```c
+for (int i = 0; i < 10; i++) {
+ mu_push_id(ctx, &i, sizeof(i));
+ if (mu_button(ctx, "x")) {
+ printf("Pressed button %d\n", i);
+ }
+ mu_pop_id(ctx);
+}
+```
+
+When we're finished processing the UI for this frame the `mu_end()`
+function should be called:
+```c
+mu_end(ctx);
+```
+
+When we're ready to draw the UI the `mu_next_command()` can be used
+to iterate the resultant commands. The function expects a `mu_Command`
+pointer initialised to `NULL`. It is safe to iterate through the commands
+list any number of times:
+```c
+mu_Command *cmd = NULL;
+while (mu_next_command(ctx, &cmd)) {
+ if (cmd->type == MU_COMMAND_TEXT) {
+ render_text(cmd->text.font, cmd->text.text, cmd->text.pos.x, cmd->text.pos.y, cmd->text.color);
+ }
+ if (cmd->type == MU_COMMAND_RECT) {
+ render_rect(cmd->rect.rect, cmd->rect.color);
+ }
+ if (cmd->type == MU_COMMAND_ICON) {
+ render_icon(cmd->icon.id, cmd->icon.rect, cmd->icon.color);
+ }
+ if (cmd->type == MU_COMMAND_CLIP) {
+ set_clip_rect(cmd->clip.rect);
+ }
+}
+```
+
+See the [`demo`](../demo) directory for a usage example.
+
+
+## Layout System
+The layout system is primarily based around *rows* — Each row
+can contain a number of *items* or *columns* each column can itself
+contain a number of rows and so forth. A row is initialised using the
+`mu_layout_row()` function, the user should specify the number of items
+on the row, an array containing the width of each item, and the height
+of the row:
+```c
+/* initialise a row of 3 items: the first item with a width
+** of 90 and the remaining two with the width of 100 */
+mu_layout_row(ctx, 3, (int[]) { 90, 100, 100 }, 0);
+```
+When a row is filled the next row is started, for example, in the above
+code 6 buttons immediately after would result in two rows. The function
+can be called again to begin a new row.
+
+As well as absolute values, width and height can be specified as `0`
+which will result in the Context's `style.size` value being used, or a
+negative value which will size the item relative to the right/bottom edge,
+thus if we wanted a row with a small button at the left, a textbox filling
+most the row and a larger button at the right, we could do the following:
+```c
+mu_layout_row(ctx, 3, (int[]) { 30, -90, -1 }, 0);
+mu_button(ctx, "X");
+mu_textbox(ctx, buf, sizeof(buf));
+mu_button(ctx, "Submit");
+```
+
+If the `items` parameter is `-1`, the `widths` parameter is ignored
+and controls will continue to be added to the row at the width last
+specified by `mu_layout_width()` or `style.size.x` if this function has
+not been called:
+```c
+mu_layout_row(ctx, -1, NULL, 0);
+mu_layout_width(ctx, -90);
+mu_textbox(ctx, buf, sizeof(buf));
+mu_layout_width(ctx, -1);
+mu_button(ctx, "Submit");
+```
+
+A column can be started at any point on a row using the
+`mu_layout_begin_column()` function. Once begun, rows will act inside
+the body of the column — all negative size values will be relative to
+the column's body as opposed to the body of the container. All new rows
+will be contained within this column until the `mu_layout_end_column()`
+function is called.
+
+Internally controls use the `mu_layout_next()` function to retrieve the
+next screen-positioned-Rect and advance the layout system, you should use
+this function when making custom controls or if you want to advance the
+layout system without placing a control.
+
+The `mu_layout_set_next()` function is provided to set the next layout
+Rect explicitly. This will be returned by `mu_layout_next()` when it is
+next called. By using the `relative` boolean you can choose to provide
+a screen-space Rect or a Rect which will have the container's position
+and scroll offset applied to it. You can peek the next Rect from the
+layout system by using the `mu_layout_next()` function to retrieve it,
+followed by `mu_layout_set_next()` to return it:
+```c
+mu_Rect rect = mu_layout_next(ctx);
+mu_layout_set_next(ctx, rect, 0);
+```
+
+If you want to position controls arbitrarily inside a container the
+`relative` argument of `mu_layout_set_next()` should be true:
+```c
+/* place a (40, 40) sized button at (300, 300) inside the container: */
+mu_layout_set_next(ctx, mu_rect(300, 300, 40, 40), 1);
+mu_button(ctx, "X");
+```
+A Rect set with `relative` true will also effect the `content_size`
+of the container, causing it to effect the scrollbars if it exceeds the
+width or height of the container's body.
+
+
+## Style Customisation
+The library provides styling support via the `mu_Style` struct and, if you
+want greater control over the look, the `draw_frame()` callback function.
+
+The `mu_Style` struct contains spacing and sizing information, as well
+as a `colors` array which maps `colorid` to `mu_Color`. The library uses
+the `style` pointer field of the context to resolve colors and spacing,
+it is safe to change this pointer or modify any fields of the resultant
+struct at any point. See [`microui.h`](../src/microui.h) for the struct's
+implementation.
+
+In addition to the style struct the context stores a `draw_frame()`
+callback function which is used whenever the *frame* of a control needs
+to be drawn, by default this function draws a rectangle using the color
+of the `colorid` argument, with a one-pixel border around it using the
+`MU_COLOR_BORDER` color.
+
+
+## Custom Controls
+The library exposes the functions used by built-in controls to allow the
+user to make custom controls. A control should take a `mu_Context*` value
+as its first argument and return a `MU_RES_...` value. Your control's
+implementation should use `mu_layout_next()` to get its destination
+Rect and advance the layout system. `mu_get_id()` should be used with
+some data unique to the control to generate an ID for that control and
+`mu_update_control()` should be used to update the context's `hover`
+and `focus` values based on the mouse input state.
+
+The `MU_OPT_HOLDFOCUS` opt value can be passed to `mu_update_control()`
+if we want the control to retain focus when the mouse button is released
+— this behaviour is used by textboxes which we want to stay focused
+to allow for text input.
+
+A control that acts as a button which displays an integer and, when
+clicked increments that integer, could be implemented as such:
+```c
+int incrementer(mu_Context *ctx, int *value) {
+ mu_Id id = mu_get_id(ctx, &value, sizeof(value));
+ mu_Rect rect = mu_layout_next(ctx);
+ mu_update_control(ctx, id, rect, 0);
+
+ /* handle input */
+ int res = 0;
+ if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
+ (*value)++;
+ res |= MU_RES_CHANGE;
+ }
+
+ /* draw */
+ char buf[32];
+ sprintf(buf, "%d", *value);
+ mu_draw_control_frame(ctx, id, rect, MU_COLOR_BUTTON, 0);
+ mu_draw_control_text(ctx, buf, rect, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER);
+
+ return res;
+}
+```
binary files /dev/null b/icons/mu_icon_check.png differ
binary files /dev/null b/icons/mu_icon_close.png differ
binary files /dev/null b/icons/mu_icon_collapsed.png differ
binary files /dev/null b/icons/mu_icon_expanded.png differ
binary files /dev/null b/icons/mu_icon_resize.png differ
--- /dev/null
+++ b/microui.c
@@ -1,0 +1,1501 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <microui.h>
+
+#define MU_REAL_FMT "%.3g"
+#define MU_SLIDER_FMT "%.2f"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define CLAMP(x, a, b) MIN(b, MAX(a, x))
+
+static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 };
+
+static u8int atlasraw[] = {
+ 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x72, 0x38,
+ 0x67, 0x38, 0x62, 0x38, 0x61, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x33, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x32, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x32, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x34, 0x33, 0x39, 0x20, 0x80,
+ 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x78, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c,
+ 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x34, 0x00, 0x80,
+ 0x14, 0x00, 0x00, 0x80, 0x5c, 0x00, 0x00, 0x54, 0x1f, 0x04, 0x1b, 0x04, 0x23, 0x24, 0x13, 0x7c,
+ 0x00, 0x3c, 0x00, 0x80, 0x20, 0x00, 0x00, 0x80, 0xc0, 0x00, 0x00, 0x04, 0x07, 0x44, 0x1f, 0x80,
+ 0x5e, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x34, 0x1b, 0x04, 0x13, 0x04,
+ 0x1b, 0x04, 0x23, 0x24, 0x17, 0x7c, 0x00, 0x2c, 0x00, 0x80, 0x21, 0x00, 0x00, 0x80, 0xe0, 0x00,
+ 0x00, 0x80, 0xea, 0x00, 0x00, 0x80, 0x2c, 0x00, 0x00, 0x54, 0x27, 0x14, 0x6f, 0x24, 0x8b, 0x44,
+ 0x13, 0x14, 0x00, 0x7c, 0x00, 0x4c, 0x83, 0x80, 0x2d, 0x00, 0x00, 0x74, 0x2f, 0x24, 0x77, 0x74,
+ 0x83, 0x7c, 0x00, 0x1c, 0x83, 0x80, 0xe1, 0x00, 0x00, 0x80, 0xeb, 0x00, 0x00, 0x7c, 0x83, 0x3c,
+ 0x8b, 0x74, 0x83, 0x24, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x7c, 0x83, 0x7c, 0x83, 0x7c, 0x87, 0x3c,
+ 0x00, 0x14, 0x00, 0x80, 0x3d, 0x00, 0x00, 0x80, 0xed, 0x00, 0x00, 0x80, 0x45, 0x00, 0x00, 0x24,
+ 0x17, 0x80, 0x22, 0x00, 0x00, 0x7c, 0x83, 0x7d, 0x97, 0x75, 0x97, 0x14, 0x00, 0x80, 0x13, 0x00,
+ 0x00, 0x80, 0xd0, 0x00, 0x00, 0x80, 0xf6, 0x00, 0x00, 0x14, 0x8b, 0x24, 0x83, 0x80, 0x2e, 0x00,
+ 0x00, 0x7c, 0x79, 0x7e, 0xa7, 0x54, 0x8b, 0x54, 0x8b, 0x80, 0x63, 0x00, 0x00, 0x7c, 0x83, 0x4c,
+ 0x00, 0x7f, 0xb7, 0x3f, 0xb7, 0x54, 0x8b, 0x04, 0x2b, 0x80, 0xec, 0x00, 0x00, 0x7c, 0x83, 0x3c,
+ 0x00, 0x80, 0x14, 0x00, 0x00, 0x80, 0x5c, 0x00, 0x00, 0x54, 0x1f, 0x04, 0x1b, 0x04, 0x23, 0x24,
+ 0x13, 0x54, 0x8b, 0x80, 0xbc, 0x00, 0x00, 0x7c, 0x83, 0x7c, 0x00, 0x7c, 0x00, 0x0c, 0x00, 0x7c,
+ 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c,
+ 0x00, 0x7c, 0x00, 0x64, 0x00, 0x80, 0x5a, 0x00, 0x00, 0x06, 0x6b, 0x14, 0x07, 0x80, 0x8f, 0x00,
+ 0x00, 0x44, 0x27, 0x7c, 0x00, 0x7c, 0x00, 0x64, 0x00, 0x80, 0x3c, 0x00, 0x00, 0x80, 0xaa, 0x00,
+ 0x00, 0x04, 0x07, 0x04, 0x83, 0x54, 0x8b, 0x44, 0x17, 0x14, 0x2f, 0x7c, 0x65, 0x7c, 0x00, 0x34,
+ 0x00, 0x54, 0x8b, 0x34, 0x17, 0x64, 0xb3, 0x7c, 0x00, 0x0c, 0x00, 0x80, 0x6e, 0x00, 0x00, 0x05,
+ 0x03, 0x35, 0x0f, 0x64, 0x8b, 0x34, 0x17, 0x7c, 0x83, 0x7c, 0x00, 0x80, 0xa5, 0x00, 0x00, 0x15,
+ 0x4f, 0x26, 0x1f, 0x64, 0x83, 0x74, 0xa3, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x63, 0x81, 0x00, 0x00,
+ 0x34, 0x27, 0x7c, 0x4d, 0x4c, 0x00, 0x80, 0x55, 0x00, 0x00, 0x80, 0x37, 0x00, 0x00, 0x04, 0x0b,
+ 0x04, 0x07, 0x04, 0x0f, 0x04, 0x0b, 0x80, 0x1e, 0x00, 0x00, 0x04, 0x0b, 0x04, 0x07, 0x64, 0x83,
+ 0x7c, 0x00, 0x7c, 0x00, 0x16, 0xef, 0x05, 0x8f, 0x75, 0x97, 0x54, 0x00, 0x7c, 0x00, 0x7c, 0x00,
+ 0x7d, 0x0f, 0x0d, 0x0f, 0x64, 0x3f,
+};
+
+static mu_Rect default_atlas_icons[] = {
+ [MU_ICON_CHECK] = {0, 0, 18, 18},
+ [MU_ICON_CLOSE] = {18, 0, 16, 16},
+ [MU_ICON_COLLAPSED] = {27, 16, 5, 7},
+ [MU_ICON_EXPANDED] = {0, 18, 7, 5},
+ [MU_ICON_RESIZE] = {18, 16, 9, 9},
+ [ATLAS_DIMENSIONS] = {0, 0, 34, 25},
+};
+
+mu_Context mu_ctx;
+mu_Style mu_style = {
+ .font = nil,
+ .size = { 68, 10 },
+ .padding = 6,
+ .spacing = 4,
+ .indent = 24,
+ .title_height = 26,
+ .scrollbar_size = 12,
+ .thumb_size = 8,
+ .colors = {nil},
+};
+
+Image *atlasimage = nil;
+mu_Rect *atlasicons = default_atlas_icons;
+u8int defaultcolors[MU_COLOR_MAX][4] = {
+ [MU_COLOR_BG] = {119, 119, 119, 255},
+ [MU_COLOR_TEXT] = {230, 230, 230, 255},
+ [MU_COLOR_BORDER] = {25, 25, 25, 255},
+ [MU_COLOR_WINDOWBG] = {50, 50, 50, 255},
+ [MU_COLOR_TITLEBG] = {25, 25, 25, 255},
+ [MU_COLOR_TITLETEXT] = {240, 240, 240, 255},
+ [MU_COLOR_PANELBG] = {0, 0, 0, 0},
+ [MU_COLOR_BUTTON] = {75, 75, 75, 255},
+ [MU_COLOR_BUTTONHOVER] = {95, 95, 95, 255},
+ [MU_COLOR_BUTTONFOCUS] = {115, 115, 115, 255},
+ [MU_COLOR_BASE] = {30, 30, 30, 255},
+ [MU_COLOR_BASEHOVER] = {35, 35, 35, 255},
+ [MU_COLOR_BASEFOCUS] = {40, 40, 40, 255},
+ [MU_COLOR_SCROLLBASE] = {43, 43, 43, 255},
+ [MU_COLOR_SCROLLTHUMB] = {30, 30, 30, 255},
+};
+
+static void
+buffer_grow(void **buf, int onesz, int *bufmax, int bufnum)
+{
+ while (*bufmax <= bufnum) {
+ *bufmax = MAX(16, *bufmax) * 2;
+ if ((*buf = realloc(*buf, *bufmax*onesz)) == nil)
+ sysfatal("not enough memory for %d items (%d bytes)", *bufmax, *bufmax*onesz);
+ }
+}
+
+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;
+}
+
+Image *
+mu_color(u8int r, u8int g, u8int b, u8int a)
+{
+ Image *c;
+ if ((c = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(r<<24 | g<<16 | b<<8, a))) == nil)
+ sysfatal("couldn't allocate color");
+ return c;
+}
+
+static Rectangle
+screenrect(mu_Rect r)
+{
+ Rectangle rect;
+ rect.min = screen->r.min;
+ rect.min.x += r.x;
+ rect.min.y += r.y;
+ rect.max = rect.min;
+ rect.max.x += r.w;
+ rect.max.y += r.h;
+ return rect;
+}
+
+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
+clip_rect(mu_Rect r1, mu_Rect r2)
+{
+ int x1 = MAX(r1.x, r2.x);
+ int y1 = MAX(r1.y, r2.y);
+ int x2 = MAX(MIN(r1.x + r1.w, r2.x + r2.w), x1);
+ int y2 = MAX(MIN(r1.y + r1.h, r2.y + r2.h), y1);
+ return mu_rect(x1, y1, x2 - x1, y2 - y1);
+}
+
+
+static int
+rect_overlaps_vec2(mu_Rect r, Point 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_Rect rect, int colorid)
+{
+ mu_draw_rect(rect, mu_style.colors[colorid]);
+ if (colorid == MU_COLOR_SCROLLBASE || colorid == MU_COLOR_SCROLLTHUMB || colorid == MU_COLOR_TITLEBG)
+ return;
+ /* draw border */
+ mu_draw_box(expand_rect(rect, 1), mu_style.colors[MU_COLOR_BORDER]);
+}
+
+static int
+text_width(Font *font, const char *text, int len)
+{
+ return stringnwidth(font, text, len >= 0 ? len : strlen(text));
+}
+
+void
+mu_init(void)
+{
+ Rectangle r;
+ int res, i;
+
+ if (atlasimage == nil) {
+ r = Rect(0, 0, atlasicons[ATLAS_DIMENSIONS].w, atlasicons[ATLAS_DIMENSIONS].h);
+ atlasimage = allocimage(display, r, RGBA32, 1, DTransparent);
+ if (memcmp(atlasraw, "compressed\n", 11) == 0)
+ res = cloadimage(atlasimage, r, atlasraw+11+5*12, sizeof(atlasraw)-11-5*12);
+ else
+ res = loadimage(atlasimage, r, atlasraw, sizeof(atlasraw));
+ if (res < 0)
+ sysfatal("failed to load atlas: %r");
+ }
+
+ if (mu_style.colors[0] == nil) {
+ for (i = 0; i < MU_COLOR_MAX; i++) {
+ mu_style.colors[i] = mu_color(
+ defaultcolors[i][0],
+ defaultcolors[i][1],
+ defaultcolors[i][2],
+ defaultcolors[i][3]
+ );
+ }
+ }
+}
+
+void
+mu_begin(void)
+{
+ mu_ctx.cmdsnum = mu_ctx.rootnum = 0;
+ mu_ctx.strnum = 0;
+ mu_ctx.scroll_target = nil;
+ mu_ctx.last_hover_root = mu_ctx.hover_root;
+ mu_ctx.hover_root = nil;
+ mu_ctx.mouse_delta.x = mu_ctx.mouse_pos.x - mu_ctx.last_mouse_pos.x;
+ mu_ctx.mouse_delta.y = mu_ctx.mouse_pos.y - mu_ctx.last_mouse_pos.y;
+}
+
+static int
+compare_zindex(const void *a, const void *b)
+{
+ return (*(mu_Container**)a)->zindex - (*(mu_Container**)b)->zindex;
+}
+
+void
+mu_end(void)
+{
+ int i, n;
+ /* check stacks */
+ assert(mu_ctx.cntnum == 0);
+ assert(mu_ctx.clipnum == 0);
+ assert(mu_ctx.idsnum == 0);
+ assert(mu_ctx.layoutsnum == 0);
+
+ /* handle scroll input */
+ if (mu_ctx.scroll_target) {
+ mu_ctx.scroll_target->scroll.x += mu_ctx.scroll_delta.x;
+ mu_ctx.scroll_target->scroll.y += mu_ctx.scroll_delta.y;
+ }
+
+ /* unset focus if focus id was not touched this frame */
+ if (!mu_ctx.updated_focus)
+ mu_ctx.focus = 0;
+ mu_ctx.updated_focus = 0;
+
+ /* bring hover root to front if mouse was pressed */
+ if (mu_ctx.mouse_pressed && mu_ctx.hover_root && mu_ctx.hover_root->zindex < mu_ctx.last_zindex)
+ mu_bring_to_front(mu_ctx.hover_root);
+
+ /* reset input state */
+ mu_ctx.key_pressed = 0;
+ mu_ctx.text_input[0] = 0;
+ mu_ctx.mouse_pressed = 0;
+ mu_ctx.scroll_delta = ZP;
+ mu_ctx.last_mouse_pos = mu_ctx.mouse_pos;
+
+ /* sort root containers by zindex */
+ n = mu_ctx.rootnum;
+ qsort(mu_ctx.root, n, sizeof(*mu_ctx.root), compare_zindex);
+
+ /* set root container jump commands */
+ for (i = 0; i < n; i++) {
+ mu_Container *cnt = mu_ctx.root[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_ctx.cmds[0].jump.dst = cnt->head + 1;
+ else
+ mu_ctx.cmds[mu_ctx.root[i - 1]->tail].jump.dst = cnt->head + 1;
+
+ /* make the last container's tail jump to the end of command list */
+ if (i == n - 1)
+ mu_ctx.cmds[cnt->tail].jump.dst = mu_ctx.cmdsnum;
+ }
+}
+
+
+void
+mu_set_focus(mu_Id id)
+{
+ mu_ctx.focus = id;
+ mu_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(const void *data, int size)
+{
+ int idx = mu_ctx.idsnum;
+ mu_Id res = (idx > 0) ? mu_ctx.ids[idx - 1] : HASH_INITIAL;
+ hash(&res, data, size);
+ mu_ctx.last_id = res;
+ return res;
+}
+
+
+void
+mu_push_id(const void *data, int size)
+{
+ buffer_grow(&mu_ctx.ids, sizeof(*mu_ctx.ids), &mu_ctx.idsmax, mu_ctx.idsnum);
+ mu_ctx.ids[mu_ctx.idsnum++] = mu_get_id(data, size);
+}
+
+void
+mu_pop_id(void)
+{
+ mu_ctx.idsnum--;
+}
+
+void
+mu_push_clip_rect(mu_Rect rect)
+{
+ mu_Rect last = mu_get_clip_rect();
+ buffer_grow(&mu_ctx.clip, sizeof(*mu_ctx.clip), &mu_ctx.clipmax, mu_ctx.clipnum);
+ mu_ctx.clip[mu_ctx.clipnum++] = clip_rect(rect, last);
+}
+
+
+void
+mu_pop_clip_rect(void)
+{
+ mu_ctx.clipnum--;
+}
+
+
+mu_Rect
+mu_get_clip_rect(void)
+{
+ assert(mu_ctx.clipnum > 0);
+ return mu_ctx.clip[mu_ctx.clipnum - 1];
+}
+
+
+int
+mu_check_clip(mu_Rect r)
+{
+ mu_Rect cr = mu_get_clip_rect();
+ if (r.x > cr.x + cr.w || r.x + r.w < cr.x || r.y > cr.y + cr.h || r.y + r.h < cr.y)
+ return 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 MU_CLIP_NONE;
+ return MU_CLIP_PART;
+}
+
+
+static void
+push_layout(mu_Rect body, Point scroll)
+{
+ mu_Layout layout;
+ int width = 0;
+ memset(&layout, 0, sizeof(mu_Layout));
+ layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h);
+ layout.max = Pt(-0x1000000, -0x1000000);
+ buffer_grow(&mu_ctx.layouts, sizeof(*mu_ctx.layouts), &mu_ctx.layoutsmax, mu_ctx.layoutsnum);
+ mu_ctx.layouts[mu_ctx.layoutsnum++] = layout;
+ mu_layout_row(1, &width, 0);
+}
+
+static mu_Layout *
+get_layout(void)
+{
+ return &mu_ctx.layouts[mu_ctx.layoutsnum - 1];
+}
+
+
+static void
+push_container(mu_Container *cnt)
+{
+ buffer_grow(&mu_ctx.cnt, sizeof(*mu_ctx.cnt), &mu_ctx.cntmax, mu_ctx.cntnum);
+ mu_ctx.cnt[mu_ctx.cntnum++] = cnt;
+ mu_push_id(&cnt, sizeof(mu_Container*));
+}
+
+
+static void
+pop_container(void)
+{
+ mu_Container *cnt = mu_get_container();
+ mu_Layout *layout = get_layout();
+ cnt->content_size.x = layout->max.x - layout->body.x;
+ cnt->content_size.y = layout->max.y - layout->body.y;
+ mu_ctx.cntnum--;
+ mu_ctx.layoutsnum--;
+ mu_pop_id();
+}
+
+
+mu_Container *
+mu_get_container(void)
+{
+ assert(mu_ctx.cntnum > 0);
+ return mu_ctx.cnt[mu_ctx.cntnum - 1];
+}
+
+
+void
+mu_init_window(mu_Container *cnt, int opt)
+{
+ memset(cnt, 0, sizeof(*cnt));
+ cnt->inited = 1;
+ cnt->open = opt & MU_OPT_CLOSED ? 0 : 1;
+ cnt->rect = mu_rect(100, 100, 300, 300);
+ mu_bring_to_front(cnt);
+}
+
+
+void
+mu_bring_to_front(mu_Container *cnt)
+{
+ cnt->zindex = ++mu_ctx.last_zindex;
+}
+
+
+/*============================================================================
+** input handlers
+**============================================================================*/
+
+void
+mu_input_mousemove(int x, int y)
+{
+ mu_ctx.mouse_pos = Pt(x, y);
+}
+
+
+void
+mu_input_mousedown(int x, int y, int btn)
+{
+ mu_input_mousemove(x, y);
+ mu_ctx.mouse_down |= btn;
+ mu_ctx.mouse_pressed |= btn;
+}
+
+
+void
+mu_input_mouseup(int x, int y, int btn)
+{
+ mu_input_mousemove(x, y);
+ mu_ctx.mouse_down &= ~btn;
+}
+
+
+void
+mu_input_scroll(int x, int y)
+{
+ mu_ctx.scroll_delta.x += x;
+ mu_ctx.scroll_delta.y += y;
+}
+
+
+void
+mu_input_keydown(int key)
+{
+ mu_ctx.key_pressed |= key;
+ mu_ctx.key_down |= key;
+}
+
+
+void
+mu_input_keyup(int key)
+{
+ mu_ctx.key_down &= ~key;
+}
+
+
+void
+mu_input_text(const char *text)
+{
+ int len = strlen(mu_ctx.text_input);
+ int size = strlen(text) + 1;
+ assert(len + size <= (int) sizeof(mu_ctx.text_input));
+ memcpy(mu_ctx.text_input + len, text, size);
+}
+
+/*============================================================================
+** commandlist
+**============================================================================*/
+
+mu_Command *
+mu_push_command(int type)
+{
+ mu_Command *cmd;
+
+ buffer_grow(&mu_ctx.cmds, sizeof(mu_Command), &mu_ctx.cmdsmax, mu_ctx.cmdsnum);
+ cmd = &mu_ctx.cmds[mu_ctx.cmdsnum++];
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->type = type;
+
+ return cmd;
+}
+
+static int
+push_jump(int dst)
+{
+ mu_Command *cmd;
+ cmd = mu_push_command(MU_COMMAND_JUMP);
+ cmd->jump.dst = dst;
+ return mu_ctx.cmdsnum-1;
+}
+
+void
+mu_set_clip(mu_Rect rect)
+{
+ mu_Command *cmd;
+ cmd = mu_push_command(MU_COMMAND_CLIP);
+ cmd->clip.rect = rect;
+}
+
+void
+mu_draw_rect(mu_Rect rect, Image *color)
+{
+ mu_Command *cmd;
+ rect = clip_rect(rect, mu_get_clip_rect());
+ if (rect.w > 0 && rect.h > 0) {
+ cmd = mu_push_command(MU_COMMAND_RECT);
+ cmd->rect.rect = rect;
+ cmd->rect.color = color;
+ }
+}
+
+void
+mu_draw_box(mu_Rect rect, Image *color)
+{
+ mu_draw_rect(mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color);
+ mu_draw_rect(mu_rect(rect.x+1, rect.y + rect.h-1, rect.w-2, 1), color);
+ mu_draw_rect(mu_rect(rect.x, rect.y, 1, rect.h), color);
+ mu_draw_rect(mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color);
+}
+
+
+void
+mu_draw_text(Font *font, const char *s, int len, Point pos, Image *color)
+{
+ mu_Command *cmd;
+ mu_Rect rect;
+ int clipped;
+
+ if (len < 0)
+ len = strlen(s);
+ rect = mu_rect(pos.x, pos.y, text_width(font, s, len), font->height);
+ clipped = mu_check_clip(rect);
+
+ if (clipped == MU_CLIP_ALL )
+ return;
+ if (clipped == MU_CLIP_PART)
+ mu_set_clip(mu_get_clip_rect());
+
+ if (mu_ctx.strmax <= mu_ctx.strnum+len+1) {
+ mu_ctx.strmax = MAX(mu_ctx.strmax, mu_ctx.strnum+len+1) * 2;
+ if ((mu_ctx.str = realloc(mu_ctx.str, mu_ctx.strmax)) == nil)
+ sysfatal("not enough memory for %d chars", mu_ctx.strmax);
+ }
+ cmd = mu_push_command(MU_COMMAND_TEXT);
+ cmd->text.s = mu_ctx.strnum;
+ memmove(mu_ctx.str+mu_ctx.strnum, s, len);
+ mu_ctx.strnum += len;
+ mu_ctx.str[mu_ctx.strnum++] = 0;
+ cmd->text.pos = pos;
+ cmd->text.color = color;
+ cmd->text.font = font;
+
+ /* reset clipping if it was set */
+ if (clipped)
+ mu_set_clip(unclipped_rect);
+}
+
+
+void
+mu_draw_icon(int id, mu_Rect rect)
+{
+ mu_Command *cmd;
+ /* do clip command if the rect isn't fully contained within the cliprect */
+ int clipped = mu_check_clip(rect);
+ if (clipped == MU_CLIP_ALL )
+ return;
+ if (clipped == MU_CLIP_PART)
+ mu_set_clip(mu_get_clip_rect());
+ /* do icon command */
+ cmd = mu_push_command(MU_COMMAND_ICON);
+ cmd->icon.id = id;
+ cmd->icon.rect = rect;
+ /* reset clipping if it was set */
+ if (clipped)
+ mu_set_clip(unclipped_rect);
+}
+
+
+/*============================================================================
+** layout
+**============================================================================*/
+
+enum
+{
+ RELATIVE = 1,
+ ABSOLUTE,
+};
+
+
+void
+mu_layout_begin_column(void)
+{
+ push_layout(mu_layout_next(), ZP);
+}
+
+
+void
+mu_layout_end_column(void)
+{
+ mu_Layout *a, *b;
+ b = get_layout();
+ mu_ctx.layoutsnum--;
+ /* inherit position/next_row/max from child layout if they are greater */
+ a = get_layout();
+ a->position.x = MAX(a->position.x, b->position.x + b->body.x - a->body.x);
+ a->next_row = MAX(a->next_row, b->next_row + b->body.y - a->body.y);
+ a->max.x = MAX(a->max.x, b->max.x);
+ a->max.y = MAX(a->max.y, b->max.y);
+}
+
+
+void
+mu_layout_row(int items, const int *widths, int height)
+{
+ mu_Layout *layout = get_layout();
+ if (widths) {
+ assert(items <= MU_MAX_WIDTHS);
+ memcpy(layout->widths, widths, items * sizeof(widths[0]));
+ }
+ layout->items = items;
+ layout->position = Pt(layout->indent, layout->next_row);
+ layout->size.y = height;
+ layout->row_index = 0;
+}
+
+
+void
+mu_layout_width(int width)
+{
+ get_layout()->size.x = width;
+}
+
+
+void
+mu_layout_height(int height)
+{
+ get_layout()->size.y = height;
+}
+
+
+void
+mu_layout_set_next(mu_Rect r, int relative)
+{
+ mu_Layout *layout = get_layout();
+ layout->next = r;
+ layout->next_type = relative ? RELATIVE : ABSOLUTE;
+}
+
+
+mu_Rect
+mu_layout_next(void)
+{
+ mu_Layout *layout = get_layout();
+ 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) {
+ mu_ctx.last_rect = res;
+ return res;
+ }
+
+ } else {
+ /* handle next row */
+ if (layout->row_index == layout->items)
+ mu_layout_row(layout->items, nil, layout->size.y);
+
+ /* position */
+ res.x = layout->position.x;
+ res.y = layout->position.y;
+
+ /* size */
+ res.w = layout->items > -1 ? layout->widths[layout->row_index] : layout->size.x;
+ res.h = layout->size.y;
+ if (res.w == 0)
+ res.w = mu_style.size.x + mu_style.padding * 2;
+ if (res.h == 0)
+ res.h = mu_style.size.y + mu_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->row_index++;
+ }
+
+ /* update position */
+ layout->position.x += res.w + mu_style.spacing;
+ layout->next_row = MAX(layout->next_row, res.y + res.h + mu_style.spacing);
+
+ /* apply body offset */
+ res.x += layout->body.x;
+ res.y += layout->body.y;
+
+ /* update max position */
+ layout->max.x = MAX(layout->max.x, res.x + res.w);
+ layout->max.y = MAX(layout->max.y, res.y + res.h);
+
+ mu_ctx.last_rect = res;
+ return res;
+}
+
+
+/*============================================================================
+** controls
+**============================================================================*/
+
+static int
+in_hover_root(void)
+{
+ int i = mu_ctx.cntnum;
+ while (i--) {
+ if (mu_ctx.cnt[i] == mu_ctx.last_hover_root)
+ return 1;
+ /* only root containers have their `head` field set; stop searching if we've
+ ** reached the current root container */
+ if (mu_ctx.cnt[i]->head >= 0)
+ break;
+ }
+ return 0;
+}
+
+
+void
+mu_draw_control_frame(mu_Id id, mu_Rect rect, int colorid, int opt)
+{
+ if (opt & MU_OPT_NOFRAME)
+ return;
+ colorid += (mu_ctx.focus == id) ? 2 : (mu_ctx.hover == id) ? 1 : 0;
+ draw_frame(rect, colorid);
+}
+
+
+void
+mu_draw_control_text(const char *str, mu_Rect rect, int colorid, int opt)
+{
+ Point pos;
+ Font *font = mu_style.font;
+ int tw = text_width(font, str, -1);
+ mu_push_clip_rect(rect);
+ pos.y = rect.y + (rect.h - font->height) / 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 - mu_style.padding;
+ else
+ pos.x = rect.x + mu_style.padding;
+
+ mu_draw_text(font, str, -1, pos, mu_style.colors[colorid]);
+ mu_pop_clip_rect();
+}
+
+
+int
+mu_mouse_over(mu_Rect rect)
+{
+ return rect_overlaps_vec2(rect, mu_ctx.mouse_pos) &&
+ rect_overlaps_vec2(mu_get_clip_rect(), mu_ctx.mouse_pos) &&
+ in_hover_root();
+}
+
+
+void
+mu_update_control(mu_Id id, mu_Rect rect, int opt)
+{
+ int mouseover = mu_mouse_over(rect);
+
+ if (mu_ctx.focus == id)
+ mu_ctx.updated_focus = 1;
+ if (opt & MU_OPT_NOINTERACT)
+ return;
+ if (mouseover && !mu_ctx.mouse_down)
+ mu_ctx.hover = id;
+
+ if (mu_ctx.focus == id) {
+ if (mu_ctx.mouse_pressed && !mouseover)
+ mu_set_focus(0);
+ if (!mu_ctx.mouse_down && ~opt & MU_OPT_HOLDFOCUS)
+ mu_set_focus(0);
+ }
+
+ if (mu_ctx.hover == id) {
+ if (!mouseover)
+ mu_ctx.hover = 0;
+ else if (mu_ctx.mouse_pressed)
+ mu_set_focus(id);
+ }
+}
+
+
+void
+mu_text(const char *text)
+{
+ const char *start, *end, *p = text;
+ int width = -1;
+ Font *font = mu_style.font;
+ Image *color = mu_style.colors[MU_COLOR_TEXT];
+ mu_layout_begin_column();
+ mu_layout_row(1, &width, font->height);
+ do {
+ mu_Rect r = mu_layout_next();
+ int w = 0;
+ start = end = p;
+ do {
+ const char* word = p;
+ while (*p && *p != ' ' && *p != '\n')
+ p++;
+ w += text_width(font, word, p - word);
+ if (w > r.w && end != start)
+ break;
+ w += text_width(font, p, 1);
+ end = p++;
+ } while (*end && *end != '\n');
+ mu_draw_text(font, start, end - start, Pt(r.x, r.y), color);
+ p = end + 1;
+ } while (*end);
+ mu_layout_end_column();
+}
+
+
+void
+mu_label(const char *text)
+{
+ mu_draw_control_text(text, mu_layout_next(), MU_COLOR_TEXT, 0);
+}
+
+
+int
+mu_button_ex(const char *label, int icon, int opt) {
+ int res = 0;
+ mu_Id id = label ? mu_get_id(label, strlen(label)) : mu_get_id(&icon, sizeof(icon));
+ mu_Rect r = mu_layout_next();
+ mu_update_control(id, r, opt);
+ /* handle click */
+ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.focus == id)
+ res |= MU_RES_SUBMIT;
+
+ /* draw */
+ mu_draw_control_frame(id, r, MU_COLOR_BUTTON, opt);
+ if (label)
+ mu_draw_control_text(label, r, MU_COLOR_TEXT, opt);
+ if (icon)
+ mu_draw_icon(icon, r);
+ return res;
+}
+
+
+int
+mu_button(const char *label)
+{
+ return mu_button_ex(label, 0, MU_OPT_ALIGNCENTER);
+}
+
+
+int
+mu_checkbox(int *state, const char *label)
+{
+ int res = 0;
+ mu_Id id = mu_get_id(&state, sizeof(state));
+ mu_Rect r = mu_layout_next();
+ mu_Rect box = mu_rect(r.x, r.y, r.h, r.h);
+ mu_update_control(id, r, 0);
+ /* handle click */
+ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.focus == id) {
+ res |= MU_RES_CHANGE;
+ *state = !*state;
+ }
+ /* draw */
+ mu_draw_control_frame(id, box, MU_COLOR_BASE, 0);
+ if (*state)
+ mu_draw_icon(MU_ICON_CHECK, box);
+
+ r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h);
+ mu_draw_control_text(label, r, MU_COLOR_TEXT, 0);
+ return res;
+}
+
+
+int
+mu_textbox_raw(char *buf, int bufsz, mu_Id id, mu_Rect r, int opt)
+{
+ int res = 0;
+ mu_update_control(id, r, opt | MU_OPT_HOLDFOCUS);
+
+ if (mu_ctx.focus == id) {
+ /* handle text input */
+ int len = strlen(buf);
+ int n = MIN(bufsz - len - 1, (int)strlen(mu_ctx.text_input));
+ if (n > 0) {
+ memcpy(buf + len, mu_ctx.text_input, n);
+ len += n;
+ buf[len] = '\0';
+ res |= MU_RES_CHANGE;
+ }
+ /* handle backspace */
+ if (mu_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;
+ }
+ if (mu_ctx.key_pressed & MU_KEY_NACK && len > 0) {
+ buf[0] = '\0';
+ res |= MU_RES_CHANGE;
+ }
+ /* handle return */
+ if (mu_ctx.key_pressed & MU_KEY_RETURN) {
+ mu_set_focus(0);
+ res |= MU_RES_SUBMIT;
+ }
+ }
+
+ /* draw */
+ mu_draw_control_frame(id, r, MU_COLOR_BASE, opt);
+ if (mu_ctx.focus == id) {
+ Image *color = mu_style.colors[MU_COLOR_TEXT];
+ Font *font = mu_style.font;
+ int textw = text_width(font, buf, -1);
+ int texth = font->height;
+ int ofx = r.w - mu_style.padding - textw - 1;
+ int textx = r.x + MIN(ofx, mu_style.padding);
+ int texty = r.y + (r.h - texth) / 2;
+ mu_push_clip_rect(r);
+ mu_draw_text(font, buf, -1, Pt(textx, texty), color);
+ mu_draw_rect(mu_rect(textx + textw, texty, 1, texth), color);
+ mu_pop_clip_rect();
+ } else {
+ mu_draw_control_text(buf, r, MU_COLOR_TEXT, opt);
+ }
+
+ return res;
+}
+
+
+static int
+number_textbox(double *value, mu_Rect r, mu_Id id)
+{
+ if (((mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.key_down & MU_KEY_SHIFT) || mu_ctx.mouse_pressed == MU_MOUSE_RIGHT) && mu_ctx.hover == id) {
+ mu_ctx.number_editing = id;
+ sprint(mu_ctx.number_buf, MU_REAL_FMT, *value);
+ }
+ if (mu_ctx.number_editing == id) {
+ int res = mu_textbox_raw(mu_ctx.number_buf, sizeof(mu_ctx.number_buf), id, r, 0);
+ if (res & MU_RES_SUBMIT || mu_ctx.focus != id) {
+ *value = strtod(mu_ctx.number_buf, nil);
+ mu_ctx.number_editing = 0;
+ } else {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+int
+mu_textbox_ex(char *buf, int bufsz, int opt)
+{
+ mu_Id id = mu_get_id(&buf, sizeof(buf));
+ mu_Rect r = mu_layout_next();
+ return mu_textbox_raw(buf, bufsz, id, r, opt);
+}
+
+
+int
+mu_textbox(char *buf, int bufsz)
+{
+ return mu_textbox_ex(buf, bufsz, 0);
+}
+
+
+int
+mu_slider_ex(double *value, double low, double high, double step, const char *fmt, int opt)
+{
+ char buf[MU_MAX_FMT + 1];
+ mu_Rect thumb;
+ int w, res = 0;
+ double normalized, last = *value, v = last;
+ mu_Id id = mu_get_id(&value, sizeof(value));
+ mu_Rect base = mu_layout_next();
+
+ /* handle text input mode */
+ if (number_textbox(&v, base, id))
+ return res;
+
+ /* handle normal mode */
+ mu_update_control(id, base, opt);
+
+ /* handle input */
+ if (mu_ctx.focus == id) {
+ if (mu_ctx.mouse_down == MU_MOUSE_LEFT)
+ v = low + ((double)(mu_ctx.mouse_pos.x - base.x) / base.w) * (high - low);
+ } else if (mu_ctx.hover == id) {
+ if ((mu_ctx.key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) == MU_KEY_LEFT) {
+ v -= step ? step : 1;
+ if (v < low)
+ v = low;
+ } else if ((mu_ctx.key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) == MU_KEY_RIGHT) {
+ v += step ? step : 1;
+ if (v > high)
+ v = high;
+ }
+ }
+
+ if (step)
+ v = ((long)((v + (v > 0 ? step/2 : (-step/2))) / step)) * step;
+ /* clamp and store value, update res */
+ *value = v = CLAMP(v, low, high);
+ if (last != v)
+ res |= MU_RES_CHANGE;
+
+ /* draw base */
+ mu_draw_control_frame(id, base, MU_COLOR_BASE, opt);
+ /* draw thumb */
+ w = mu_style.thumb_size;
+ normalized = (v - low) / (high - low);
+ thumb = mu_rect(base.x + normalized * (base.w - w), base.y, w, base.h);
+ mu_draw_control_frame(id, thumb, MU_COLOR_BUTTON, opt);
+ /* draw text */
+ sprint(buf, fmt, v);
+ mu_draw_control_text(buf, base, MU_COLOR_TEXT, opt);
+
+ return res;
+}
+
+
+int
+mu_slider(double *value, double low, double high)
+{
+ return mu_slider_ex(value, low, high, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER);
+}
+
+
+int
+mu_number_ex(double *value, double step, const char *fmt, int opt)
+{
+ char buf[MU_MAX_FMT + 1];
+ int res = 0;
+ mu_Id id = mu_get_id(&value, sizeof(value));
+ mu_Rect base = mu_layout_next();
+ double last = *value;
+
+ /* handle text input mode */
+ if (number_textbox(value, base, id))
+ return res;
+
+ /* handle normal mode */
+ mu_update_control(id, base, opt);
+
+ /* handle input */
+ if (mu_ctx.focus == id && mu_ctx.mouse_down == MU_MOUSE_LEFT)
+ *value += mu_ctx.mouse_delta.x * step;
+
+ /* set flag if value changed */
+ if (*value != last)
+ res |= MU_RES_CHANGE;
+
+ /* draw base */
+ mu_draw_control_frame(id, base, MU_COLOR_BASE, opt);
+ /* draw text */
+ sprint(buf, fmt, *value);
+ mu_draw_control_text(buf, base, MU_COLOR_TEXT, opt);
+
+ return res;
+}
+
+
+int
+mu_number(double *value, double step)
+{
+ return mu_number_ex(value, step, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER);
+}
+
+
+static int
+header(int *state, const char *label, int istreenode)
+{
+ mu_Rect r;
+ mu_Id id;
+ int width = -1;
+ mu_layout_row(1, &width, 0);
+ r = mu_layout_next();
+ id = mu_get_id(&state, sizeof(state));
+ mu_update_control(id, r, 0);
+ /* handle click */
+ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.focus == id)
+ *state = !(*state);
+
+ /* draw */
+ if (istreenode) {
+ if (mu_ctx.hover == id)
+ draw_frame(r, MU_COLOR_BUTTONHOVER);
+ } else {
+ mu_draw_control_frame(id, r, MU_COLOR_BUTTON, 0);
+ }
+ mu_draw_icon(
+ *state ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED,
+ mu_rect(r.x, r.y, r.h, r.h)
+ );
+ r.x += r.h - mu_style.padding;
+ r.w -= r.h - mu_style.padding;
+ mu_draw_control_text(label, r, MU_COLOR_TEXT, 0);
+ return *state ? MU_RES_ACTIVE : 0;
+}
+
+
+int
+mu_header(int *state, const char *label)
+{
+ return header(state, label, 0);
+}
+
+
+int
+mu_begin_treenode(int *state, const char *label)
+{
+ int res = header(state, label, 1);
+ if (res & MU_RES_ACTIVE) {
+ get_layout()->indent += mu_style.indent;
+ mu_push_id(&state, sizeof(void*));
+ }
+ return res;
+}
+
+
+void
+mu_end_treenode(void)
+{
+ get_layout()->indent -= mu_style.indent;
+ mu_pop_id();
+}
+
+static void
+scrollbar(mu_Container *cnt, mu_Rect *b, Point cs, int v)
+{
+ /* only add scrollbar if content size is larger than body */
+ int maxscroll = v ? cs.y - b->h : cs.x - b->w;
+
+ if (maxscroll > 0 && (v ? b->h : b->x) > 0) {
+ mu_Rect base, thumb;
+ mu_Id id = mu_get_id(v ? "!scrollbary" : "!scrollbarx", 11);
+
+ /* get sizing / positioning */
+ base = *b;
+ if (v) {
+ base.x = b->x + b->w;
+ base.w = mu_style.scrollbar_size;
+ } else {
+ base.y = b->y + b->h;
+ base.h = mu_style.scrollbar_size;
+ }
+
+ /* handle input */
+ mu_update_control(id, base, 0);
+ if (mu_ctx.focus == id && mu_ctx.mouse_down == MU_MOUSE_LEFT) {
+ if (v)
+ cnt->scroll.y += mu_ctx.mouse_delta.y * cs.y / base.h;
+ else
+ cnt->scroll.x += mu_ctx.mouse_delta.x * cs.x / base.w;
+ }
+ /* clamp scroll to limits */
+ if (v)
+ cnt->scroll.y = CLAMP(cnt->scroll.y, 0, maxscroll);
+ else
+ cnt->scroll.x = CLAMP(cnt->scroll.x, 0, maxscroll);
+
+ /* draw base and thumb */
+ draw_frame(base, MU_COLOR_SCROLLBASE);
+ thumb = base;
+ if (v) {
+ thumb.h = MAX(mu_style.thumb_size, base.h * b->h / cs.y);
+ thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll;
+ } else {
+ thumb.w = MAX(mu_style.thumb_size, base.w * b->w / cs.x);
+ thumb.x += cnt->scroll.x * (base.w - thumb.w) / maxscroll;
+ }
+ draw_frame(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(*b))
+ mu_ctx.scroll_target = cnt;
+ } else if (v) {
+ cnt->scroll.y = 0;
+ } else {
+ cnt->scroll.x = 0;
+ }
+}
+
+static void
+scrollbars(mu_Container *cnt, mu_Rect *body)
+{
+ int sz = mu_style.scrollbar_size;
+ Point cs = cnt->content_size;
+ cs.x += mu_style.padding * 2;
+ cs.y += mu_style.padding * 2;
+ mu_push_clip_rect(*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(cnt, body, cs, 1);
+ scrollbar(cnt, body, cs, 0);
+ mu_pop_clip_rect();
+}
+
+static void
+push_container_body(mu_Container *cnt, mu_Rect body, int opt)
+{
+ if (~opt & MU_OPT_NOSCROLL)
+ scrollbars(cnt, &body);
+ push_layout(expand_rect(body, -mu_style.padding), cnt->scroll);
+ cnt->body = body;
+}
+
+static void
+begin_root_container(mu_Container *cnt)
+{
+ push_container(cnt);
+
+ /* push container to roots list and push head command */
+ buffer_grow(&mu_ctx.root, sizeof(*mu_ctx.root), &mu_ctx.rootmax, mu_ctx.rootnum);
+ mu_ctx.root[mu_ctx.rootnum++] = cnt;
+ cnt->head = push_jump(-1);
+
+ /* 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, mu_ctx.mouse_pos) && (!mu_ctx.hover_root || cnt->zindex > mu_ctx.hover_root->zindex))
+ mu_ctx.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 */
+ buffer_grow(&mu_ctx.clip, sizeof(*mu_ctx.clip), &mu_ctx.clipmax, mu_ctx.clipnum);
+ mu_ctx.clip[mu_ctx.clipnum++] = unclipped_rect;
+}
+
+
+static void
+end_root_container(void)
+{
+ /* 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_container();
+ cnt->tail = push_jump(-1);
+ mu_ctx.cmds[cnt->head].jump.dst = mu_ctx.cmdsnum;
+ /* pop base clip rect and container */
+ mu_pop_clip_rect();
+ pop_container();
+}
+
+
+int
+mu_begin_window_ex(mu_Container *cnt, const char *title, int opt)
+{
+ mu_Rect rect, body, titlerect;
+
+ if (!cnt->inited)
+ mu_init_window(cnt, opt);
+ if (!cnt->open)
+ return 0;
+
+ begin_root_container(cnt);
+ rect = cnt->rect;
+ body = rect;
+
+ /* draw frame */
+ if (~opt & MU_OPT_NOFRAME)
+ draw_frame(rect, MU_COLOR_WINDOWBG);
+
+ /* moving all windows by "dragging" background */
+ if (mu_ctx.last_hover_root == nil && mu_ctx.hover_root == nil && mu_ctx.mouse_pressed == MU_MOUSE_LEFT)
+ mu_ctx.moving = 1;
+ else if (mu_ctx.mouse_down != MU_MOUSE_LEFT)
+ mu_ctx.moving = 0;
+ if (mu_ctx.moving) {
+ cnt->rect.x += mu_ctx.mouse_delta.x;
+ cnt->rect.y += mu_ctx.mouse_delta.y;
+ }
+
+ /* do title bar */
+ titlerect = rect;
+ titlerect.h = mu_style.title_height;
+ if (~opt & MU_OPT_NOTITLE) {
+ mu_Id id = mu_get_id("!title", 6);
+
+ draw_frame(titlerect, MU_COLOR_TITLEBG);
+
+ /* do title text */
+ mu_update_control(id, titlerect, opt);
+ mu_draw_control_text(title, titlerect, MU_COLOR_TITLETEXT, opt);
+ if (id == mu_ctx.focus && mu_ctx.mouse_down == MU_MOUSE_LEFT) {
+ cnt->rect.x += mu_ctx.mouse_delta.x;
+ cnt->rect.y += mu_ctx.mouse_delta.y;
+ }
+ body.y += titlerect.h;
+ body.h -= titlerect.h;
+
+ /* do `close` button */
+ if (~opt & MU_OPT_NOCLOSE) {
+ mu_Id id = mu_get_id("!close", 6);
+ mu_Rect r = mu_rect(
+ titlerect.x + titlerect.w - titlerect.h,
+ titlerect.y, titlerect.h, titlerect.h
+ );
+ titlerect.w -= r.w;
+ mu_draw_icon(MU_ICON_CLOSE, r);
+ mu_update_control(id, r, opt);
+ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && id == mu_ctx.focus)
+ cnt->open = 0;
+ }
+ }
+
+ push_container_body(cnt, body, opt);
+
+ /* do `resize` handle */
+ if (~opt & MU_OPT_NORESIZE) {
+ int sz = mu_style.scrollbar_size;
+ mu_Id id = mu_get_id("!resize", 7);
+ mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
+ mu_update_control(id, r, opt);
+ mu_draw_icon(MU_ICON_RESIZE, r);
+ if (id == mu_ctx.focus && mu_ctx.mouse_down == MU_MOUSE_LEFT) {
+ cnt->rect.w = MAX(96, cnt->rect.w + mu_ctx.mouse_delta.x);
+ cnt->rect.h = MAX(64, cnt->rect.h + mu_ctx.mouse_delta.y);
+ }
+ }
+
+ /* resize to content size */
+ if (opt & MU_OPT_AUTOSIZE) {
+ mu_Rect r = get_layout()->body;
+ if (opt & MU_OPT_AUTOSIZE_W)
+ cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w);
+ if (opt & MU_OPT_AUTOSIZE_H)
+ 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 && mu_ctx.mouse_pressed && mu_ctx.last_hover_root != cnt)
+ cnt->open = 0;
+
+ mu_push_clip_rect(cnt->body);
+ return MU_RES_ACTIVE;
+}
+
+
+int
+mu_begin_window(mu_Container *cnt, const char *title)
+{
+ return mu_begin_window_ex(cnt, title, 0);
+}
+
+
+void
+mu_end_window(void)
+{
+ mu_pop_clip_rect();
+ end_root_container();
+}
+
+
+void
+mu_open_popup(mu_Container *cnt)
+{
+ /* set as hover root so popup isn't closed in begin_window_ex() */
+ mu_ctx.last_hover_root = mu_ctx.hover_root = cnt;
+ /* init container if not inited */
+ if (!cnt->inited)
+ mu_init_window(cnt, 0);
+ /* position at mouse cursor, open and bring-to-front */
+ cnt->rect = mu_rect(mu_ctx.mouse_pos.x, mu_ctx.mouse_pos.y, 0, 0);
+ cnt->open = 1;
+ mu_bring_to_front(cnt);
+}
+
+
+int
+mu_begin_popup(mu_Container *cnt)
+{
+ return mu_begin_window_ex(cnt, "", MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE | MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED);
+}
+
+
+void
+mu_end_popup(void)
+{
+ mu_end_window();
+}
+
+
+void
+mu_begin_panel_ex(mu_Container *cnt, int opt)
+{
+ cnt->rect = mu_layout_next();
+ if (~opt & MU_OPT_NOFRAME)
+ draw_frame(cnt->rect, MU_COLOR_PANELBG);
+
+ push_container(cnt);
+ push_container_body(cnt, cnt->rect, opt);
+ mu_push_clip_rect(cnt->body);
+}
+
+
+void
+mu_begin_panel(mu_Container *cnt)
+{
+ mu_begin_panel_ex(cnt, 0);
+}
+
+
+void
+mu_end_panel(void)
+{
+ mu_pop_clip_rect();
+ pop_container();
+}
+
+int
+mu_render(void)
+{
+ mu_Command *cmd;
+ mu_Rect r, iconr;
+
+ if (memcmp(&mu_ctx.screen, &screen->r, sizeof(mu_ctx.screen)) != 0)
+ mu_ctx.screen = screen->r;
+ else if (mu_ctx.oldcmdsnum == mu_ctx.cmdsnum && memcmp(mu_ctx.oldcmds, mu_ctx.cmds, mu_ctx.cmdsnum*sizeof(mu_Command)) == 0)
+ if (mu_ctx.oldstrnum == mu_ctx.strnum && memcmp(mu_ctx.oldstr, mu_ctx.str, mu_ctx.strnum) == 0)
+ return 0;
+
+ if (mu_ctx.oldcmdsmax != mu_ctx.cmdsmax && (mu_ctx.oldcmds = realloc(mu_ctx.oldcmds, mu_ctx.cmdsmax*sizeof(mu_Command))) == nil)
+ sysfatal("couldn't allocate memory for old cmds");
+ mu_ctx.oldcmdsmax = mu_ctx.cmdsmax;
+ mu_ctx.oldcmdsnum = mu_ctx.cmdsnum;
+ memmove(mu_ctx.oldcmds, mu_ctx.cmds, mu_ctx.cmdsnum*sizeof(mu_Command));
+
+ if (mu_ctx.oldstrmax != mu_ctx.strmax && (mu_ctx.oldstr = realloc(mu_ctx.oldstr, mu_ctx.strmax)) == nil)
+ sysfatal("couldn't allocate memory for old strings");
+ mu_ctx.oldstrmax = mu_ctx.strmax;
+ mu_ctx.oldstrnum = mu_ctx.strnum;
+ memmove(mu_ctx.oldstr, mu_ctx.str, mu_ctx.strnum);
+
+ draw(screen, screen->r, mu_style.colors[MU_COLOR_BG], nil, ZP);
+
+ for (cmd = mu_ctx.cmds; cmd < mu_ctx.cmds + mu_ctx.cmdsnum;) {
+ switch (cmd->type) {
+ case MU_COMMAND_TEXT:
+ if (cmd->text.color != nil)
+ string(screen, addpt(screen->r.min, cmd->text.pos), cmd->text.color, ZP, mu_style.font, mu_ctx.str+cmd->text.s);
+ break;
+
+ case MU_COMMAND_RECT:
+ if (cmd->rect.color != nil)
+ draw(screen, screenrect(cmd->rect.rect), cmd->rect.color, nil, ZP);
+ break;
+
+ case MU_COMMAND_ICON:
+ r = cmd->icon.rect;
+ iconr = atlasicons[cmd->icon.id];
+ r.x += (r.w - iconr.w) / 2;
+ r.y += (r.h - iconr.h) / 2;
+ r.w = iconr.w;
+ r.h = iconr.h;
+ draw(screen, screenrect(r), atlasimage, nil, Pt(iconr.x, iconr.y));
+ break;
+
+ case MU_COMMAND_CLIP:
+ replclipr(screen, 0, screenrect(cmd->clip.rect));
+ break;
+
+ case MU_COMMAND_JUMP:
+ if (cmd->jump.dst < 0)
+ return 1;
+ cmd = &mu_ctx.cmds[cmd->jump.dst];
+ continue;
+ }
+
+ cmd++;
+ }
+
+ return 1;
+}
--- /dev/null
+++ b/microui.h
@@ -1,0 +1,322 @@
+#pragma lib "libmicroui.a"
+
+typedef unsigned mu_Id;
+
+typedef struct mu_Command mu_Command;
+typedef struct mu_Container mu_Container;
+typedef struct mu_Context mu_Context;
+typedef struct mu_Layout mu_Layout;
+typedef struct mu_Style mu_Style;
+typedef struct mu_Rect mu_Rect;
+
+enum
+{
+ MU_MAX_WIDTHS = 16,
+ MU_MAX_FMT = 127,
+};
+
+enum
+{
+ MU_CLIP_NONE,
+ MU_CLIP_PART,
+ MU_CLIP_ALL,
+};
+
+enum
+{
+ MU_COMMAND_JUMP,
+ MU_COMMAND_CLIP,
+ MU_COMMAND_RECT,
+ MU_COMMAND_TEXT,
+ MU_COMMAND_ICON,
+ MU_COMMAND_MAX,
+};
+
+enum
+{
+ MU_COLOR_BG,
+ 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_CHECK,
+ MU_ICON_CLOSE,
+ MU_ICON_COLLAPSED,
+ MU_ICON_EXPANDED,
+ MU_ICON_RESIZE,
+ ATLAS_DIMENSIONS,
+};
+
+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_W = 1<<9,
+ MU_OPT_AUTOSIZE_H = 1<<10,
+ MU_OPT_AUTOSIZE = (MU_OPT_AUTOSIZE_W|MU_OPT_AUTOSIZE_H),
+ MU_OPT_POPUP = 1<<10,
+ MU_OPT_CLOSED = 1<<11,
+};
+
+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,
+ MU_KEY_NACK = 1<<5,
+ MU_KEY_LEFT = 1<<6,
+ MU_KEY_RIGHT = 1<<7,
+};
+
+struct mu_Rect
+{
+ int x;
+ int y;
+ int w;
+ int h;
+};
+
+struct mu_Command
+{
+ int type;
+
+ union
+ {
+ struct
+ {
+ int dst;
+ }jump;
+
+ struct
+ {
+ mu_Rect rect;
+ }clip;
+
+ struct
+ {
+ Image *color;
+ mu_Rect rect;
+ }rect;
+
+ struct
+ {
+ Font *font;
+ Image *color;
+ int s;
+ Point pos;
+ }text;
+
+ struct
+ {
+ mu_Rect rect;
+ int id;
+ }icon;
+ };
+};
+
+struct mu_Layout
+{
+ mu_Rect body;
+ mu_Rect next;
+ Point position;
+ Point size;
+ Point max;
+ int widths[MU_MAX_WIDTHS];
+ int items;
+ int row_index;
+ int next_row;
+ int next_type;
+ int indent;
+};
+
+struct mu_Container
+{
+ int head, tail;
+ mu_Rect rect;
+ mu_Rect body;
+ Point content_size;
+ Point scroll;
+ int inited;
+ int zindex;
+ int open;
+};
+
+struct mu_Style
+{
+ Font *font;
+ Point size;
+ int padding;
+ int spacing;
+ int indent;
+ int title_height;
+ int scrollbar_size;
+ int thumb_size;
+ Image *colors[MU_COLOR_MAX];
+};
+
+struct mu_Context
+{
+ /* core state */
+ mu_Id hover;
+ mu_Id focus;
+ mu_Id last_id;
+ mu_Rect last_rect;
+ int last_zindex;
+ int updated_focus;
+ mu_Container *hover_root;
+ mu_Container *last_hover_root;
+ mu_Container *scroll_target;
+ char number_buf[MU_MAX_FMT];
+ mu_Id number_editing;
+ Rectangle screen;
+ int moving;
+
+ /* buffers */
+ char *str, *oldstr;
+ int strmax, strnum, oldstrmax, oldstrnum;
+
+ mu_Container **root;
+ int rootmax, rootnum;
+
+ mu_Container **cnt;
+ int cntmax, cntnum;
+
+ mu_Rect *clip;
+ int clipmax, clipnum;
+
+ mu_Id *ids;
+ int idsmax, idsnum;
+
+ mu_Layout *layouts;
+ int layoutsmax, layoutsnum;
+
+ mu_Command *cmds, *oldcmds;
+ int cmdsmax, cmdsnum, oldcmdsmax, oldcmdsnum;
+
+ /* input state */
+ Point mouse_pos;
+ Point last_mouse_pos;
+ Point mouse_delta;
+ Point scroll_delta;
+ int mouse_down;
+ int mouse_pressed;
+ int key_down;
+ int key_pressed;
+ char text_input[64];
+};
+
+extern Image *atlasimage;
+extern mu_Rect *atlasicons;
+extern u8int defaultcolors[MU_COLOR_MAX][4];
+extern mu_Context mu_ctx;
+extern mu_Style mu_style;
+
+mu_Rect mu_rect(int x, int y, int w, int h);
+Image *mu_color(u8int r, u8int g, u8int b, u8int a);
+
+void mu_init(void);
+int mu_render(void);
+void mu_begin(void);
+void mu_end(void);
+void mu_set_focus(mu_Id id);
+mu_Id mu_get_id(const void *data, int size);
+void mu_push_id(const void *data, int size);
+void mu_pop_id(void);
+void mu_push_clip_rect(mu_Rect rect);
+void mu_pop_clip_rect(void);
+mu_Rect mu_get_clip_rect(void);
+int mu_check_clip(mu_Rect r);
+mu_Container *mu_get_container(void);
+void mu_init_window(mu_Container *cnt, int opt);
+void mu_bring_to_front(mu_Container *cnt);
+
+void mu_input_mousemove(int x, int y);
+void mu_input_mousedown(int x, int y, int btn);
+void mu_input_mouseup(int x, int y, int btn);
+void mu_input_scroll(int x, int y);
+void mu_input_keydown(int key);
+void mu_input_keyup(int key);
+void mu_input_text(const char *text);
+
+mu_Command *mu_push_command(int type);
+void mu_set_clip(mu_Rect rect);
+void mu_draw_rect(mu_Rect rect, Image *color);
+void mu_draw_box(mu_Rect rect, Image *color);
+void mu_draw_text(Font *font, const char *str, int len, Point pos, Image *color);
+void mu_draw_icon(int id, mu_Rect rect);
+
+void mu_layout_row(int items, const int *widths, int height);
+void mu_layout_width(int width);
+void mu_layout_height(int height);
+void mu_layout_begin_column(void);
+void mu_layout_end_column(void);
+void mu_layout_set_next(mu_Rect r, int relative);
+mu_Rect mu_layout_next(void);
+
+void mu_draw_control_frame(mu_Id id, mu_Rect rect, int colorid, int opt);
+void mu_draw_control_text(const char *str, mu_Rect rect, int colorid, int opt);
+int mu_mouse_over(mu_Rect rect);
+void mu_update_control(mu_Id id, mu_Rect rect, int opt);
+
+void mu_text(const char *text);
+void mu_label(const char *text);
+int mu_button_ex(const char *label, int icon, int opt);
+int mu_button(const char *label);
+int mu_checkbox(int *state, const char *label);
+int mu_textbox_raw(char *buf, int bufsz, mu_Id id, mu_Rect r, int opt);
+int mu_textbox_ex(char *buf, int bufsz, int opt);
+int mu_textbox(char *buf, int bufsz);
+int mu_slider_ex(double *value, double low, double high, double step, const char *fmt, int opt);
+int mu_slider(double *value, double low, double high);
+int mu_number_ex(double *value, double step, const char *fmt, int opt);
+int mu_number(double *value, double step);
+int mu_header(int *state, const char *label);
+int mu_begin_treenode(int *state, const char *label);
+void mu_end_treenode(void);
+int mu_begin_window_ex(mu_Container *cnt, const char *title, int opt);
+int mu_begin_window(mu_Container *cnt, const char *title);
+void mu_end_window(void);
+void mu_open_popup(mu_Container *cnt);
+int mu_begin_popup(mu_Container *cnt);
+void mu_end_popup(void);
+void mu_begin_panel_ex(mu_Container *cnt, int opt);
+void mu_begin_panel(mu_Container *cnt);
+void mu_end_panel(void);
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,13 @@
+</$objtype/mkfile
+LIB=/$objtype/lib/libmicroui.a
+
+OFILES=\
+ microui.$O\
+
+HFILES=\
+ /sys/include/microui.h\
+
+/sys/include/%.h: %.h
+ cp $stem.h /sys/include/$stem.h
+
+</sys/src/cmd/mksyslib