• Jump To … +
    microui-header.h microui-source.c
  • microui-source.c

  • §
    /*
    ** Copyright (c) 2020 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 <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "microui-header.h"
  • §

    Poor man’s assert

    #define expect(x) do {                                               \
        if (!(x)) {                                                      \
          fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \
            __FILE__, __LINE__, #x);                                     \
          abort();                                                       \
        }                                                                \
      } while (0)
  • §

    Usage

    • Overview
    • Getting Started
    • Layout System
    • Style Customisation
    • Custom Controls
  • §

    Initializing mu_Context

    Before use a mu_Context should be initialised:

    mu_Context *ctx = malloc(sizeof(mu_Context));
    mu_init(ctx);
    

    Following which the context’s text_width and text_height callback functions should be set:

    ctx->text_width = text_width;
    ctx->text_height = text_height;
    
    static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid);
    static mu_Style default_style;
    
    void mu_init(mu_Context *ctx,
                 int (*text_width)(mu_Font font, const char *str, int len),
                 int (*text_height)(mu_Font font)) {
      memset(ctx, 0, sizeof(*ctx));
  • §

    ctx->draw_frame = draw_frame; // why is this flexibility necessary?

      
      ctx->_style = default_style;
      ctx->style = &ctx->_style;
      ctx->text_width = text_width;
      ctx->text_height = text_height;
    }
  • §

    The default style is encoded in a struct which represents TODO

    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 */
      }
    };
  • §

    The Main Loop

    The overall structure when using the library is as follows:

    initialise `mu_Context`
    
    main loop:
      1. call `mu_input_...` functions
      2. call `mu_begin()`
      3. process ui
      4. call `mu_end()`
      5. iterate commands/callbacks/events with
           `mu_command_next()`
    
  • §

    1. Input handling with mu_input_...

    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.

    
    /*============================================================================
    ** 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;
  • §

    NOTE: vvv this code is NOT present, so mouse_pressed tracks a different state than mouse_down. ctx->mouse_pressed &= ~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;
  • §

    NOTE: vvv this code is NOT present, so key_pressed tracks a different state. ctx->key_pressed &= ~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);
    }
  • §

    Macros for push & pop

    These macros are used to push and pop into a C stack data structure.

    #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)
  • §

    Metadata: ID management

    /* 32bit fnv-1a hash */
    #define HASH_INITIAL 2166136261
  • §

    Implementation of the Fowler-Noll-Vo hash function.

    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; // size of stack.
      mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL;
      hash(&res, data, size);
      ctx->last_id = res;
      return res;
    }
  • §

    create a new ID.

    void mu_push_id(mu_Context *ctx, const void *data, int size) {
      push(ctx->id_stack, mu_get_id(ctx, data, size));
    }
  • §

    pop the last ID that was pushed.

    void mu_pop_id(mu_Context *ctx) {
      pop(ctx->id_stack);
    }
  • §

    2. Call mu_finalize_events_begin_draw

  • §

    After handling the input the mu_finalize_events_begin_draw() function must be called before processing your UI:

    mu_finalize_events_begin_draw(ctx);
    
    void mu_finalize_events_begin_draw(mu_Context *ctx) {
  • §

    check that text_width and text_height are initialized.

      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++;
    }
  • §

    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_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.

    if (mu_begin_window(ctx, "My Window", 
          mu_rect(10, 10, 300, 400))) {
      /* 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.

    static mu_Container* get_container(mu_Context *ctx, mu_Id id, int opt);
    static void mu_begin_window_ex_begin_root_container(mu_Context *ctx, mu_Container *cnt);
    static void push_container_body(  mu_Context *ctx, mu_Container *cnt, mu_Rect body, int opt);
    static mu_Layout* get_layout(mu_Context *ctx);
    
    
    
    int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt) {
      mu_Rect body;
  • §

    hash object based on title.

      mu_Id id = mu_get_id(ctx, title, strlen(title));
  • §

    find the container for this, and raise it to the front.

      mu_Container *cnt = get_container(ctx, id, opt);
  • §

    if we can’t find a container, or it is closed, give up.

      if (!cnt || !cnt->open) { return 0; }
  • §

    push the container ID onto the stack. (TODO: why?)

      push(ctx->id_stack, id);
  • §

    if container is new(?), set its rect. (TODO: WHY?)

      if (cnt->rect.w == 0) { cnt->rect = rect; }
    
      mu_begin_window_ex_begin_root_container(ctx, cnt);
      rect = body = cnt->rect;
    
      /* draw frame */
  • §

    if opt does NOT have MU_OPT_NOFRAME set:

      if (~opt & MU_OPT_NOFRAME) {
        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;
        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;
    }
    
    
    static void end_root_container(mu_Context *ctx);
    void mu_pop_clip_rect(mu_Context *ctx);
    
    void mu_end_window(mu_Context *ctx) {
      mu_pop_clip_rect(ctx);
      end_root_container(ctx);
    }
  • §

    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:

    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 typically generated from the name/label passed to the function, or, in the case of sliders and checkboxes the value pointer. 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:

    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:

    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:

    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);
      }
    }
    

    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:

    /* 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:

    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 0, 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:

    mu_layout_row(ctx, 0, 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:

    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:

    /* 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 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:

    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;
    }
    
    
    #define unused(x) ((void) (x))
    
    
    static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 };
  • §

    Vectors

    these are used to define graphics primitives.

    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;
    }
  • §

    Expand

    expands a rectangle by n in all directions

    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);
    }
  • §

    Intersect

    Intersects two rectangles.

    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);
    }
  • §

    Overlap Check if vector lies in rectangle

    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;
    }
  • §

    Draw Frame: draw a frame, with an optional border.

    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_border_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_BORDER]);
      }
    }
  • §

    Compare Z index Retuurn difference of Z indeces between containers a and b.

    static int compare_zindex(const void *a, const void *b) {
      return (*(mu_Container**) a)->zindex - (*(mu_Container**) b)->zindex;
    }
  • §

    End

  • §
  • §

    Check that all stacks have been flushed, apply scrolling and events.

    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);
  • §

    TODO: what is a jump command? sset 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;
    }
    
    
    
    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);
    }
  • §

    return top of layout stack

    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) {
  • §

    TODO: why is this || ?

        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;
  • §

    bring container to front by increasing z-index.

      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 n = -1, f = ctx->frame;
      for (int i = 0; i < len; i++) {
  • §

    find rightmost old slot and reuse it. TODO: why rightmost?

        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;
    }
    
    
    
    
    /*============================================================================
    ** 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;
    }
  • §

    return 1 if there is a command to be processsed, and 0 if all commands are exhausted.

    int mu_next_command(mu_Context *ctx, mu_Command **cmd) {
      if (*cmd) {
  • §

    go to the next location in memory where we have a command.

        *cmd = (mu_Command*) (((char*) *cmd) + (*cmd)->base.size);
      } else {
  • §

    start at first command.

        *cmd = (mu_Command*) ctx->command_list.items;
      }
  • §

    follow jump commands till we reach a non-jump command, or reach end of list.

      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;
    }
  • §

    queue a clipping command.

    void mu_draw_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;
    }
  • §

    queue a draw rectangle command.

    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;
      }
    }
  • §

    Draw a box of 1px width around the rectangle.

    void mu_draw_border_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_draw_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_draw_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_draw_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_draw_clip(ctx, unclipped_rect); }
    }
    
    
    /*============================================================================
    ** layout
    **============================================================================*/
  • §

    Should we have begin/end for row as well as column?

    
    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;
  • §

    top of layout stack; the column we had begun

      b = get_layout(ctx);
      pop(ctx->layout_stack);
      /* inherit position/next_row/max from child layout if they are greater */
      a = get_layout(ctx);
  • §

    TODO: think about these computations. The idea is to reflow the parent of the column, based on the column data.

      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);
    }
  • §

    create a new layout row.

    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;
    }
  • §

    set layout width.

    void mu_layout_width(mu_Context *ctx, int width) {
      get_layout(ctx)->size.x = width;
    }
  • §

    set layout height.

    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;
    }
  • §

    key function that performs layouting!

    mu_Rect mu_layout_next(mu_Context *ctx) {
      mu_Layout *layout = get_layout(ctx);
      mu_Style *style = ctx->style;
      mu_Rect res;
  • §

    no idea what it means for next_type to be unset!

      if (layout->next_type) {
        /* handle rect set by `mu_layout_set_next` */
        int type = layout->next_type;
        layout->next_type = 0;
        res = layout->next;
  • §

    TODO: we don’t increment item_index?

        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 zero, then auto-size??

        if (res.w == 0) { res.w = style->size.x + style->padding * 2; }
        if (res.h == 0) { res.h = style->size.y + style->padding * 2; }
  • §

    negative width, height is interpreted as: leave this much room from the border.

        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
    **============================================================================*/
  • §

    check if hover_root is currently being drawn. This is checked by walking the container stack, looking for our element.

    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;
    }
  • §

    draw a frame which colors based on focus/hover. Hence, a “control frame”, such as a button.

    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;
      draw_frame(ctx, rect, colorid);
    }
  • §

    draw text clipped to the rectangle.

    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);
    }
  • §

    check that mouse is where we are, and that we are being drawn, and that we are being hovered on (TODO: I guess the last check is to make sure there is nothing above us, z-index wise?)

    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);
    }
  • §

    update the state of the context relative to the object id, which inhabits location rect. this updates:

    • ctx->updated_focus: whether focus was updated.
    • ctx->hover: whether this element is being hovered on.
    • mu_set_focus(): if this element should be focused, which sets:
    • ctx->focus: the ID of the item being focused.
    • ctx->updated_focus: whether focus has been updated.
    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; // submit a text box if attached.
      }
      /* 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;
    }
  • §

    draw a textbox with the text from buf, and also merge data from ctx->input_text, and then handle key presses such ash backspace and ENTER.

    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 */
  • §

    TODO: what is a continuation byte?

          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;
    }
  • §

    draw textbox which holds numbers.

    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)
      {
        v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w;
        if (step) { v = (((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) { 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);
    }
  • §

    what precisely is a treenode?

    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 */                                             \
          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;            \
          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;
    }
  • §

    helper for mu_begin_window to create a root container.

    static void mu_begin_window_ex_begin_root_container(mu_Context *ctx, mu_Container *cnt) {
      /* push container into stack of containers */
      push(ctx->container_stack, cnt);
      /* push container to roots list and push head command */
  • §

    TODO: what is jump?

      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.
      ** TODO: why would someone create a root container inside another root container?
      */
  • §

    this resets the clip stack. mu_push_clip_rect pushes a sequence of clips..

      push(ctx->clip_stack, unclipped_rect);
    }
  • §

    TODO: figure out the exact invariants

    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);
    }
    
    
    
    
    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);
    }
  • §

    What is a panel, versus a window, versus a container?

    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) {
        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);
    }