╭ ui.zig ───────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const xit = @import("xit"); 3 const xitui = xit.xitui; 4 const term = xitui.terminal; 5 const wgt = xitui.widget; 6 const layout = xitui.layout; 7 const inp = xitui.input; 8 const Grid = xitui.grid.Grid; 9 const Focus = xitui.focus.Focus; 10 11 pub fn run(io: std.Io, allocator: std.mem.Allocator) !void { 12 var root = Widget{ .widget_list = try WidgetList.init(allocator) }; 13 defer root.deinit(); 14 15 try root.build(.{ 16 .min_size = .{ .width = null, .height = null }, 17 .max_size = .{ .width = 10, .height = 10 }, 18 }, root.getFocus()); 19 if (root.getFocus().child_id) |child_id| { 20 try root.getFocus().setFocus(child_id); 21 } 22 23 var terminal = try term.Terminal.init(io, allocator); 24 defer terminal.deinit(io); 25 26 var last_size = layout.Size{ .width = 0, .height = 0 }; 27 var last_grid = try Grid.init(allocator, last_size); 28 defer last_grid.deinit(); 29 30 while (!term.quit) { 31 try terminal.render(&root, &last_grid, &last_size); 32 33 while (try terminal.readKey(io)) |key| { 34 switch (key) { 35 .codepoint => |cp| if (cp == 'q') return, 36 else => {}, 37 } 38 try root.input(key, root.getFocus()); 39 } 40 41 try root.build(.{ 42 .min_size = .{ .width = null, .height = null }, 43 .max_size = .{ .width = last_size.width, .height = last_size.height }, 44 }, root.getFocus()); 45 46 try std.Io.sleep(io, .fromMilliseconds(5), .real); 47 } 48 } 49 50 const WidgetList = struct { 51 allocator: std.mem.Allocator, 52 scroll: wgt.Scroll(Widget), 53 54 pub fn init(allocator: std.mem.Allocator) !WidgetList { 55 var self = blk: { 56 var inner_box = try wgt.Box(Widget).init(allocator, null, .vert); 57 errdefer inner_box.deinit(); 58 59 var scroll = try wgt.Scroll(Widget).init(allocator, .{ .box = inner_box }, .vert); 60 errdefer scroll.deinit(); 61 62 break :blk WidgetList{ 63 .allocator = allocator, 64 .scroll = scroll, 65 }; 66 }; 67 errdefer self.deinit(); 68 69 const inner_box = &self.scroll.child.box; 70 71 { 72 var text_box = try wgt.TextBox(Widget).init(allocator, "this is a TextBox", .single, .none); 73 errdefer text_box.deinit(); 74 text_box.getFocus().focusable = true; 75 try inner_box.children.put(allocator, text_box.getFocus().id, .{ .widget = .{ .text_box = text_box }, .rect = null, .min_size = null }); 76 } 77 78 { 79 var text_box = try wgt.TextBox(Widget).init(allocator, "this is a\nmulti-line TextBox", .single, .none); 80 errdefer text_box.deinit(); 81 text_box.getFocus().focusable = true; 82 try inner_box.children.put(allocator, text_box.getFocus().id, .{ .widget = .{ .text_box = text_box }, .rect = null, .min_size = null }); 83 } 84 85 if (inner_box.children.count() > 0) { 86 self.scroll.getFocus().child_id = inner_box.children.keys()[0]; 87 } 88 89 return self; 90 } 91 92 pub fn deinit(self: *WidgetList) void { 93 self.scroll.deinit(); 94 } 95 96 pub fn build(self: *WidgetList, constraint: layout.Constraint, root_focus: *Focus) !void { 97 self.clearGrid(); 98 const children = &self.scroll.child.box.children; 99 for (children.keys(), children.values()) |id, *commit| { 100 commit.widget.text_box.border_style = if (self.getFocus().child_id == id) 101 (if (root_focus.grandchild_id == id) .double else .single) 102 else 103 .hidden; 104 } 105 try self.scroll.build(constraint, root_focus); 106 } 107 108 pub fn input(self: *WidgetList, key: inp.Key, root_focus: *Focus) !void { 109 if (self.getFocus().child_id) |child_id| { 110 const children = &self.scroll.child.box.children; 111 if (children.getIndex(child_id)) |current_index| { 112 var index = current_index; 113 114 switch (key) { 115 .arrow_up => { 116 index -|= 1; 117 }, 118 .arrow_down => { 119 if (index + 1 < children.count()) { 120 index += 1; 121 } 122 }, 123 .home => { 124 index = 0; 125 }, 126 .end => { 127 if (children.count() > 0) { 128 index = children.count() - 1; 129 } 130 }, 131 .page_up => { 132 if (self.getGrid()) |grid| { 133 const half_count = (grid.size.height / 3) / 2; 134 index -|= half_count; 135 } 136 }, 137 .page_down => { 138 if (self.getGrid()) |grid| { 139 if (children.count() > 0) { 140 const half_count = (grid.size.height / 3) / 2; 141 index = @min(index + half_count, children.count() - 1); 142 } 143 } 144 }, 145 else => {}, 146 } 147 148 if (index != current_index) { 149 try root_focus.setFocus(children.keys()[index]); 150 self.updateScroll(index); 151 } 152 } 153 } 154 } 155 156 pub fn clearGrid(self: *WidgetList) void { 157 self.scroll.clearGrid(); 158 } 159 160 pub fn getGrid(self: WidgetList) ?Grid { 161 return self.scroll.getGrid(); 162 } 163 164 pub fn getFocus(self: *WidgetList) *Focus { 165 return self.scroll.getFocus(); 166 } 167 168 pub fn getSelectedIndex(self: WidgetList) ?usize { 169 if (self.scroll.child.box.focus.child_id) |child_id| { 170 const children = &self.scroll.child.box.children; 171 return children.getIndex(child_id); 172 } else { 173 return null; 174 } 175 } 176 177 fn updateScroll(self: *WidgetList, index: usize) void { 178 const left_box = &self.scroll.child.box; 179 if (left_box.children.values()[index].rect) |rect| { 180 self.scroll.scrollToRect(rect); 181 } 182 } 183 }; 184 185 pub const Widget = union(enum) { 186 text: wgt.Text(Widget), 187 box: wgt.Box(Widget), 188 text_box: wgt.TextBox(Widget), 189 scroll: wgt.Scroll(Widget), 190 widget_list: WidgetList, 191 192 pub fn deinit(self: *Widget) void { 193 switch (self.*) { 194 inline else => |*case| case.deinit(), 195 } 196 } 197 198 pub fn build(self: *Widget, constraint: layout.Constraint, root_focus: *Focus) anyerror!void { 199 switch (self.*) { 200 inline else => |*case| try case.build(constraint, root_focus), 201 } 202 } 203 204 pub fn input(self: *Widget, key: inp.Key, root_focus: *Focus) anyerror!void { 205 switch (self.*) { 206 inline else => |*case| try case.input(key, root_focus), 207 } 208 } 209 210 pub fn clearGrid(self: *Widget) void { 211 switch (self.*) { 212 inline else => |*case| case.clearGrid(), 213 } 214 } 215 216 pub fn getGrid(self: Widget) ?Grid { 217 switch (self) { 218 inline else => |*case| return case.getGrid(), 219 } 220 } 221 222 pub fn getFocus(self: *Widget) *Focus { 223 switch (self.*) { 224 inline else => |*case| return case.getFocus(), 225 } 226 } 227 }; 228