A charm-like tui library
README.md

HEX #

A collection of terminal libraries.

zig fetch --save git+https://tangled.org/poecoh.com/hex
// build.zig
const std = @import("std");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const hex = b.dependency("hex", .{ .target = target });
    const matcha = hex.module("matcha");
    const blush = hex.module("blush");
    const leaves = hex.module("leaves");
    const dream = hex.module("dream");
    const zli = hex.module("zli");
}

BLUSH #

Derived from lipgloss. This module handles styling and layout.

MATCHA #

Derived from bubbletea. This module handles model interfaces and

LEAVES #

Based off of modules in bubbles, this module contains pre-built tui related interfaces.

DREAM #

Originally based off of wish. This is very much unstable in it's current iteration.

ZLI #

Basic usage #

// src/root.zig
const std = @import("std");
const matcha = @import("matcha");
const blush = @import("blush");
const Writer = std.Io.Writer;

pub const Demo = struct {
    model: matcha.Model = .{
        .vtable = &.{
            .init = init,
            .update = update,
            .view = view,
        },
    },
    cursor: usize = 0,
    items: [3][]const u8 = .{ "buy carrots", "buy celery", "buy kohlrabi" },
    selected: [3]bool = .{ false, false, false },

    fn init(_: *matcha.Model) ?matcha.Cmd {
        return null;
    }

    fn update(m: *matcha.Model, msg: matcha.Msg) ?matcha.Cmd {
        const self: *Demo = @fieldParentPtr("model", m);
        switch (msg) {
            .key_press => |k| {
                switch (k.kind) {
                    .rune => switch (k.rune) {
                        'q' => return .quit,
                        'j' => self.cursor = @min(self.cursor + 1, self.items.len - 1),
                        'k' => self.cursor -|= 1,
                        else => {},
                    },
                    .down => self.cursor = @min(self.cursor + 1, self.items.len - 1),
                    .up => self.cursor -|= 1,
                    .space, .enter => self.selected[self.cursor] = !self.selected[self.cursor],
                    else => {},
                }
            },
            else => {},
        }
        return null;
    }

    fn view(m: *matcha.Model, w: *Writer) Writer.Error!matcha.View {
        const self: *Demo = @fieldParentPtr("model", m);
        try w.writeAll("what do we need from the market?\n\n");
        for (self.items, 0..) |item, i| {
            const cursor: u8 = if (i == self.cursor) '>' else ' ';
            const check: u8 = if (self.selected[i]) 'x' else ' ';
            try w.print("{c} [{c}] {s}\n", .{ cursor, check, item });
        }
        try w.writeAll("\nq quit  k/j or up/down move  space toggle\n");
        return .{ .window_title = "matcha demo" };
    }
};
test {
    std.testing.refAllDecls(@This());
}

// src/main.zig
const std = @import("std");
const blush = @import("blush");
const matcha = @import("matcha");
const Writer = std.Io.Writer;
const Demo = @import("lib").Demo;

pub fn main(init: std.process.Init) !void {
    const gpa = init.gpa;
    var demo: Demo = .{};
    var program: matcha.Program = try .init(gpa, init.io, &demo.model, .{});
    defer program.deinit();
    try program.run();
}