rat_ftable/
rowselection.rs1use crate::event::TableOutcome;
2use crate::{TableSelection, TableState};
3use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
4use rat_focus::HasFocus;
5use rat_scrolled::ScrollAreaState;
6use rat_scrolled::event::ScrollOutcome;
7use ratatui_crossterm::crossterm::event::Event;
8use std::cmp::{max, min};
9
10#[derive(Debug, Default, Clone)]
17pub struct RowSelection {
18 pub lead_row: Option<usize>,
20 pub scroll_selected: bool,
22}
23
24impl TableSelection for RowSelection {
25 fn count(&self) -> usize {
26 if self.lead_row.is_some() { 1 } else { 0 }
27 }
28
29 fn is_selected_row(&self, row: usize) -> bool {
30 self.lead_row == Some(row)
31 }
32
33 fn is_selected_column(&self, _column: usize) -> bool {
34 false
35 }
36
37 fn is_selected_cell(&self, _column: usize, _row: usize) -> bool {
38 false
39 }
40
41 fn lead_selection(&self) -> Option<(usize, usize)> {
42 self.lead_row.map(|v| (0, v))
43 }
44
45 fn validate_rows(&mut self, rows: usize) {
46 if let Some(lead_row) = self.lead_row {
47 if rows == 0 {
48 self.lead_row = None;
49 } else if lead_row >= rows {
50 self.lead_row = Some(rows - 1);
51 }
52 }
53 }
54
55 fn validate_cols(&mut self, _cols: usize) {}
56
57 #[allow(clippy::collapsible_if)]
59 fn items_added(&mut self, pos: usize, n: usize) {
60 if let Some(lead_row) = self.lead_row {
61 if lead_row > pos {
62 self.lead_row = Some(lead_row.saturating_add(n));
63 }
64 }
65 }
66
67 fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {
69 if let Some(lead_row) = self.lead_row {
70 if rows == 0 {
71 self.lead_row = None;
72 } else if lead_row == pos && lead_row + n >= rows {
73 self.lead_row = Some(rows.saturating_sub(1))
74 } else if lead_row > pos {
75 self.lead_row = Some(lead_row.saturating_sub(n).min(pos));
76 }
77 }
78 }
79}
80
81impl RowSelection {
82 pub fn new() -> RowSelection {
84 Self::default()
85 }
86
87 pub fn clear(&mut self) {
89 self.lead_row = None;
90 }
91
92 pub fn scroll_selected(&self) -> bool {
94 self.scroll_selected
95 }
96
97 pub fn set_scroll_selected(&mut self, scroll: bool) {
99 self.scroll_selected = scroll;
100 }
101
102 pub fn selected(&self) -> Option<usize> {
104 self.lead_row
105 }
106
107 pub fn has_selection(&self) -> bool {
109 self.lead_row.is_some()
110 }
111
112 pub fn select(&mut self, select: Option<usize>) -> bool {
115 let old_row = self.lead_row;
116 self.lead_row = select;
117 old_row != self.lead_row
118 }
119
120 pub fn move_to(&mut self, select: usize, maximum: usize) -> bool {
122 let old_row = self.lead_row;
123 self.lead_row = Some(min(select, maximum));
124 old_row != self.lead_row
125 }
126
127 pub fn move_down(&mut self, n: usize, maximum: usize) -> bool {
129 let old_row = self.lead_row;
130 self.lead_row = Some(self.lead_row.map_or(0, |v| min(v + n, maximum)));
131 old_row != self.lead_row
132 }
133
134 pub fn move_up(&mut self, n: usize, maximum: usize) -> bool {
136 let old_row = self.lead_row;
137 self.lead_row = Some(self.lead_row.map_or(maximum, |v| v.saturating_sub(n)));
138 old_row != self.lead_row
139 }
140}
141
142impl HandleEvent<Event, Regular, TableOutcome> for TableState<RowSelection> {
143 fn handle(&mut self, event: &Event, _keymap: Regular) -> TableOutcome {
144 let res = if self.is_focused() {
145 match event {
146 ct_event!(keycode press Up) => {
147 if self.move_up(1) {
148 TableOutcome::Selected
149 } else {
150 TableOutcome::Unchanged
151 }
152 }
153 ct_event!(keycode press Down) => {
154 if self.move_down(1) {
155 TableOutcome::Selected
156 } else {
157 TableOutcome::Unchanged
158 }
159 }
160 ct_event!(keycode press CONTROL-Up)
161 | ct_event!(keycode press CONTROL-Home)
162 | ct_event!(keycode press Home) => {
163 if self.move_to(0) {
164 TableOutcome::Selected
165 } else {
166 TableOutcome::Unchanged
167 }
168 }
169 ct_event!(keycode press CONTROL-Down)
170 | ct_event!(keycode press CONTROL-End)
171 | ct_event!(keycode press End) => {
172 if self.move_to(self.rows.saturating_sub(1)) {
173 TableOutcome::Selected
174 } else {
175 TableOutcome::Unchanged
176 }
177 }
178 ct_event!(keycode press PageUp) => {
179 if self.move_up(max(1, self.page_len().saturating_sub(1))) {
180 TableOutcome::Selected
181 } else {
182 TableOutcome::Unchanged
183 }
184 }
185 ct_event!(keycode press PageDown) => {
186 if self.move_down(max(1, self.page_len().saturating_sub(1))) {
187 TableOutcome::Selected
188 } else {
189 TableOutcome::Unchanged
190 }
191 }
192 ct_event!(keycode press Left) => {
193 if self.scroll_left(1) {
194 TableOutcome::Changed
195 } else {
196 TableOutcome::Unchanged
197 }
198 }
199 ct_event!(keycode press Right) => {
200 if self.scroll_right(1) {
201 TableOutcome::Changed
202 } else {
203 TableOutcome::Unchanged
204 }
205 }
206 ct_event!(keycode press CONTROL-Left) => {
207 if self.scroll_to_x(0) {
208 TableOutcome::Changed
209 } else {
210 TableOutcome::Unchanged
211 }
212 }
213 ct_event!(keycode press CONTROL-Right) => {
214 if self.scroll_to_x(self.x_max_offset()) {
215 TableOutcome::Changed
216 } else {
217 TableOutcome::Unchanged
218 }
219 }
220 _ => TableOutcome::Continue,
221 }
222 } else {
223 TableOutcome::Continue
224 };
225
226 if res == TableOutcome::Continue {
227 self.handle(event, MouseOnly)
228 } else {
229 res
230 }
231 }
232}
233
234impl HandleEvent<Event, MouseOnly, TableOutcome> for TableState<RowSelection> {
235 fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> TableOutcome {
236 if !self.has_mouse_focus() {
237 return TableOutcome::Continue;
238 }
239
240 let mut r = match event {
241 ct_event!(mouse any for m) if self.mouse.drag(self.table_area, m) => {
242 if self.move_to(self.row_at_drag((m.column, m.row))) {
243 TableOutcome::Selected
244 } else {
245 TableOutcome::Unchanged
246 }
247 }
248 ct_event!(mouse down Left for column, row) => {
249 if self.table_area.contains((*column, *row).into()) {
250 if let Some(new_row) = self.row_at_clicked((*column, *row)) {
251 if self.move_to(new_row) {
252 TableOutcome::Selected
253 } else {
254 TableOutcome::Unchanged
255 }
256 } else {
257 TableOutcome::Continue
258 }
259 } else {
260 TableOutcome::Continue
261 }
262 }
263
264 _ => TableOutcome::Continue,
265 };
266
267 r = r.or_else(|| {
268 let mut sas = ScrollAreaState::new()
269 .area(self.inner)
270 .h_scroll(&mut self.hscroll)
271 .v_scroll(&mut self.vscroll);
272 match sas.handle(event, MouseOnly) {
273 ScrollOutcome::Up(v) => {
274 if self.selection.scroll_selected() {
275 if self.move_up(1) {
276 TableOutcome::Selected
277 } else {
278 TableOutcome::Unchanged
279 }
280 } else {
281 if self.scroll_up(v) {
282 TableOutcome::Changed
283 } else {
284 TableOutcome::Unchanged
285 }
286 }
287 }
288 ScrollOutcome::Down(v) => {
289 if self.selection.scroll_selected() {
290 if self.move_down(1) {
291 TableOutcome::Selected
292 } else {
293 TableOutcome::Unchanged
294 }
295 } else {
296 if self.scroll_down(v) {
297 TableOutcome::Changed
298 } else {
299 TableOutcome::Unchanged
300 }
301 }
302 }
303 ScrollOutcome::VPos(v) => {
304 if self.selection.scroll_selected {
305 if self.move_to(self.remap_offset_selection(v)) {
306 TableOutcome::Selected
307 } else {
308 TableOutcome::Unchanged
309 }
310 } else {
311 if self.set_row_offset(self.vscroll.limited_offset(v)) {
312 TableOutcome::Changed
313 } else {
314 TableOutcome::Unchanged
315 }
316 }
317 }
318 ScrollOutcome::Left(v) => {
319 if self.scroll_left(v) {
320 TableOutcome::Changed
321 } else {
322 TableOutcome::Unchanged
323 }
324 }
325 ScrollOutcome::Right(v) => {
326 if self.scroll_right(v) {
327 TableOutcome::Changed
328 } else {
329 TableOutcome::Unchanged
330 }
331 }
332 ScrollOutcome::HPos(v) => {
333 if self.set_x_offset(self.hscroll.limited_offset(v)) {
334 TableOutcome::Changed
335 } else {
336 TableOutcome::Unchanged
337 }
338 }
339
340 ScrollOutcome::Continue => TableOutcome::Continue,
341 ScrollOutcome::Unchanged => TableOutcome::Unchanged,
342 ScrollOutcome::Changed => TableOutcome::Changed,
343 }
344 });
345
346 r
347 }
348}
349
350pub fn handle_events(
354 state: &mut TableState<RowSelection>,
355 focus: bool,
356 event: &Event,
357) -> TableOutcome {
358 state.focus.set(focus);
359 state.handle(event, Regular)
360}
361
362pub fn handle_mouse_events(state: &mut TableState<RowSelection>, event: &Event) -> TableOutcome {
364 state.handle(event, MouseOnly)
365}