╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │1208 const has_status = @hasField(Event, "status"); │ │1209 const supports_conflicts = Event.merge_policy == .field_conflicts; │ │1210 const supports_drafts = @hasField(Self, "drafts"); │ │ 358 - const supports_forks = @hasField(Entry, "fork_exists"); │ │1211 const FieldConflict = if (supports_conflicts) Data.FieldConflict else void;│ │1212 const ViewKind = Data.ViewKind; │ │1213 const thread_name = Data.thread_name; │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────╮ │1214 + const DetailType = Data.Detail;│ │1215 │ │1216 return struct { │ │1217 const This = @This(); │ ╰─────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │1221 box: wgt.Box(Widget), // vert: [header_index] = tabs, [stack_index] = stack│ │1222 data: *const Self, │ │1223 session: *ui.Session, │ │ 371 - // per-split state, indexed like the stack's split children: the thread the│ │ 372 - // pane shows and its focus ids. │ │ 373 - detailed_index: [view_count]?usize, │ │ 374 - title_id: [view_count]?usize, │ │ 375 - description_id: [view_count]?usize, │ │ 376 - author_id: [view_count]?usize, │ │ 377 - detail_only: bool, │ │1224 │ │1225 const header_index: usize = 0; │ │1226 const stack_index: usize = 1; │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1278 │ │1279 fn entryDraft(entry: Entry) bool { │ │1280 return if (supports_drafts) entry.draft else false; │ │ 435 - } │ │ 436 - │ │ 437 - fn entryHasFork(entry: Entry) bool { │ │ 438 - return if (supports_forks) entry.fork_exists else false; │ │ 439 - } │ │ 440 - │ │ 441 - const StatusChange = struct { │ │ 442 - action: []const u8, │ │ 443 - status: Status, │ │ 444 - }; │ │ 445 - │ │ 446 - fn statusChange(status: Status) ?StatusChange { │ │ 447 - const name = @tagName(status); │ │ 448 - if (std.mem.eql(u8, name, "open")) return .{ │ │ 449 - .action = "close", │ │ 450 - .status = std.meta.stringToEnum(Status, "closed") orelse return null, │ │ 451 - }; │ │ 452 - if (std.mem.eql(u8, name, "closed")) return .{ │ │ 453 - .action = "open", │ │ 454 - .status = std.meta.stringToEnum(Status, "open") orelse return null, │ │ 455 - }; │ │ 456 - return null; │ │1281 } │ │1282 │ │1283 fn listRoute(identity: []const u8, status: Status, tag: []const u8, selected: []const u8) ?ui.RoutablePage {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │1288 return if (supports_drafts) Data.draftsRoute(identity) else null; │ │1289 } │ │1290 │ │ 467 - fn forkRoute(identity: []const u8, id: []const u8) ?ui.RoutablePage { │ │ 468 - return if (supports_forks) Data.forkRoute(identity, id) else null; │ │ 469 - } │ │ 470 - │ │1291 fn conflictsRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │1292 return if (supports_conflicts) Data.conflictsRoute(identity, selected) else null;│ │1293 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────╮ │1485 .box = outer, │ │1486 .data = data, │ │1487 .session = session, │ │ 668 - .detailed_index = @splat(null),│ │ 669 - .title_id = @splat(null), │ │ 670 - .description_id = @splat(null),│ │ 671 - .author_id = @splat(null), │ │ 672 - .detail_only = false, │ │1488 }; │ │1489 } │ │1490 │ ╰─────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 676 - // build the selected thread's existing detail pane without its list or │ │ 677 - // thread-page header, for pages that provide their own surrounding UI. │ │ 678 - pub fn initDetail(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !This { │ │ 679 - var self = try init(allocator, data, session); │ │ 680 - errdefer self.deinit(allocator); │ │ 681 - self.detail_only = true; │ │ 682 - self.box.getFocus().child_id = self.box.children.keys()[stack_index]; │ │ 683 - const index = self.selectedSplitIndex() orelse return error.NotFound; │ │ 684 - self.resultsBox(index).getFocus().child_id = self.resultsBox(index).children.keys()[detail_index]; │ │ 685 - return self; │ │ 686 - } │ │ 687 - │ │1491 // the master-detail split showing `status`'s window, or the conflicts │ │1492 // window when null. │ │1493 fn initSplit(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self, status_maybe: ?Status, drafts: bool) !wgt.Box(Widget) {│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1528 try box.children.put(allocator, list_scroll.getFocus().id, .{ .widget = .{ .scroll = list_scroll }, .rect = null, .min_size = .{ .width = list_max_width, .height = null }, .max_size = .{ .width = list_max_width, .height = null } });│ │1529 } │ │1530 │ │ 728 - // the detail pane — a frame around its scrollable contents │ │1531 + // frame the detail pane inside the master-detail split │ │1532 { │ │ 730 - var detail_outer = blk: { │ │ 731 - var detail_scroll = blk2: { │ │ 732 - var detail_inner = try wgt.Box(Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │ 733 - errdefer detail_inner.deinit(allocator); │ │ 734 - // fill the pane (content top-left, scroll bar pinned to the │ │ 735 - // edge) rather than shrinking to the description. │ │ 736 - break :blk2 try wgt.Scroll(Widget).init(allocator, .{ .box = detail_inner }, .{ .direction = .vert, .web_native = !session.is_terminal, .fill = true }); │ │ 737 - }; │ │ 738 - errdefer detail_scroll.deinit(allocator); │ │ 739 - var frame = try wgt.Box(Widget).init(allocator, .{ .border_style = .hidden, .direction = .vert }); │ │ 740 - errdefer frame.deinit(allocator); │ │ 741 - // the frame's selected child is its scroll, so the focus chain │ │ 742 - // reaches the detail contents. │ │ 743 - frame.getFocus().child_id = detail_scroll.getFocus().id; │ │ 744 - try frame.children.put(allocator, detail_scroll.getFocus().id, .{ .widget = .{ .scroll = detail_scroll }, .rect = null, .min_size = null }); │ │ 745 - break :blk frame; │ │ 746 - }; │ │ 747 - errdefer detail_outer.deinit(allocator); │ │ 748 - try box.children.put(allocator, detail_outer.getFocus().id, .{ .widget = .{ .box = detail_outer }, .rect = null, .min_size = .{ .width = detail_min_width, .height = null } }); │ │1533 + var frame = try wgt.Box(Widget).init(allocator, .{ .border_style = .hidden, .direction = .vert }); │ │1534 + errdefer frame.deinit(allocator); │ │1535 + var detail_view = try DetailType.init(allocator, data, session, null, .{}); │ │1536 + errdefer detail_view.deinit(allocator); │ │1537 + frame.getFocus().child_id = detail_view.getFocus().id; │ │1538 + try frame.children.put(allocator, detail_view.getFocus().id, .{ .widget = @unionInit(Widget, Data.detail_widget_name, detail_view), .rect = null, .min_size = null }); │ │1539 + try box.children.put(allocator, frame.getFocus().id, .{ .widget = .{ .box = frame }, .rect = null, .min_size = .{ .width = detail_min_width, .height = null } }); │ │1540 } │ │1541 │ │1542 box.getFocus().child_id = box.children.keys()[list_index]; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1844 row.getFocus().focusable = true; │ │1845 if (link.len != 0) row.getFocus().kind = .{ .custom = link }; │ │1846 try box.children.put(allocator, row.getFocus().id, .{ .widget = .{ .text_box = row }, .rect = null, .min_size = null, .max_size = .{ .width = null, .height = 5 } }); │ │1056 - } │ │1057 - │ │1058 - fn addToolButton(allocator: std.mem.Allocator, row: *wgt.Box(Widget), label: []const u8, action: []const u8) !void { │ │1059 - var button = try wgt.TextBox.init(allocator, label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │1060 - errdefer button.deinit(allocator); │ │1061 - button.getFocus().focusable = true; │ │1062 - button.getFocus().kind = .{ .custom = action }; │ │1063 - try row.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = .{ .width = try xitui.width.displayWidth(label) + 2, .height = null } });│ │1847 } │ │1848 │ │1849 pub fn deinit(self: *This, allocator: std.mem.Allocator) void { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1903 │ │1904 fn listBox(self: *This, index: usize) *wgt.Box(Widget) { │ │1905 return &self.listScroll(index).child.box; │ │1123 - } │ │1124 - │ │1125 - // the tool row is the first child inside the detail scroll. │ │1126 - const tool_row_index: usize = 0; │ │1127 - const detail_scroll_index: usize = 0; │ │1128 - │ │1129 - fn detailOuter(self: *This, index: usize) *wgt.Box(Widget) { │ │1130 - return &self.resultsBox(index).children.values()[detail_index].widget.box; │ │1131 - } │ │1132 - │ │1133 - fn toolRow(self: *This, index: usize) *wgt.Box(Widget) { │ │1134 - return &self.detailInner(index).children.values()[tool_row_index].widget.box; │ │1906 } │ │1907 │ │1137 - fn detailScroll(self: *This, index: usize) *wgt.Scroll(Widget) { │ │1138 - return &self.detailOuter(index).children.values()[detail_scroll_index].widget.scroll;│ │1908 + fn detail(self: *This, index: usize) *DetailType { │ │1909 + const frame = &self.resultsBox(index).children.values()[detail_index].widget.box; │ │1910 + return &@field(frame.children.values()[0].widget, Data.detail_widget_name); │ │1911 } │ │1912 │ │1141 - fn detailInner(self: *This, index: usize) *wgt.Box(Widget) { │ │1142 - return &self.detailScroll(index).child.box; │ │1143 - } │ │1144 - │ │1913 fn window(self: *This, index: usize) *const Window { │ │1914 if (supports_conflicts and index == conflict_view_index) return &self.data.conflicts;│ │1915 if (supports_drafts and index == drafts_view_index) return &self.data.drafts; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯