╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │560 + tab_ids: [2]usize, │ │561 button_id: ?usize, │ │562 │ │521 - pub fn init(allocator: std.mem.Allocator, data: *const Header, session: *ui.Session) !Header.View { │ │563 + pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !Header.View { │ │564 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │565 errdefer box.deinit(allocator); │ │566 + var tab_ids: [2]usize = undefined; │ │567 + inline for ([_]ui.RoutablePage.EventsView{ .active, .removed }, 0..) |view, i| { │ │568 + const route = ui.RoutablePage.repoEventsRoute(data.identity, view, null, "") orelse return error.RouteTooLong; │ │569 + const count = data.window(view).count; │ │570 + const label = try std.fmt.allocPrint(session.page_arena.allocator(), "{s} ({d})", .{ @tagName(view), count }); │ │571 + var tab = try wgt.TextBox(ui.Widget).init(allocator, label, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .none }); │ │572 + errdefer tab.deinit(allocator); │ │573 + tab.getFocus().focusable = true; │ │574 + tab.getFocus().kind = .{ .custom = try std.fmt.allocPrint(session.page_arena.allocator(), "ai:{s}", .{try route.toUrl(session.page_arena)}) }; │ │575 + tab_ids[i] = tab.getFocus().id; │ │576 + try box.children.put(allocator, tab.getFocus().id, .{ .widget = .{ .text_box = tab }, .rect = null, .min_size = .{ .width = label.len + 2, .height = null } });│ │577 + } │ │578 + │ │579 var button_id: ?usize = null; │ │525 - if (data.is_local) { │ │580 + if (data.header.sync_status != null) { │ │581 box.getFocus().kind = .{ .custom = "form:/sync" }; │ │582 var button = try wgt.TextBox(ui.Widget).init(allocator, "sync", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │583 errdefer button.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │584 button.getFocus().focusable = true; │ │585 button.getFocus().kind = .{ .custom = "submit" }; │ │586 button_id = button.getFocus().id; │ │532 - box.getFocus().child_id = button_id; │ │587 try box.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = null });│ │588 } │ │589 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │536 - var status = try wgt.TextBox(ui.Widget).init(allocator, data.status, .{ .border_style = .hidden, .wrap_kind = .none }); │ │537 - errdefer status.deinit(allocator); │ │538 - try box.children.put(allocator, status.getFocus().id, .{ .widget = .{ .text_box = status }, .rect = null, .min_size = null, .shrink = true }); │ │539 - return .{ .box = box, .session = session, .button_id = button_id }; │ │590 + if (data.header.sync_status) |status_text| { │ │591 + var status = try wgt.TextBox(ui.Widget).init(allocator, status_text, .{ .border_style = .hidden, .wrap_kind = .none }); │ │592 + errdefer status.deinit(allocator); │ │593 + try box.children.put(allocator, status.getFocus().id, .{ .widget = .{ .text_box = status }, .rect = null, .min_size = null, .shrink = true });│ │594 + } │ │595 + box.getFocus().child_id = tab_ids[@intFromEnum(data.view)]; │ │596 + return .{ .box = box, .session = session, .tab_ids = tab_ids, .button_id = button_id }; │ │597 } │ │598 │ │599 pub fn deinit(self: *Header.View, allocator: std.mem.Allocator) void { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │602 │ │603 pub fn build(self: *Header.View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │604 self.clearGrid(); │ │605 + for (self.tab_ids) |id| { │ │606 + const child = self.box.children.getPtr(id) orelse continue; │ │607 + const tab = switch (child.widget) { │ │608 + .text_box => |*text_box| text_box, │ │609 + else => continue, │ │610 + }; │ │611 + tab.options.border_style = if (self.box.getFocus().child_id == id) .single else .hidden; │ │612 + } │ │613 try self.box.build(allocator, constraint, root_focus); │ │614 } │ │615 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │616 pub fn input(self: *Header.View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │552 - const button_id = self.button_id orelse return; │ │553 - if (!self.session.is_terminal) return; │ │554 - if (self.session.host_request != null) return; │ │617 + const selected = self.box.getFocus().child_id orelse return; │ │618 + const current = self.box.children.getIndex(selected) orelse return; │ │619 + const count = 2 + @as(usize, @intFromBool(self.button_id != null)); │ │620 + if (inp.moveTab(key, current, count)) |next| { │ │621 + root_focus.setFocus(self.box.children.keys()[next]); │ │622 + return; │ │623 + } │ │624 switch (key) { │ │556 - .enter => self.requestSync(), │ │557 - .mouse => |mouse| if (inp.leftClickOn(root_focus, button_id, mouse)) self.requestSync(), │ │625 + .enter => if (self.syncFocused()) self.requestSync(), │ │626 + .mouse => |mouse| if (self.button_id) |id| if (inp.leftClickOn(root_focus, id, mouse)) self.requestSync(),│ │627 else => {}, │ │628 } │ │629 _ = allocator; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │630 } │ │631 │ │632 fn requestSync(self: *Header.View) void { │ │633 + if (!self.session.is_terminal) return; │ │634 + if (self.session.host_request != null) return; │ │635 self.session.host_request = .sync_events; │ │636 const button_id = self.button_id orelse return; │ │637 const child = self.box.children.getPtr(button_id) orelse return;│ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │646 }; │ │647 } │ │648 │ │578 - pub fn focusButton(self: *Header.View, root_focus: *Focus) bool { │ │649 + fn syncFocused(self: *Header.View) bool { │ │650 const button_id = self.button_id orelse return false; │ │580 - root_focus.setFocus(button_id); │ │581 - return true; │ │651 + return self.box.getFocus().child_id == button_id; │ │652 + } │ │653 + │ │654 + pub fn getSelectedIndex(self: *Header.View) ?usize { │ │655 + const id = self.box.getFocus().child_id orelse return null; │ │656 + return std.mem.indexOfScalar(usize, &self.tab_ids, id); │ │657 } │ │658 │ │584 - pub fn isFocusable(self: *Header.View) bool { │ │585 - return self.button_id != null; │ │659 + pub fn focusCurrent(self: *Header.View, root_focus: *Focus) void { │ │660 + root_focus.setFocus(self.box.getFocus().child_id orelse self.tab_ids[0]);│ │661 } │ │662 │ │663 pub fn clearGrid(self: *Header.View) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │691 session.data.sync_failure = null; │ │692 switch (session.data.current_page) { │ │693 .repo_events => |route| { │ │619 - if (ui.RoutablePage.repoEventsRoute(route.name.slice(), null, "")) |first| │ │694 + if (ui.RoutablePage.repoEventsRoute(route.name.slice(), route.view, null, "")) |first|│ │695 session.data.current_page = first; │ │696 }, │ │697 else => {}, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │120 const refs_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try refs_route.toUrl(session.page_arena)}); │ │121 const issues_route = ui.RoutablePage.repoIssuesRoute(identity, .open, data.issues_tag, "") orelse return error.RouteTooLong;│ │122 const issues_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try issues_route.toUrl(session.page_arena)}); │ │123 - const events_route = ui.RoutablePage.repoEventsRoute(identity, null, "") orelse return error.RouteTooLong; │ │123 + const events_route = ui.RoutablePage.repoEventsRoute(identity, .active, null, "") orelse return error.RouteTooLong; │ │124 const events_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try events_route.toUrl(session.page_arena)}); │ │125 const settings_route = ui.RoutablePage{ .repo_settings = Array.from(identity) orelse return error.RouteTooLong }; │ │126 const settings_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try settings_route.toUrl(session.page_arena)}); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────╮ │573 description_id: [stack_child_max]?usize, │ │574 author_id: [stack_child_max]?usize, │ │575 status_button_id: [stack_child_max]?usize,│ │576 + remove_button_id: [stack_child_max]?usize,│ │577 │ │578 const header_index: usize = 0; │ │579 const stack_index: usize = 1; │ ╰───────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯