╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ UserNotifications.zig ────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
192 } 193 { 194 var keys = try ui.UserKeys.View.init(allocator, &data.ssh_keys, session, keys_route); 195 errdefer keys.deinit(allocator); 196 try stack.children.put(allocator, keys.getFocus().id, .{ .user_keys = keys }); 197 } 198 { 199 var delete = try ui.Delete.View.init(allocator, .{ .target = .{ .user = session.data.user_name orelse unreachable } }, session, delete_route); 200 errdefer delete.deinit(allocator); 201 try stack.children.put(allocator, delete.getFocus().id, .{ .delete_form = delete }); 202 } 203 stack.getFocus().child_id = stack.children.keys()[@backingInt(data.view)]; 204 try box.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null }); 205 } 206 box.getFocus().child_id = box.children.keys()[content_index]; 207 return .{ .box = box, .data = data, .session = session, .control_ids = control_ids, .view_index = @backingInt(data.view) }; 208 } 209 210 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 211 self.box.deinit(allocator); 212 } 213 214 fn headerBox(self: *View) *wgt.Box(ui.Widget) { 215 return self.box.children.values()[header_index].widget.header_scroll.row(); 216 } 217 218 fn contentStack(self: *View) *wgt.Stack(ui.Widget) { 219 return &self.box.children.values()[content_index].widget.stack; 220 } 221 222 fn headerActive(self: *View) bool { 223 return self.box.getFocus().child_id == self.box.children.keys()[header_index]; 224 } 225 226 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 227 self.clearGrid(); 228 const header = self.headerBox(); 229 // focusing a sub tab shows its content, while the logout button leaves it 230 if (header.getFocus().child_id) |child_id| if (std.mem.indexOfScalar(usize, self.control_ids[0..tab_count], child_id)) |index| { 231 self.view_index = index; 232 }; 233 for (self.control_ids[0..tab_count], 0..) |id, index| { 234 const tab = header.children.getPtr(id) orelse unreachable; 235 ui.widget.markSelected(&tab.widget.text_box, index == self.view_index); 236 } 237 self.contentStack().getFocus().child_id = self.contentStack().children.keys()[self.view_index]; 238 // clear the incoming min height so the list scrolls within what's left 239 try self.box.build(allocator, .{ 240 .min_size = .{ .width = null, .height = null }, 241 .max_size = constraint.max_size, 242 }, root_focus); 243 } 244 245 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 246 const logout_id = self.control_ids[logout_index]; 247 if (key == .mouse and inp.leftClickOn(root_focus, logout_id, key.mouse)) return logOut(self.session); 248 const direction = inp.vertDirection(key); 249 if (self.headerActive()) { 250 const focused = root_focus.grandchild_id orelse return; 251 if (key == .enter and focused == logout_id) return logOut(self.session); 252 if (direction == .down) { 253 // the empty message has nothing to focus 254 if (self.view_index == @backingInt(ui.RoutablePage.UserView.notifications) and self.data.isEmpty()) return; 255 root_focus.setFocus(self.contentStack().getFocus().id); 256 } else if (std.mem.indexOfScalar(usize, &self.control_ids, focused)) |current| { 257 if (inp.moveTab(key, current, self.control_ids.len)) |next| root_focus.setFocus(self.control_ids[next]); 258 } 259 return; 260 } 261 if (direction == .up) if (self.contentStack().getSelected()) |selected| if (selected.atTop(root_focus)) { 262 root_focus.setFocus(self.control_ids[self.view_index]); 263 return; 264 }; 265 try self.contentStack().input(allocator, key, root_focus); 266 } 267 268 // moving up from the sub header returns to the page's tabs 269 pub fn atTop(self: *View) bool { 270 return self.headerActive(); 271 } 272 273 // arriving from the page's tabs lands on the sub header, not the content 274 pub fn focusHeader(self: *View, root_focus: *Focus) bool { 275 root_focus.setFocus(self.control_ids[self.view_index]); 276 return true; 277 } 278 279 pub fn clearGrid(self: *View) void { 280 self.box.clearGrid(); 281 } 282 283 pub fn getGrid(self: View) ?Grid { 284 return self.box.getGrid(); 285 } 286 287 pub fn getFocus(self: *View) *Focus { 288 return self.box.getFocus(); 289 } 290 }; 291