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