oli_tui/app/
utils.rs

1// Utility functions for the App
2
3/// A scrollable state for managing UI scrolling and positioning
4#[derive(Debug, Clone, Default)]
5pub struct ScrollState {
6    /// Current scroll position (0 = top of content)
7    pub position: usize,
8    /// Flag indicating if content should auto-scroll to bottom on new content
9    pub follow_bottom: bool,
10    /// Total content size (in lines) - updated by each render cycle
11    pub content_size: usize,
12    /// Visible area size (in lines) - updated by each render cycle
13    pub viewport_size: usize,
14}
15
16impl ScrollState {
17    /// Create a new scroll state
18    pub fn new() -> Self {
19        Self {
20            position: 0,
21            follow_bottom: true,
22            content_size: 0,
23            viewport_size: 0,
24        }
25    }
26
27    /// Update the content and viewport sizes
28    pub fn update_dimensions(&mut self, content_size: usize, viewport_size: usize) {
29        self.content_size = content_size;
30        self.viewport_size = viewport_size;
31
32        // If we're following the bottom, update position
33        if self.follow_bottom {
34            self.scroll_to_bottom();
35        } else {
36            // Ensure position is still valid after update
37            self.clamp_position();
38        }
39    }
40
41    /// Get the maximum valid scroll position
42    pub fn max_scroll(&self) -> usize {
43        self.content_size.saturating_sub(self.viewport_size)
44    }
45
46    /// Ensure scroll position is within valid bounds
47    pub fn clamp_position(&mut self) {
48        let max = self.max_scroll();
49        if self.position > max {
50            self.position = max;
51        }
52    }
53
54    /// Scroll down by the specified amount
55    pub fn scroll_down(&mut self, amount: usize) {
56        let max = self.max_scroll();
57
58        // If we're already at max scroll, just turn on follow
59        if self.position >= max {
60            self.follow_bottom = true;
61            return;
62        }
63
64        // Calculate new position without going beyond max
65        self.position = (self.position + amount).min(max);
66
67        // If we've scrolled to the bottom, enable follow
68        self.follow_bottom = self.position >= max;
69    }
70
71    /// Scroll up by the specified amount
72    pub fn scroll_up(&mut self, amount: usize) {
73        // Whenever we scroll up, we disable following
74        self.follow_bottom = false;
75
76        // Don't underflow below 0
77        self.position = self.position.saturating_sub(amount);
78    }
79
80    /// Scroll to the top of the content
81    pub fn scroll_to_top(&mut self) {
82        self.follow_bottom = false;
83        self.position = 0;
84    }
85
86    /// Scroll to the bottom of the content
87    pub fn scroll_to_bottom(&mut self) {
88        self.follow_bottom = true;
89        self.position = self.max_scroll();
90    }
91
92    /// Page up (scroll up by viewport height)
93    pub fn page_up(&mut self) {
94        self.scroll_up(self.viewport_size.saturating_sub(1).max(1));
95    }
96
97    /// Page down (scroll down by viewport height)
98    pub fn page_down(&mut self) {
99        self.scroll_down(self.viewport_size.saturating_sub(1).max(1));
100    }
101
102    /// Determine if we need to show the "more above" indicator
103    pub fn has_more_above(&self) -> bool {
104        self.position > 0
105    }
106
107    /// Determine if we need to show the "more below" indicator
108    pub fn has_more_below(&self) -> bool {
109        self.position < self.max_scroll()
110    }
111}
112
113/// Interface for scrollable components
114pub trait Scrollable {
115    /// Get a mutable reference to the message scroll state
116    fn message_scroll_state(&mut self) -> &mut ScrollState;
117
118    /// Get a mutable reference to the task scroll state
119    fn task_scroll_state(&mut self) -> &mut ScrollState;
120
121    /// Scroll message view up by amount
122    fn scroll_up(&mut self, amount: usize) {
123        self.message_scroll_state().scroll_up(amount);
124    }
125
126    /// Scroll message view down by amount
127    fn scroll_down(&mut self, amount: usize) {
128        self.message_scroll_state().scroll_down(amount);
129    }
130
131    /// Auto scroll messages to bottom
132    fn auto_scroll_to_bottom(&mut self) {
133        self.message_scroll_state().scroll_to_bottom();
134    }
135
136    /// Scroll task list up by amount
137    fn scroll_tasks_up(&mut self, amount: usize) {
138        self.task_scroll_state().scroll_up(amount);
139    }
140
141    /// Scroll task list down by amount
142    fn scroll_tasks_down(&mut self, amount: usize) {
143        self.task_scroll_state().scroll_down(amount);
144    }
145}
146
147// Error handling utilities
148pub trait ErrorHandler {
149    fn handle_error(&mut self, message: String);
150}