diff --git a/examples/termbox2-demo/CMakeLists.txt b/examples/termbox2-demo/CMakeLists.txt
new file mode 100644
index 0000000..4dbbb3d
--- /dev/null
+++ b/examples/termbox2-demo/CMakeLists.txt
@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.25)
+project(clay_examples_termbox2_demo C)
+set(CMAKE_C_STANDARD 99)
+
+include(FetchContent)
+set(FETCHCONTENT_QUIET FALSE)
+
+FetchContent_Declare(
+ termbox2
+ GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
+ GIT_TAG "8ee9dc17e1ca61c630f91db0aa7f81fa29a32040"
+ GIT_PROGRESS TRUE
+ GIT_SHALLOW TRUE
+)
+FetchContent_MakeAvailable(termbox2)
+
+add_executable(clay_examples_termbox2_demo main.c)
+
+target_compile_options(clay_examples_termbox2_demo PUBLIC)
+target_include_directories(clay_examples_termbox2_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR})
+
+add_custom_command(
+ TARGET clay_examples_termbox2_demo POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ ${CMAKE_CURRENT_SOURCE_DIR}/resources
+ ${CMAKE_CURRENT_BINARY_DIR}/resources)
diff --git a/examples/termbox2-demo/main.c b/examples/termbox2-demo/main.c
new file mode 100644
index 0000000..26107a6
--- /dev/null
+++ b/examples/termbox2-demo/main.c
@@ -0,0 +1,818 @@
+/*
+ Unlicense
+
+ Copyright (c) 2025 Mivirl
+
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ 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 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.
+
+ For more information, please refer to
+*/
+
+#define CLAY_IMPLEMENTATION
+#include "../../clay.h"
+#include "../../renderers/termbox2/clay_renderer_termbox2.c"
+
+#define TB_IMPL
+#include "termbox2.h"
+
+
+// -------------------------------------------------------------------------------------------------
+// -- Data structures
+
+typedef struct {
+ unsigned char *data;
+ long size;
+} image;
+
+
+// -------------------------------------------------------------------------------------------------
+// -- Internal state
+
+// If the program should exit the main render/interaction loop
+bool end_loop = false;
+
+// If the debug tools should be displayed
+bool debugMode = false;
+
+
+// -------------------------------------------------------------------------------------------------
+// -- Internal utility functions
+
+/**
+ Load an image into memory as bytes
+
+ \param filename Filepath to image to be loaded
+ */
+image load_image(const char *filename)
+{
+ image rv = { .data = NULL, .size = 0 };
+
+ if (NULL == filename) {
+ return rv;
+ }
+
+ FILE *image_file = NULL;
+
+ image_file = fopen(filename, "r");
+ if (NULL == image_file) {
+ fprintf(stderr, "Failed to open image %s: %s\n", filename, strerror(errno));
+ return rv;
+ }
+
+ int err = fseek(image_file, 0, SEEK_END);
+ if (-1 == err) {
+ fprintf(stderr, "Failed to seek image %s: %s\n", filename, strerror(errno));
+ goto done;
+ }
+
+ long image_size = ftell(image_file);
+ if (-1 == image_size) {
+ fprintf(stderr, "Failed to get filesize for image %s: %s\n", filename, strerror(errno));
+ goto done;
+ }
+ rewind(image_file);
+
+ unsigned char *image_data = malloc(image_size);
+ if (NULL == image_data) {
+ fprintf(stderr, "Failed to allocate memory for image %s: %s\n", filename, strerror(errno));
+ goto done;
+ }
+
+ size_t written = fread(image_data, image_size, 1, image_file);
+ if (1 != written) {
+ fprintf(stderr, "Failed to load image %s into memory: %s\n", filename, strerror(errno));
+ goto error_read;
+ }
+
+ rv.data = image_data;
+ rv.size = image_size;
+ goto done;
+
+error_read:
+ free(image_data);
+done:
+ fclose(image_file);
+ return rv;
+}
+
+/**
+ Free an image from memory
+
+ \param image Image to free
+ */
+void free_image(image image)
+{
+ free(image.data);
+ image.data = NULL;
+ image.size = 0;
+}
+
+/**
+ Block until an event is received for termbox
+ */
+void termbox_waitfor_event(void)
+{
+ int termbox_ttyfd, termbox_resizefd;
+ tb_get_fds(&termbox_ttyfd, &termbox_resizefd);
+ int nfds = CLAY__MAX(termbox_ttyfd, termbox_resizefd) + 1;
+ fd_set monitor_set;
+ FD_ZERO(&monitor_set);
+ FD_SET(termbox_ttyfd, &monitor_set);
+ FD_SET(termbox_resizefd, &monitor_set);
+ select(nfds, &monitor_set, NULL, NULL, NULL);
+}
+
+
+// -------------------------------------------------------------------------------------------------
+// -- Clay components
+
+void component_text_pair(const char *key, const char *value)
+{
+ size_t keylen = strlen(key);
+ size_t vallen = strlen(value);
+ Clay_String keytext = (Clay_String) {
+ .length = keylen,
+ .chars = key,
+ };
+ Clay_String valtext = (Clay_String) {
+ .length = vallen,
+ .chars = value,
+ };
+ Clay_TextElementConfig *textconfig =
+ CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } });
+ CLAY({
+ .layout = {
+ .sizing = {
+ .width = {
+ .size.minMax = {
+ .min = strlen("Border chars CLAY_TB_BORDER_CHARS_UNICODE") * Clay_Termbox_Cell_Width(),
+ }
+ },
+ }
+ },
+ }) {
+ CLAY_TEXT(keytext, textconfig);
+ CLAY({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { }
+ CLAY_TEXT(valtext, textconfig);
+ }
+
+}
+
+void component_termbox_settings(void)
+{
+ CLAY({
+ .floating = {
+ .attachTo = CLAY_ATTACH_TO_PARENT,
+ .zIndex = 1,
+ .attachPoints = { CLAY_ATTACH_POINT_CENTER_CENTER, CLAY_ATTACH_POINT_CENTER_TOP },
+ .offset = { 0, 0 }
+ },
+ }) {
+ CLAY({
+ .layout = {
+ .sizing = CLAY_SIZING_FIT(),
+ .padding = {
+ 6 * Clay_Termbox_Cell_Width(),
+ 6 * Clay_Termbox_Cell_Width(),
+ 2 * Clay_Termbox_Cell_Height(),
+ 2 * Clay_Termbox_Cell_Height(),
+ }
+ },
+ .border = {
+ .width = CLAY_BORDER_ALL(1),
+ .color = { 0x00, 0x00, 0x00, 0xff }
+ },
+ .backgroundColor = { 0x7f, 0x00, 0x00, 0x7f }
+ }) {
+ const char *color_mode = NULL;
+ switch (clay_tb_color_mode) {
+ case TB_OUTPUT_NORMAL: {
+ color_mode = "TB_OUTPUT_NORMAL";
+ break;
+ }
+ case TB_OUTPUT_256: {
+ color_mode = "TB_OUTPUT_256";
+ break;
+ }
+ case TB_OUTPUT_216: {
+ color_mode = "TB_OUTPUT_216";
+ break;
+ }
+ case TB_OUTPUT_GRAYSCALE: {
+ color_mode = "TB_OUTPUT_GRAYSCALE";
+ break;
+ }
+ case TB_OUTPUT_TRUECOLOR: {
+ color_mode = "TB_OUTPUT_TRUECOLOR";
+ break;
+ }
+ case CLAY_TB_OUTPUT_NOCOLOR: {
+ color_mode = "CLAY_TB_OUTPUT_NOCOLOR";
+ break;
+ }
+ default: {
+ color_mode = "INVALID";
+ break;
+ }
+ }
+ const char *border_mode = NULL;
+ switch (clay_tb_border_mode) {
+ case CLAY_TB_BORDER_MODE_ROUND: {
+ border_mode = "CLAY_TB_BORDER_MODE_ROUND";
+ break;
+ }
+ case CLAY_TB_BORDER_MODE_MINIMUM: {
+ border_mode = "CLAY_TB_BORDER_MODE_MINIMUM";
+ break;
+ }
+ default: {
+ color_mode = "INVALID";
+ break;
+ }
+ }
+ const char *border_chars = NULL;
+ switch (clay_tb_border_chars) {
+ case CLAY_TB_BORDER_CHARS_ASCII: {
+ border_chars = "CLAY_TB_BORDER_CHARS_ASCII";
+ break;
+ }
+ case CLAY_TB_BORDER_CHARS_UNICODE: {
+ border_chars = "CLAY_TB_BORDER_CHARS_UNICODE";
+ break;
+ }
+ case CLAY_TB_BORDER_CHARS_NONE: {
+ border_chars = "CLAY_TB_BORDER_CHARS_NONE";
+ break;
+ }
+ default: {
+ border_chars = "INVALID";
+ break;
+ }
+ }
+ const char *transparency = NULL;
+ if (clay_tb_transparency) {
+ transparency = "true";
+ } else {
+ transparency = "false";
+ }
+
+ CLAY({
+ .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM },
+ }) {
+ component_text_pair("Color mode", color_mode);
+ component_text_pair("Border mode", border_mode);
+ component_text_pair("Border chars", border_chars);
+ component_text_pair("Transparency", transparency);
+ }
+ }
+ }
+}
+
+void component_color_palette(void)
+{
+ CLAY({
+ .layout = {
+ .childGap = 16,
+ .padding = {
+ 2 * Clay_Termbox_Cell_Width(),
+ 2 * Clay_Termbox_Cell_Width(),
+ 2 * Clay_Termbox_Cell_Height(),
+ 2 * Clay_Termbox_Cell_Height(),
+ }
+ },
+ .border = {
+ .width = CLAY_BORDER_OUTSIDE(2),
+ .color = { 0x00, 0x00, 0x00, 0xff }
+ },
+ .backgroundColor = { 0x7f, 0x7f, 0x7f, 0xff }
+ }) {
+ for (int type = 0; type < 2; ++type) {
+ CLAY({
+ .layout ={
+ .layoutDirection = CLAY_TOP_TO_BOTTOM,
+ .sizing = CLAY_SIZING_FIT(),
+ .childGap = Clay_Termbox_Cell_Height()
+ },
+ }) {
+ for (float ri = 0; ri < 4; ri += 1) {
+ CLAY({
+ .layout ={
+ .sizing = CLAY_SIZING_FIT(),
+ .childGap = Clay_Termbox_Cell_Width()
+ },
+ }) {
+ for (float r = ri * 0x44; r < (ri + 1) * 0x44; r += 0x22) {
+ CLAY({
+ .layout ={
+ .layoutDirection = CLAY_TOP_TO_BOTTOM,
+ .sizing = CLAY_SIZING_FIT(),
+ },
+ }) {
+ for (float g = 0; g < 0xff; g += 0x22) {
+ CLAY({
+ .layout ={
+ .sizing = CLAY_SIZING_FIT(),
+ },
+ }) {
+ for (float b = 0; b < 0xff; b += 0x22) {
+ Clay_Color color = { r, g, b, 0x7f };
+
+ Clay_LayoutConfig layout = (Clay_LayoutConfig) {
+ .sizing = {
+ .width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()),
+ .height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height())
+ }
+ };
+ if (0 == type) {
+ CLAY({
+ .layout = layout,
+ .backgroundColor = color
+ }) {}
+ } else if (1 == type) {
+ CLAY({
+ .layout = layout,
+ }) {
+ CLAY_TEXT(CLAY_STRING("#"), CLAY_TEXT_CONFIG({ .textColor = color }));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void component_unicode_text(void)
+{
+ CLAY({
+ .layout = {
+ .sizing = CLAY_SIZING_FIT(),
+ .padding = {
+ 2 * Clay_Termbox_Cell_Width(),
+ 2 * Clay_Termbox_Cell_Width(),
+ 2 * Clay_Termbox_Cell_Height(),
+ 2 * Clay_Termbox_Cell_Height(),
+ }
+ },
+ .backgroundColor = { 0xcc, 0xbb, 0xaa, 0xff },
+ .border = {
+ // This border should still be displayed in CLAY_TB_BORDER_MODE_ROUND mode
+ .width = {
+ 0.75 * Clay_Termbox_Cell_Width(),
+ 0.75 * Clay_Termbox_Cell_Width(),
+ 0.75 * Clay_Termbox_Cell_Height(),
+ 0.75 * Clay_Termbox_Cell_Height(),
+ },
+ .color = { 0x33, 0x33, 0x33, 0xff },
+ },
+ }) {
+ CLAY_TEXT(
+ CLAY_STRING("Non-ascii character tests:\n"
+ "\n"
+ "(from https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html)\n"
+ " Mathematics and Sciences:\n"
+ " ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),\n"
+ " ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B),\n"
+ " 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm\n"
+ "\n"
+ " Compact font selection example text:\n"
+ " ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n"
+ " abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n"
+ " –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n"
+ " ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა\n"
+ "\n"
+ "(from https://blog.denisbider.com/2015/09/when-monospace-fonts-arent-unicode.html):\n"
+ " aeioucsz\n"
+ " áéíóúčšž\n"
+ " 台北1234\n"
+ " QRS12\n"
+ " アイウ1234\n"
+ "\n"
+ "(from https://stackoverflow.com/a/1644280)\n"
+ " ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)."
+ ),
+ CLAY_TEXT_CONFIG({ .textColor = { 0x11, 0x11, 0x11, 0xff } })
+ );
+ }
+}
+
+void component_keybinds(void)
+{
+ CLAY({
+ .layout = {
+ .sizing = CLAY_SIZING_FIT(),
+ .padding = {
+ 4 * Clay_Termbox_Cell_Width(),
+ 4 * Clay_Termbox_Cell_Width(),
+ 2 * Clay_Termbox_Cell_Height(),
+ 2 * Clay_Termbox_Cell_Height(),
+ }
+ },
+ .backgroundColor = { 0x00, 0x7f, 0x7f, 0xff }
+ }) {
+ CLAY_TEXT(
+ CLAY_STRING(
+ "Termbox2 renderer test\n"
+ "\n"
+ "Keybinds:\n"
+ " c/C - Cycle through color modes\n"
+ " b/B - Cycle through border modes\n"
+ " h/H - Cycle through border characters\n"
+ " t/T - Toggle transparency\n"
+ " d/D - Toggle debug mode\n"
+ " q/Q - Quit\n"
+ ),
+ CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }})
+ );
+ }
+}
+
+void component_image(image image)
+{
+ CLAY({
+ .layout = {
+ .sizing = {
+ .width = CLAY_SIZING_FIXED(200),
+ .height = CLAY_SIZING_FIXED(200),
+ },
+ },
+ .image = {
+ .imageData = &image,
+ .sourceDimensions = { 512, 512 }
+ }
+ }) { }
+}
+
+void component_mouse_data(void)
+{
+ CLAY({
+ .layout = {
+ //.layoutDirection = CLAY_TOP_TO_BOTTOM,
+ .sizing = {
+ .width = CLAY_SIZING_GROW(),
+ },
+ },
+ }) {
+ Clay_Context* context = Clay_GetCurrentContext();
+ Clay_Vector2 mouse_position = context->pointerInfo.position;
+
+ Clay_LayoutConfig layout = (Clay_LayoutConfig) {
+ .sizing = {
+ .width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()),
+ .height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height())
+ }
+ };
+
+ float v = 255 * mouse_position.x / Clay_Termbox_Width();
+ v = (0 > v) ? 0 : v;
+ v = (255 < v) ? 255 : v;
+ Clay_Color color = (Clay_Color) { v, v, v, 0xff };
+
+ CLAY({
+ .layout = layout,
+ .backgroundColor = color
+ }) {}
+
+ v = 255 * mouse_position.y / Clay_Termbox_Height();
+ v = (0 > v) ? 0 : v;
+ v = (255 < v) ? 255 : v;
+ color = (Clay_Color) { v, v, v, 0xff };
+
+
+ CLAY({
+ .layout = layout,
+ .backgroundColor = color
+ }) {}
+
+ }
+}
+
+void component_bordered_text(void)
+{
+ CLAY({
+ .layout = {
+ .layoutDirection = CLAY_TOP_TO_BOTTOM,
+ .sizing = {
+ .width = CLAY_SIZING_FIT(450),
+ .height = CLAY_SIZING_FIT(),
+ },
+ .padding = CLAY_PADDING_ALL(32)
+ },
+ .backgroundColor = { 0x24, 0x55, 0x34, 0xff },
+ }) {
+ CLAY({
+ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0xaa, 0x00, 0x00, 0xff } },
+ }) {
+ CLAY_TEXT(
+ CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
+ }
+ CLAY({
+ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0xaa, 0x00, 0xff } },
+ }) {
+ CLAY_TEXT(CLAY_STRING("of the border width"),
+ CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
+ }
+ CLAY({
+ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } },
+ }) {
+ CLAY_TEXT(CLAY_STRING("and overlap for multiple lines\nof text"),
+ CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
+ }
+ CLAY({
+ .border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } },
+ }) {
+ CLAY_TEXT(CLAY_STRING("this text\nis long enough\nto display all\n borders\naround it"),
+ CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
+ }
+ }
+}
+
+Clay_RenderCommandArray CreateLayout(image image)
+{
+ Clay_BeginLayout();
+ CLAY({
+ .layout = {
+ .sizing = {
+ .width = CLAY_SIZING_GROW(),
+ .height = CLAY_SIZING_GROW()
+ },
+ .childAlignment = {
+ .x = CLAY_ALIGN_X_CENTER,
+ .y = CLAY_ALIGN_Y_CENTER
+ },
+ .childGap = 64
+ },
+ .backgroundColor = { 0x24, 0x24, 0x24, 0xff }
+ }) {
+ CLAY({
+ .layout = {
+ .childAlignment = {
+ .x = CLAY_ALIGN_X_RIGHT,
+ },
+ .layoutDirection = CLAY_TOP_TO_BOTTOM,
+ .sizing = CLAY_SIZING_FIT(),
+ },
+ }) {
+ component_keybinds();
+ component_unicode_text();
+ }
+ CLAY({
+ .layout = {
+ .layoutDirection = CLAY_TOP_TO_BOTTOM,
+ .childGap = 32,
+ .sizing = CLAY_SIZING_FIT(),
+ },
+ }) {
+ component_termbox_settings();
+ component_image(image);
+ component_image(image);
+ component_mouse_data();
+ component_bordered_text();
+ }
+
+ component_color_palette();
+ }
+ return Clay_EndLayout();
+}
+
+
+// -------------------------------------------------------------------------------------------------
+// -- Interactive functions
+
+void handle_clay_errors(Clay_ErrorData errorData)
+{
+ Clay_Termbox_Close();
+ fprintf(stderr, "%s", errorData.errorText.chars);
+ exit(1);
+}
+
+/**
+ Process events received from termbox2 and handle interaction
+ */
+void handle_termbox_events(void)
+{
+ // Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing
+ // If an event is already available, this doesn't wait. Will not wait due to the previous call
+ // to termbox_waitfor_event. Increasing the wait time reduces load without reducing
+ // responsiveness (but will of course prevent other code from running on this thread while it's
+ // waiting)
+ struct tb_event evt;
+ int ms_to_wait = 100;
+ int err = tb_peek_event(&evt, ms_to_wait);
+
+ switch (err) {
+ default:
+ case TB_ERR_NO_EVENT: {
+ break;
+ }
+ case TB_ERR_POLL: {
+ if (EINTR != tb_last_errno()) {
+ Clay_Termbox_Close();
+ fprintf(stderr, "Failed to read event from TTY\n");
+ exit(1);
+ }
+ break;
+ }
+ case TB_OK: {
+ switch (evt.type) {
+ case TB_EVENT_RESIZE: {
+ Clay_SetLayoutDimensions((Clay_Dimensions) {
+ Clay_Termbox_Width(),
+ Clay_Termbox_Height()
+ });
+ break;
+ }
+ case TB_EVENT_KEY: {
+ if (TB_KEY_CTRL_C == evt.key) {
+ end_loop = true;
+ break;
+ }
+ switch (evt.ch) {
+ case 'q':
+ case 'Q': {
+ end_loop = true;
+ break;
+ }
+ case 'd':
+ case 'D': {
+ debugMode = !debugMode;
+ Clay_SetDebugModeEnabled(debugMode);
+ break;
+ }
+ case 'c': {
+ int new_mode = clay_tb_color_mode - 1;
+ new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR;
+ Clay_Termbox_Set_Color_Mode(new_mode);
+ break;
+ }
+ case 'C': {
+ int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1);
+ Clay_Termbox_Set_Color_Mode(new_mode);
+ break;
+ }
+ case 'b': {
+ enum border_mode new_mode = clay_tb_border_mode - 1;
+ new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
+ ? new_mode
+ : CLAY_TB_BORDER_MODE_MINIMUM;
+ Clay_Termbox_Set_Border_Mode(new_mode);
+ break;
+ }
+ case 'B': {
+ enum border_mode new_mode = (clay_tb_border_mode + 1)
+ % (CLAY_TB_BORDER_MODE_MINIMUM + 1);
+ new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
+ ? new_mode
+ : CLAY_TB_BORDER_MODE_ROUND;
+ Clay_Termbox_Set_Border_Mode(new_mode);
+ break;
+ }
+ case 'h': {
+ enum border_chars new_chars = clay_tb_border_chars - 1;
+ new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
+ ? new_chars
+ : CLAY_TB_BORDER_CHARS_NONE;
+ Clay_Termbox_Set_Border_Chars(new_chars);
+ break;
+ }
+ case 'H': {
+ enum border_chars new_chars
+ = (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1);
+ new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
+ ? new_chars
+ : CLAY_TB_BORDER_CHARS_ASCII;
+ Clay_Termbox_Set_Border_Chars(new_chars);
+ break;
+ }
+ case 't':
+ case 'T': {
+ Clay_Termbox_Set_Transparency(!clay_tb_transparency);
+ }
+ }
+ break;
+ }
+ case TB_EVENT_MOUSE: {
+ Clay_Vector2 mousePosition = {
+ (float)evt.x * Clay_Termbox_Cell_Width(),
+ (float)evt.y * Clay_Termbox_Cell_Height()
+ };
+
+ // Mouse release events may not be produced by all terminals, and will
+ // be sent during hover, so can't be used to detect when the mouse has
+ // been released
+
+ switch (evt.key) {
+ case TB_KEY_MOUSE_LEFT: {
+ Clay_SetPointerState(mousePosition, true);
+ break;
+ }
+ case TB_KEY_MOUSE_RIGHT: {
+ Clay_SetPointerState(mousePosition, false);
+ break;
+ }
+ case TB_KEY_MOUSE_MIDDLE: {
+ Clay_SetPointerState(mousePosition, false);
+ break;
+ }
+ case TB_KEY_MOUSE_RELEASE: {
+ Clay_SetPointerState(mousePosition, false);
+ break;
+ }
+ case TB_KEY_MOUSE_WHEEL_UP: {
+ Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() };
+ Clay_UpdateScrollContainers(false, scrollDelta, 1);
+ break;
+ }
+ case TB_KEY_MOUSE_WHEEL_DOWN: {
+ Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() };
+ Clay_UpdateScrollContainers(false, scrollDelta, 1);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+
+int main(void)
+{
+ image shark_image = load_image("resources/512px-Shark_antwerp_zoo.jpeg");
+ if (NULL == shark_image.data) { exit(1); }
+
+ int num_elements = 3 * 8192;
+ Clay_SetMaxElementCount(num_elements);
+
+ uint64_t size = Clay_MinMemorySize();
+ void *memory = malloc(size);
+ if (NULL == memory) { exit(1); }
+
+ Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory);
+
+ Clay_Termbox_Initialize(
+ TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, false);
+
+ Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() },
+ (Clay_ErrorHandler) { handle_clay_errors, NULL });
+
+ Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL);
+
+ // Initial render before waiting for events
+ Clay_SetLayoutDimensions((Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() });
+ Clay_RenderCommandArray commands = CreateLayout(shark_image);
+ tb_clear();
+ Clay_Termbox_Render(commands);
+ tb_present();
+
+ while (!end_loop) {
+ // Block until event is available. Optional, but reduces load since this demo is purely
+ // synchronous to user input.
+ termbox_waitfor_event();
+
+ handle_termbox_events();
+
+ Clay_RenderCommandArray commands = CreateLayout(shark_image);
+
+ tb_clear();
+ Clay_Termbox_Render(commands);
+ tb_present();
+ }
+
+ Clay_Termbox_Close();
+ free_image(shark_image);
+ return 0;
+}
diff --git a/examples/termbox2-demo/readme.md b/examples/termbox2-demo/readme.md
new file mode 100644
index 0000000..4a8f305
--- /dev/null
+++ b/examples/termbox2-demo/readme.md
@@ -0,0 +1,61 @@
+# Termbox2 renderer demo
+
+Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2)
+
+This demo shows a color palette and a few different components. It allows
+changing configuration settings for colors, border size rounding mode,
+characters used for borders, and transparency.
+
+```
+Keybinds:
+c/C - Cycle through color modes
+b/B - Cycle through border modes
+h/H - Cycle through border characters
+t/T - Toggle transparency
+d/D - Toggle debug mode
+q/Q - Quit
+```
+
+Configuration can be also be overriden by environment variables:
+- `CLAY_TB_COLOR_MODE`
+ - `NORMAL`
+ - `256`
+ - `216`
+ - `GRAYSCALE`
+ - `TRUECOLOR`
+ - `NOCOLOR`
+- `CLAY_TB_BORDER_CHARS`
+ - `DEFAULT`
+ - `ASCII`
+ - `UNICODE`
+ - `NONE`
+- `CLAY_TB_TRANSPARENCY`
+ - `1`
+ - `0`
+- `CLAY_TB_CELL_PIXELS`
+ - `widthxheight`
+
+## Building
+
+Build the binary with cmake
+
+```sh
+mkdir build
+cd build
+cmake ..
+make
+```
+
+Then run the executable:
+
+```sh
+./clay_examples_termbox2_demo
+```
+
+## Attributions
+
+Resources used:
+- `512px-Shark_antwerp_zoo.jpeg`
+ - Retrieved from
+ - License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en)
+ - No changes made
diff --git a/examples/termbox2-demo/resources/512px-Shark_antwerp_zoo.jpeg b/examples/termbox2-demo/resources/512px-Shark_antwerp_zoo.jpeg
new file mode 100644
index 0000000..a487d8e
Binary files /dev/null and b/examples/termbox2-demo/resources/512px-Shark_antwerp_zoo.jpeg differ