Fix scroll: PgUp/PgDn move cursor in place, add Scrollbar widget

SectionTree.handle_nav() now takes viewport height:
- PgUp/PgDn move both cursor and viewport by one page, keeping the
  cursor at the same screen position
- Home/End jump to first/last item
- scroll_to_selected() uses actual viewport height instead of
  hardcoded 30

Added render_scrollable() in widgets.rs: renders a Paragraph with a
vertical Scrollbar when content exceeds the viewport. Used by the
conscious and subconscious screens.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-07 19:07:00 -04:00
parent 818cdcc4e5
commit 19bb6d02e3
3 changed files with 74 additions and 39 deletions

View file

@ -9,13 +9,13 @@ use ratatui::{
layout::{Constraint, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{List, ListItem, ListState, Paragraph, Wrap},
widgets::{List, ListItem, ListState},
Frame,
crossterm::event::KeyCode,
};
use super::{App, ScreenView, screen_legend};
use super::widgets::{SectionTree, pane_block_focused, format_age, format_ts_age};
use super::widgets::{SectionTree, pane_block_focused, render_scrollable, format_age, format_ts_age};
use crate::agent::context::ContextSection;
#[derive(Clone, Copy, PartialEq)]
@ -82,7 +82,7 @@ impl ScreenView for SubconsciousScreen {
}
_ => {}
}
Pane::Outputs => self.output_tree.handle_nav(code, &output_sections),
Pane::Outputs => self.output_tree.handle_nav(code, &output_sections, area.height),
Pane::History => match code {
KeyCode::Up => self.history_scroll = self.history_scroll.saturating_sub(3),
KeyCode::Down => self.history_scroll += 3,
@ -90,7 +90,7 @@ impl ScreenView for SubconsciousScreen {
KeyCode::PageDown => self.history_scroll += 20,
_ => {}
}
Pane::Context => self.context_tree.handle_nav(code, &context_sections),
Pane::Context => self.context_tree.handle_nav(code, &context_sections, area.height),
}
}
}
@ -207,11 +207,9 @@ impl SubconsciousScreen {
self.output_tree.render_sections(&sections, &mut lines);
}
let para = Paragraph::new(lines)
.block(pane_block_focused("state", self.focus == Pane::Outputs))
.wrap(Wrap { trim: false })
.scroll((self.output_tree.scroll, 0));
frame.render_widget(para, area);
render_scrollable(frame, area, lines,
pane_block_focused("state", self.focus == Pane::Outputs),
self.output_tree.scroll);
}
fn draw_history(&self, frame: &mut Frame, area: Rect, app: &App) {
@ -248,11 +246,9 @@ impl SubconsciousScreen {
}
}
let para = Paragraph::new(lines)
.block(pane_block_focused(&title, self.focus == Pane::History))
.wrap(Wrap { trim: false })
.scroll((self.history_scroll, 0));
frame.render_widget(para, area);
render_scrollable(frame, area, lines,
pane_block_focused(&title, self.focus == Pane::History),
self.history_scroll);
}
fn draw_context(
@ -277,10 +273,8 @@ impl SubconsciousScreen {
.map(|s| s.name.as_str())
.unwrap_or("");
let para = Paragraph::new(lines)
.block(pane_block_focused(title, self.focus == Pane::Context))
.wrap(Wrap { trim: false })
.scroll((self.context_tree.scroll, 0));
frame.render_widget(para, area);
render_scrollable(frame, area, lines,
pane_block_focused(title, self.focus == Pane::Context),
self.context_tree.scroll);
}
}