feat(ui): scroll the focused sidebar panel + refine the nav overlay (#21, ADR-0046 DC3 + DC2)
DC3 — navigation-mode scroll: the focused Tables / Relationships panel scrolls (Up/Down by a line, PageUp/PageDown by its visible-row count). Per-panel offsets are clamped to content at render time, and the renderer reports each panel's visible rows for paging — mirroring the output panel's scroll. render_items_panel / render_relationships_panel take &mut App, count their rows, and store+clamp the offset before building the borrowing lines. DC2 refinement: the expand-on-focus overlay now clears only the sidebar strip plus a one-column gutter, leaving the base output/input/hint visible (unchanged) to the right rather than blanking the whole area — truer to "underneath keeps its layout", with the gutter keeping the cut-off edge clean (chosen after eyeballing both variants). ADR DC2 and the overlay snapshot updated to match. Tests: line/page scroll move only the focused panel and clamp; the render clamps a past-the-end offset so the last row stays visible.
This commit is contained in:
+77
-2
@@ -323,6 +323,14 @@ pub struct App {
|
||||
/// diagram's side-by-side vs vertical layout choice. Defaults to
|
||||
/// `80` until the first render measures the real width.
|
||||
pub last_output_width: u16,
|
||||
/// Top visible row of the Tables / Relationships sidebar panels
|
||||
/// while scrolled in navigation mode (ADR-0046 DC3), with the most
|
||||
/// recent visible-row count the renderer reported for each (used to
|
||||
/// page-scroll and to clamp the offset). `0` = showing from the top.
|
||||
pub tables_scroll: usize,
|
||||
pub relationships_scroll: usize,
|
||||
pub last_tables_visible: usize,
|
||||
pub last_relationships_visible: usize,
|
||||
/// Prettified display name of the currently-open project,
|
||||
/// rendered in the status bar (P-NAME-3, ADR-0015 §2). `None`
|
||||
/// during very-early startup before the runtime has opened a
|
||||
@@ -491,6 +499,10 @@ impl App {
|
||||
last_output_visible: 0,
|
||||
last_output_total_wrapped: 0,
|
||||
last_output_width: 80,
|
||||
tables_scroll: 0,
|
||||
relationships_scroll: 0,
|
||||
last_tables_visible: 0,
|
||||
last_relationships_visible: 0,
|
||||
project_name: None,
|
||||
project_is_temp: false,
|
||||
fatal_message: None,
|
||||
@@ -973,12 +985,40 @@ impl App {
|
||||
/// (wired in DC3); every other key is inert because the command
|
||||
/// input is occluded by the expanded sidebar overlay.
|
||||
fn handle_nav_key(&mut self, key: KeyEvent) -> Vec<Action> {
|
||||
if key.code == KeyCode::Esc {
|
||||
self.nav_exit();
|
||||
match key.code {
|
||||
KeyCode::Esc => self.nav_exit(),
|
||||
KeyCode::Up => self.nav_scroll(-1),
|
||||
KeyCode::Down => self.nav_scroll(1),
|
||||
KeyCode::PageUp => self.nav_scroll_page(-1),
|
||||
KeyCode::PageDown => self.nav_scroll_page(1),
|
||||
_ => {}
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Scroll the focused sidebar panel by `lines` (ADR-0046 DC3); the
|
||||
/// renderer clamps the offset to the panel's content on the next
|
||||
/// frame, so over-scrolling is harmless.
|
||||
const fn nav_scroll(&mut self, lines: i32) {
|
||||
let slot = match self.nav_focus {
|
||||
NavFocus::SidebarTables => &mut self.tables_scroll,
|
||||
NavFocus::SidebarRelationships => &mut self.relationships_scroll,
|
||||
NavFocus::Input => return,
|
||||
};
|
||||
*slot = slot.saturating_add_signed(lines as isize);
|
||||
}
|
||||
|
||||
/// Page-scroll the focused panel by its last reported visible-row
|
||||
/// count (ADR-0046 DC3).
|
||||
fn nav_scroll_page(&mut self, dir: i32) {
|
||||
let visible = match self.nav_focus {
|
||||
NavFocus::SidebarTables => self.last_tables_visible,
|
||||
NavFocus::SidebarRelationships => self.last_relationships_visible,
|
||||
NavFocus::Input => return,
|
||||
};
|
||||
self.nav_scroll(dir * (visible.max(1) as i32));
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) -> Vec<Action> {
|
||||
// On Windows, key events fire for both Press and Release;
|
||||
// honour only Press to avoid double-handling. Other
|
||||
@@ -5240,6 +5280,41 @@ mod tests {
|
||||
assert!(actions.is_empty(), "Enter does not submit in navigation mode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nav_scroll_keys_move_only_the_focused_panel() {
|
||||
// ADR-0046 DC3: Up/Down line-scroll the focused sidebar panel.
|
||||
let mut app = App::new();
|
||||
app.nav_focus = NavFocus::SidebarTables;
|
||||
app.update(key(KeyCode::Down));
|
||||
app.update(key(KeyCode::Down));
|
||||
assert_eq!(app.tables_scroll, 2);
|
||||
assert_eq!(app.relationships_scroll, 0, "only the focused panel scrolls");
|
||||
app.update(key(KeyCode::Up));
|
||||
assert_eq!(app.tables_scroll, 1);
|
||||
// Up saturates at the top.
|
||||
app.update(key(KeyCode::Up));
|
||||
app.update(key(KeyCode::Up));
|
||||
assert_eq!(app.tables_scroll, 0);
|
||||
// Switching focus moves the other panel instead.
|
||||
app.nav_focus = NavFocus::SidebarRelationships;
|
||||
app.update(key(KeyCode::Down));
|
||||
assert_eq!(app.relationships_scroll, 1);
|
||||
assert_eq!(app.tables_scroll, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nav_page_scroll_uses_the_panels_visible_rows() {
|
||||
// ADR-0046 DC3: PageUp/PageDown move by the last reported
|
||||
// visible-row count.
|
||||
let mut app = App::new();
|
||||
app.nav_focus = NavFocus::SidebarTables;
|
||||
app.last_tables_visible = 10;
|
||||
app.update(key(KeyCode::PageDown));
|
||||
assert_eq!(app.tables_scroll, 10);
|
||||
app.update(key(KeyCode::PageUp));
|
||||
assert_eq!(app.tables_scroll, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_scroll_offset_resets_when_the_buffer_is_replaced() {
|
||||
// ADR-0046 DA3: the horizontal scroll offset must not leak from
|
||||
|
||||
Reference in New Issue
Block a user