╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │412 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │413 self.clearGrid(); │ │414 │ │415 + // each sub header tab maps 1:1 to a stack child by position. │ │416 + if (self.subHeader().getSelectedIndex()) |index| { │ │417 + const stack = self.viewStack(); │ │418 + stack.getFocus().child_id = stack.children.keys()[index]; │ │419 + } │ │420 + │ │421 // swap the detail pane to the selected issue when it changes. │ │422 try self.refreshDetail(allocator); │ │423 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │455 } │ │456 } │ │457 if (self.tagFlow()) |tf| tf.selected = self.tagsFocused(); │ │458 + self.tagsView().selected = self.tagsViewActive(); │ │459 │ │460 // cap the list at list_max_width only while the detail pane fits beside│ │461 // it. the box drops the detail when the width can't hold both minimums,│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────╮ │539 } │ │540 return; │ │541 } │ │481 - if (self.detailActive()) { │ │542 + if (self.tagsViewActive()) { │ │543 + self.tagsViewInput(key, root_focus); │ │544 + } else if (self.detailActive()) { │ │545 try self.detailInput(key, root_focus);│ │546 } else { │ │547 try self.listInput(key, root_focus); │ ╰───────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │548 } │ │549 } │ │550 │ │551 + // arrow keys move the tag selection; up from the top row crosses to the │ │552 + // sub header tabs. │ │553 + fn tagsViewInput(self: *View, key: Key, root_focus: *Focus) void { │ │554 + const tf = self.tagsView(); │ │555 + const cid = tf.focus.child_id orelse return; │ │556 + const cur = tf.indexOfFocusId(cid) orelse return; │ │557 + const count = tf.text_boxes.items.len; │ │558 + switch (key) { │ │559 + .arrow_left => if (cur > 0) root_focus.setFocus(tf.text_boxes.items[cur - 1].getFocus().id), │ │560 + .arrow_right => if (cur + 1 < count) root_focus.setFocus(tf.text_boxes.items[cur + 1].getFocus().id), │ │561 + .arrow_up => if (tf.rowStep(cur, false)) |i| root_focus.setFocus(tf.text_boxes.items[i].getFocus().id) else self.focusSubHeader(root_focus),│ │562 + .arrow_down => if (tf.rowStep(cur, true)) |i| root_focus.setFocus(tf.text_boxes.items[i].getFocus().id), │ │563 + .home => root_focus.setFocus(tf.text_boxes.items[0].getFocus().id), │ │564 + .end => root_focus.setFocus(tf.text_boxes.items[count - 1].getFocus().id), │ │565 + else => {}, │ │566 + } │ │567 + } │ │568 + │ │569 fn listInput(self: *View, key: Key, root_focus: *Focus) !void { │ │570 // up/down (and the scroll wheel) move the selection a row; page up/down │ │571 // jump a fixed amount. right/Enter cross into the detail pane. up from │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────────────╮ │src/ui/Repo/Issues/SubHeader.zig│ ╰────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │15 box: wgt.Box(ui.Widget), │ │16 tab_ids: std.AutoArrayHashMapUnmanaged(usize, void), │ │17 │ │18 - pub fn init(allocator: std.mem.Allocator, session: *ui.Session, count: usize) !View { │ │18 + // `tag` is the url-encoded tag filter ("" = unfiltered). │ │19 + pub fn init(allocator: std.mem.Allocator, session: *ui.Session, count: usize, tag: []const u8) !View { │ │20 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = .hidden, .rounded_corners = true, .direction = .horiz });│ │21 errdefer box.deinit(allocator); │ │22 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │23 var tab_ids: std.AutoArrayHashMapUnmanaged(usize, void) = .empty;│ │24 errdefer tab_ids.deinit(allocator); │ │25 + │ │26 + const aa = session.page_arena.allocator(); │ │27 │ │28 // results tab, labeled with the listing's issue count │ │29 { │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │27 - const label = try std.fmt.allocPrint(session.page_arena.allocator(), "results ({d})", .{count}); │ │30 + const label = try std.fmt.allocPrint(aa, "results ({d})", .{count}); │ │31 + var text_box = try wgt.TextBox(ui.Widget).init(allocator, label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │32 + errdefer text_box.deinit(allocator); │ │33 + text_box.getFocus().focusable = true; │ │34 + try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │35 + try box.children.put(allocator, text_box.getFocus().id, .{ │ │36 + .widget = .{ .text_box = text_box }, │ │37 + .rect = null, │ │38 + .min_size = .{ .width = label.len + 2, .height = null }, │ │39 + }); │ │40 + } │ │41 + │ │42 + // tags tab, labeled with the active tag filter │ │43 + { │ │44 + const label = if (tag.len == 0) "tags" else blk: { │ │45 + const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, tag)); │ │46 + break :blk try std.fmt.allocPrint(aa, "tags ({s})", .{decoded}); │ │47 + }; │ │48 var text_box = try wgt.TextBox(ui.Widget).init(allocator, label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │49 errdefer text_box.deinit(allocator); │ │50 text_box.getFocus().focusable = true; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯