sericom_core/screen_buffer/
ui_command.rs1use crate::screen_buffer::Position;
2
3use super::{Cursor, Line, ScreenBuffer};
4
5#[non_exhaustive]
7#[derive(Clone, Debug)]
8pub enum UICommand {
9 ScrollUp(usize),
11 ScrollDown(usize),
13 ScrollBottom,
15 ScrollTop,
17 StartSelection(Position),
19 UpdateSelection(Position),
21 CopySelection,
23 ClearBuffer,
25}
26
27pub(crate) trait UIAction {
28 fn scroll_up(&mut self, lines: usize);
29 fn scroll_down(&mut self, lines: usize);
30 fn scroll_to_bottom(&mut self);
31 fn scroll_to_top(&mut self);
32 fn start_selection(&mut self, pos: Position);
33 fn update_selection(&mut self, pos: Position);
34 fn clear_selection(&mut self);
35 fn copy_to_clipboard(&mut self) -> std::io::Result<()>;
36 fn clear_buffer(&mut self);
37 fn clear_screen(&mut self);
38}
39
40impl UIAction for ScreenBuffer {
41 fn scroll_up(&mut self, lines: usize) {
43 if self.view_start >= lines {
44 self.view_start -= lines;
45 } else {
46 self.view_start = 0;
47 }
48 self.clear_selection();
49 self.needs_render = true;
50 }
51
52 fn scroll_down(&mut self, lines: usize) {
54 let max_view_start = self.lines.len().saturating_sub(self.height as usize);
55 self.view_start = (self.view_start + lines).min(max_view_start);
56 self.clear_selection();
57 self.needs_render = true;
58 }
59
60 fn scroll_to_bottom(&mut self) {
63 self.view_start = self.lines.len().saturating_sub(self.height as usize);
64 self.needs_render = true;
65 }
66
67 fn scroll_to_top(&mut self) {
69 self.view_start = 0;
70 self.needs_render = true;
71 }
72
73 fn start_selection(&mut self, pos: Position) {
77 let absolute_line = self.view_start + pos.y;
78 self.clear_selection();
79 self.selection_start = Some((pos.x, absolute_line));
80 self.needs_render = true;
81 }
82
83 fn update_selection(&mut self, pos: Position) {
86 let absolute_line = self.view_start + pos.y;
87 self.selection_end = Some((pos.x, absolute_line));
88 self.update_selection_highlighting();
89 self.needs_render = true;
90 }
91
92 fn clear_selection(&mut self) {
94 for line in &mut self.lines {
95 line.clear_selection();
96 }
97 self.selection_start = None;
98 self.selection_end = None;
99 self.needs_render = true;
100 }
101
102 fn copy_to_clipboard(&mut self) -> std::io::Result<()> {
104 use crossterm::{clipboard, execute};
105
106 let selected_text = self.get_selected_text();
107 if !selected_text.is_empty() {
108 execute!(
109 std::io::stdout(),
110 clipboard::CopyToClipboard::to_clipboard_from(selected_text)
111 )?;
112 }
113 self.clear_selection();
114 Ok(())
115 }
116
117 fn clear_buffer(&mut self) {
121 self.lines.clear();
122 self.view_start = 0;
123 self.set_cursor_pos((0_u16, 0_usize));
124 self.lines.push_back(Line::new(self.width as usize));
125 self.needs_render = true;
126 }
127
128 fn clear_screen(&mut self) {
130 for _ in 0..self.height {
131 self.lines.push_back(Line::new(self.width as usize));
132 }
133 self.view_start = self.lines.len().saturating_sub(self.height as usize);
134 self.needs_render = true;
135 }
136}
137
138impl ScreenBuffer {
139 fn update_selection_highlighting(&mut self) {
140 for line in &mut self.lines {
141 line.clear_selection();
142 }
143
144 if let (Some((start_x, start_line)), Some((end_x, end_line))) =
145 (self.selection_start, self.selection_end)
146 {
147 let (start_line, start_x, end_line, end_x) =
148 if start_line < end_line || (start_line == end_line && start_x <= end_x) {
150 (start_line, start_x, end_line, end_x)
151 } else {
152 (end_line, end_x, start_line, start_x)
153 };
154
155 for line_idx in start_line..=end_line {
156 if let Some(line) = self.lines.get_mut(line_idx) {
157 let line_start_x = if line_idx == start_line { start_x } else { 0 };
158 let line_end_x = if line_idx == end_line {
159 end_x
160 } else {
161 self.width - 1
162 };
163
164 for x in line_start_x..=line_end_x.min(self.width - 1) {
165 if let Some(cell) = line.get_mut_cell(x as usize) {
166 cell.is_selected = true;
167 }
168 }
169 }
170 }
171 }
172 }
173
174 fn get_selected_text(&self) -> String {
175 if let (Some((start_x, start_line)), Some((end_x, end_line))) =
176 (self.selection_start, self.selection_end)
177 {
178 let (start_line, start_x, end_line, end_x) =
179 if start_line < end_line || (start_line == end_line && start_x <= end_x) {
180 (start_line, start_x, end_line, end_x)
181 } else {
182 (end_line, end_x, start_line, start_x)
183 };
184
185 let mut result = String::new();
186
187 for line_idx in start_line..=end_line {
188 if let Some(line) = self.lines.get(line_idx) {
189 let line_start_x = if line_idx == start_line { start_x } else { 0 };
190 let line_end_x = if line_idx == end_line {
191 end_x
192 } else {
193 self.width - 1
194 };
195
196 for x in line_start_x..=line_end_x.min(self.width - 1) {
197 if let Some(cell) = line.get_cell(x as usize) {
198 result.push(cell.character);
199 }
200 }
201
202 if line_idx < end_line {
203 result.push('\n');
204 }
205 }
206 }
207
208 result.trim_end().to_string()
209 } else {
210 String::new()
211 }
212 }
213}