//! 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)), ); }