╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────╮ │1190 repo_header: Repo.Header.View, │ │1191 repo_files_sub_header: Repo.Files.SubHeader.View, │ │1192 repo_commits_sub_header: Repo.Commits.SubHeader.View,│ │1193 + repo_issues_sub_header: Repo.Issues.SubHeader.View, │ │1194 repo_files: Repo.Files.View, │ │1195 repo_commits: Repo.Commits.View, │ │1196 repo_refs: Repo.Refs.View, │ ╰───────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │11 const Grid = xitui.grid.Grid; │ │12 const Focus = xitui.focus.Focus; │ │13 const inp = @import("../input.zig"); │ │14 + │ │15 +pub const SubHeader = @import("Issues/SubHeader.zig"); │ │16 │ │17 // how many issues one window shows before a "next" link appears.│ │18 pub const page_size = 20; │ ╰─────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │37 prev_id: ?[]const u8, │ │38 // the id of the next window's first issue, or null when this is the last window.│ │39 next_id: ?[]const u8, │ │40 +// how many issues the listing holds across all windows. │ │41 +count: usize, │ │42 │ │43 const Self = @This(); │ │44 │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────╮ │51 .issues = &.{}, │ │52 .prev_id = null,│ │53 .next_id = null,│ │54 + .count = 0, │ │55 }; │ │56 } │ │57 │ ╰────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │183 .issues = issues.items, │ │184 .prev_id = prev_id, │ │185 .next_id = next_id, │ │186 + .count = @intCast(try issue_id_set.count()),│ │187 }; │ │188 } │ │189 │ ╰─────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │190 pub const View = struct { │ │185 - // a horizontal split with the issue list on the left and a detail pane on │ │186 - // the right showing the selected issue's description. │ │187 - box: wgt.Box(ui.Widget), │ │191 + // a vertical box: the sub header tabs on top, then a stack holding the │ │192 + // results view — a horizontal split with the issue list on the left and a │ │193 + // detail pane on the right showing the selected issue's description. │ │194 + box: wgt.Box(ui.Widget), // vert: [sub_header_index] = tabs, [stack_index] = stack│ │195 data: *const Self, │ │196 session: *ui.Session, │ │197 // the issue whose description the pane currently shows (index into data.issues). │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │198 detailed_index: ?usize, │ │199 │ │200 + const sub_header_index: usize = 0; │ │201 + const stack_index: usize = 1; │ │202 + // indices within the results box (the horizontal split inside the stack).│ │203 const list_index: usize = 0; │ │204 const detail_index: usize = 1; │ │205 const list_max_width: usize = 40; │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │206 const detail_min_width: usize = 40; │ │207 │ │208 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { │ │209 + var outer = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │210 + errdefer outer.deinit(allocator); │ │211 + │ │212 + // the tabs at the top. │ │213 + { │ │214 + var sub_header = try SubHeader.View.init(allocator, session, data.count); │ │215 + errdefer sub_header.deinit(allocator); │ │216 + try outer.children.put(allocator, sub_header.getFocus().id, .{ .widget = .{ .repo_issues_sub_header = sub_header }, .rect = null, .min_size = null });│ │217 + } │ │218 + │ │219 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ │220 errdefer box.deinit(allocator); │ │221 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │269 │ │270 box.getFocus().child_id = box.children.keys()[list_index]; │ │271 │ │272 + // the split sits alone in a stack so future tabs can swap other views │ │273 + // in. the stack enters `outer` before the split enters the stack, so an │ │274 + // error frees the split exactly once. │ │275 + { │ │276 + var stack = try wgt.Stack(ui.Widget).init(allocator); │ │277 + errdefer stack.deinit(allocator); │ │278 + try outer.children.put(allocator, stack.getFocus().id, .{ .widget = .{ .stack = stack }, .rect = null, .min_size = null });│ │279 + } │ │280 + const stack = &outer.children.values()[stack_index].widget.stack; │ │281 + stack.getFocus().child_id = box.getFocus().id; │ │282 + try stack.children.put(allocator, box.getFocus().id, .{ .box = box }); │ │283 + │ │284 + // focus entering the view lands on the tabs first. │ │285 + outer.getFocus().child_id = outer.children.keys()[sub_header_index]; │ │286 + │ │287 return .{ │ │253 - .box = box, │ │288 + .box = outer, │ │289 .data = data, │ │290 .session = session, │ │291 .detailed_index = null, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │302 │ │303 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { │ │304 self.box.deinit(allocator); │ │305 + } │ │306 + │ │307 + fn subHeader(self: *View) *SubHeader.View { │ │308 + return &self.box.children.values()[sub_header_index].widget.repo_issues_sub_header; │ │309 + } │ │310 + │ │311 + // the master-detail split inside the stack. │ │312 + fn resultsBox(self: *View) *wgt.Box(ui.Widget) { │ │313 + return &self.box.children.values()[stack_index].widget.stack.children.values()[0].box;│ │314 } │ │315 │ │316 fn listScroll(self: *View) *wgt.Scroll(ui.Widget) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯