╭ changed ────────────╮ │src/ui/Home/Users.zig│ ╰─────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │59 return result; │ │60 } │ │61 │ │62 +// a first window without users, which shows the empty message │ │63 +fn isEmpty(self: *const Self) bool { │ │64 + return self.users.len == 0 and self.start == 0; │ │65 +} │ │66 + │ │67 pub const View = struct { │ │68 // a vertical box: the search sub-header above the scrolling list. focus│ │69 // points at the header's box or the list's selected row. │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 96 try box.children.put(allocator, header.getFocus().id, .{ .widget = .{ .box = header }, .rect = null, .min_size = .{ .width = null, .height = ui.widget.SearchBox.min_size.height } });│ │ 97 } │ │ 98 │ │ 94 - { │ │ 99 + // an empty first window has no rows, not even paging ones │ │100 + if (data.isEmpty()) { │ │101 + var center = try ui.widget.initEmpty(allocator); │ │102 + errdefer center.deinit(allocator); │ │103 + try box.children.put(allocator, center.getFocus().id, .{ .widget = .{ .center = center }, .rect = null, .min_size = null }); │ │104 + } else { │ │105 var list = try ui.widget.FlowBox.Scroll.init(allocator, .{ .cell_height = 1 }, !session.is_terminal); │ │106 errdefer list.deinit(allocator); │ │107 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │159 const direction = inp.vertDirection(key); │ │160 if (self.headerActive()) { │ │161 const search_box = self.searchBox(); │ │152 - if (direction == .down) return root_focus.setFocus(self.listScroll().getFocus().id);│ │162 + if (direction == .down) { │ │163 + // the empty message has nothing to focus │ │164 + if (!self.data.isEmpty()) root_focus.setFocus(self.listScroll().getFocus().id); │ │165 + return; │ │166 + } │ │167 if (key == .enter) { │ │168 const text = try search_box.text(allocator); │ │169 defer allocator.free(text); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │291 .commits = &.{}, │ │292 .next_start = null, │ │293 }; │ │294 +} │ │295 + │ │296 +// a log without commits, which shows the empty message │ │297 +fn isEmpty(self: *const Self) bool { │ │298 + return self.commits.len == 0; │ │299 } │ │300 │ │301 // the commit message, trimmed and read into `aa`, plus whether its preview was│ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │399 errdefer header_view.deinit(allocator); │ │400 // a row taller than the header, leaving a blank line beneath it │ │401 try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .search_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 4 } });│ │402 + } │ │403 + │ │404 + // an empty list shows the empty message in place of the split │ │405 + if (data.isEmpty()) { │ │406 + var center = try ui.widget.initEmpty(allocator); │ │407 + errdefer center.deinit(allocator); │ │408 + try outer.children.put(allocator, center.getFocus().id, .{ .widget = .{ .center = center }, .rect = null, .min_size = null }); │ │409 + // with nothing below to focus, focus rests on the sub-header │ │410 + outer.getFocus().child_id = outer.children.keys()[header_index]; │ │411 + return .{ .box = outer, .data = data, .session = session }; │ │412 } │ │413 │ │414 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │566 │ │567 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │568 self.clearGrid(); │ │554 - if (self.data.no_commits) return self.box.build(allocator, constraint, root_focus); │ │569 + if (self.data.no_commits or self.data.isEmpty()) return self.box.build(allocator, constraint, root_focus); │ │570 │ │571 // whether the detail pane fits beside the list, which decides the list │ │572 // cap below and whether the detail carries a way back to the list │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │715 // active means there is one │ │716 const header_view = self.header() orelse unreachable; │ │717 if (direction == .down) { │ │703 - root_focus.setFocus(self.contentBox().getFocus().id); │ │718 + // the empty message has nothing to focus │ │719 + if (!self.data.isEmpty()) root_focus.setFocus(self.contentBox().getFocus().id);│ │720 return; │ │721 } │ │722 if (key == .enter) if (try header_view.submittedText(allocator)) |text| { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │822 │ │823 // only the header's own row sits directly below the repository header. │ │824 pub fn atTop(self: *View) bool { │ │809 - if (self.data.no_commits) return true; │ │825 + if (self.data.no_commits or self.data.isEmpty()) return true; │ │826 const focusable = if (self.header()) |header_view| header_view.hasFocusable() else false;│ │827 return self.headerActive() or (!focusable and self.contentAtTop()); │ │828 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │35 count: usize = 0, │ │36 events: []const Item = &.{}, │ │37 next: ?EventRef = null, │ │38 + │ │39 + // a window without events, which shows the empty message│ │40 + fn isEmpty(self: *const Window) bool { │ │41 + return self.events.len == 0; │ │42 + } │ │43 }; │ │44 │ │45 identity: []const u8, │ ╰─────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │396 var content_stack = try wgt.Stack(ui.Widget).init(allocator); │ │397 errdefer content_stack.deinit(allocator); │ │398 inline for ([_]ui.RoutablePage.EventsView{ .active, .removed }) |view| { │ │394 - var content_box = try initContent(allocator, data, session, view); │ │395 - errdefer content_box.deinit(allocator); │ │396 - try content_stack.children.put(allocator, content_box.getFocus().id, .{ .box = content_box }); │ │399 + var view_content = try initContent(allocator, data, session, view); │ │400 + errdefer view_content.deinit(allocator); │ │401 + try content_stack.children.put(allocator, view_content.getFocus().id, view_content); │ │402 } │ │403 content_stack.getFocus().child_id = content_stack.children.keys()[@backingInt(data.view)]; │ │404 try outer.children.put(allocator, content_stack.getFocus().id, .{ .widget = .{ .stack = content_stack }, .rect = null, .min_size = null });│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯