1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// SPDX-License-Identifier: MIT OR Apache-2.0
//! 2026-09-26: Section navigation: the sidebar order, `1`-`7` jumps, `⇥` cycling, and the two click entry points.
//!
//! Owner: server tui.
//! Invariants:
//! - `nav_rows` is `Section::ALL` in order, each section expanded into its
//! `Section::subs` rows: the order `render::draw_sidebar` draws (it expands
//! only the active section) and `events::sidebar_row` hit-tests.
use super::app::{App, BenchSub, Focus, MainSub, TermSub};
use super::help_state::HelpSub;
use super::section::Section;
impl App {
/// 2026-09-26: Which subsection of `s` is active, as an index into [`Section::subs`].
pub fn sub_index(&self, s: Section) -> usize {
match s {
Section::Main => (self.main_sub == MainSub::Kernels) as usize,
Section::Benchmarks => (self.bench_sub == BenchSub::History) as usize,
Section::Terminal => (self.term_sub == TermSub::Chat) as usize,
Section::Help => (self.help.sub == HelpSub::Report) as usize,
_ => 0,
}
}
fn set_sub(&mut self, s: Section, i: usize) {
match s {
Section::Main => {
self.main_sub = if i == 0 {
MainSub::Overview
} else {
MainSub::Kernels
}
}
Section::Benchmarks => {
self.bench_sub = if i == 0 {
BenchSub::Suite
} else {
BenchSub::History
}
}
Section::Terminal => self.term_sub = if i == 0 { TermSub::Ops } else { TermSub::Chat },
Section::Help => {
self.help.sub = if i == 0 {
HelpSub::Guide
} else {
HelpSub::Report
}
}
_ => {}
}
}
/// 2026-09-26: Every navigable sidebar row, flattened in the order the sidebar draws them:
/// one entry per subsection, or a single entry for a section that has none.
pub(super) fn nav_rows() -> Vec<(Section, usize)> {
Section::ALL
.iter()
.flat_map(|s| (0..s.subs().len().max(1)).map(move |i| (*s, i)))
.collect()
}
pub(super) fn jump(&mut self, s: Section) {
if self.section != s {
self.repaint = true;
}
if self.section == s {
// 2026-09-26: Repeat-press cycles this section's subsections.
let n = s.subs().len();
if n > 1 {
self.set_sub(s, (self.sub_index(s) + 1) % n);
}
}
self.section = s;
self.focus = Focus::Content;
}
/// 2026-09-26: `⇥` / `⇧⇥` walk every row of [`Self::nav_rows`], subsection rows included.
pub(super) fn cycle_section(&mut self, dir: i32) {
let rows = Self::nav_rows();
let cur = rows
.iter()
.position(|(s, i)| *s == self.section && *i == self.sub_index(*s))
.unwrap_or(0) as i32;
let (s, i) = rows[((cur + dir).rem_euclid(rows.len() as i32)) as usize];
self.section = s;
self.set_sub(s, i);
self.focus = Focus::Content;
}
pub fn sidebar_click(&mut self, row_in_sidebar: usize) {
// 2026-09-26: `row_in_sidebar` is an index into `Section::ALL`; `events::sidebar_row` has already removed the subsection rows.
if let Some(s) = Section::ALL.get(row_in_sidebar) {
self.jump(*s);
}
}
/// 2026-09-26: Click on one of the active section's subsection rows, the only ones the sidebar draws.
/// Selects that row outright; a repeat press of the section's key cycles instead.
pub fn sidebar_sub_click(&mut self, sub: usize) {
if sub < self.section.subs().len() {
self.set_sub(self.section, sub);
self.focus = Focus::Content;
}
}
}