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