╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Users.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
137 try box.children.put(allocator, list.getFocus().id, .{ .widget = .{ .flow_box_scroll = list }, .rect = null, .min_size = null }); 138 } 139 140 // search results start in the box so the term can be refined right away 141 box.getFocus().child_id = box.children.keys()[if (data.search != null) header_index else list_index]; 142 return .{ .box = box, .data = data, .session = session }; 143 } 144 145 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 146 self.box.deinit(allocator); 147 } 148 149 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 150 self.clearGrid(); 151 // clear the incoming min height so the list scrolls within what's left 152 try self.box.build(allocator, .{ 153 .min_size = .{ .width = null, .height = null }, 154 .max_size = constraint.max_size, 155 }, root_focus); 156 } 157 158 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 159 const direction = inp.vertDirection(key); 160 if (self.headerActive()) { 161 const search_box = self.searchBox(); 162 if (direction == .down) { 163 // the empty message has nothing to focus 164 if (!self.data.isEmpty()) root_focus.setFocus(self.listScroll().getFocus().id); 165 return; 166 } 167 if (key == .enter) { 168 const text = try search_box.text(allocator); 169 defer allocator.free(text); 170 return self.submit(text); 171 } 172 return search_box.input(allocator, key, root_focus); 173 } 174 // up from the first row reaches the search box 175 if (direction == .up and self.listScroll().atTop()) return self.focusHeader(root_focus); 176 try self.listScroll().input(allocator, key, root_focus); 177 } 178 179 // the users narrowed to `text` from the first window. an empty box on an 180 // unsearched page has nothing to clear. 181 fn submit(self: *View, text: []const u8) !void { 182 if (text.len == 0 and self.data.search == null) return; 183 try self.session.navigate((ui.RoutablePage{ .home_users = .{} }).withSearch(text) orelse return); 184 } 185 186 fn searchBox(self: *View) *ui.widget.SearchBox { 187 return &self.box.children.values()[header_index].widget.box.children.values()[0].widget.search_box; 188 } 189 190 fn listScroll(self: *View) *ui.widget.FlowBox.Scroll { 191 return &self.box.children.values()[list_index].widget.flow_box_scroll; 192 } 193 194 fn headerActive(self: *View) bool { 195 return self.box.getFocus().child_id == self.box.children.keys()[header_index]; 196 } 197 198 pub fn clearGrid(self: *View) void { 199 self.box.clearGrid(); 200 } 201 202 pub fn getGrid(self: View) ?Grid { 203 return self.box.getGrid(); 204 } 205 206 pub fn getFocus(self: *View) *Focus { 207 return self.box.getFocus(); 208 } 209 210 // up leaves the tab from the search box 211 pub fn atTop(self: *View) bool { 212 return self.headerActive(); 213 } 214 215 pub fn focusHeader(self: *View, root_focus: *Focus) void { 216 root_focus.setFocus(self.searchBox().getFocus().id); 217 } 218 }; 219