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::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};
pub(crate) struct ConsciousScreen {
@ -177,6 +177,7 @@ impl ScreenView for ConsciousScreen {
.title_top(Line::from(screen_legend()).left_aligned())
.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::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)]
enum Pane { Agents, Outputs, History, Context }
@ -282,7 +282,7 @@ impl SubconsciousScreen {
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 mut lines: Vec<Line> = Vec::new();
@ -297,7 +297,8 @@ impl SubconsciousScreen {
let mut block = pane_block_focused("state", self.focus == Pane::Outputs);
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) {
@ -351,7 +352,7 @@ impl SubconsciousScreen {
}
fn draw_context(
&self,
&mut self,
frame: &mut Frame,
area: Rect,
sections: &[SectionView],
@ -374,6 +375,7 @@ impl SubconsciousScreen {
let mut block = pane_block_focused(title, self.focus == Pane::Context);
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
use ratatui::{
layout::{Margin, Rect},
style::{Color, Modifier, Style},
text::Line,
widgets::{Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap},
Frame,
widgets::{Block, Borders},
crossterm::event::KeyCode,
};
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
@ -139,12 +108,12 @@ pub fn render_scrollable(
pub struct SectionTree {
pub selected: Option<usize>,
pub expanded: std::collections::HashSet<usize>,
pub scroll: u16,
pub scroll: super::scroll_pane::ScrollPaneState,
}
impl SectionTree {
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 {
@ -184,14 +153,14 @@ impl SectionTree {
KeyCode::PageUp => {
let sel = self.selected.unwrap_or(0);
self.selected = Some(sel.saturating_sub(page));
self.scroll = self.scroll.saturating_sub(page as u16);
self.scroll.scroll_up(page as u16);
return;
}
KeyCode::PageDown => {
let max = item_count.saturating_sub(1);
let sel = self.selected.map_or(0, |s| (s + page).min(max));
self.selected = Some(sel);
self.scroll += page as u16;
self.scroll.scroll_down(page as u16);
return;
}
KeyCode::Home => {
@ -228,10 +197,10 @@ impl SectionTree {
if let Some(sel) = self.selected {
let sel_line = sel as u16;
let visible = height.saturating_sub(2);
if sel_line < self.scroll {
self.scroll = sel_line;
} else if sel_line >= self.scroll + visible {
self.scroll = sel_line.saturating_sub(visible.saturating_sub(1));
if sel_line < self.scroll.offset {
self.scroll.offset = sel_line;
} else if sel_line >= self.scroll.offset + visible {
self.scroll.offset = sel_line.saturating_sub(visible.saturating_sub(1));
}
}
}