╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Users.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
118 var items: std.ArrayList(ui.widget.FlowBox.Item) = .empty; 119 if (data.start > 0) { 120 var prev = route; 121 prev.home_users.start = data.start -| page_size; 122 try items.append(aa, .{ .text = "← previous", .link = try aa.print("a:{s}", .{try prev.toUrl(&arena)}) }); 123 } 124 for (data.users) |user| 125 // clicking a user opens their page; the "a:" prefix makes the web 126 // renderer emit an <a href="/foo"> anchor. 127 try items.append(aa, .{ 128 .text = user.name, 129 .link = try aa.print("a:/{s}", .{user.name}), 130 }); 131 if (data.next_start) |next_start| { 132 var next = route; 133 next.home_users.start = next_start; 134 try items.append(aa, .{ .text = "next →", .link = try aa.print("a:{s}", .{try next.toUrl(&arena)}) }); 135 } 136 try list.setItems(allocator, items.items); 137 138 try box.children.put(allocator, list.getFocus().id, .{ .widget = .{ .flow_box_scroll = list }, .rect = null, .min_size = null }); 139 } 140 141 // search results start in the box so the term can be refined right away 142 box.getFocus().child_id = box.children.keys()[if (data.search != null) header_index else list_index]; 143 return .{ .box = box, .data = data, .session = session }; 144 } 145 146 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 147 self.box.deinit(allocator); 148 } 149 150 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 151 self.clearGrid(); 152 // clear the incoming min height so the list scrolls within what's left 153 try self.box.build(allocator, .{ 154 .min_size = .{ .width = null, .height = null }, 155 .max_size = constraint.max_size, 156 }, root_focus); 157 } 158 159 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 160 const direction = inp.vertDirection(key); 161 if (self.headerActive()) { 162 const search_box = self.searchBox(); 163 if (direction == .down) { 164 // the empty message has nothing to focus 165 if (!self.data.isEmpty()) root_focus.setFocus(self.listScroll().getFocus().id); 166 return; 167 } 168 if (key == .enter) { 169 const text = try search_box.text(allocator); 170 defer allocator.free(text); 171 return self.submit(text); 172 } 173 return search_box.input(allocator, key, root_focus); 174 } 175 // up from the first row reaches the search box 176 if (direction == .up and self.listScroll().atTop()) return self.focusHeader(root_focus); 177 try self.listScroll().input(allocator, key, root_focus); 178 } 179 180 // the users narrowed to `text` from the first window. an empty box on an 181 // unsearched page has nothing to clear. 182 fn submit(self: *View, text: []const u8) !void { 183 if (text.len == 0 and self.data.search == null) return; 184 try self.session.navigate((ui.RoutablePage{ .home_users = .{} }).withSearch(text) orelse return); 185 } 186 187 fn searchBox(self: *View) *ui.widget.SearchBox { 188 return &self.box.children.values()[header_index].widget.header_scroll.row().children.values()[0].widget.search_box; 189 } 190 191 fn listScroll(self: *View) *ui.widget.FlowBox.Scroll { 192 return &self.box.children.values()[list_index].widget.flow_box_scroll; 193 } 194 195 fn headerActive(self: *View) bool { 196 return self.box.getFocus().child_id == self.box.children.keys()[header_index]; 197 } 198 199 pub fn clearGrid(self: *View) void { 200 self.box.clearGrid(); 201 } 202 203 pub fn getGrid(self: View) ?Grid { 204 return self.box.getGrid(); 205 } 206 207 pub fn getFocus(self: *View) *Focus { 208 return self.box.getFocus(); 209 } 210 211 // up leaves the tab from the search box 212 pub fn atTop(self: *View) bool { 213 return self.headerActive(); 214 } 215 216 pub fn focusHeader(self: *View, root_focus: *Focus) void { 217 root_focus.setFocus(self.searchBox().getFocus().id); 218 } 219 }; 220