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