╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────╮ │293 } │ │294 } │ │295 │ │223 - fn updateScroll(self: *WidgetList, index: usize) void { │ │224 - const left_box = &self.scroll.child.box; │ │225 - if (left_box.children.values()[index].rect) |rect| { │ │296 + fn updateScroll(self: *SelectableList, index: usize) void {│ │297 + const inner_box = &self.scroll.child.box; │ │298 + if (inner_box.children.values()[index].rect) |rect| { │ │299 self.scroll.scrollToRect(rect); │ │300 } │ │301 } │ ╰────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │302 }; │ │303 │ │231 -pub const Widget = union(enum) { │ │232 - text: wgt.Text(Widget), │ │304 +// horizontal two-pane view: users on the left, the selected user's │ │305 +// repos on the right. selecting a user repopulates the repo pane. │ │306 +const UserRepoView = struct { │ │307 + allocator: std.mem.Allocator, │ │308 box: wgt.Box(Widget), │ │234 - text_box: wgt.TextBox(Widget), │ │235 - scroll: wgt.Scroll(Widget), │ │236 - widget_list: WidgetList, │ │309 + page: *const pg.UserRepoPage, │ │310 + last_user_index: ?usize, │ │311 + │ │312 + const user_list_index: usize = 0; │ │313 + const repo_list_index: usize = 1; │ │314 + │ │315 + pub fn init(allocator: std.mem.Allocator, page: *const pg.UserRepoPage) !UserRepoView { │ │316 + var self = blk: { │ │317 + var box = try wgt.Box(Widget).init(allocator, .{ .border_style = null, .rounded_corners = true, .direction = .horiz }); │ │318 + errdefer box.deinit(); │ │319 + │ │320 + { │ │321 + var user_list = try SelectableList.init(allocator); │ │322 + errdefer user_list.deinit(); │ │323 + try box.children.put(allocator, user_list.getFocus().id, .{ .widget = .{ .selectable_list = user_list }, .rect = null, .min_size = .{ .width = 30, .height = null } });│ │324 + } │ │325 + │ │326 + { │ │327 + var repo_list = try SelectableList.init(allocator); │ │328 + errdefer repo_list.deinit(); │ │329 + try box.children.put(allocator, repo_list.getFocus().id, .{ .widget = .{ .selectable_list = repo_list }, .rect = null, .min_size = .{ .width = 40, .height = null } });│ │330 + } │ │331 + │ │332 + break :blk UserRepoView{ │ │333 + .allocator = allocator, │ │334 + .box = box, │ │335 + .page = page, │ │336 + .last_user_index = null, │ │337 + }; │ │338 + }; │ │339 + errdefer self.deinit(); │ │340 + │ │341 + // populate the user list once at init using a temporary arena │ │342 + // for the formatted lines (setItems dupes them). │ │343 + var arena = std.heap.ArenaAllocator.init(allocator); │ │344 + defer arena.deinit(); │ │345 + const aa = arena.allocator(); │ │346 │ │238 - pub fn deinit(self: *Widget) void { │ │239 - switch (self.*) { │ │240 - inline else => |*case| case.deinit(), │ │347 + const user_lines = try aa.alloc([]const u8, page.users.len); │ │348 + for (page.users, 0..) |uwr, i| { │ │349 + user_lines[i] = try std.fmt.allocPrint(aa, "{s} ({s})", .{ uwr.user.name, uwr.user.display_name }); │ │350 } │ │351 + const user_list = &self.box.children.values()[user_list_index].widget.selectable_list; │ │352 + try user_list.setItems(user_lines); │ │353 + │ │354 + self.getFocus().child_id = self.box.children.keys()[user_list_index]; │ │355 + try self.updateRepos(); │ │356 + │ │357 + return self; │ │358 } │ │359 │ │244 - pub fn build(self: *Widget, constraint: layout.Constraint, root_focus: *Focus) anyerror!void { │ │245 - switch (self.*) { │ │246 - inline else => |*case| try case.build(constraint, root_focus), │ │360 + pub fn deinit(self: *UserRepoView) void { │ │361 + self.box.deinit(); │ │362 + } │ │363 + │ │364 + pub fn build(self: *UserRepoView, constraint: layout.Constraint, root_focus: *Focus) !void { │ │365 + self.clearGrid(); │ │366 + try self.box.build(constraint, root_focus); │ │367 + } │ │368 + │ │369 + pub fn input(self: *UserRepoView, key: inp.Key, root_focus: *Focus) !void { │ │370 + if (self.getFocus().child_id) |child_id| { │ │371 + if (self.box.children.getIndex(child_id)) |current_index| { │ │372 + const child = &self.box.children.values()[current_index].widget; │ │373 + │ │374 + const index = blk: { │ │375 + switch (key) { │ │376 + .arrow_left => { │ │377 + if (current_index == repo_list_index) { │ │378 + break :blk user_list_index; │ │379 + } │ │380 + }, │ │381 + .arrow_right => { │ │382 + if (current_index == user_list_index) { │ │383 + break :blk repo_list_index; │ │384 + } │ │385 + }, │ │386 + .codepoint => { │ │387 + switch (key.codepoint) { │ │388 + 13 => { │ │389 + if (current_index == user_list_index) { │ │390 + break :blk repo_list_index; │ │391 + } │ │392 + }, │ │393 + 127, '\x1B' => { │ │394 + if (current_index == repo_list_index) { │ │395 + break :blk user_list_index; │ │396 + } │ │397 + }, │ │398 + else => {}, │ │399 + } │ │400 + }, │ │401 + else => {}, │ │402 + } │ │403 + try child.input(key, root_focus); │ │404 + if (current_index == user_list_index) { │ │405 + try self.updateRepos(); │ │406 + } │ │407 + break :blk current_index; │ │408 + }; │ │409 + │ │410 + if (index != current_index) { │ │411 + try root_focus.setFocus(self.box.children.keys()[index]); │ │412 + } │ │413 + } │ │414 } │ │415 } │ │416 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │250 - pub fn input(self: *Widget, key: inp.Key, root_focus: *Focus) anyerror!void { │ │251 - switch (self.*) { │ │252 - inline else => |*case| try case.input(key, root_focus), │ │253 - } │ │417 + pub fn clearGrid(self: *UserRepoView) void { │ │418 + self.box.clearGrid(); │ │419 } │ │420 │ │256 - pub fn clearGrid(self: *Widget) void { │ │257 - switch (self.*) { │ │258 - inline else => |*case| case.clearGrid(), │ │259 - } │ │421 + pub fn getGrid(self: UserRepoView) ?Grid { │ │422 + return self.box.getGrid(); │ │423 } │ │424 │ │262 - pub fn getGrid(self: Widget) ?Grid { │ │263 - switch (self) { │ │264 - inline else => |*case| return case.getGrid(), │ │265 - } │ │425 + pub fn getFocus(self: *UserRepoView) *Focus { │ │426 + return self.box.getFocus(); │ │427 } │ │428 │ │268 - pub fn getFocus(self: *Widget) *Focus { │ │269 - switch (self.*) { │ │270 - inline else => |*case| return case.getFocus(), │ │429 + fn updateRepos(self: *UserRepoView) !void { │ │430 + const user_list = &self.box.children.values()[user_list_index].widget.selectable_list; │ │431 + const repo_list = &self.box.children.values()[repo_list_index].widget.selectable_list; │ │432 + │ │433 + const user_index = user_list.getSelectedIndex(); │ │434 + if (user_index == self.last_user_index) return; │ │435 + self.last_user_index = user_index; │ │436 + │ │437 + const repos = if (user_index) |i| self.page.users[i].repos else &.{}; │ │438 + │ │439 + var arena = std.heap.ArenaAllocator.init(self.allocator); │ │440 + defer arena.deinit(); │ │441 + const aa = arena.allocator(); │ │442 + │ │443 + const repo_lines = try aa.alloc([]const u8, repos.len); │ │444 + for (repos, 0..) |repo_event, i| { │ │445 + repo_lines[i] = try std.fmt.allocPrint(aa, "{s} - {s}", .{ repo_event.name, repo_event.description });│ │446 } │ │447 + try repo_list.setItems(repo_lines); │ │448 } │ │449 }; │ │450 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯