shithub: microui

Download patch

ref: 9bcef8453a0197d571290b6ea8b5c593c3d6e9d2
parent: 6f5f943b97fd091996911d67ff0c862ee4c33fd7
author: Sigrid Haflínudóttir <[email protected]>
date: Wed Jan 1 11:30:36 EST 2020

take platform-specific parts of the demo out to separate files

--- a/demo/build.sh
+++ b/demo/build.sh
@@ -1,3 +1,3 @@
-#!/bin/bash
-gcc main.c renderer.c ../src/microui.c -I../src\
-    -Wall -std=c11 -pedantic -lSDL2 -lGL -lm -O3 -g
+#!/usr/bin/env bash
+gcc common.c sdl2.c renderer_gl.c ../src/microui.c -I../src\
+    -Wall -Wextra -std=c11 -pedantic -lSDL2 -lGL -lm -O3 -g
--- /dev/null
+++ b/demo/common.c
@@ -1,0 +1,229 @@
+#include <stdio.h>
+#include <string.h>
+#include "common.h"
+
+
+static char logbuf[64000];
+static  int logbuf_updated = 0;
+
+float bg[3] = { 90, 95, 100 };
+
+
+static void write_log(const char *text) {
+  if (logbuf[0]) { strcat(logbuf, "\n"); }
+  strcat(logbuf, text);
+  logbuf_updated = 1;
+}
+
+
+static void test_window(mu_Context *ctx) {
+  static mu_Container window;
+
+  /* init window manually so we can set its position and size */
+  if (!window.inited) {
+    mu_init_window(ctx, &window, 0);
+    window.rect = mu_rect(40, 40, 300, 450);
+  }
+
+  /* limit window to minimum size */
+  window.rect.w = mu_max(window.rect.w, 240);
+  window.rect.h = mu_max(window.rect.h, 300);
+
+
+  /* do window */
+  if (mu_begin_window(ctx, &window, "Demo Window")) {
+
+    /* window info */
+    static int show_info = 0;
+    if (mu_header(ctx, &show_info, "Window Info")) {
+      char buf[64];
+      mu_layout_row(ctx, 2, (int[]) { 54, -1 }, 0);
+      mu_label(ctx,"Position:");
+      sprintf(buf, "%d, %d", window.rect.x, window.rect.y); mu_label(ctx, buf);
+      mu_label(ctx, "Size:");
+      sprintf(buf, "%d, %d", window.rect.w, window.rect.h); mu_label(ctx, buf);
+    }
+
+    /* labels + buttons */
+    static int show_buttons = 1;
+    if (mu_header(ctx, &show_buttons, "Test Buttons")) {
+      mu_layout_row(ctx, 3, (int[]) { 86, -110, -1 }, 0);
+      mu_label(ctx, "Test buttons 1:");
+      if (mu_button(ctx, "Button 1")) { write_log("Pressed button 1"); }
+      if (mu_button(ctx, "Button 2")) { write_log("Pressed button 2"); }
+      mu_label(ctx, "Test buttons 2:");
+      if (mu_button(ctx, "Button 3")) { write_log("Pressed button 3"); }
+      if (mu_button(ctx, "Button 4")) { write_log("Pressed button 4"); }
+    }
+
+    /* tree */
+    static int show_tree = 1;
+    if (mu_header(ctx, &show_tree, "Tree and Text")) {
+      mu_layout_row(ctx, 2, (int[]) { 140, -1 }, 0);
+      mu_layout_begin_column(ctx);
+      static int states[8];
+      if (mu_begin_treenode(ctx, &states[0], "Test 1")) {
+        if (mu_begin_treenode(ctx, &states[1], "Test 1a")) {
+          mu_label(ctx, "Hello");
+          mu_label(ctx, "world");
+          mu_end_treenode(ctx);
+        }
+        if (mu_begin_treenode(ctx, &states[2], "Test 1b")) {
+          if (mu_button(ctx, "Button 1")) { write_log("Pressed button 1"); }
+          if (mu_button(ctx, "Button 2")) { write_log("Pressed button 2"); }
+          mu_end_treenode(ctx);
+        }
+        mu_end_treenode(ctx);
+      }
+      if (mu_begin_treenode(ctx, &states[3], "Test 2")) {
+        mu_layout_row(ctx, 2, (int[]) { 54, 54 }, 0);
+        if (mu_button(ctx, "Button 3")) { write_log("Pressed button 3"); }
+        if (mu_button(ctx, "Button 4")) { write_log("Pressed button 4"); }
+        if (mu_button(ctx, "Button 5")) { write_log("Pressed button 5"); }
+        if (mu_button(ctx, "Button 6")) { write_log("Pressed button 6"); }
+        mu_end_treenode(ctx);
+      }
+      if (mu_begin_treenode(ctx, &states[4], "Test 3")) {
+        static int checks[3] = { 1, 0, 1 };
+        mu_checkbox(ctx, &checks[0], "Checkbox 1");
+        mu_checkbox(ctx, &checks[1], "Checkbox 2");
+        mu_checkbox(ctx, &checks[2], "Checkbox 3");
+        mu_end_treenode(ctx);
+      }
+      mu_layout_end_column(ctx);
+
+      mu_layout_begin_column(ctx);
+      mu_layout_row(ctx, 1, (int[]) { -1 }, 0);
+      mu_text(ctx, "Lorem ipsum dolor sit amet, consectetur adipiscing "
+        "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "
+        "ipsum, eu varius magna felis a nulla.");
+      mu_layout_end_column(ctx);
+    }
+
+    /* background color sliders */
+    static int show_sliders = 1;
+    if (mu_header(ctx, &show_sliders, "Background Color")) {
+      mu_layout_row(ctx, 2, (int[]) { -78, -1 }, 74);
+      /* sliders */
+      mu_layout_begin_column(ctx);
+      mu_layout_row(ctx, 2, (int[]) { 46, -1 }, 0);
+      mu_label(ctx, "Red:");   mu_slider(ctx, &bg[0], 0, 255);
+      mu_label(ctx, "Green:"); mu_slider(ctx, &bg[1], 0, 255);
+      mu_label(ctx, "Blue:");  mu_slider(ctx, &bg[2], 0, 255);
+      mu_layout_end_column(ctx);
+      /* color preview */
+      mu_Rect r = mu_layout_next(ctx);
+      mu_draw_rect(ctx, r, mu_color(bg[0], bg[1], bg[2], 255));
+      char buf[32];
+      sprintf(buf, "#%02X%02X%02X", (int) bg[0], (int) bg[1], (int) bg[2]);
+      mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER);
+    }
+
+    mu_end_window(ctx);
+  }
+}
+
+
+static void log_window(mu_Context *ctx) {
+  static mu_Container window;
+
+  /* init window manually so we can set its position and size */
+  if (!window.inited) {
+    mu_init_window(ctx, &window, 0);
+    window.rect = mu_rect(350, 40, 300, 200);
+  }
+
+  if (mu_begin_window(ctx, &window, "Log Window")) {
+
+    /* output text panel */
+    static mu_Container panel;
+    mu_layout_row(ctx, 1, (int[]) { -1 }, -28);
+    mu_begin_panel(ctx, &panel);
+    mu_layout_row(ctx, 1, (int[]) { -1 }, -1);
+    mu_text(ctx, logbuf);
+    mu_end_panel(ctx);
+    if (logbuf_updated) {
+      panel.scroll.y = panel.content_size.y;
+      logbuf_updated = 0;
+    }
+
+    /* input textbox + submit button */
+    static char buf[128];
+    int submitted = 0;
+    mu_layout_row(ctx, 2, (int[]) { -70, -1 }, 0);
+    if (mu_textbox(ctx, buf, sizeof(buf)) & MU_RES_SUBMIT) {
+      mu_set_focus(ctx, ctx->last_id);
+      submitted = 1;
+    }
+    if (mu_button(ctx, "Submit")) { submitted = 1; }
+    if (submitted) {
+      write_log(buf);
+      buf[0] = '\0';
+    }
+
+    mu_end_window(ctx);
+  }
+}
+
+
+static int uint8_slider(mu_Context *ctx, unsigned char *value, int low, int high) {
+  static float tmp;
+  mu_push_id(ctx, &value, sizeof(value));
+  tmp = *value;
+  int res = mu_slider_ex(ctx, &tmp, low, high, 0, "%.0f", MU_OPT_ALIGNCENTER);
+  *value = tmp;
+  mu_pop_id(ctx);
+  return res;
+}
+
+
+static void style_window(mu_Context *ctx) {
+  static mu_Container window;
+
+  /* init window manually so we can set its position and size */
+  if (!window.inited) {
+    mu_init_window(ctx, &window, 0);
+    window.rect = mu_rect(350, 250, 300, 240);
+  }
+
+  static struct { const char *label; int idx; } colors[] = {
+    { "text:",         MU_COLOR_TEXT        },
+    { "border:",       MU_COLOR_BORDER      },
+    { "windowbg:",     MU_COLOR_WINDOWBG    },
+    { "titlebg:",      MU_COLOR_TITLEBG     },
+    { "titletext:",    MU_COLOR_TITLETEXT   },
+    { "panelbg:",      MU_COLOR_PANELBG     },
+    { "button:",       MU_COLOR_BUTTON      },
+    { "buttonhover:",  MU_COLOR_BUTTONHOVER },
+    { "buttonfocus:",  MU_COLOR_BUTTONFOCUS },
+    { "base:",         MU_COLOR_BASE        },
+    { "basehover:",    MU_COLOR_BASEHOVER   },
+    { "basefocus:",    MU_COLOR_BASEFOCUS   },
+    { "scrollbase:",   MU_COLOR_SCROLLBASE  },
+    { "scrollthumb:",  MU_COLOR_SCROLLTHUMB },
+    { NULL }
+  };
+
+  if (mu_begin_window(ctx, &window, "Style Editor")) {
+    int sw = mu_get_container(ctx)->body.w * 0.14;
+    mu_layout_row(ctx, 6, (int[]) { 80, sw, sw, sw, sw, -1 }, 0);
+    for (int i = 0; colors[i].label; i++) {
+      mu_label(ctx, colors[i].label);
+      uint8_slider(ctx, &ctx->style->colors[i].r, 0, 255);
+      uint8_slider(ctx, &ctx->style->colors[i].g, 0, 255);
+      uint8_slider(ctx, &ctx->style->colors[i].b, 0, 255);
+      uint8_slider(ctx, &ctx->style->colors[i].a, 0, 255);
+      mu_draw_rect(ctx, mu_layout_next(ctx), ctx->style->colors[i]);
+    }
+    mu_end_window(ctx);
+  }
+}
+
+
+void process_frame(mu_Context *ctx) {
+  mu_begin(ctx);
+  test_window(ctx);
+  log_window(ctx);
+  style_window(ctx);
+  mu_end(ctx);
+}
--- /dev/null
+++ b/demo/common.h
@@ -1,0 +1,11 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "microui.h"
+#include "renderer.h"
+
+extern float bg[3];
+
+void process_frame(mu_Context *ctx);
+
+#endif
--- a/demo/main.c
+++ /dev/null
@@ -1,320 +1,0 @@
-#include <SDL2/SDL.h>
-#include <stdio.h>
-#include "renderer.h"
-#include "microui.h"
-
-
-static  char logbuf[64000];
-static   int logbuf_updated = 0;
-static float bg[3] = { 90, 95, 100 };
-
-
-static void write_log(const char *text) {
-  if (logbuf[0]) { strcat(logbuf, "\n"); }
-  strcat(logbuf, text);
-  logbuf_updated = 1;
-}
-
-
-static void test_window(mu_Context *ctx) {
-  static mu_Container window;
-
-  /* init window manually so we can set its position and size */
-  if (!window.inited) {
-    mu_init_window(ctx, &window, 0);
-    window.rect = mu_rect(40, 40, 300, 450);
-  }
-
-  /* limit window to minimum size */
-  window.rect.w = mu_max(window.rect.w, 240);
-  window.rect.h = mu_max(window.rect.h, 300);
-
-
-  /* do window */
-  if (mu_begin_window(ctx, &window, "Demo Window")) {
-
-    /* window info */
-    static int show_info = 0;
-    if (mu_header(ctx, &show_info, "Window Info")) {
-      char buf[64];
-      mu_layout_row(ctx, 2, (int[]) { 54, -1 }, 0);
-      mu_label(ctx,"Position:");
-      sprintf(buf, "%d, %d", window.rect.x, window.rect.y); mu_label(ctx, buf);
-      mu_label(ctx, "Size:");
-      sprintf(buf, "%d, %d", window.rect.w, window.rect.h); mu_label(ctx, buf);
-    }
-
-    /* labels + buttons */
-    static int show_buttons = 1;
-    if (mu_header(ctx, &show_buttons, "Test Buttons")) {
-      mu_layout_row(ctx, 3, (int[]) { 86, -110, -1 }, 0);
-      mu_label(ctx, "Test buttons 1:");
-      if (mu_button(ctx, "Button 1")) { write_log("Pressed button 1"); }
-      if (mu_button(ctx, "Button 2")) { write_log("Pressed button 2"); }
-      mu_label(ctx, "Test buttons 2:");
-      if (mu_button(ctx, "Button 3")) { write_log("Pressed button 3"); }
-      if (mu_button(ctx, "Button 4")) { write_log("Pressed button 4"); }
-    }
-
-    /* tree */
-    static int show_tree = 1;
-    if (mu_header(ctx, &show_tree, "Tree and Text")) {
-      mu_layout_row(ctx, 2, (int[]) { 140, -1 }, 0);
-      mu_layout_begin_column(ctx);
-      static int states[8];
-      if (mu_begin_treenode(ctx, &states[0], "Test 1")) {
-        if (mu_begin_treenode(ctx, &states[1], "Test 1a")) {
-          mu_label(ctx, "Hello");
-          mu_label(ctx, "world");
-          mu_end_treenode(ctx);
-        }
-        if (mu_begin_treenode(ctx, &states[2], "Test 1b")) {
-          if (mu_button(ctx, "Button 1")) { write_log("Pressed button 1"); }
-          if (mu_button(ctx, "Button 2")) { write_log("Pressed button 2"); }
-          mu_end_treenode(ctx);
-        }
-        mu_end_treenode(ctx);
-      }
-      if (mu_begin_treenode(ctx, &states[3], "Test 2")) {
-        mu_layout_row(ctx, 2, (int[]) { 54, 54 }, 0);
-        if (mu_button(ctx, "Button 3")) { write_log("Pressed button 3"); }
-        if (mu_button(ctx, "Button 4")) { write_log("Pressed button 4"); }
-        if (mu_button(ctx, "Button 5")) { write_log("Pressed button 5"); }
-        if (mu_button(ctx, "Button 6")) { write_log("Pressed button 6"); }
-        mu_end_treenode(ctx);
-      }
-      if (mu_begin_treenode(ctx, &states[4], "Test 3")) {
-        static int checks[3] = { 1, 0, 1 };
-        mu_checkbox(ctx, &checks[0], "Checkbox 1");
-        mu_checkbox(ctx, &checks[1], "Checkbox 2");
-        mu_checkbox(ctx, &checks[2], "Checkbox 3");
-        mu_end_treenode(ctx);
-      }
-      mu_layout_end_column(ctx);
-
-      mu_layout_begin_column(ctx);
-      mu_layout_row(ctx, 1, (int[]) { -1 }, 0);
-      mu_text(ctx, "Lorem ipsum dolor sit amet, consectetur adipiscing "
-        "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "
-        "ipsum, eu varius magna felis a nulla.");
-      mu_layout_end_column(ctx);
-    }
-
-    /* background color sliders */
-    static int show_sliders = 1;
-    if (mu_header(ctx, &show_sliders, "Background Color")) {
-      mu_layout_row(ctx, 2, (int[]) { -78, -1 }, 74);
-      /* sliders */
-      mu_layout_begin_column(ctx);
-      mu_layout_row(ctx, 2, (int[]) { 46, -1 }, 0);
-      mu_label(ctx, "Red:");   mu_slider(ctx, &bg[0], 0, 255);
-      mu_label(ctx, "Green:"); mu_slider(ctx, &bg[1], 0, 255);
-      mu_label(ctx, "Blue:");  mu_slider(ctx, &bg[2], 0, 255);
-      mu_layout_end_column(ctx);
-      /* color preview */
-      mu_Rect r = mu_layout_next(ctx);
-      mu_draw_rect(ctx, r, mu_color(bg[0], bg[1], bg[2], 255));
-      char buf[32];
-      sprintf(buf, "#%02X%02X%02X", (int) bg[0], (int) bg[1], (int) bg[2]);
-      mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, MU_OPT_ALIGNCENTER);
-    }
-
-    mu_end_window(ctx);
-  }
-}
-
-
-static void log_window(mu_Context *ctx) {
-  static mu_Container window;
-
-  /* init window manually so we can set its position and size */
-  if (!window.inited) {
-    mu_init_window(ctx, &window, 0);
-    window.rect = mu_rect(350, 40, 300, 200);
-  }
-
-  if (mu_begin_window(ctx, &window, "Log Window")) {
-
-    /* output text panel */
-    static mu_Container panel;
-    mu_layout_row(ctx, 1, (int[]) { -1 }, -28);
-    mu_begin_panel(ctx, &panel);
-    mu_layout_row(ctx, 1, (int[]) { -1 }, -1);
-    mu_text(ctx, logbuf);
-    mu_end_panel(ctx);
-    if (logbuf_updated) {
-      panel.scroll.y = panel.content_size.y;
-      logbuf_updated = 0;
-    }
-
-    /* input textbox + submit button */
-    static char buf[128];
-    int submitted = 0;
-    mu_layout_row(ctx, 2, (int[]) { -70, -1 }, 0);
-    if (mu_textbox(ctx, buf, sizeof(buf)) & MU_RES_SUBMIT) {
-      mu_set_focus(ctx, ctx->last_id);
-      submitted = 1;
-    }
-    if (mu_button(ctx, "Submit")) { submitted = 1; }
-    if (submitted) {
-      write_log(buf);
-      buf[0] = '\0';
-    }
-
-    mu_end_window(ctx);
-  }
-}
-
-
-static int uint8_slider(mu_Context *ctx, unsigned char *value, int low, int high) {
-  static float tmp;
-  mu_push_id(ctx, &value, sizeof(value));
-  tmp = *value;
-  int res = mu_slider_ex(ctx, &tmp, low, high, 0, "%.0f", MU_OPT_ALIGNCENTER);
-  *value = tmp;
-  mu_pop_id(ctx);
-  return res;
-}
-
-
-static void style_window(mu_Context *ctx) {
-  static mu_Container window;
-
-  /* init window manually so we can set its position and size */
-  if (!window.inited) {
-    mu_init_window(ctx, &window, 0);
-    window.rect = mu_rect(350, 250, 300, 240);
-  }
-
-  static struct { const char *label; int idx; } colors[] = {
-    { "text:",         MU_COLOR_TEXT        },
-    { "border:",       MU_COLOR_BORDER      },
-    { "windowbg:",     MU_COLOR_WINDOWBG    },
-    { "titlebg:",      MU_COLOR_TITLEBG     },
-    { "titletext:",    MU_COLOR_TITLETEXT   },
-    { "panelbg:",      MU_COLOR_PANELBG     },
-    { "button:",       MU_COLOR_BUTTON      },
-    { "buttonhover:",  MU_COLOR_BUTTONHOVER },
-    { "buttonfocus:",  MU_COLOR_BUTTONFOCUS },
-    { "base:",         MU_COLOR_BASE        },
-    { "basehover:",    MU_COLOR_BASEHOVER   },
-    { "basefocus:",    MU_COLOR_BASEFOCUS   },
-    { "scrollbase:",   MU_COLOR_SCROLLBASE  },
-    { "scrollthumb:",  MU_COLOR_SCROLLTHUMB },
-    { NULL }
-  };
-
-  if (mu_begin_window(ctx, &window, "Style Editor")) {
-    int sw = mu_get_container(ctx)->body.w * 0.14;
-    mu_layout_row(ctx, 6, (int[]) { 80, sw, sw, sw, sw, -1 }, 0);
-    for (int i = 0; colors[i].label; i++) {
-      mu_label(ctx, colors[i].label);
-      uint8_slider(ctx, &ctx->style->colors[i].r, 0, 255);
-      uint8_slider(ctx, &ctx->style->colors[i].g, 0, 255);
-      uint8_slider(ctx, &ctx->style->colors[i].b, 0, 255);
-      uint8_slider(ctx, &ctx->style->colors[i].a, 0, 255);
-      mu_draw_rect(ctx, mu_layout_next(ctx), ctx->style->colors[i]);
-    }
-    mu_end_window(ctx);
-  }
-}
-
-
-static void process_frame(mu_Context *ctx) {
-  mu_begin(ctx);
-  test_window(ctx);
-  log_window(ctx);
-  style_window(ctx);
-  mu_end(ctx);
-}
-
-
-
-static const char button_map[256] = {
-  [ SDL_BUTTON_LEFT   & 0xff ] =  MU_MOUSE_LEFT,
-  [ SDL_BUTTON_RIGHT  & 0xff ] =  MU_MOUSE_RIGHT,
-  [ SDL_BUTTON_MIDDLE & 0xff ] =  MU_MOUSE_MIDDLE,
-};
-
-static const char key_map[256] = {
-  [ SDLK_LSHIFT       & 0xff ] = MU_KEY_SHIFT,
-  [ SDLK_RSHIFT       & 0xff ] = MU_KEY_SHIFT,
-  [ SDLK_LCTRL        & 0xff ] = MU_KEY_CTRL,
-  [ SDLK_RCTRL        & 0xff ] = MU_KEY_CTRL,
-  [ SDLK_LALT         & 0xff ] = MU_KEY_ALT,
-  [ SDLK_RALT         & 0xff ] = MU_KEY_ALT,
-  [ SDLK_RETURN       & 0xff ] = MU_KEY_RETURN,
-  [ SDLK_BACKSPACE    & 0xff ] = MU_KEY_BACKSPACE,
-};
-
-
-static int text_width(mu_Font font, const char *text, int len) {
-  if (len == -1) { len = strlen(text); }
-  return r_get_text_width(text, len);
-}
-
-static int text_height(mu_Font font) {
-  return r_get_text_height();
-}
-
-
-int main(int argc, char **argv) {
-  /* init SDL and renderer */
-  SDL_Init(SDL_INIT_EVERYTHING);
-  r_init();
-
-  /* init microui */
-  mu_Context *ctx = malloc(sizeof(mu_Context));
-  mu_init(ctx);
-  ctx->text_width = text_width;
-  ctx->text_height = text_height;
-
-  /* main loop */
-  for (;;) {
-    /* handle SDL events */
-    SDL_Event e;
-    while (SDL_PollEvent(&e)) {
-      switch (e.type) {
-        case SDL_QUIT: exit(EXIT_SUCCESS); break;
-        case SDL_MOUSEMOTION: mu_input_mousemove(ctx, e.motion.x, e.motion.y); break;
-        case SDL_MOUSEWHEEL: mu_input_scroll(ctx, 0, e.wheel.y * -30); break;
-        case SDL_TEXTINPUT: mu_input_text(ctx, e.text.text); break;
-
-        case SDL_MOUSEBUTTONDOWN:
-        case SDL_MOUSEBUTTONUP: {
-          int b = button_map[e.button.button & 0xff];
-          if (b && e.type == SDL_MOUSEBUTTONDOWN) { mu_input_mousedown(ctx, e.button.x, e.button.y, b); }
-          if (b && e.type ==   SDL_MOUSEBUTTONUP) { mu_input_mouseup(ctx, e.button.x, e.button.y, b);   }
-        }
-
-        case SDL_KEYDOWN:
-        case SDL_KEYUP: {
-          int c = key_map[e.key.keysym.sym & 0xff];
-          if (c && e.type == SDL_KEYDOWN) { mu_input_keydown(ctx, c); }
-          if (c && e.type ==   SDL_KEYUP) { mu_input_keyup(ctx, c);   }
-          break;
-        }
-      }
-    }
-
-    /* process frame */
-    process_frame(ctx);
-
-    /* render */
-    r_clear(mu_color(bg[0], bg[1], bg[2], 255));
-    mu_Command *cmd = NULL;
-    while (mu_next_command(ctx, &cmd)) {
-      switch (cmd->type) {
-        case MU_COMMAND_TEXT: r_draw_text(cmd->text.str, cmd->text.pos, cmd->text.color); break;
-        case MU_COMMAND_RECT: r_draw_rect(cmd->rect.rect, cmd->rect.color); break;
-        case MU_COMMAND_ICON: r_draw_icon(cmd->icon.id, cmd->icon.rect, cmd->icon.color); break;
-        case MU_COMMAND_CLIP: r_set_clip_rect(cmd->clip.rect); break;
-      }
-    }
-    r_present();
-  }
-
-  return 0;
-}
-
-
--- a/demo/renderer.c
+++ /dev/null
@@ -1,185 +1,0 @@
-#include <SDL2/SDL.h>
-#include <SDL2/SDL_opengl.h>
-#include <assert.h>
-#include "renderer.h"
-#include "atlas.inl"
-
-#define BUFFER_SIZE 16384
-
-static GLfloat   tex_buf[BUFFER_SIZE *  8];
-static GLfloat  vert_buf[BUFFER_SIZE *  8];
-static GLubyte color_buf[BUFFER_SIZE * 16];
-static GLuint  index_buf[BUFFER_SIZE *  6];
-
-static int width  = 800;
-static int height = 600;
-static int buf_idx;
-
-static SDL_Window *window;
-
-
-void r_init(void) {
-  /* init SDL window */
-  window = SDL_CreateWindow(
-    NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
-    width, height, SDL_WINDOW_OPENGL);
-  SDL_GL_CreateContext(window);
-
-  /* init gl */
-  glEnable(GL_BLEND);
-  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-  glDisable(GL_CULL_FACE);
-  glDisable(GL_DEPTH_TEST);
-  glEnable(GL_SCISSOR_TEST);
-  glEnable(GL_TEXTURE_2D);
-  glEnableClientState(GL_VERTEX_ARRAY);
-  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-  glEnableClientState(GL_COLOR_ARRAY);
-
-  /* init texture */
-  GLuint id;
-  glGenTextures(1, &id);
-  glBindTexture(GL_TEXTURE_2D, id);
-  glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, ATLAS_WIDTH, ATLAS_HEIGHT, 0,
-    GL_ALPHA, GL_UNSIGNED_BYTE, atlas_texture);
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-  assert(glGetError() == 0);
-}
-
-
-static void flush(void) {
-  if (buf_idx == 0) { return; }
-
-  glViewport(0, 0, width, height);
-  glMatrixMode(GL_PROJECTION);
-  glPushMatrix();
-  glLoadIdentity();
-  glOrtho(0.0f, width, height, 0.0f, -1.0f, +1.0f);
-  glMatrixMode(GL_MODELVIEW);
-  glPushMatrix();
-  glLoadIdentity();
-
-  glTexCoordPointer(2, GL_FLOAT, 0, tex_buf);
-  glVertexPointer(2, GL_FLOAT, 0, vert_buf);
-  glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_buf);
-  glDrawElements(GL_TRIANGLES, buf_idx * 6, GL_UNSIGNED_INT, index_buf);
-
-  glMatrixMode(GL_MODELVIEW);
-  glPopMatrix();
-  glMatrixMode(GL_PROJECTION);
-  glPopMatrix();
-
-  buf_idx = 0;
-}
-
-
-static void push_quad(mu_Rect dst, mu_Rect src, mu_Color color) {
-  if (buf_idx == BUFFER_SIZE) { flush(); }
-
-  int texvert_idx = buf_idx *  8;
-  int   color_idx = buf_idx * 16;
-  int element_idx = buf_idx *  4;
-  int   index_idx = buf_idx *  6;
-  buf_idx++;
-
-  /* update texture buffer */
-  float x = src.x / (float) ATLAS_WIDTH;
-  float y = src.y / (float) ATLAS_HEIGHT;
-  float w = src.w / (float) ATLAS_WIDTH;
-  float h = src.h / (float) ATLAS_HEIGHT;
-  tex_buf[texvert_idx + 0] = x;
-  tex_buf[texvert_idx + 1] = y;
-  tex_buf[texvert_idx + 2] = x + w;
-  tex_buf[texvert_idx + 3] = y;
-  tex_buf[texvert_idx + 4] = x;
-  tex_buf[texvert_idx + 5] = y + h;
-  tex_buf[texvert_idx + 6] = x + w;
-  tex_buf[texvert_idx + 7] = y + h;
-
-  /* update vertex buffer */
-  vert_buf[texvert_idx + 0] = dst.x;
-  vert_buf[texvert_idx + 1] = dst.y;
-  vert_buf[texvert_idx + 2] = dst.x + dst.w;
-  vert_buf[texvert_idx + 3] = dst.y;
-  vert_buf[texvert_idx + 4] = dst.x;
-  vert_buf[texvert_idx + 5] = dst.y + dst.h;
-  vert_buf[texvert_idx + 6] = dst.x + dst.w;
-  vert_buf[texvert_idx + 7] = dst.y + dst.h;
-
-  /* update color buffer */
-  memcpy(color_buf + color_idx +  0, &color, 4);
-  memcpy(color_buf + color_idx +  4, &color, 4);
-  memcpy(color_buf + color_idx +  8, &color, 4);
-  memcpy(color_buf + color_idx + 12, &color, 4);
-
-  /* update index buffer */
-  index_buf[index_idx + 0] = element_idx + 0;
-  index_buf[index_idx + 1] = element_idx + 1;
-  index_buf[index_idx + 2] = element_idx + 2;
-  index_buf[index_idx + 3] = element_idx + 2;
-  index_buf[index_idx + 4] = element_idx + 3;
-  index_buf[index_idx + 5] = element_idx + 1;
-}
-
-
-void r_draw_rect(mu_Rect rect, mu_Color color) {
-  push_quad(rect, atlas[ATLAS_WHITE], color);
-}
-
-
-void r_draw_text(const char *text, mu_Vec2 pos, mu_Color color) {
-  mu_Rect dst = { pos.x, pos.y, 0, 0 };
-  for (const char *p = text; *p; p++) {
-    if ((*p & 0xc0) == 0x80) { continue; }
-    int chr = mu_min((unsigned char) *p, 127);
-    mu_Rect src = atlas[ATLAS_FONT + chr];
-    dst.w = src.w;
-    dst.h = src.h;
-    push_quad(dst, src, color);
-    dst.x += dst.w;
-  }
-}
-
-
-void r_draw_icon(int id, mu_Rect rect, mu_Color color) {
-  mu_Rect src = atlas[id];
-  int x = rect.x + (rect.w - src.w) / 2;
-  int y = rect.y + (rect.h - src.h) / 2;
-  push_quad(mu_rect(x, y, src.w, src.h), src, color);
-}
-
-
-int r_get_text_width(const char *text, int len) {
-  int res = 0;
-  for (const char *p = text; *p && len--; p++) {
-    if ((*p & 0xc0) == 0x80) { continue; }
-    int chr = mu_min((unsigned char) *p, 127);
-    res += atlas[ATLAS_FONT + chr].w;
-  }
-  return res;
-}
-
-
-int r_get_text_height(void) {
-  return 18;
-}
-
-
-void r_set_clip_rect(mu_Rect rect) {
-  flush();
-  glScissor(rect.x, height - (rect.y + rect.h), rect.w, rect.h);
-}
-
-
-void r_clear(mu_Color clr) {
-  flush();
-  glClearColor(clr.r / 255., clr.g / 255., clr.b / 255., clr.a / 255.);
-  glClear(GL_COLOR_BUFFER_BIT);
-}
-
-
-void r_present(void) {
-  flush();
-  SDL_GL_SwapWindow(window);
-}
--- a/demo/renderer.h
+++ b/demo/renderer.h
@@ -1,14 +1,12 @@
 #ifndef RENDERER_H
 #define RENDERER_H
 
-#include "microui.h"
-
 void r_init(void);
 void r_draw_rect(mu_Rect rect, mu_Color color);
-void r_draw_text(const char *text, mu_Vec2 pos, mu_Color color);
+void r_draw_text(mu_Font font, const char *text, mu_Vec2 pos, mu_Color color);
 void r_draw_icon(int id, mu_Rect rect, mu_Color color);
- int r_get_text_width(const char *text, int len);
- int r_get_text_height(void);
+ int r_get_text_width(mu_Font font, const char *text, int len);
+ int r_get_text_height(mu_Font font);
 void r_set_clip_rect(mu_Rect rect);
 void r_clear(mu_Color color);
 void r_present(void);
--- /dev/null
+++ b/demo/renderer_gl.c
@@ -1,0 +1,190 @@
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_opengl.h>
+#include <assert.h>
+#include "common.h"
+#include "atlas.inl"
+
+#define BUFFER_SIZE 16384
+#define unused(x) ((void) (x))
+
+static GLfloat   tex_buf[BUFFER_SIZE *  8];
+static GLfloat  vert_buf[BUFFER_SIZE *  8];
+static GLubyte color_buf[BUFFER_SIZE * 16];
+static GLuint  index_buf[BUFFER_SIZE *  6];
+
+static int width  = 800;
+static int height = 600;
+static int buf_idx;
+
+static SDL_Window *window;
+
+
+void r_init(void) {
+  /* init SDL window */
+  window = SDL_CreateWindow(
+    NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+    width, height, SDL_WINDOW_OPENGL);
+  SDL_GL_CreateContext(window);
+
+  /* init gl */
+  glEnable(GL_BLEND);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  glDisable(GL_CULL_FACE);
+  glDisable(GL_DEPTH_TEST);
+  glEnable(GL_SCISSOR_TEST);
+  glEnable(GL_TEXTURE_2D);
+  glEnableClientState(GL_VERTEX_ARRAY);
+  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+  glEnableClientState(GL_COLOR_ARRAY);
+
+  /* init texture */
+  GLuint id;
+  glGenTextures(1, &id);
+  glBindTexture(GL_TEXTURE_2D, id);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, ATLAS_WIDTH, ATLAS_HEIGHT, 0,
+    GL_ALPHA, GL_UNSIGNED_BYTE, atlas_texture);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  assert(glGetError() == 0);
+}
+
+
+static void flush(void) {
+  if (buf_idx == 0) { return; }
+
+  glViewport(0, 0, width, height);
+  glMatrixMode(GL_PROJECTION);
+  glPushMatrix();
+  glLoadIdentity();
+  glOrtho(0.0f, width, height, 0.0f, -1.0f, +1.0f);
+  glMatrixMode(GL_MODELVIEW);
+  glPushMatrix();
+  glLoadIdentity();
+
+  glTexCoordPointer(2, GL_FLOAT, 0, tex_buf);
+  glVertexPointer(2, GL_FLOAT, 0, vert_buf);
+  glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_buf);
+  glDrawElements(GL_TRIANGLES, buf_idx * 6, GL_UNSIGNED_INT, index_buf);
+
+  glMatrixMode(GL_MODELVIEW);
+  glPopMatrix();
+  glMatrixMode(GL_PROJECTION);
+  glPopMatrix();
+
+  buf_idx = 0;
+}
+
+
+static void push_quad(mu_Rect dst, mu_Rect src, mu_Color color) {
+  if (buf_idx == BUFFER_SIZE) { flush(); }
+
+  int texvert_idx = buf_idx *  8;
+  int   color_idx = buf_idx * 16;
+  int element_idx = buf_idx *  4;
+  int   index_idx = buf_idx *  6;
+  buf_idx++;
+
+  /* update texture buffer */
+  float x = src.x / (float) ATLAS_WIDTH;
+  float y = src.y / (float) ATLAS_HEIGHT;
+  float w = src.w / (float) ATLAS_WIDTH;
+  float h = src.h / (float) ATLAS_HEIGHT;
+  tex_buf[texvert_idx + 0] = x;
+  tex_buf[texvert_idx + 1] = y;
+  tex_buf[texvert_idx + 2] = x + w;
+  tex_buf[texvert_idx + 3] = y;
+  tex_buf[texvert_idx + 4] = x;
+  tex_buf[texvert_idx + 5] = y + h;
+  tex_buf[texvert_idx + 6] = x + w;
+  tex_buf[texvert_idx + 7] = y + h;
+
+  /* update vertex buffer */
+  vert_buf[texvert_idx + 0] = dst.x;
+  vert_buf[texvert_idx + 1] = dst.y;
+  vert_buf[texvert_idx + 2] = dst.x + dst.w;
+  vert_buf[texvert_idx + 3] = dst.y;
+  vert_buf[texvert_idx + 4] = dst.x;
+  vert_buf[texvert_idx + 5] = dst.y + dst.h;
+  vert_buf[texvert_idx + 6] = dst.x + dst.w;
+  vert_buf[texvert_idx + 7] = dst.y + dst.h;
+
+  /* update color buffer */
+  memcpy(color_buf + color_idx +  0, &color, 4);
+  memcpy(color_buf + color_idx +  4, &color, 4);
+  memcpy(color_buf + color_idx +  8, &color, 4);
+  memcpy(color_buf + color_idx + 12, &color, 4);
+
+  /* update index buffer */
+  index_buf[index_idx + 0] = element_idx + 0;
+  index_buf[index_idx + 1] = element_idx + 1;
+  index_buf[index_idx + 2] = element_idx + 2;
+  index_buf[index_idx + 3] = element_idx + 2;
+  index_buf[index_idx + 4] = element_idx + 3;
+  index_buf[index_idx + 5] = element_idx + 1;
+}
+
+
+void r_draw_rect(mu_Rect rect, mu_Color color) {
+  push_quad(rect, atlas[ATLAS_WHITE], color);
+}
+
+
+void r_draw_text(mu_Font font, const char *text, mu_Vec2 pos, mu_Color color) {
+  mu_Rect dst = { pos.x, pos.y, 0, 0 };
+  unused(font);
+  for (const char *p = text; *p; p++) {
+    if ((*p & 0xc0) == 0x80) { continue; }
+    int chr = mu_min((unsigned char) *p, 127);
+    mu_Rect src = atlas[ATLAS_FONT + chr];
+    dst.w = src.w;
+    dst.h = src.h;
+    push_quad(dst, src, color);
+    dst.x += dst.w;
+  }
+}
+
+
+void r_draw_icon(int id, mu_Rect rect, mu_Color color) {
+  mu_Rect src = atlas[id];
+  int x = rect.x + (rect.w - src.w) / 2;
+  int y = rect.y + (rect.h - src.h) / 2;
+  push_quad(mu_rect(x, y, src.w, src.h), src, color);
+}
+
+
+int r_get_text_width(mu_Font font, const char *text, int len) {
+  int res = 0;
+  unused(font);
+  if (len == -1) { len = strlen(text); }
+  for (const char *p = text; *p && len--; p++) {
+    if ((*p & 0xc0) == 0x80) { continue; }
+    int chr = mu_min((unsigned char) *p, 127);
+    res += atlas[ATLAS_FONT + chr].w;
+  }
+  return res;
+}
+
+
+int r_get_text_height(mu_Font font) {
+  unused(font);
+  return 18;
+}
+
+
+void r_set_clip_rect(mu_Rect rect) {
+  flush();
+  glScissor(rect.x, height - (rect.y + rect.h), rect.w, rect.h);
+}
+
+
+void r_clear(mu_Color clr) {
+  flush();
+  glClearColor(clr.r / 255., clr.g / 255., clr.b / 255., clr.a / 255.);
+  glClear(GL_COLOR_BUFFER_BIT);
+}
+
+
+void r_present(void) {
+  flush();
+  SDL_GL_SwapWindow(window);
+}
--- /dev/null
+++ b/demo/sdl2.c
@@ -1,0 +1,86 @@
+#include <SDL2/SDL.h>
+#include "common.h"
+
+#define unused(x) ((void) (x))
+
+static const char button_map[256] = {
+  [ SDL_BUTTON_LEFT   & 0xff ] =  MU_MOUSE_LEFT,
+  [ SDL_BUTTON_RIGHT  & 0xff ] =  MU_MOUSE_RIGHT,
+  [ SDL_BUTTON_MIDDLE & 0xff ] =  MU_MOUSE_MIDDLE,
+};
+
+static const char key_map[256] = {
+  [ SDLK_LSHIFT       & 0xff ] = MU_KEY_SHIFT,
+  [ SDLK_RSHIFT       & 0xff ] = MU_KEY_SHIFT,
+  [ SDLK_LCTRL        & 0xff ] = MU_KEY_CTRL,
+  [ SDLK_RCTRL        & 0xff ] = MU_KEY_CTRL,
+  [ SDLK_LALT         & 0xff ] = MU_KEY_ALT,
+  [ SDLK_RALT         & 0xff ] = MU_KEY_ALT,
+  [ SDLK_RETURN       & 0xff ] = MU_KEY_RETURN,
+  [ SDLK_BACKSPACE    & 0xff ] = MU_KEY_BACKSPACE,
+};
+
+
+int main(int argc, char **argv) {
+  mu_Font dummy = {0};
+
+  unused(argc); unused(argv);
+
+  /* init SDL and renderer */
+  SDL_Init(SDL_INIT_EVERYTHING);
+  r_init();
+
+  /* init microui */
+  mu_Context *ctx = malloc(sizeof(mu_Context));
+  mu_init(ctx);
+  ctx->text_width = r_get_text_width;
+  ctx->text_height = r_get_text_height;
+
+  /* main loop */
+  for (;;) {
+    /* handle SDL events */
+    SDL_Event e;
+    while (SDL_PollEvent(&e)) {
+      switch (e.type) {
+        case SDL_QUIT: exit(EXIT_SUCCESS); break;
+        case SDL_MOUSEMOTION: mu_input_mousemove(ctx, e.motion.x, e.motion.y); break;
+        case SDL_MOUSEWHEEL: mu_input_scroll(ctx, 0, e.wheel.y * -30); break;
+        case SDL_TEXTINPUT: mu_input_text(ctx, e.text.text); break;
+
+        case SDL_MOUSEBUTTONDOWN:
+        case SDL_MOUSEBUTTONUP: {
+          int b = button_map[e.button.button & 0xff];
+          if (b && e.type == SDL_MOUSEBUTTONDOWN) { mu_input_mousedown(ctx, e.button.x, e.button.y, b); }
+          if (b && e.type ==   SDL_MOUSEBUTTONUP) { mu_input_mouseup(ctx, e.button.x, e.button.y, b);   }
+          break;
+        }
+
+        case SDL_KEYDOWN:
+        case SDL_KEYUP: {
+          int c = key_map[e.key.keysym.sym & 0xff];
+          if (c && e.type == SDL_KEYDOWN) { mu_input_keydown(ctx, c); }
+          if (c && e.type ==   SDL_KEYUP) { mu_input_keyup(ctx, c);   }
+          break;
+        }
+      }
+    }
+
+    /* process frame */
+    process_frame(ctx);
+
+    /* render */
+    r_clear(mu_color(bg[0], bg[1], bg[2], 255));
+    mu_Command *cmd = NULL;
+    while (mu_next_command(ctx, &cmd)) {
+      switch (cmd->type) {
+        case MU_COMMAND_TEXT: r_draw_text(dummy, cmd->text.str, cmd->text.pos, cmd->text.color); break;
+        case MU_COMMAND_RECT: r_draw_rect(cmd->rect.rect, cmd->rect.color); break;
+        case MU_COMMAND_ICON: r_draw_icon(cmd->icon.id, cmd->icon.rect, cmd->icon.color); break;
+        case MU_COMMAND_CLIP: r_set_clip_rect(cmd->clip.rect); break;
+      }
+    }
+    r_present();
+  }
+
+  return 0;
+}