oli_server/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        // Calculate the max position (content size - viewport size)
90        self.position = self.max_scroll();
91    }
92
93    /// Page up (scroll up by viewport height)
94    pub fn page_up(&mut self) {
95        self.scroll_up(self.viewport_size.saturating_sub(1).max(1));
96    }
97
98    /// Page down (scroll down by viewport height)
99    pub fn page_down(&mut self) {
100        self.scroll_down(self.viewport_size.saturating_sub(1).max(1));
101    }
102
103    /// Determine if we need to show the "more above" indicator
104    pub fn has_more_above(&self) -> bool {
105        self.position > 0
106    }
107
108    /// Determine if we need to show the "more below" indicator
109    pub fn has_more_below(&self) -> bool {
110        self.position < self.max_scroll()
111    }
112}
113
114/// Interface for scrollable components
115pub trait Scrollable {
116    /// Get a mutable reference to the message scroll state
117    fn message_scroll_state(&mut self) -> &mut ScrollState;
118
119    /// Get a mutable reference to the task scroll state
120    fn task_scroll_state(&mut self) -> &mut ScrollState;
121
122    /// Scroll message view up by amount
123    fn scroll_up(&mut self, amount: usize) {
124        self.message_scroll_state().scroll_up(amount);
125    }
126
127    /// Scroll message view down by amount
128    fn scroll_down(&mut self, amount: usize) {
129        self.message_scroll_state().scroll_down(amount);
130    }
131
132    /// Auto scroll messages to bottom
133    fn auto_scroll_to_bottom(&mut self) {
134        self.message_scroll_state().scroll_to_bottom();
135    }
136
137    /// Scroll task list up by amount
138    fn scroll_tasks_up(&mut self, amount: usize) {
139        self.task_scroll_state().scroll_up(amount);
140    }
141
142    /// Scroll task list down by amount
143    fn scroll_tasks_down(&mut self, amount: usize) {
144        self.task_scroll_state().scroll_down(amount);
145    }
146}
147
148// Error handling utilities
149pub trait ErrorHandler {
150    fn handle_error(&mut self, message: String);
151}