New modules (13): - src/resize.zig: SIGWINCH terminal resize detection - src/drag.zig: Mouse drag state and Splitter panels - src/diagnostic.zig: Elm-style error messages with code snippets - src/debug.zig: Debug overlay (FPS, timing, widget count) - src/profile.zig: Performance profiling with scoped timers - src/sixel.zig: Sixel graphics encoding for terminal images - src/async_loop.zig: epoll-based async event loop with timers - src/compose.zig: Widget composition utilities - src/shortcuts.zig: Keyboard shortcut registry - src/widgets/logo.zig: ASCII art logo widget Enhanced modules: - src/layout.zig: Added Constraint.ratio(num, denom) - src/terminal.zig: Integrated resize handling - src/root.zig: Re-exports all new modules New examples (9): - resize_demo, splitter_demo, dirtree_demo - help_demo, markdown_demo, progress_demo - spinner_demo, syntax_demo, viewport_demo Package manager: - build.zig.zon: Zig package manager support Stats: 60+ source files, 186+ tests, 20 executables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
176 lines
6.2 KiB
Zig
176 lines
6.2 KiB
Zig
//! Viewport Demo - Shows scrollable content
|
|
//!
|
|
//! Run with: zig build viewport-demo
|
|
|
|
const std = @import("std");
|
|
const zcatui = @import("zcatui");
|
|
|
|
const Terminal = zcatui.Terminal;
|
|
const Rect = zcatui.Rect;
|
|
const Buffer = zcatui.Buffer;
|
|
const Style = zcatui.Style;
|
|
const Color = zcatui.Color;
|
|
const Block = zcatui.widgets.Block;
|
|
const Borders = zcatui.widgets.Borders;
|
|
|
|
// Sample content - pre-generated long text
|
|
const sample_content =
|
|
\\ 1 | === Welcome to the Viewport Demo ===
|
|
\\ 2 | This demonstrates scrollable content.
|
|
\\ 3 | Use j/k or arrows to scroll.
|
|
\\ 4 |
|
|
\\ 5 | Lorem ipsum dolor sit amet, line 5.
|
|
\\ 6 | Lorem ipsum dolor sit amet, line 6.
|
|
\\ 7 | Lorem ipsum dolor sit amet, line 7.
|
|
\\ 8 | Lorem ipsum dolor sit amet, line 8.
|
|
\\ 9 | Lorem ipsum dolor sit amet, line 9.
|
|
\\ 10 | === Section 1 ===
|
|
\\ 11 | Lorem ipsum dolor sit amet, line 11.
|
|
\\ 12 | Lorem ipsum dolor sit amet, line 12.
|
|
\\ 13 | Lorem ipsum dolor sit amet, line 13.
|
|
\\ 14 | Lorem ipsum dolor sit amet, line 14.
|
|
\\ 15 | --- subsection ---
|
|
\\ 16 | Lorem ipsum dolor sit amet, line 16.
|
|
\\ 17 | Lorem ipsum dolor sit amet, line 17.
|
|
\\ 18 | Lorem ipsum dolor sit amet, line 18.
|
|
\\ 19 | Lorem ipsum dolor sit amet, line 19.
|
|
\\ 20 | === Section 2 ===
|
|
\\ 21 | Lorem ipsum dolor sit amet, line 21.
|
|
\\ 22 | Lorem ipsum dolor sit amet, line 22.
|
|
\\ 23 | Lorem ipsum dolor sit amet, line 23.
|
|
\\ 24 | Lorem ipsum dolor sit amet, line 24.
|
|
\\ 25 | --- subsection ---
|
|
\\ 26 | Lorem ipsum dolor sit amet, line 26.
|
|
\\ 27 | Lorem ipsum dolor sit amet, line 27.
|
|
\\ 28 | Lorem ipsum dolor sit amet, line 28.
|
|
\\ 29 | Lorem ipsum dolor sit amet, line 29.
|
|
\\ 30 | === Section 3 ===
|
|
\\ 31 | Lorem ipsum dolor sit amet, line 31.
|
|
\\ 32 | Lorem ipsum dolor sit amet, line 32.
|
|
\\ 33 | Lorem ipsum dolor sit amet, line 33.
|
|
\\ 34 | Lorem ipsum dolor sit amet, line 34.
|
|
\\ 35 | --- subsection ---
|
|
\\ 36 | Lorem ipsum dolor sit amet, line 36.
|
|
\\ 37 | Lorem ipsum dolor sit amet, line 37.
|
|
\\ 38 | Lorem ipsum dolor sit amet, line 38.
|
|
\\ 39 | Lorem ipsum dolor sit amet, line 39.
|
|
\\ 40 | === Section 4 ===
|
|
\\ 41 | Lorem ipsum dolor sit amet, line 41.
|
|
\\ 42 | Lorem ipsum dolor sit amet, line 42.
|
|
\\ 43 | Lorem ipsum dolor sit amet, line 43.
|
|
\\ 44 | Lorem ipsum dolor sit amet, line 44.
|
|
\\ 45 | --- subsection ---
|
|
\\ 46 | Lorem ipsum dolor sit amet, line 46.
|
|
\\ 47 | Lorem ipsum dolor sit amet, line 47.
|
|
\\ 48 | Lorem ipsum dolor sit amet, line 48.
|
|
\\ 49 | Lorem ipsum dolor sit amet, line 49.
|
|
\\ 50 | === End of content ===
|
|
;
|
|
|
|
/// State for the demo
|
|
const State = struct {
|
|
offset_y: u16 = 0,
|
|
content_height: u16 = 50,
|
|
running: bool = true,
|
|
view_height: u16 = 20,
|
|
};
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
var term = try Terminal.init(allocator);
|
|
defer term.deinit();
|
|
|
|
// Enable mouse for scroll wheel
|
|
try term.enableMouseCapture();
|
|
|
|
var state = State{};
|
|
|
|
while (state.running) {
|
|
state.view_height = term.area().height -| 4;
|
|
try term.drawWithContext(&state, render);
|
|
|
|
if (try term.pollEvent(100)) |event| {
|
|
switch (event) {
|
|
.key => |key| {
|
|
switch (key.code) {
|
|
.char => |c| {
|
|
if (c == 'q') state.running = false;
|
|
if (c == 'j') state.offset_y +|= 1;
|
|
if (c == 'k' and state.offset_y > 0) state.offset_y -= 1;
|
|
if (c == 'g') state.offset_y = 0;
|
|
if (c == 'G') state.offset_y = state.content_height -| state.view_height;
|
|
},
|
|
.up => if (state.offset_y > 0) {
|
|
state.offset_y -= 1;
|
|
},
|
|
.down => state.offset_y +|= 1,
|
|
.page_up => state.offset_y -|= state.view_height,
|
|
.page_down => state.offset_y +|= state.view_height,
|
|
.home => state.offset_y = 0,
|
|
.end => state.offset_y = state.content_height -| state.view_height,
|
|
.esc => state.running = false,
|
|
else => {},
|
|
}
|
|
},
|
|
.mouse => |mouse| {
|
|
switch (mouse.kind) {
|
|
.scroll_up => if (state.offset_y >= 3) {
|
|
state.offset_y -= 3;
|
|
},
|
|
.scroll_down => state.offset_y +|= 3,
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render(state: *State, area: Rect, buf: *Buffer) void {
|
|
// Main border
|
|
const block = Block.init()
|
|
.title(" Viewport Demo ")
|
|
.setBorders(Borders.all)
|
|
.borderStyle((Style{}).fg(Color.cyan));
|
|
block.render(area, buf);
|
|
|
|
// Content area
|
|
const content_area = Rect.init(1, 1, area.width -| 2, area.height -| 3);
|
|
|
|
// Render visible content manually
|
|
var lines = std.mem.splitScalar(u8, sample_content, '\n');
|
|
var line_num: u16 = 0;
|
|
var y: u16 = 0;
|
|
while (lines.next()) |line| {
|
|
if (line_num >= state.offset_y and y < content_area.height) {
|
|
_ = buf.setString(content_area.x, content_area.y + y, line, Style{});
|
|
y += 1;
|
|
}
|
|
line_num += 1;
|
|
}
|
|
|
|
// Scrollbar indicator
|
|
const scroll_pct: u16 = if (state.content_height > state.view_height)
|
|
@min(state.offset_y * 100 / (state.content_height -| state.view_height), 100)
|
|
else
|
|
0;
|
|
|
|
// Footer with scroll info
|
|
var footer_buf: [128]u8 = undefined;
|
|
const footer = std.fmt.bufPrint(&footer_buf, "Line {d}/{d} ({d}%) | j/k up/down scroll, g/G top/bottom, PgUp/PgDn, q quit", .{
|
|
state.offset_y + 1,
|
|
state.content_height,
|
|
scroll_pct,
|
|
}) catch "...";
|
|
|
|
_ = buf.setString(
|
|
2,
|
|
area.height -| 1,
|
|
footer,
|
|
(Style{}).fg(Color.rgb(100, 100, 100)),
|
|
);
|
|
}
|