Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 36 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,8 @@ A delightful TUI framework for Zig, inspired by [Bubble Tea](https://github.com/
## Installation

Add ZigZag to your `build.zig.zon`:

```zig
.dependencies = .{
.zigzag = .{
.url = "https://github.com/meszmate/zigzag/archive/refs/heads/main.tar.gz",
.hash = "...",
},
},
// To pin a specific version instead:
// .url = "https://github.com/meszmate/zigzag/archive/refs/tags/v0.1.0.tar.gz",
```sh
zig fetch --save git+https://github.com/meszmate/zigzag#main
```

Then in your `build.zig`:
Expand Down Expand Up @@ -82,14 +74,14 @@ const Model = struct {
}

pub fn view(self: *const Model, ctx: *const zz.Context) []const u8 {
const style = (zz.Style{}).bold(true).fg(zz.Color.cyan());
const style = (zz.Style{}).bold(true).fg(.cyan);
const text = std.fmt.allocPrint(ctx.allocator, "Count: {d}\n\nPress q to quit", .{self.count}) catch "Error";
return style.render(ctx.allocator, text) catch text;
}
};

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
defer _ = gpa.deinit();

var program = try zz.Program(Model).init(gpa.allocator());
Expand Down Expand Up @@ -171,15 +163,15 @@ The styling system is inspired by Lipgloss:
const style = (zz.Style{})
.bold(true)
.italic(true)
.fg(zz.Color.cyan())
.bg(zz.Color.black())
.fg(.cyan)
.bg(.black)
.paddingAll(1)
.marginAll(2)
.marginBackground(zz.Color.gray(3))
.borderAll(zz.Border.rounded)
.borderForeground(zz.Color.magenta())
.borderTopForeground(zz.Color.cyan()) // Per-side border colors
.borderBottomForeground(zz.Color.green())
.marginBackground(.gray(3))
.borderAll(.rounded)
.borderForeground(.magenta)
.borderTopForeground(.cyan) // Per-side border colors
.borderBottomForeground(.green)
.tabWidth(4)
.width(40)
.alignH(.center);
Expand All @@ -188,11 +180,11 @@ const output = try style.render(allocator, "Hello, World!");
// render() does not append an implicit trailing '\n'

// Text transforms
const upper_style = (zz.Style{}).transform(zz.transforms.uppercase);
const upper_style = (zz.Style{}).transform(.uppercase);
const shouting = try upper_style.render(allocator, "hello"); // "HELLO"

// Inline mode is useful when embedding block-styled output in a single line
const inline = (zz.Style{}).fg(zz.Color.cyan()).inline_style(true);
const inline = (zz.Style{}).fg(.cyan).inline_style(true);

// Whitespace formatting controls
const ws_style = (zz.Style{})
Expand All @@ -204,7 +196,7 @@ const ws_style = (zz.Style{})
const derived = style.unsetBold().unsetPadding().unsetBorder();

// Style inheritance (unset values inherit from parent)
const child = (zz.Style{}).fg(zz.Color.red()).inherit(style);
const child = (zz.Style{}).fg(.red).inherit(style);

// Style ranges - apply different styles to byte ranges
const ranges = &[_]zz.StyleRange{
Expand All @@ -220,9 +212,9 @@ const highlighted = try zz.renderWithHighlights(allocator, "hello", &.{0, 2}, hi

```zig
// Basic ANSI colors
zz.Color.red()
zz.Color.cyan()
zz.Color.brightGreen()
zz.Color.red
zz.Color.cyan
zz.Color.brightGreen

// 256-color palette
zz.Color.color256(123)
Expand All @@ -234,9 +226,9 @@ zz.Color.hex("#FF8040")

// Adaptive colors (change based on terminal capabilities)
const adaptive = zz.AdaptiveColor{
.true_color = zz.Color.hex("#FF8040"),
.color_256 = zz.Color.color256(208),
.ansi = zz.Color.red(),
.true_color = .hex("#FF8040"),
.color_256 = .color256(208),
.ansi = .red,
};
const resolved = adaptive.resolve(ctx.true_color, ctx.color_256);

Expand All @@ -245,7 +237,7 @@ const resolved = adaptive.resolve(ctx.true_color, ctx.color_256);
// ctx.is_dark_background: bool

// Color interpolation (for gradients)
const mid = zz.interpolateColor(zz.Color.red(), zz.Color.green(), 0.5);
const mid = zz.interpolateColor(.red, .green, 0.5);
```

### Borders
Expand Down Expand Up @@ -314,8 +306,8 @@ try viewport.setContent(long_text);
viewport.setWrap(true);
viewport.setScrollbarChars("·", "█");
viewport.setScrollbarStyle(
(zz.Style{}).fg(zz.Color.gray(8)).inline_style(true),
(zz.Style{}).fg(zz.Color.cyan()).inline_style(true),
(zz.Style{}).fg(.gray(8)).inline_style(true),
(zz.Style{}).fg(.cyan).inline_style(true),
);
viewport.handleKey(key_event); // Supports j/k, Page Up/Down, etc.
```
Expand All @@ -327,7 +319,7 @@ Progress bar with optional color gradients:
```zig
var progress = zz.Progress.init();
progress.setWidth(40);
progress.setGradient(zz.Color.hex("#FF6B6B"), zz.Color.hex("#4ECDC4"));
progress.setGradient(.hex("#FF6B6B"), .hex("#4ECDC4"));
progress.setPercent(75);
const bar = try progress.view(allocator);
```
Expand Down Expand Up @@ -398,7 +390,7 @@ Mini chart using Unicode block elements with configurable bucketing, ranges, and
var spark = zz.Sparkline.init(allocator);
spark.setWidth(20);
spark.setSummary(.average);
spark.setGradient(zz.Color.hex("#F97316"), zz.Color.hex("#22C55E"));
spark.setGradient(.hex("#F97316"), .hex("#22C55E"));
try spark.push(10.0);
try spark.push(25.0);
try spark.push(15.0);
Expand All @@ -421,7 +413,7 @@ chart.x_axis = .{ .title = "Time", .tick_count = 5, .show_grid = true };
chart.y_axis = .{ .title = "CPU", .tick_count = 5, .show_grid = true };

var dataset = try zz.ChartDataset.init(allocator, "load");
dataset.setStyle((zz.Style{}).fg(zz.Color.cyan()).bold(true));
dataset.setStyle((zz.Style{}).fg(.cyan).bold(true));
dataset.setShowPoints(true);
dataset.setInterpolation(.monotone_cubic);
dataset.setInterpolationSteps(10);
Expand All @@ -443,8 +435,8 @@ Vertical or horizontal bar chart with labels, values, and positive/negative base
var bars = zz.BarChart.init(allocator);
bars.setOrientation(.horizontal);
bars.show_values = true;
try bars.addBar(try zz.Bar.init(allocator, "api", 31));
try bars.addBar(try zz.Bar.init(allocator, "db", -12));
try bars.addBar(try .init(allocator, "api", 31));
try bars.addBar(try .init(allocator, "db", -12));
const view = try bars.view(allocator);
```

Expand All @@ -459,8 +451,8 @@ defer canvas.deinit();
canvas.setSize(24, 10);
canvas.setMarker(.braille);
canvas.setRanges(.{ .min = -1, .max = 1 }, .{ .min = -1, .max = 1 });
try canvas.drawLineStyled(-1, -1, 1, 1, (zz.Style{}).fg(zz.Color.yellow()), null);
try canvas.drawPointStyled(0.25, 0.7, (zz.Style{}).fg(zz.Color.cyan()), null);
try canvas.drawLineStyled(-1, -1, 1, 1, (zz.Style{}).fg(.yellow), null);
try canvas.drawPointStyled(0.25, 0.7, (zz.Style{}).fg(.cyan), null);
const view = try canvas.view(allocator);
```

Expand Down Expand Up @@ -578,8 +570,8 @@ gauge.label = "CPU";
gauge.full_char = "\xe2\x96\x88"; // Customizable fill character
gauge.empty_char = "\xe2\x96\x91"; // Customizable empty character
gauge.thresholds = &.{
.{ .value = 80, .color = zz.Color.yellow() },
.{ .value = 90, .color = zz.Color.red() },
.{ .value = 80, .color = .yellow },
.{ .value = 90, .color = .red },
};
const output = gauge.view(allocator);
```
Expand Down Expand Up @@ -615,7 +607,7 @@ cal.week_start_monday = true;
cal.day_headers_mon = .{ "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" }; // Customizable
cal.month_names = .{ "Jan", "Feb", ... }; // Customizable
cal.prev_symbol = "\xe2\x97\x80"; // Customizable nav symbols
cal.addMarkedDate(25, zz.Color.red());
cal.addMarkedDate(25, .red);
cal.update(key_event); // Arrows, Enter, PgUp/PgDn, Shift+L/R
const output = cal.view(allocator);
```
Expand Down Expand Up @@ -883,9 +875,9 @@ var fg: zz.FocusGroup(3) = .{ .wrap = false };

// Custom focus ring colors
const fs = zz.FocusStyle{
.focused_border_fg = zz.Color.green(),
.blurred_border_fg = zz.Color.gray(8),
.border_chars = zz.Border.double,
.focused_border_fg = .green,
.blurred_border_fg = .gray(8),
.border_chars = .double,
};
```

Expand Down
78 changes: 39 additions & 39 deletions examples/accessibility.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ const Model = struct {

pub fn init(self: *Model, _: *zz.Context) zz.Cmd(Msg) {
self.pairs = .{
.{ .name = "White on Black", .fg = zz.Color.white(), .bg = zz.Color.black() },
.{ .name = "Black on White", .fg = zz.Color.black(), .bg = zz.Color.white() },
.{ .name = "Cyan on Dark", .fg = zz.Color.cyan(), .bg = zz.Color.fromRgb(30, 30, 46) },
.{ .name = "Gray on Gray", .fg = zz.Color.fromRgb(120, 120, 120), .bg = zz.Color.fromRgb(140, 140, 140) },
.{ .name = "Yellow on White", .fg = zz.Color.yellow(), .bg = zz.Color.white() },
.{ .name = "Green on Black", .fg = zz.Color.green(), .bg = zz.Color.black() },
.{ .name = "Red on Dark Red", .fg = zz.Color.red(), .bg = zz.Color.fromRgb(60, 10, 10) },
.{ .name = "Blue on Blue", .fg = zz.Color.fromRgb(100, 100, 200), .bg = zz.Color.fromRgb(80, 80, 180) },
.{ .name = "White on Black", .fg = .white, .bg = .black },
.{ .name = "Black on White", .fg = .black, .bg = .white },
.{ .name = "Cyan on Dark", .fg = .cyan, .bg = .fromRgb(30, 30, 46) },
.{ .name = "Gray on Gray", .fg = .fromRgb(120, 120, 120), .bg = .fromRgb(140, 140, 140) },
.{ .name = "Yellow on White", .fg = .yellow, .bg = .white },
.{ .name = "Green on Black", .fg = .green, .bg = .black },
.{ .name = "Red on Dark Red", .fg = .red, .bg = .fromRgb(60, 10, 10) },
.{ .name = "Blue on Blue", .fg = .fromRgb(100, 100, 200), .bg = .fromRgb(80, 80, 180) },
};
self.selected = 0;
return .none;
Expand All @@ -53,11 +53,11 @@ const Model = struct {
const w = &result.writer;

// Title
var title_s = zz.Style{};
title_s = title_s.bold(true);
title_s = title_s.fg(zz.Color.white());
title_s = title_s.inline_style(true);
const title = title_s.render(ctx.allocator, "Accessibility: WCAG Contrast Checker") catch "A11y Demo";
const title = (zz.Style{})
.bold(true)
.fg(.white)
.inline_style(true)
.render(ctx.allocator, "Accessibility: WCAG Contrast Checker") catch "A11y Demo";
w.print("{s}\n\n", .{title}) catch {};

// Table header
Expand All @@ -78,10 +78,10 @@ const Model = struct {

// Level badge color
const level_color: zz.Color = switch (level) {
.aaa => zz.Color.green(),
.aa => zz.Color.cyan(),
.aa_large => zz.Color.yellow(),
.fail => zz.Color.red(),
.aaa => .green,
.aa => .cyan,
.aa_large => .yellow,
.fail => .red,
};

// Row indicator
Expand All @@ -99,23 +99,23 @@ const Model = struct {
w.print("{d:.1}:1 ", .{ratio}) catch {};

// Level badge
var badge_s = zz.Style{};
badge_s = badge_s.fg(level_color);
badge_s = badge_s.bold(true);
badge_s = badge_s.inline_style(true);
const badge = badge_s.render(ctx.allocator, level_name) catch level_name;
const badge = (zz.Style{})
.fg(level_color)
.bold(true)
.inline_style(true)
.render(ctx.allocator, level_name) catch level_name;
w.print("{s}", .{badge}) catch {};
const badge_len = level_name.len;
if (badge_len < 10) {
for (0..10 - badge_len) |_| w.writeByte(' ') catch {};
}

// Sample text with the actual colors
var sample_s = zz.Style{};
sample_s = sample_s.fg(pair.fg);
sample_s = sample_s.bg(pair.bg);
sample_s = sample_s.inline_style(true);
const sample = sample_s.render(ctx.allocator, " Sample Text ") catch "Sample";
const sample = (zz.Style{})
.fg(pair.fg)
.bg(pair.bg)
.inline_style(true)
.render(ctx.allocator, " Sample Text ") catch "Sample";
w.print("{s}", .{sample}) catch {};

w.writeByte('\n') catch {};
Expand All @@ -138,32 +138,32 @@ const Model = struct {
},
};
const label_text = a11y_label.format(ctx.allocator) catch "?";
var label_s = zz.Style{};
label_s = label_s.fg(zz.Color.gray(14));
label_s = label_s.inline_style(true);
const label_rendered = label_s.render(ctx.allocator, label_text) catch label_text;
w.print("Screen reader: {s}\n", .{label_rendered}) catch {};
const label = (zz.Style{})
.fg(.gray(14))
.inline_style(true)
.render(ctx.allocator, label_text) catch label_text;
w.print("Screen reader: {s}\n", .{label}) catch {};

// Suggested foreground
const suggested = zz.a11y.suggestForeground(pair.bg);
const sugg_name: []const u8 = if (std.meta.eql(suggested, zz.Color.white())) "white" else "black";
const sugg_name: []const u8 = if (std.meta.eql(suggested, .white)) "white" else "black";
w.print("Suggested foreground for this bg: {s}\n", .{sugg_name}) catch {};

// Help
w.writeAll("\n") catch {};
var help_s = zz.Style{};
help_s = help_s.fg(zz.Color.gray(12));
help_s = help_s.inline_style(true);
const help = help_s.render(ctx.allocator, "Up/Down: select pair | q: quit") catch "";
const help = (zz.Style{})
.fg(.gray(12))
.inline_style(true)
.render(ctx.allocator, "Up/Down: select pair | q: quit") catch "";
w.writeAll(help) catch {};

return result.toOwnedSlice() catch "Error";
}
};

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
defer std.debug.assert(gpa.deinit() == .ok);

var program = try zz.Program(Model).init(gpa.allocator());
defer program.deinit();
Expand Down
2 changes: 1 addition & 1 deletion examples/action_system.zig
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ const Model = struct {

var title = zz.Style{};
title = title.bold(true);
title = title.fg(zz.Color.cyan());
title = title.fg(zz.Color.cyan);
title = title.inline_style(true);
const title_str = title.render(alloc, "ActionRegistry — one source of truth") catch "";

Expand Down
10 changes: 5 additions & 5 deletions examples/animation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const Model = struct {
pub fn view(self: *const Model, ctx: *const zz.Context) []const u8 {
var title_style = zz.Style{};
title_style = title_style.bold(true);
title_style = title_style.fg(zz.Color.magenta());
title_style = title_style.fg(zz.Color.magenta);
title_style = title_style.inline_style(true);
const title = title_style.render(ctx.allocator, "Animation & Easing Demo") catch "Animation";

Expand All @@ -101,7 +101,7 @@ const Model = struct {
for (&self.tweens, self.easing_names) |*tw, name| {
// Label
var label_style = zz.Style{};
label_style = label_style.fg(zz.Color.cyan());
label_style = label_style.fg(zz.Color.cyan);
label_style = label_style.inline_style(true);
const label = std.fmt.allocPrint(ctx.allocator, "{s:>20}: ", .{name}) catch "";
const styled_label = label_style.render(ctx.allocator, label) catch label;
Expand All @@ -112,7 +112,7 @@ const Model = struct {
for (0..30) |i| {
if (i == pos) {
var dot_style = zz.Style{};
dot_style = dot_style.fg(zz.Color.green());
dot_style = dot_style.fg(zz.Color.green);
dot_style = dot_style.bold(true);
dot_style = dot_style.inline_style(true);
const dot = dot_style.render(ctx.allocator, "●") catch "o";
Expand All @@ -131,13 +131,13 @@ const Model = struct {
// Color tween demo
writer.writeAll("\n") catch {};
var color_label_style = zz.Style{};
color_label_style = color_label_style.fg(zz.Color.cyan());
color_label_style = color_label_style.fg(zz.Color.cyan);
color_label_style = color_label_style.inline_style(true);
const color_label = color_label_style.render(ctx.allocator, " Color tween: ") catch "";
writer.writeAll(color_label) catch {};

const ct = self.color_tween.value();
const color = zz.tweenColor(zz.Color.red(), zz.Color.cyan(), ct);
const color = zz.tweenColor(zz.Color.red, zz.Color.cyan, ct);
var cs = zz.Style{};
cs = cs.fg(color);
cs = cs.bold(true);
Expand Down
Loading
Loading