╭ Home.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const ui = @import("../ui.zig"); 3 const xit = @import("xit"); 4 const rp = xit.repo; 5 const hash = xit.hash; 6 const xitui = xit.xitui; 7 const wgt = xitui.widget; 8 const layout = xitui.layout; 9 const inp = xitui.input; 10 const Grid = xitui.grid.Grid; 11 const Focus = xitui.focus.Focus; 12 13 pub const Users = @import("./Home/Users.zig"); 14 pub const Repos = @import("./Home/Repos.zig"); 15 pub const Header = @import("./Home/Header.zig"); 16 17 header: Header, 18 users: Users, 19 repos: Repos, 20 21 const Self = @This(); 22 23 pub fn init( 24 comptime repo_opts: rp.RepoOpts(.xit), 25 arena: *std.heap.ArenaAllocator, 26 repo: *rp.Repo(.xit, repo_opts), 27 ) !Self { 28 const DB = rp.Repo(.xit, repo_opts).DB; 29 30 const history = try DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); 31 32 const moment_cursor = try history.getCursor(-1) orelse return error.NotFound; 33 const moment = try DB.HashMap(.read_only).init(moment_cursor); 34 35 const last_object_id_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy-last-object-id")) orelse return error.NotFound; 36 var last_object_id: [hash.byteLen(repo_opts.hash)]u8 = undefined; 37 _ = try last_object_id_cursor.readBytes(&last_object_id); 38 39 const haxy_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, "haxy")) orelse return error.NotFound; 40 const haxy = try DB.ArrayList(.read_only).init(haxy_cursor); 41 42 const haxy_moments_cursor = try haxy.getCursor(-1) orelse return error.NotFound; 43 const haxy_moments = try DB.HashMap(.read_only).init(haxy_moments_cursor); 44 45 const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound; 46 const haxy_moment = try DB.HashMap(.read_only).init(haxy_moment_cursor); 47 48 return .{ 49 .header = Header.init(), 50 .users = try Users.init(repo_opts, arena, haxy_moment), 51 .repos = try Repos.init(repo_opts, arena, haxy_moment), 52 }; 53 } 54 55 pub const View = struct { 56 box: wgt.Box(ui.Widget), 57 data: *const Self, 58 59 const header_index: usize = 0; 60 const stack_index: usize = 1; 61 62 pub fn init(allocator: std.mem.Allocator, data: *const Self) !View { 63 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); 64 errdefer box.deinit(); 65 66 { 67 var header_view = try Header.View.init(allocator, &data.header); 68 errdefer header_view.deinit(); 69 try box.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .home_header = header_view }, .rect = null, .min_size = null }); 70 } 71 72 { 73 var stack = wgt.Stack(ui.Widget).init(allocator); 74 errdefer stack.deinit(); 75 76 { 77 var users_view = try Users.View.init(allocator, &data.users); 78 errdefer users_view.deinit(); 79 try stack.children.put(stack.allocator, users_view.getFocus().id, .{ .home_users = users_view }); 80 } 81 82 { 83 var repos_view = try Repos.View.init(allocator, &data.repos); 84 errdefer repos_view.deinit(); 85 try stack.children.put(stack.allocator, repos_view.getFocus().id, .{ .home_repos = repos_view }); 86 } 87 88 try box.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null }); 89 } 90 91 var self = View{ 92 .box = box, 93 .data = data, 94 }; 95 self.getFocus().child_id = box.children.keys()[header_index]; 96 return self; 97 } 98 99 pub fn deinit(self: *View) void { 100 self.box.deinit(); 101 } 102 103 pub fn build(self: *View, constraint: layout.Constraint, root_focus: *Focus) !void { 104 self.clearGrid(); 105 const header = &self.box.children.values()[header_index].widget.home_header; 106 const stack = &self.box.children.values()[stack_index].widget.stack; 107 if (header.getSelectedIndex()) |index| { 108 stack.getFocus().child_id = stack.children.keys()[index]; 109 } 110 try self.box.build(constraint, root_focus); 111 } 112 113 pub fn input(self: *View, key: inp.Key, root_focus: *Focus) !void { 114 if (self.getFocus().child_id) |child_id| { 115 if (self.box.children.getIndex(child_id)) |current_index| { 116 const child = &self.box.children.values()[current_index].widget; 117 var index = current_index; 118 119 // arrow up/down (and scroll wheel) move focus between the 120 // header and the stack below it, matching radargit's GitUI. 121 const Direction = enum { up, down, none }; 122 const direction: Direction = switch (key) { 123 .arrow_up => .up, 124 .arrow_down => .down, 125 .mouse => |mouse| if (mouse.action == .scroll) 126 (if (mouse.action.scroll == .up) .up else .down) 127 else 128 .none, 129 else => .none, 130 }; 131 132 switch (direction) { 133 .up => { 134 switch (child.*) { 135 .home_header => { 136 try child.input(key, root_focus); 137 }, 138 .stack => { 139 if (child.stack.getSelected()) |selected_widget| { 140 const at_top = switch (selected_widget.*) { 141 .home_users => |*v| v.getSelectedIndex() == 0, 142 .home_repos => |*v| v.getSelectedIndex() == 0, 143 else => false, 144 }; 145 if (at_top) { 146 index = header_index; 147 } else { 148 try child.input(key, root_focus); 149 } 150 } 151 }, 152 else => {}, 153 } 154 }, 155 .down => { 156 switch (child.*) { 157 .home_header => { 158 index = stack_index; 159 }, 160 .stack => { 161 try child.input(key, root_focus); 162 }, 163 else => {}, 164 } 165 }, 166 .none => { 167 try child.input(key, root_focus); 168 }, 169 } 170 171 if (index != current_index) { 172 try root_focus.setFocus(self.box.children.keys()[index]); 173 } 174 } 175 } 176 } 177 178 pub fn clearGrid(self: *View) void { 179 self.box.clearGrid(); 180 } 181 182 pub fn getGrid(self: View) ?Grid { 183 return self.box.getGrid(); 184 } 185 186 pub fn getFocus(self: *View) *Focus { 187 return self.box.getFocus(); 188 } 189 }; 190