summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kon/konui.h624
-rw-r--r--src/impl/konui.c2
2 files changed, 626 insertions, 0 deletions
diff --git a/kon/konui.h b/kon/konui.h
new file mode 100644
index 0000000..e03b7fe
--- /dev/null
+++ b/kon/konui.h
@@ -0,0 +1,624 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * konui.h
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * 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.
+ */
+
+#ifndef KONUI_H
+#define KONUI_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include "konwinlib.h"
+#include "konsofren.h"
+#include "konfont.h"
+
+/*** definition ***/
+
+typedef struct kon_ui kon_ui_t;
+
+typedef struct kon_uiTheme {
+ uint32_t panel, title, text, textDim, widget, hover, active, accent, border;
+ int padding, spacing;
+} kon_uiTheme_t;
+
+/* you own this, the ui moves it when the title bar is dragged. w is required, h = 0 sizes to the content */
+typedef struct kon_uiPanel {
+ int x, y, w, h;
+ int collapsed;
+ int contentH;
+} kon_uiPanel_t;
+
+kon_ui_t *kon_uiCreate(kon_font_t *font);
+void kon_uiDestroy(kon_ui_t *ui);
+kon_uiTheme_t *kon_uiTheme(kon_ui_t *ui);
+
+/* mouse coordinates have to be in the space of the framebuffer you draw to. use kon_windowToGame for the game one */
+void kon_uiFeedEvent(kon_ui_t *ui, const kon_event_t *event);
+
+void kon_uiBegin(kon_ui_t *ui, kon_framebuffer_t *fb);
+void kon_uiEnd(kon_ui_t *ui);
+
+/* true when the ui is using the mouse or keyboard, so the game should leave them alone */
+int kon_uiWantsMouse(const kon_ui_t *ui);
+int kon_uiWantsKeyboard(const kon_ui_t *ui);
+
+/* always call kon_uiEndPanel, even when this returned 0 (collapsed) */
+int kon_uiBeginPanel(kon_ui_t *ui, const char *title, kon_uiPanel_t *panel);
+void kon_uiEndPanel(kon_ui_t *ui);
+
+/* anything after ## in a label is part of the id but not drawn, so "ok##a" and "ok##b" are different buttons */
+void kon_uiLabel(kon_ui_t *ui, const char *text);
+void kon_uiLabelf(kon_ui_t *ui, const char *format, ...);
+int kon_uiButton(kon_ui_t *ui, const char *label);
+int kon_uiCheckbox(kon_ui_t *ui, const char *label, int *value);
+int kon_uiSliderFloat(kon_ui_t *ui, const char *label, float *value, float min, float max);
+int kon_uiSliderInt(kon_ui_t *ui, const char *label, int *value, int min, int max);
+int kon_uiSelectable(kon_ui_t *ui, const char *label, int selected);
+
+/* returns 1 when enter is pressed. label is shown while the field is empty */
+int kon_uiTextInput(kon_ui_t *ui, const char *label, char *buffer, size_t size);
+
+void kon_uiSeparator(kon_ui_t *ui);
+void kon_uiSameLine(kon_ui_t *ui);
+void kon_uiPushId(kon_ui_t *ui, int id);
+void kon_uiPopId(kon_ui_t *ui);
+
+kon_rect_t kon_uiLastRect(const kon_ui_t *ui);
+
+#endif
+
+/*** implementation ***/
+
+#ifdef KONUI_IMPLEMENTATION
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#define KON_UI_IDS 16
+#define KON_UI_TEXTQUEUE 32
+
+struct kon_ui {
+ kon_font_t *font;
+ kon_framebuffer_t *fb;
+ kon_uiTheme_t theme;
+ int lineH, ascent;
+
+ int mouseX, mouseY;
+ int mouseDown, mousePressed, mouseReleased;
+ int keyPressed[KON_KEY_COUNT];
+ unsigned int text[KON_UI_TEXTQUEUE];
+ int textCount;
+
+ uint32_t active, focus;
+ uint32_t topPanel, topPanelNext;
+ int wantsMouse;
+ int focusKept;
+ int dragX, dragY;
+
+ uint32_t ids[KON_UI_IDS];
+ int idCount;
+
+ kon_uiPanel_t *panel;
+ uint32_t panelId;
+ int cx, cy, availW;
+ int sameLine;
+ kon_rect_t last;
+ int cursor;
+};
+
+/*** private helpers ***/
+
+static uint32_t kon_uiHash_(const kon_ui_t *ui, const char *s)
+{
+ uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u;
+
+ while (*s) {
+ h ^= (unsigned char)*s++;
+ h *= 16777619u;
+ }
+ return h ? h : 1;
+}
+
+static size_t kon_uiVisibleLength_(const char *s)
+{
+ size_t n = 0;
+
+ while (s[n]) {
+ if (s[n] == '#' && s[n + 1] == '#') break;
+ n++;
+ }
+ return n;
+}
+
+static int kon_uiInside_(const kon_ui_t *ui, kon_rect_t r)
+{
+ return ui->mouseX >= r.x && ui->mouseX < r.x + r.w && ui->mouseY >= r.y && ui->mouseY < r.y + r.h;
+}
+
+/* the mouse can only reach this widget if its panel is the topmost one under it */
+static int kon_uiReachable_(const kon_ui_t *ui)
+{
+ return ui->topPanel == ui->panelId;
+}
+
+static int kon_uiTextWidth_(const kon_ui_t *ui, const char *s, size_t n)
+{
+ char buffer[256];
+ int w = 0;
+
+ if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1;
+ memcpy(buffer, s, n);
+ buffer[n] = 0;
+ kon_measureText(ui->font, buffer, &w, NULL);
+ return w;
+}
+
+static void kon_uiDrawText_(const kon_ui_t *ui, int x, int y, const char *s, size_t n, uint32_t color)
+{
+ char buffer[256];
+
+ if (n > sizeof(buffer) - 1) n = sizeof(buffer) - 1;
+ memcpy(buffer, s, n);
+ buffer[n] = 0;
+ kon_drawText(ui->fb, ui->font, x, y + ui->ascent, buffer, color);
+}
+
+static kon_rect_t kon_uiNextRect_(kon_ui_t *ui, int w, int h)
+{
+ kon_rect_t r;
+
+ if (ui->sameLine) {
+ r.x = ui->last.x + ui->last.w + ui->theme.spacing;
+ r.y = ui->last.y;
+ } else {
+ r.x = ui->cx;
+ r.y = ui->cy;
+ }
+ r.w = w > 0 ? w : ui->availW;
+ r.h = h;
+
+ if (!ui->sameLine) ui->cy += h + ui->theme.spacing;
+ ui->sameLine = 0;
+ ui->last = r;
+ return r;
+}
+
+static void kon_uiFill_(const kon_ui_t *ui, kon_rect_t r, uint32_t color)
+{
+ kon_fillRectangle(ui->fb, r.x, r.y, r.w, r.h, color);
+}
+
+static void kon_uiOutline_(const kon_ui_t *ui, kon_rect_t r, uint32_t color)
+{
+ kon_drawRectangle(ui->fb, r.x, r.y, r.w - 1, r.h - 1, color);
+}
+
+/* hover and press handling shared by everything clickable. returns 1 when it was clicked */
+static int kon_uiBehave_(kon_ui_t *ui, uint32_t id, kon_rect_t r, int *hovered)
+{
+ int over = kon_uiReachable_(ui) && kon_uiInside_(ui, r) && (ui->active == 0 || ui->active == id);
+ int clicked = 0;
+
+ if (over && ui->mousePressed) ui->active = id;
+ if (ui->active == id && ui->mouseReleased) {
+ clicked = over;
+ ui->active = 0;
+ }
+ if (hovered) *hovered = over;
+ return clicked;
+}
+
+static uint32_t kon_uiWidgetColor_(const kon_ui_t *ui, uint32_t id, int hovered)
+{
+ if (ui->active == id) return ui->theme.active;
+ return hovered ? ui->theme.hover : ui->theme.widget;
+}
+
+/*** ui ***/
+
+kon_ui_t *kon_uiCreate(kon_font_t *font)
+{
+ kon_ui_t *ui;
+ int h = 0;
+
+ if (!font) return NULL;
+ ui = calloc(1, sizeof(kon_ui_t));
+ if (!ui) return NULL;
+
+ ui->font = font;
+ kon_measureText(font, "A", NULL, &h);
+ ui->ascent = h * 4 / 5;
+ ui->lineH = h + 4;
+
+ ui->theme.panel = 0xE0202028;
+ ui->theme.title = 0xFF383848;
+ ui->theme.text = 0xFFE8E8F0;
+ ui->theme.textDim = 0xFF8888A0;
+ ui->theme.widget = 0xFF404058;
+ ui->theme.hover = 0xFF505070;
+ ui->theme.active = 0xFF6060A0;
+ ui->theme.accent = 0xFF7088E0;
+ ui->theme.border = 0xFF101018;
+ ui->theme.padding = 4;
+ ui->theme.spacing = 3;
+ return ui;
+}
+
+void kon_uiDestroy(kon_ui_t *ui)
+{
+ free(ui);
+}
+
+kon_uiTheme_t *kon_uiTheme(kon_ui_t *ui)
+{
+ return &ui->theme;
+}
+
+void kon_uiFeedEvent(kon_ui_t *ui, const kon_event_t *event)
+{
+ switch (event->type) {
+ case KON_EVENT_MOUSE_MOVE:
+ ui->mouseX = event->mouseX;
+ ui->mouseY = event->mouseY;
+ break;
+ case KON_EVENT_MOUSE_DOWN:
+ ui->mouseX = event->mouseX;
+ ui->mouseY = event->mouseY;
+ if (event->mouseButton == KON_MOUSE_LEFT) {
+ ui->mouseDown = 1;
+ ui->mousePressed = 1;
+ }
+ break;
+ case KON_EVENT_MOUSE_UP:
+ ui->mouseX = event->mouseX;
+ ui->mouseY = event->mouseY;
+ if (event->mouseButton == KON_MOUSE_LEFT) {
+ ui->mouseDown = 0;
+ ui->mouseReleased = 1;
+ }
+ break;
+ case KON_EVENT_KEY_DOWN:
+ if (event->key >= 0 && event->key < KON_KEY_COUNT) ui->keyPressed[event->key] = 1;
+ break;
+ case KON_EVENT_TEXT:
+ if (ui->textCount < KON_UI_TEXTQUEUE) ui->text[ui->textCount++] = event->codepoint;
+ break;
+ default:
+ break;
+ }
+}
+
+void kon_uiBegin(kon_ui_t *ui, kon_framebuffer_t *fb)
+{
+ ui->fb = fb;
+ ui->topPanel = ui->topPanelNext;
+ ui->topPanelNext = 0;
+ ui->focusKept = 0;
+ ui->panel = NULL;
+ ui->panelId = 0;
+ ui->idCount = 0;
+ ui->sameLine = 0;
+ ui->cx = ui->theme.padding;
+ ui->cy = ui->theme.padding;
+ ui->availW = fb->width - ui->theme.padding * 2;
+}
+
+void kon_uiEnd(kon_ui_t *ui)
+{
+ if (ui->mousePressed && !ui->focusKept) ui->focus = 0;
+ if (!ui->mouseDown && ui->active) ui->active = 0;
+
+ ui->wantsMouse = ui->topPanelNext != 0 || ui->active != 0;
+
+ ui->mousePressed = 0;
+ ui->mouseReleased = 0;
+ memset(ui->keyPressed, 0, sizeof(ui->keyPressed));
+ ui->textCount = 0;
+ ui->fb = NULL;
+}
+
+int kon_uiWantsMouse(const kon_ui_t *ui)
+{
+ return ui->wantsMouse;
+}
+
+int kon_uiWantsKeyboard(const kon_ui_t *ui)
+{
+ return ui->focus != 0;
+}
+
+void kon_uiPushId(kon_ui_t *ui, int id)
+{
+ uint32_t h = ui->idCount ? ui->ids[ui->idCount - 1] : 2166136261u;
+
+ if (ui->idCount == KON_UI_IDS) return;
+ h ^= (uint32_t)id;
+ h *= 16777619u;
+ ui->ids[ui->idCount++] = h;
+}
+
+void kon_uiPopId(kon_ui_t *ui)
+{
+ if (ui->idCount) ui->idCount--;
+}
+
+kon_rect_t kon_uiLastRect(const kon_ui_t *ui)
+{
+ return ui->last;
+}
+
+/*** panels ***/
+
+int kon_uiBeginPanel(kon_ui_t *ui, const char *title, kon_uiPanel_t *panel)
+{
+ uint32_t id = kon_uiHash_(ui, title);
+ uint32_t dragId = id ^ 0x9E3779B9u;
+ int pad = ui->theme.padding, titleH = ui->lineH + pad;
+ kon_rect_t whole, bar, arrow;
+ int h, hovered;
+
+ if (ui->panel) return 0;
+ if (panel->w <= 0) panel->w = 220;
+
+ h = panel->collapsed ? titleH : (panel->h > 0 ? panel->h : panel->contentH);
+ if (h < titleH) h = titleH;
+
+ whole = KON_RECT(panel->x, panel->y, panel->w, h);
+ bar = KON_RECT(panel->x, panel->y, panel->w, titleH);
+ arrow = KON_RECT(panel->x, panel->y, titleH, titleH);
+
+ if (kon_uiInside_(ui, whole)) ui->topPanelNext = id;
+
+ ui->panelId = id;
+ ui->panel = panel;
+
+ if (kon_uiBehave_(ui, id ^ 1u, arrow, &hovered)) panel->collapsed = !panel->collapsed;
+
+ if (ui->active == 0 && ui->mousePressed && kon_uiReachable_(ui) && kon_uiInside_(ui, bar) && !kon_uiInside_(ui, arrow)) {
+ ui->active = dragId;
+ ui->dragX = ui->mouseX - panel->x;
+ ui->dragY = ui->mouseY - panel->y;
+ }
+ if (ui->active == dragId) {
+ panel->x = ui->mouseX - ui->dragX;
+ panel->y = ui->mouseY - ui->dragY;
+ if (panel->x < 40 - panel->w) panel->x = 40 - panel->w;
+ if (panel->x > ui->fb->width - 40) panel->x = ui->fb->width - 40;
+ if (panel->y < 0) panel->y = 0;
+ if (panel->y > ui->fb->height - titleH) panel->y = ui->fb->height - titleH;
+ if (ui->mouseReleased) ui->active = 0;
+ whole.x = bar.x = arrow.x = panel->x;
+ whole.y = bar.y = arrow.y = panel->y;
+ }
+
+ kon_uiFill_(ui, whole, ui->theme.panel);
+ kon_uiFill_(ui, bar, ui->theme.title);
+ kon_uiOutline_(ui, whole, ui->theme.border);
+ kon_uiDrawText_(ui, arrow.x + pad, arrow.y + pad / 2, panel->collapsed ? "+" : "-", 1, ui->theme.textDim);
+ kon_uiDrawText_(ui, panel->x + titleH, panel->y + pad / 2, title, kon_uiVisibleLength_(title), ui->theme.text);
+
+ ui->cx = panel->x + pad;
+ ui->cy = panel->y + titleH + pad;
+ ui->availW = panel->w - pad * 2;
+ ui->sameLine = 0;
+ return !panel->collapsed;
+}
+
+void kon_uiEndPanel(kon_ui_t *ui)
+{
+ if (!ui->panel) return;
+ ui->panel->contentH = ui->cy - ui->panel->y + ui->theme.padding - ui->theme.spacing;
+ ui->panel = NULL;
+ ui->panelId = 0;
+ ui->cx = ui->theme.padding;
+ ui->cy = ui->theme.padding;
+ ui->availW = ui->fb->width - ui->theme.padding * 2;
+}
+
+/*** widgets ***/
+
+void kon_uiLabel(kon_ui_t *ui, const char *text)
+{
+ size_t n = kon_uiVisibleLength_(text);
+ kon_rect_t r = kon_uiNextRect_(ui, kon_uiTextWidth_(ui, text, n) + 1, ui->lineH);
+
+ kon_uiDrawText_(ui, r.x, r.y, text, n, ui->theme.text);
+}
+
+void kon_uiLabelf(kon_ui_t *ui, const char *format, ...)
+{
+ char buffer[256];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+ kon_uiLabel(ui, buffer);
+}
+
+int kon_uiButton(kon_ui_t *ui, const char *label)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding, textW = kon_uiTextWidth_(ui, label, n);
+ kon_rect_t r = kon_uiNextRect_(ui, ui->sameLine ? textW + pad * 2 : 0, ui->lineH + pad);
+ int hovered, clicked;
+
+ clicked = kon_uiBehave_(ui, id, r, &hovered);
+ kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered));
+ kon_uiOutline_(ui, r, ui->theme.border);
+ kon_uiDrawText_(ui, r.x + (r.w - textW) / 2, r.y + pad / 2 + 1, label, n, ui->theme.text);
+ return clicked;
+}
+
+int kon_uiCheckbox(kon_ui_t *ui, const char *label, int *value)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int box = ui->lineH, pad = ui->theme.padding;
+ kon_rect_t r = kon_uiNextRect_(ui, box + pad + kon_uiTextWidth_(ui, label, n) + 1, box);
+ kon_rect_t b = KON_RECT(r.x, r.y, box, box);
+ int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered);
+
+ if (clicked) *value = !*value;
+ kon_uiFill_(ui, b, kon_uiWidgetColor_(ui, id, hovered));
+ kon_uiOutline_(ui, b, ui->theme.border);
+ if (*value) kon_uiFill_(ui, KON_RECT(b.x + 3, b.y + 3, b.w - 6, b.h - 6), ui->theme.accent);
+ kon_uiDrawText_(ui, r.x + box + pad, r.y, label, n, ui->theme.text);
+ return clicked;
+}
+
+int kon_uiSliderFloat(kon_ui_t *ui, const char *label, float *value, float min, float max)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding;
+ kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad);
+ char text[64];
+ int hovered, changed = 0, fill;
+ float t;
+
+ kon_uiBehave_(ui, id, r, &hovered);
+
+ if (ui->active == id && r.w > 1 && max > min) {
+ float v;
+
+ t = (float)(ui->mouseX - r.x) / (float)(r.w - 1);
+ if (t < 0.0f) t = 0.0f;
+ if (t > 1.0f) t = 1.0f;
+ v = min + t * (max - min);
+ if (v != *value) {
+ *value = v;
+ changed = 1;
+ }
+ }
+
+ t = max > min ? (*value - min) / (max - min) : 0.0f;
+ if (t < 0.0f) t = 0.0f;
+ if (t > 1.0f) t = 1.0f;
+ fill = (int)(t * (float)(r.w - 2));
+
+ kon_uiFill_(ui, r, kon_uiWidgetColor_(ui, id, hovered));
+ kon_uiFill_(ui, KON_RECT(r.x + 1, r.y + 1, fill, r.h - 2), ui->theme.accent);
+ kon_uiOutline_(ui, r, ui->theme.border);
+
+ snprintf(text, sizeof(text), "%.*s: %.2f", (int)(n > 40 ? 40 : n), label, (double)*value);
+ kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, text, strlen(text), ui->theme.text);
+ return changed;
+}
+
+int kon_uiSliderInt(kon_ui_t *ui, const char *label, int *value, int min, int max)
+{
+ float v = (float)*value;
+ int changed = kon_uiSliderFloat(ui, label, &v, (float)min, (float)max);
+
+ if (changed) *value = (int)(v + (v < 0 ? -0.5f : 0.5f));
+ return changed;
+}
+
+int kon_uiSelectable(kon_ui_t *ui, const char *label, int selected)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding;
+ kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH);
+ int hovered, clicked = kon_uiBehave_(ui, id, r, &hovered);
+
+ if (selected) kon_uiFill_(ui, r, ui->theme.active);
+ else if (hovered) kon_uiFill_(ui, r, ui->theme.hover);
+ kon_uiDrawText_(ui, r.x + pad, r.y, label, n, ui->theme.text);
+ return clicked;
+}
+
+int kon_uiTextInput(kon_ui_t *ui, const char *label, char *buffer, size_t size)
+{
+ uint32_t id = kon_uiHash_(ui, label);
+ size_t n = kon_uiVisibleLength_(label);
+ int pad = ui->theme.padding, hovered, entered = 0, i;
+ size_t length = strlen(buffer);
+ kon_rect_t r = kon_uiNextRect_(ui, 0, ui->lineH + pad);
+
+ kon_uiBehave_(ui, id, r, &hovered);
+ if (hovered && ui->mousePressed) {
+ ui->focus = id;
+ ui->cursor = (int)length;
+ }
+ if (ui->focus == id) {
+ ui->focusKept = ui->focusKept || (hovered && ui->mousePressed);
+ if (ui->cursor > (int)length) ui->cursor = (int)length;
+
+ for (i = 0; i < ui->textCount; i++) {
+ unsigned int c = ui->text[i];
+
+ if (c < 32 || c > 126 || length + 1 >= size) continue;
+ memmove(buffer + ui->cursor + 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1);
+ buffer[ui->cursor++] = (char)c;
+ length++;
+ }
+ if (ui->keyPressed[KON_KEY_BACKSPACE] && ui->cursor > 0) {
+ memmove(buffer + ui->cursor - 1, buffer + ui->cursor, length - (size_t)ui->cursor + 1);
+ ui->cursor--;
+ length--;
+ }
+ if (ui->keyPressed[KON_KEY_DELETE] && ui->cursor < (int)length) {
+ memmove(buffer + ui->cursor, buffer + ui->cursor + 1, length - (size_t)ui->cursor);
+ length--;
+ }
+ if (ui->keyPressed[KON_KEY_LEFT] && ui->cursor > 0) ui->cursor--;
+ if (ui->keyPressed[KON_KEY_RIGHT] && ui->cursor < (int)length) ui->cursor++;
+ if (ui->keyPressed[KON_KEY_ENTER]) entered = 1;
+ if (ui->keyPressed[KON_KEY_ESCAPE]) ui->focus = 0;
+ }
+
+ kon_uiFill_(ui, r, ui->focus == id ? ui->theme.hover : ui->theme.widget);
+ kon_uiOutline_(ui, r, ui->focus == id ? ui->theme.accent : ui->theme.border);
+
+ if (length == 0 && ui->focus != id) {
+ kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, label, n, ui->theme.textDim);
+ } else {
+ kon_uiDrawText_(ui, r.x + pad, r.y + pad / 2 + 1, buffer, length, ui->theme.text);
+ if (ui->focus == id) {
+ int cx = r.x + pad + kon_uiTextWidth_(ui, buffer, (size_t)ui->cursor);
+
+ kon_uiFill_(ui, KON_RECT(cx, r.y + 2, 1, r.h - 4), ui->theme.text);
+ }
+ }
+ return entered;
+}
+
+void kon_uiSeparator(kon_ui_t *ui)
+{
+ kon_rect_t r = kon_uiNextRect_(ui, 0, 1);
+
+ kon_uiFill_(ui, r, ui->theme.border);
+}
+
+void kon_uiSameLine(kon_ui_t *ui)
+{
+ ui->sameLine = 1;
+}
+
+#endif /* KONUI_IMPLEMENTATION */
diff --git a/src/impl/konui.c b/src/impl/konui.c
new file mode 100644
index 0000000..d7290c3
--- /dev/null
+++ b/src/impl/konui.c
@@ -0,0 +1,2 @@
+#define KONUI_IMPLEMENTATION
+#include "konui.h"