Form widgets: - Checkbox and CheckboxGroup for boolean inputs - RadioGroup for single-selection options - Select dropdown with keyboard navigation - Slider and RangeSlider for numeric inputs - TextArea for multi-line text input UI utilities: - StatusBar for bottom-of-screen information - Toast and ToastManager for notifications Examples: - form_demo.zig: Interactive form widgets showcase - panel_demo.zig: Docking panel system demo Documentation: - Complete README.md with Quick Start, widget examples, and API reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
324 lines
7.7 KiB
Markdown
324 lines
7.7 KiB
Markdown
# zcatui
|
|
|
|
A Terminal User Interface (TUI) library for Zig, inspired by [ratatui](https://github.com/ratatui/ratatui).
|
|
|
|
> **zcatui** = "zcat" + "ui" (a nod to ratatui and Zig's mascot)
|
|
|
|
## Features
|
|
|
|
### Core
|
|
- **Immediate mode rendering** with double buffering and diff-based updates
|
|
- **Flexible layout system** with constraints (Length, Min, Max, Percentage, Ratio)
|
|
- **Rich styling** with 16/256/RGB colors and modifiers (bold, italic, underline, etc.)
|
|
- **Full event handling** for keyboard and mouse input
|
|
- **Cross-terminal compatibility** via ANSI escape sequences
|
|
|
|
### Widgets (30+)
|
|
|
|
| Category | Widgets |
|
|
|----------|---------|
|
|
| **Basic** | Block, Paragraph, List, Table, Tabs |
|
|
| **Data** | Gauge, LineGauge, Sparkline, BarChart, Chart, Canvas |
|
|
| **Input** | Input (text field), TextArea, Checkbox, RadioGroup, Select, Slider |
|
|
| **Navigation** | Menu, MenuBar, ContextMenu, Tree, FilePicker |
|
|
| **Overlays** | Popup, Modal, Tooltip, Toast |
|
|
| **Layout** | Panel, PanelSplit, TabbedPanel, DockingPanel, ScrollView, VirtualList |
|
|
| **Utilities** | Scrollbar, Calendar, StatusBar, Clear |
|
|
|
|
### Terminal Extensions
|
|
- **Clipboard** (OSC 52) - Read/write system clipboard
|
|
- **Hyperlinks** (OSC 8) - Clickable links in terminal
|
|
- **Notifications** (OSC 9/777) - Desktop notifications
|
|
- **Images** (Kitty/iTerm2) - Display images in terminal
|
|
- **Cursor control** - Style, visibility, position
|
|
|
|
### Advanced Features
|
|
- **Animation system** with easing functions
|
|
- **Lazy rendering** with caching and throttling
|
|
- **Virtual scrolling** for large datasets
|
|
- **LEGO panel system** for complex layouts
|
|
|
|
## Quick Start
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
const zcatui = @import("zcatui");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
// Initialize terminal
|
|
var term = try zcatui.Terminal.init(allocator);
|
|
defer term.deinit();
|
|
|
|
// Main loop
|
|
while (true) {
|
|
try term.draw(render);
|
|
|
|
if (try term.pollEvent(100)) |event| {
|
|
if (event == .key) {
|
|
if (event.key.code == .char and event.key.code.char == 'q') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render(area: zcatui.Rect, buf: *zcatui.Buffer) void {
|
|
const block = zcatui.widgets.Block.init()
|
|
.title(" Hello zcatui! ")
|
|
.setBorders(zcatui.widgets.Borders.all)
|
|
.style(zcatui.Style.default.fg(zcatui.Color.cyan));
|
|
block.render(area, buf);
|
|
}
|
|
```
|
|
|
|
## Installation
|
|
|
|
Add zcatui to your `build.zig.zon`:
|
|
|
|
```zig
|
|
.dependencies = .{
|
|
.zcatui = .{
|
|
.url = "https://git.reugenio.com/reugenio/zcatui/archive/main.tar.gz",
|
|
// Add hash after first build attempt
|
|
},
|
|
},
|
|
```
|
|
|
|
Then in `build.zig`:
|
|
|
|
```zig
|
|
const zcatui = b.dependency("zcatui", .{
|
|
.target = target,
|
|
.optimize = optimize,
|
|
});
|
|
exe.root_module.addImport("zcatui", zcatui.module("zcatui"));
|
|
```
|
|
|
|
## Examples
|
|
|
|
Run examples with:
|
|
|
|
```bash
|
|
zig build hello # Basic hello world
|
|
zig build events-demo # Keyboard/mouse events
|
|
zig build list-demo # List widget
|
|
zig build table-demo # Table widget
|
|
zig build dashboard # Dashboard with multiple widgets
|
|
zig build input-demo # Text input
|
|
zig build animation-demo # Animations
|
|
zig build menu-demo # Menus and modals
|
|
zig build form-demo # Form widgets
|
|
zig build panel-demo # Panel system
|
|
```
|
|
|
|
## Widget Examples
|
|
|
|
### Layout
|
|
|
|
```zig
|
|
const Layout = zcatui.Layout;
|
|
const Constraint = zcatui.Constraint;
|
|
|
|
// Split area vertically
|
|
const chunks = Layout.vertical(&.{
|
|
Constraint.length(3), // Fixed 3 rows
|
|
Constraint.percentage(50), // 50% of remaining
|
|
Constraint.min(5), // At least 5 rows
|
|
}).split(area);
|
|
|
|
// Split horizontally
|
|
const cols = Layout.horizontal(&.{
|
|
Constraint.ratio(1, 3), // 1/3 of width
|
|
Constraint.ratio(2, 3), // 2/3 of width
|
|
}).split(area);
|
|
```
|
|
|
|
### Block with Borders
|
|
|
|
```zig
|
|
const Block = zcatui.widgets.Block;
|
|
const Borders = zcatui.widgets.Borders;
|
|
|
|
const block = Block.init()
|
|
.title(" My Panel ")
|
|
.setBorders(Borders.all)
|
|
.style(Style.default.fg(Color.cyan));
|
|
block.render(area, buf);
|
|
|
|
const inner = block.inner(area); // Get content area
|
|
```
|
|
|
|
### List
|
|
|
|
```zig
|
|
const List = zcatui.widgets.List;
|
|
const ListItem = zcatui.widgets.ListItem;
|
|
|
|
const items = &[_]ListItem{
|
|
ListItem.init("Item 1"),
|
|
ListItem.init("Item 2").style(Style.default.fg(Color.green)),
|
|
ListItem.init("Item 3"),
|
|
};
|
|
|
|
var list = List.init(items)
|
|
.block(Block.init().title("List").setBorders(Borders.all))
|
|
.highlightStyle(Style.default.bg(Color.blue));
|
|
list.renderStateful(area, buf, &list_state);
|
|
```
|
|
|
|
### Form Widgets
|
|
|
|
```zig
|
|
// Checkbox
|
|
const checkbox = Checkbox.init("Enable feature")
|
|
.setChecked(true)
|
|
.setFocused(is_focused);
|
|
checkbox.render(area, buf);
|
|
|
|
// Radio buttons
|
|
var radio = RadioGroup.init(&.{"Option A", "Option B", "Option C"})
|
|
.setSelected(1);
|
|
radio.render(area, buf);
|
|
|
|
// Dropdown select
|
|
var select = Select.init(&.{"Small", "Medium", "Large"})
|
|
.setPlaceholder("Choose size...");
|
|
select.render(area, buf);
|
|
|
|
// Slider
|
|
const slider = Slider.init(0, 100)
|
|
.setValue(50)
|
|
.setLabel("Volume");
|
|
slider.render(area, buf);
|
|
```
|
|
|
|
### Charts
|
|
|
|
```zig
|
|
// Bar chart
|
|
const BarChart = zcatui.widgets.BarChart;
|
|
const chart = BarChart.init()
|
|
.data(&.{
|
|
.{ .label = "A", .value = 10 },
|
|
.{ .label = "B", .value = 20 },
|
|
.{ .label = "C", .value = 15 },
|
|
})
|
|
.barWidth(5);
|
|
chart.render(area, buf);
|
|
|
|
// Sparkline
|
|
const Sparkline = zcatui.widgets.Sparkline;
|
|
const sparkline = Sparkline.init(&.{1, 4, 2, 8, 5, 3, 9, 2});
|
|
sparkline.render(area, buf);
|
|
```
|
|
|
|
### Popups and Modals
|
|
|
|
```zig
|
|
const Modal = zcatui.widgets.Modal;
|
|
const confirmDialog = zcatui.widgets.confirmDialog;
|
|
|
|
// Quick confirm dialog
|
|
const modal = confirmDialog("Confirm", &.{"Are you sure?"});
|
|
modal.render(area, buf);
|
|
|
|
// Handle button press
|
|
if (modal.getFocusedButton() == 0) {
|
|
// OK pressed
|
|
}
|
|
```
|
|
|
|
### Toast Notifications
|
|
|
|
```zig
|
|
const ToastManager = zcatui.widgets.ToastManager;
|
|
|
|
var toasts = ToastManager.init();
|
|
|
|
// Show notifications
|
|
toasts.info("Information message");
|
|
toasts.success("Operation completed!");
|
|
toasts.warning("Warning!");
|
|
toasts.showError("Error occurred");
|
|
|
|
// In render loop
|
|
toasts.update();
|
|
toasts.render(area, buf);
|
|
```
|
|
|
|
## Terminal Extensions
|
|
|
|
### Clipboard
|
|
|
|
```zig
|
|
const Clipboard = zcatui.Clipboard;
|
|
|
|
// Write to clipboard
|
|
try Clipboard.write(writer, "Hello clipboard!");
|
|
|
|
// Read (async - response comes via terminal)
|
|
try Clipboard.requestRead(writer);
|
|
```
|
|
|
|
### Hyperlinks
|
|
|
|
```zig
|
|
const Hyperlink = zcatui.Hyperlink;
|
|
|
|
const link = Hyperlink.init("https://example.com", "Click here");
|
|
try link.write(writer);
|
|
```
|
|
|
|
### Notifications
|
|
|
|
```zig
|
|
const notification = zcatui.notification;
|
|
|
|
try notification.notify(writer, "Build complete!");
|
|
try notification.notifyWithTitle(writer, "zcatui", "Task finished");
|
|
```
|
|
|
|
### Images
|
|
|
|
```zig
|
|
const image = zcatui.image;
|
|
|
|
// Display image (Kitty protocol)
|
|
try image.Kitty.displayFile(writer, "/path/to/image.png", .{
|
|
.width = 40,
|
|
.height = 20,
|
|
});
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────┐ ┌────────┐ ┌──────────┐
|
|
│ Application │───▶│ Buffer │───▶│ Terminal │
|
|
│ (widgets) │ │ (diff) │ │ (output) │
|
|
└─────────────┘ └────────┘ └──────────┘
|
|
```
|
|
|
|
1. Application renders widgets to a Buffer
|
|
2. Buffer is compared (diff) with previous frame
|
|
3. Only changes are sent to terminal (efficient)
|
|
|
|
## Requirements
|
|
|
|
- Zig 0.15.x
|
|
- POSIX terminal (Linux, macOS) or Windows Terminal
|
|
- Terminal with ANSI escape sequence support
|
|
|
|
## License
|
|
|
|
MIT
|
|
|
|
## Credits
|
|
|
|
- Inspired by [ratatui](https://github.com/ratatui/ratatui) (Rust)
|
|
- Built with Zig
|