1use crate::_private::NonExhaustive;
2use crate::derive::derive_table_widget_state;
3use crate::edit::{Mode, TableEditorState};
4use crate::selection::RowSelection;
5use crate::table::TableState;
6use rat_cursor::HasScreenCursor;
7use rat_event::crossterm::event::Event;
8use rat_event::util::MouseFlags;
9use rat_event::{HandleEvent, Outcome, Regular, ct_event, event_flow};
10use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
11use rat_reloc::RelocatableState;
12use ratatui_core::layout::Rect;
13
14pub struct TableEditState<T, E> {
28 pub mode: Mode,
30 pub table: TableState<RowSelection>,
32 pub editor: E,
34 pub data: Vec<T>,
36
37 pub mouse: MouseFlags,
38
39 pub non_exhaustive: NonExhaustive,
40}
41
42impl<T, E> Default for TableEditState<T, E>
43where
44 E: Default,
45{
46 fn default() -> Self {
47 Self {
48 mode: Default::default(),
49 table: Default::default(),
50 editor: Default::default(),
51 data: Default::default(),
52 mouse: Default::default(),
53 non_exhaustive: NonExhaustive,
54 }
55 }
56}
57
58impl<T, E> HasFocus for TableEditState<T, E> {
59 fn build(&self, builder: &mut FocusBuilder) {
60 builder.leaf_widget(self);
61 }
62
63 fn focus(&self) -> FocusFlag {
64 self.table.focus.clone()
65 }
66
67 fn area(&self) -> Rect {
68 self.table.area
69 }
70
71 fn navigable(&self) -> Navigation {
72 match self.mode {
73 Mode::View => self.table.focus.navigable(),
74 Mode::Edit | Mode::Insert => Navigation::Lock,
75 }
76 }
77}
78
79impl<T, E> HasScreenCursor for TableEditState<T, E>
80where
81 E: HasScreenCursor,
82{
83 fn screen_cursor(&self) -> Option<(u16, u16)> {
84 match self.mode {
85 Mode::View => None,
86 Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
87 }
88 }
89}
90
91impl<T, E> RelocatableState for TableEditState<T, E>
92where
93 E: RelocatableState,
94{
95 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
96 self.table.relocate(shift, clip);
97 match self.mode {
98 Mode::View => {}
99 Mode::Edit | Mode::Insert => {
100 self.editor.relocate(shift, clip);
101 }
102 }
103 }
104}
105
106impl<T, E> TableEditState<T, E> {
107 pub fn new(editor: E) -> Self {
108 Self {
109 mode: Mode::View,
110 table: Default::default(),
111 editor,
112 data: Default::default(),
113 mouse: Default::default(),
114 non_exhaustive: NonExhaustive,
115 }
116 }
117
118 pub fn named(name: &str, editor: E) -> Self {
119 Self {
120 mode: Mode::View,
121 table: TableState::named(name),
122 editor,
123 data: Default::default(),
124 mouse: Default::default(),
125 non_exhaustive: NonExhaustive,
126 }
127 }
128
129 pub fn with(mut self, data: impl IntoIterator<Item = T>) -> Self {
131 self.data.extend(data.into_iter());
132 self
133 }
134}
135
136impl<T, E> TableEditState<T, E> {
137 derive_table_widget_state!(BASE);
138}
139
140impl<T, E> TableEditState<T, E> {
141 derive_table_widget_state!(AREAS);
142}
143
144impl<T, E> TableEditState<T, E> {
145 derive_table_widget_state!(OFFSET);
146}
147
148impl<T, E> TableEditState<T, E> {
149 derive_table_widget_state!(SELECTION);
150}
151
152impl<T, E> TableEditState<T, E> {
153 derive_table_widget_state!(ROWSELECTION);
154}
155
156impl<T, E> TableEditState<T, E>
157where
158 E: TableEditorState<T>,
159 T: Clone,
160{
161 pub fn set_value(&mut self, data: Vec<T>) {
163 self.data = data;
164 self.table.rows_changed(self.data.len());
165 }
166
167 pub fn value(&self) -> &Vec<T> {
173 &self.data
174 }
175
176 pub fn clear(&mut self) {
178 self.mode = Mode::View;
179 self.table.clear_offset();
180 self.table.clear_selection();
181 self.data.clear();
182 }
183
184 pub fn is_editing(&self) -> bool {
186 self.mode == Mode::Edit || self.mode == Mode::Insert
187 }
188
189 pub fn is_insert(&self) -> bool {
191 self.mode == Mode::Insert
192 }
193
194 pub fn remove(&mut self, row: usize) {
196 if self.mode != Mode::View {
197 return;
198 }
199 if row < self.data.len() {
200 self.data.remove(row);
201 self.table.items_removed(row, 1);
202 if !self.table.scroll_to_row(row) {
203 self.table.scroll_to_row(row.saturating_sub(1));
204 }
205 }
206 }
207
208 pub fn edit_new<'a>(&mut self, row: usize, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
210 if self.mode != Mode::View {
211 return Ok(());
212 }
213
214 let value = self.editor.create_value(ctx)?;
215 self.editor.set_value(value.clone(), ctx)?;
216 self._start(None, row, Mode::Insert, ctx)?;
217
218 self.data.insert(row, value);
219 self.table.items_added(row, 1);
220
221 Ok(())
222 }
223
224 pub fn edit<'a>(
227 &mut self,
228 col: Option<usize>,
229 row: usize,
230 ctx: &'a E::Context<'a>,
231 ) -> Result<(), E::Err> {
232 if self.mode != Mode::View {
233 return Ok(());
234 }
235
236 let value = &self.data[row];
237 self.editor.set_value(value.clone(), ctx)?;
238 self._start(col, row, Mode::Edit, ctx)?;
239
240 Ok(())
241 }
242
243 pub fn cancel<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
246 if self.mode == Mode::View {
247 return Ok(());
248 }
249 let Some(row) = self.table.selected_checked() else {
250 return Ok(());
251 };
252
253 self._stop(ctx)?;
254
255 if self.mode == Mode::Insert {
256 self.data.remove(row);
257 self.table.items_removed(row, 1);
258 }
259
260 Ok(())
261 }
262
263 pub fn commit<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
265 if self.mode == Mode::View {
266 return Ok(());
267 }
268 let Some(row) = self.table.selected_checked() else {
269 return Ok(());
270 };
271
272 self._stop(ctx)?;
273
274 let value = self.editor.value(ctx)?;
275 if let Some(value) = value {
276 self.data[row] = value;
277 } else {
278 self.data.remove(row);
279 self.table.items_removed(row, 1);
280 }
281
282 Ok(())
283 }
284
285 pub fn commit_and_append<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
289 self.commit(ctx)?;
290 if let Some(row) = self.table.selected_checked() {
291 self.edit_new(row + 1, ctx)?;
292 }
293 Ok(())
294 }
295
296 pub fn commit_and_edit<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
300 let Some(row) = self.table.selected_checked() else {
301 return Ok(());
302 };
303
304 self.commit(ctx)?;
305 if row + 1 < self.data.len() {
306 self.table.select(Some(row + 1));
307 self.edit(None, row + 1, ctx)?;
308 }
309 Ok(())
310 }
311
312 fn _start<'a>(
313 &mut self,
314 col: Option<usize>,
315 row: usize,
316 mode: Mode,
317 ctx: &'a E::Context<'a>,
318 ) -> Result<(), E::Err> {
319 self.mode = mode;
320 self.table.move_to(row);
321 self.editor.start(col, ctx)?;
322 if let Some(col) = self.editor.focused_col() {
323 self.table.scroll_to_col(col);
324 } else {
325 self.table.scroll_to_col(0);
326 }
327 Ok(())
328 }
329
330 fn _stop<'a>(&mut self, ctx: &'a E::Context<'a>) -> Result<(), E::Err> {
331 self.mode = Mode::View;
332 self.editor.stop(ctx)?;
333 self.table.scroll_to_col(0);
334 Ok(())
335 }
336}
337
338impl<'a, T, S> HandleEvent<Event, &'a S::Context<'a>, Result<Outcome, S::Err>>
339 for TableEditState<T, S>
340where
341 S: HandleEvent<Event, &'a S::Context<'a>, Result<Outcome, S::Err>>,
342 S: TableEditorState<T>,
343 T: Clone,
344{
345 #[allow(clippy::collapsible_if)]
346 fn handle(&mut self, event: &Event, ctx: &'a S::Context<'a>) -> Result<Outcome, S::Err> {
347 if self.mode == Mode::Edit || self.mode == Mode::Insert {
348 if self.is_focused() {
349 event_flow!({
350 let r = self.editor.handle(event, ctx)?;
351 if let Some(col) = self.editor.focused_col() {
352 if self.table.scroll_to_col(col) {
353 Outcome::Changed
354 } else {
355 r
356 }
357 } else {
358 r
359 }
360 });
361
362 event_flow!(match event {
363 ct_event!(keycode press Esc) => {
364 self.cancel(ctx)?;
365 Outcome::Changed
366 }
367 ct_event!(keycode press Enter) => {
368 if self.table.selected_checked() < Some(self.table.rows().saturating_sub(1))
369 {
370 self.commit_and_edit(ctx)?;
371 Outcome::Changed
372 } else {
373 self.commit_and_append(ctx)?;
374 Outcome::Changed
375 }
376 }
377 ct_event!(keycode press Up) => {
378 self.commit(ctx)?;
379 if self.data.is_empty() {
380 self.edit_new(0, ctx)?;
381 } else if let Some(row) = self.table.selected_checked()
382 && row > 0
383 {
384 self.table.select(Some(row - 1));
385 }
386 Outcome::Changed
387 }
388 ct_event!(keycode press Down) => {
389 self.commit(ctx)?;
390 if self.data.is_empty() {
391 self.edit_new(0, ctx)?;
392 } else if let Some(row) = self.table.selected_checked()
393 && row + 1 < self.data.len()
394 {
395 self.table.select(Some(row + 1));
396 }
397 Outcome::Changed
398 }
399 _ => Outcome::Continue,
400 });
401 }
402
403 Ok(Outcome::Continue)
404 } else {
405 if self.table.focus.gained_focus() {
406 if self.data.is_empty() {
407 self.edit_new(0, ctx)?;
408 }
409 }
410
411 event_flow!(match event {
412 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table.table_area, m) => {
413 if let Some((col, row)) = self.table.cell_at_clicked((m.column, m.row)) {
414 self.edit(Some(col), row, ctx)?;
415 Outcome::Changed
416 } else {
417 Outcome::Continue
418 }
419 }
420 _ => Outcome::Continue,
421 });
422
423 if self.is_focused() {
424 event_flow!(match event {
425 ct_event!(keycode press Insert) => {
426 if let Some(row) = self.table.selected() {
427 self.edit_new(row, ctx)?;
428 }
429 Outcome::Changed
430 }
431 ct_event!(keycode press Delete) => {
432 if let Some(row) = self.table.selected_checked() {
433 self.remove(row);
434 if self.data.is_empty() {
435 self.edit_new(0, ctx)?;
436 }
437 }
438 Outcome::Changed
439 }
440 ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
441 if let Some(row) = self.table.selected_checked() {
442 self.edit(None, row, ctx)?;
443 Outcome::Changed
444 } else if self.table.rows() == 0 {
445 self.edit_new(0, ctx)?;
446 Outcome::Changed
447 } else {
448 Outcome::Continue
449 }
450 }
451 ct_event!(keycode press Down) => {
452 if let Some(row) = self.table.selected() {
453 if row == self.table.rows().saturating_sub(1) {
454 self.edit_new(row + 1, ctx)?;
455 Outcome::Changed
456 } else {
457 Outcome::Continue
458 }
459 } else if self.table.rows() == 0 {
460 self.edit_new(0, ctx)?;
461 Outcome::Changed
462 } else {
463 Outcome::Continue
464 }
465 }
466 _ => {
467 Outcome::Continue
468 }
469 });
470 }
471
472 event_flow!(self.table.handle(event, Regular));
473
474 Ok(Outcome::Continue)
475 }
476 }
477}