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