Convert SectionTree and all remaining callers to ScrollPane

SectionTree.scroll is now a ScrollPaneState. All callers of
render_scrollable replaced with ScrollPane::render_stateful_widget.

Deleted render_scrollable and its imports — no hand-rolled scroll
rendering remains outside of scroll_pane.rs.

Co-Authored-By: Kent Overstreet <kent.overstreet@gmail.com>
This commit is contained in:
ProofOfConcept 2026-04-11 01:42:49 -04:00
parent 4a1f5acb85
commit e17118e4c9
3 changed files with 19 additions and 47 deletions

View file

@ -6,7 +6,7 @@ use ratatui::{
}; };
use super::{App, ScreenView, screen_legend}; use super::{App, ScreenView, screen_legend};
use super::widgets::{SectionTree, SectionView, section_to_view, pane_block, render_scrollable, tree_legend}; use super::widgets::{SectionTree, SectionView, section_to_view, pane_block, tree_legend};
use crate::agent::context::{AstNode, NodeBody, Ast}; use crate::agent::context::{AstNode, NodeBody, Ast};
pub(crate) struct ConsciousScreen { pub(crate) struct ConsciousScreen {
@ -177,6 +177,7 @@ impl ScreenView for ConsciousScreen {
.title_top(Line::from(screen_legend()).left_aligned()) .title_top(Line::from(screen_legend()).left_aligned())
.title_bottom(tree_legend()); .title_bottom(tree_legend());
render_scrollable(frame, area, lines, block, self.tree.scroll); let widget = super::scroll_pane::ScrollPane::new(&lines).block(block);
frame.render_stateful_widget(widget, area, &mut self.tree.scroll);
} }
} }

View file

@ -15,7 +15,7 @@ use ratatui::{
}; };
use super::{App, ScreenView, screen_legend}; use super::{App, ScreenView, screen_legend};
use super::widgets::{SectionTree, SectionView, section_to_view, pane_block_focused, render_scrollable, tree_legend, format_age, format_ts_age}; use super::widgets::{SectionTree, SectionView, section_to_view, pane_block_focused, tree_legend, format_age, format_ts_age};
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
enum Pane { Agents, Outputs, History, Context } enum Pane { Agents, Outputs, History, Context }
@ -282,7 +282,7 @@ impl SubconsciousScreen {
frame.render_stateful_widget(list, area, &mut self.list_state); frame.render_stateful_widget(list, area, &mut self.list_state);
} }
fn draw_outputs(&self, frame: &mut Frame, area: Rect, app: &App) { fn draw_outputs(&mut self, frame: &mut Frame, area: Rect, app: &App) {
let sections = self.output_sections(app); let sections = self.output_sections(app);
let mut lines: Vec<Line> = Vec::new(); let mut lines: Vec<Line> = Vec::new();
@ -297,7 +297,8 @@ impl SubconsciousScreen {
let mut block = pane_block_focused("state", self.focus == Pane::Outputs); let mut block = pane_block_focused("state", self.focus == Pane::Outputs);
if self.focus == Pane::Outputs { block = block.title_bottom(tree_legend()); } if self.focus == Pane::Outputs { block = block.title_bottom(tree_legend()); }
render_scrollable(frame, area, lines, block, self.output_tree.scroll); let widget = super::scroll_pane::ScrollPane::new(&lines).block(block);
frame.render_stateful_widget(widget, area, &mut self.output_tree.scroll);
} }
fn draw_history(&mut self, frame: &mut Frame, area: Rect, app: &App) { fn draw_history(&mut self, frame: &mut Frame, area: Rect, app: &App) {
@ -351,7 +352,7 @@ impl SubconsciousScreen {
} }
fn draw_context( fn draw_context(
&self, &mut self,
frame: &mut Frame, frame: &mut Frame,
area: Rect, area: Rect,
sections: &[SectionView], sections: &[SectionView],
@ -374,6 +375,7 @@ impl SubconsciousScreen {
let mut block = pane_block_focused(title, self.focus == Pane::Context); let mut block = pane_block_focused(title, self.focus == Pane::Context);
if self.focus == Pane::Context { block = block.title_bottom(tree_legend()); } if self.focus == Pane::Context { block = block.title_bottom(tree_legend()); }
render_scrollable(frame, area, lines, block, self.context_tree.scroll); let widget = super::scroll_pane::ScrollPane::new(&lines).block(block);
frame.render_stateful_widget(widget, area, &mut self.context_tree.scroll);
} }
} }

View file

@ -1,11 +1,9 @@
// widgets.rs — Shared TUI helpers and reusable components // widgets.rs — Shared TUI helpers and reusable components
use ratatui::{ use ratatui::{
layout::{Margin, Rect},
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::Line, text::Line,
widgets::{Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap}, widgets::{Block, Borders},
Frame,
crossterm::event::KeyCode, crossterm::event::KeyCode,
}; };
use crate::agent::context::{AstNode, Ast}; use crate::agent::context::{AstNode, Ast};
@ -102,35 +100,6 @@ pub fn tree_legend() -> Line<'static> {
) )
} }
/// Render a scrollable paragraph with a vertical scrollbar.
///
/// Legacy wrapper — callers that manage their own `u16` scroll offset
/// use this. For new code, prefer ScrollPane + ScrollPaneState.
pub fn render_scrollable(
frame: &mut Frame,
area: Rect,
lines: Vec<Line<'_>>,
block: Block<'_>,
scroll: u16,
) {
let content_len = lines.len();
let para = Paragraph::new(lines)
.block(block)
.wrap(Wrap { trim: false })
.scroll((scroll, 0));
frame.render_widget(para, area);
let visible = area.height.saturating_sub(2) as usize;
if content_len > visible {
let mut sb_state = ScrollbarState::new(content_len)
.position(scroll as usize);
frame.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalRight),
area.inner(Margin { vertical: 1, horizontal: 0 }),
&mut sb_state,
);
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// SectionTree — expand/collapse tree renderer for ContextSection // SectionTree — expand/collapse tree renderer for ContextSection
@ -139,12 +108,12 @@ pub fn render_scrollable(
pub struct SectionTree { pub struct SectionTree {
pub selected: Option<usize>, pub selected: Option<usize>,
pub expanded: std::collections::HashSet<usize>, pub expanded: std::collections::HashSet<usize>,
pub scroll: u16, pub scroll: super::scroll_pane::ScrollPaneState,
} }
impl SectionTree { impl SectionTree {
pub fn new() -> Self { pub fn new() -> Self {
Self { selected: None, expanded: std::collections::HashSet::new(), scroll: 0 } Self { selected: None, expanded: std::collections::HashSet::new(), scroll: super::scroll_pane::ScrollPaneState::new() }
} }
fn total_nodes(&self, sections: &[SectionView]) -> usize { fn total_nodes(&self, sections: &[SectionView]) -> usize {
@ -184,14 +153,14 @@ impl SectionTree {
KeyCode::PageUp => { KeyCode::PageUp => {
let sel = self.selected.unwrap_or(0); let sel = self.selected.unwrap_or(0);
self.selected = Some(sel.saturating_sub(page)); self.selected = Some(sel.saturating_sub(page));
self.scroll = self.scroll.saturating_sub(page as u16); self.scroll.scroll_up(page as u16);
return; return;
} }
KeyCode::PageDown => { KeyCode::PageDown => {
let max = item_count.saturating_sub(1); let max = item_count.saturating_sub(1);
let sel = self.selected.map_or(0, |s| (s + page).min(max)); let sel = self.selected.map_or(0, |s| (s + page).min(max));
self.selected = Some(sel); self.selected = Some(sel);
self.scroll += page as u16; self.scroll.scroll_down(page as u16);
return; return;
} }
KeyCode::Home => { KeyCode::Home => {
@ -228,10 +197,10 @@ impl SectionTree {
if let Some(sel) = self.selected { if let Some(sel) = self.selected {
let sel_line = sel as u16; let sel_line = sel as u16;
let visible = height.saturating_sub(2); let visible = height.saturating_sub(2);
if sel_line < self.scroll { if sel_line < self.scroll.offset {
self.scroll = sel_line; self.scroll.offset = sel_line;
} else if sel_line >= self.scroll + visible { } else if sel_line >= self.scroll.offset + visible {
self.scroll = sel_line.saturating_sub(visible.saturating_sub(1)); self.scroll.offset = sel_line.saturating_sub(visible.saturating_sub(1));
} }
} }
} }