Skip to main content

codetether_agent/tui/app/state/
scroll.rs

1//! Chat and tool-preview scroll methods.
2//!
3//! Sentinel scheme: `chat_scroll >= 1_000_000` ("follow latest") is clamped
4//! to `chat_last_max_scroll` at render time. `tool_preview_scroll` mirrors
5//! the same scheme via [`TOOL_PREVIEW_FOLLOW`] so the tool panel auto-follows
6//! the most recent activity until the user manually scrolls up.
7
8/// Sentinel: tool panel auto-follows (scrolls to bottom of latest activity).
9pub const TOOL_PREVIEW_FOLLOW: usize = 1_000_000;
10
11impl super::AppState {
12    pub fn scroll_up(&mut self, amount: usize) {
13        // Manual scroll-up disengages auto-follow so streaming output
14        // stops yanking the user back to the bottom of the chat.
15        self.chat_auto_follow = false;
16        let base = if self.chat_scroll >= 1_000_000 {
17            self.chat_last_max_scroll
18        } else {
19            self.chat_scroll
20        };
21        self.chat_scroll = base.saturating_sub(amount);
22    }
23
24    pub fn scroll_down(&mut self, amount: usize) {
25        if self.chat_scroll >= 1_000_000 {
26            return;
27        }
28        let next = self.chat_scroll.saturating_add(amount);
29        if next >= self.chat_last_max_scroll {
30            self.scroll_to_bottom();
31        } else {
32            self.chat_scroll = next;
33        }
34    }
35
36    /// Set sentinel value — clamped to actual content height at render time.
37    /// Re-engages [`AppState::chat_auto_follow`] so subsequent session events
38    /// keep the user pinned to the latest output.
39    pub fn scroll_to_bottom(&mut self) {
40        self.chat_scroll = 1_000_000;
41        self.chat_auto_follow = true;
42    }
43
44    pub fn scroll_to_top(&mut self) {
45        self.chat_scroll = 0;
46    }
47
48    pub fn set_chat_max_scroll(&mut self, max_scroll: usize) {
49        self.chat_last_max_scroll = max_scroll;
50        if max_scroll == 0 {
51            self.chat_scroll = 0;
52        } else if self.chat_scroll < 1_000_000 {
53            self.chat_scroll = self.chat_scroll.min(max_scroll);
54        }
55    }
56
57    pub fn scroll_tool_preview_up(&mut self, amount: usize) {
58        // Manual scroll cancels auto-follow.
59        let base = if self.tool_preview_scroll >= TOOL_PREVIEW_FOLLOW {
60            self.tool_preview_last_max_scroll
61        } else {
62            self.tool_preview_scroll
63        };
64        self.tool_preview_scroll = base.saturating_sub(amount);
65    }
66
67    pub fn scroll_tool_preview_down(&mut self, amount: usize) {
68        if self.tool_preview_scroll >= TOOL_PREVIEW_FOLLOW {
69            return;
70        }
71        let next = self.tool_preview_scroll.saturating_add(amount);
72        // Re-enable auto-follow when hitting bottom.
73        self.tool_preview_scroll = if next >= self.tool_preview_last_max_scroll {
74            TOOL_PREVIEW_FOLLOW
75        } else {
76            next
77        };
78    }
79
80    pub fn reset_tool_preview_scroll(&mut self) {
81        self.tool_preview_scroll = TOOL_PREVIEW_FOLLOW;
82    }
83
84    pub fn set_tool_preview_max_scroll(&mut self, max_scroll: usize) {
85        self.tool_preview_last_max_scroll = max_scroll;
86        self.tool_preview_scroll = if self.tool_preview_scroll >= TOOL_PREVIEW_FOLLOW {
87            max_scroll
88        } else {
89            self.tool_preview_scroll.min(max_scroll)
90        };
91    }
92}