╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │448 }; │ │449 errdefer scroll.deinit(allocator); │ │450 scroll.getFocus().mode = .mouse; │ │447 - try box.children.put(allocator, scroll.getFocus().id, .{ .widget = .{ .scroll = scroll }, .rect = null, .min_size = .{ .width = detail_min_width, .height = null } });│ │451 + frame.getFocus().child_id = scroll.getFocus().id; │ │452 + try frame.children.put(allocator, scroll.getFocus().id, .{ .widget = .{ .scroll = scroll }, .rect = null, .min_size = null }); │ │453 + try box.children.put(allocator, frame.getFocus().id, .{ .widget = .{ .box = frame }, .rect = null, .min_size = .{ .width = detail_min_width, .height = null } }); │ │454 } │ │455 box.getFocus().child_id = box.children.keys()[if (data.items.len > 0) 1 else 0]; │ │456 try outer.children.put(allocator, box.getFocus().id, .{ .widget = .{ .box = box }, .rect = null, .min_size = null }); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │512 } │ │513 fn listScroll(self: *View) *wgt.Scroll(ui.Widget) { │ │514 return &self.contentBox().children.values()[0].widget.scroll; │ │515 + } │ │516 + fn detailFrame(self: *View) *wgt.Box(ui.Widget) { │ │517 + return &self.contentBox().children.values()[1].widget.box; │ │518 } │ │519 fn detailScroll(self: *View) *wgt.Scroll(ui.Widget) { │ │511 - return &self.contentBox().children.values()[1].widget.scroll; │ │520 + return &self.detailFrame().children.values()[ui.widget.list_pane_index].widget.scroll;│ │521 } │ │522 fn selected(self: *View) ?usize { │ │523 const rows = &self.listScroll().child.box; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │525 return if (index < self.data.items.len) index else null; │ │526 } │ │527 fn detailActive(self: *View) bool { │ │519 - return self.contentBox().getFocus().child_id == self.detailScroll().getFocus().id;│ │528 + return self.contentBox().getFocus().child_id == self.detailFrame().getFocus().id; │ │529 } │ │530 │ │531 fn refreshDetail(self: *View, allocator: std.mem.Allocator) !void { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │564 self.detailScroll().x = 0; │ │565 self.detailScroll().y = 0; │ │566 self.detailScroll().getFocus().version +%= 1; │ │567 + ui.widget.setListBackLink(self.detailFrame(), item.link);│ │568 self.detailed_index = index; │ │569 } │ │570 │ ╰──────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │583 } │ │584 for (rows.children.keys(), rows.children.values()) |id, *child| ui.widget.markSelected(&child.widget.text_box, rows.getFocus().child_id == id);│ │585 const both_fit = if (constraint.max_size.width) |width| width >= list_max_width + detail_min_width else true; │ │586 + ui.widget.showListBack(self.detailFrame(), !both_fit); │ │587 self.contentBox().children.values()[0].max_size = if (both_fit) .{ .width = list_max_width, .height = null } else null; │ │588 const width = if (constraint.max_size.width) |value| if (both_fit) value - list_max_width else value else detail_min_width; │ │589 self.contentBox().children.values()[1].min_size = .{ .width = width, .height = null }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │603 if (direction == .up) root_focus.setFocus(self.headerBox().getFocus().id) else try self.clearInput(key, root_focus);│ │604 return; │ │605 } │ │606 + // the way back to the list, by enter or a click │ │607 + if (self.detailActive()) if (ui.widget.listBackId(self.detailFrame())) |id| { │ │608 + if (inp.activated(root_focus, id, key)) return self.focusList(root_focus); │ │609 + if (root_focus.grandchild_id == id) return switch (direction) { │ │610 + .up => root_focus.setFocus(self.headerBox().getFocus().id), │ │611 + .down => root_focus.setFocus(self.detailScroll().getFocus().id), │ │612 + .none => if (key == .arrow_left) self.focusList(root_focus), │ │613 + }; │ │614 + if (direction == .up and self.contentAtTop()) return root_focus.setFocus(id); │ │615 + }; │ │616 if (direction == .up and self.contentAtTop()) { │ │617 root_focus.setFocus(self.headerBox().getFocus().id); │ │618 return; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │629 if (inp.rowDelta(key, @intCast(details.children.count()))) |delta| { │ │630 ui.widget.moveRowFocus(details, self.detailScroll(), root_focus, delta); │ │631 } else if (key == .arrow_left) { │ │611 - root_focus.setFocus(self.listScroll().getFocus().id); │ │632 + self.focusList(root_focus); │ │633 } │ │634 } else if (inp.rowDelta(key, @intCast(self.listScroll().child.box.children.count()))) |delta| {│ │635 ui.widget.moveRowFocus(&self.listScroll().child.box, self.listScroll(), root_focus, delta);│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │636 } else switch (key) { │ │616 - .arrow_right, .enter => if (self.selected() != null) { │ │617 - root_focus.setFocus(self.detailScroll().getFocus().id); │ │618 - }, │ │637 + .arrow_right, .enter => if (self.selected() != null) self.focusDetail(root_focus),│ │638 else => {}, │ │639 } │ │640 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │641 │ │642 + // enter the detail pane, landing on the way back to the list when it shows │ │643 + fn focusDetail(self: *View, root_focus: *Focus) void { │ │644 + if (ui.widget.listBackId(self.detailFrame()) != null) return ui.widget.focusListBack(self.detailFrame(), root_focus);│ │645 + root_focus.setFocus(self.detailScroll().getFocus().id); │ │646 + } │ │647 + │ │648 + fn focusList(self: *View, root_focus: *Focus) void { │ │649 + root_focus.setFocus(self.listScroll().getFocus().id); │ │650 + } │ │651 + │ │652 // the confirm button submits the form the web renderer posts │ │653 fn clearInput(self: *View, key: Key, root_focus: *Focus) !void { │ │654 const center = &self.box.children.values()[content_index].widget.center; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │129 pub fn clickOnSelectedRow(list: *wgt.Box(Widget), root_focus: *Focus, mouse: xitui.input.Mouse) bool { │ │130 const cid = list.getFocus().child_id orelse return false; │ │131 return inp.leftClickOn(root_focus, cid, mouse); │ │132 +} │ │133 + │ │134 +// a detail pane's way back to a list that isn't shown beside it. it leads the │ │135 +// pane's frame, above the pane's content, hidden while the list shows. │ │136 +const list_back_index = 0; │ │137 +pub const list_pane_index = 1; │ │138 + │ │139 +pub fn addListBack(allocator: std.mem.Allocator, frame: *wgt.Box(Widget), label: []const u8) !void { │ │140 + var back = try wgt.TextBox.init(allocator, label, .{ .border = .single, .round_corners = true, .wrap_kind = .none }); │ │141 + errdefer back.deinit(allocator); │ │142 + back.getFocus().mode = .all; │ │143 + try frame.children.put(allocator, back.getFocus().id, .{ .widget = .{ .text_box = back }, .rect = null, .min_size = null, .hidden = true });│ │144 +} │ │145 + │ │146 +// point the way back at the shown list row's in-page link, so both hosts hand │ │147 +// its press and enter to the view instead of navigating │ │148 +pub fn setListBackLink(frame: *wgt.Box(Widget), link: []const u8) void { │ │149 + frame.children.values()[list_back_index].widget.text_box.getFocus().kind = .{ .custom = link }; │ │150 +} │ │151 + │ │152 +pub fn showListBack(frame: *wgt.Box(Widget), shown: bool) void { │ │153 + frame.children.values()[list_back_index].hidden = !shown; │ │154 + if (!shown and frame.getFocus().child_id == frame.children.keys()[list_back_index]) │ │155 + frame.getFocus().child_id = frame.children.keys()[list_pane_index]; │ │156 +} │ │157 + │ │158 +// the way back's focus id while it shows │ │159 +pub fn listBackId(frame: *wgt.Box(Widget)) ?usize { │ │160 + return if (frame.children.values()[list_back_index].hidden) null else frame.children.keys()[list_back_index]; │ │161 } │ │162 │ │163 +// enter the pane on the way back. selecting it (then focusing the frame) also │ │164 +// handles the too-narrow case where the pane isn't laid out yet. │ │165 +pub fn focusListBack(frame: *wgt.Box(Widget), root_focus: *Focus) void { │ │166 + frame.getFocus().child_id = frame.children.keys()[list_back_index]; │ │167 + root_focus.setFocus(frame.getFocus().id); │ │168 +} │ │169 + │ │170 pub const back_button_width = 3; │ │171 const back_button_height = 3; │ │172 const back_kind = "back"; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯