1use crate::edit::{Mode, TableEditor, TableEditorState};
12use crate::rowselection::RowSelection;
13use crate::util::clear_buf_area;
14use crate::{Table, TableState};
15use log::warn;
16use rat_cursor::HasScreenCursor;
17use rat_event::util::MouseFlags;
18use rat_event::{HandleEvent, Outcome, Regular, ct_event, event_flow, try_flow};
19use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
20use rat_reloc::RelocatableState;
21use ratatui::buffer::Buffer;
22use ratatui::layout::Rect;
23use ratatui::widgets::StatefulWidget;
24use std::fmt::{Debug, Formatter};
25
26#[allow(clippy::type_complexity)]
33pub struct EditableTableVec<'a, E>
34where
35 E: TableEditor + 'a,
36{
37 table: Box<
38 dyn for<'b> Fn(
39 &'b [<<E as TableEditor>::State as TableEditorState>::Value],
40 ) -> Table<'b, RowSelection>
41 + 'a,
42 >,
43 editor: E,
44}
45
46pub struct EditableTableVecState<S>
52where
53 S: TableEditorState,
54{
55 pub mode: Mode,
57
58 pub table: TableState<RowSelection>,
60 pub editor: S,
62
63 pub data: Vec<S::Value>,
65
66 pub mouse: MouseFlags,
67}
68
69impl<'a, E> EditableTableVec<'a, E>
70where
71 E: TableEditor + 'a,
72{
73 pub fn new(
81 table: impl for<'b> Fn(
82 &'b [<<E as TableEditor>::State as TableEditorState>::Value],
83 ) -> Table<'b, RowSelection>
84 + 'a,
85 editor: E,
86 ) -> Self {
87 Self {
88 table: Box::new(table),
89 editor,
90 }
91 }
92}
93
94impl<'a, E> Debug for EditableTableVec<'a, E>
95where
96 E: Debug,
97 E: TableEditor + 'a,
98{
99 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100 f.debug_struct("EditVec")
101 .field("table", &"..dyn..")
102 .field("editor", &self.editor)
103 .finish()
104 }
105}
106
107impl<'a, E> StatefulWidget for EditableTableVec<'a, E>
108where
109 E: TableEditor + 'a,
110{
111 type State = EditableTableVecState<E::State>;
112
113 #[allow(clippy::collapsible_else_if)]
114 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
115 let table = (self.table)(&state.data);
116 table.render(area, buf, &mut state.table);
117
118 if state.mode == Mode::Insert || state.mode == Mode::Edit {
119 if let Some(row) = state.table.selected() {
120 if let Some((row_area, cell_areas)) = state.table.row_cells(row) {
122 clear_buf_area(row_area, buf);
123 self.editor
124 .render(row_area, &cell_areas, buf, &mut state.editor);
125 }
126 } else {
127 if cfg!(feature = "perf_warnings") {
128 warn!("no row selection, not rendering editor");
129 }
130 }
131 }
132 }
133}
134
135impl<S> Default for EditableTableVecState<S>
136where
137 S: TableEditorState + Default,
138{
139 fn default() -> Self {
140 Self {
141 mode: Mode::View,
142 table: Default::default(),
143 editor: S::default(),
144 data: Vec::default(),
145 mouse: Default::default(),
146 }
147 }
148}
149
150impl<S> Debug for EditableTableVecState<S>
151where
152 S: TableEditorState + Debug,
153 S::Value: Debug,
154{
155 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156 f.debug_struct("EditVecState")
157 .field("mode", &self.mode)
158 .field("table", &self.table)
159 .field("editor", &self.editor)
160 .field("editor_data", &self.data)
161 .field("mouse", &self.mouse)
162 .finish()
163 }
164}
165
166impl<S> HasFocus for EditableTableVecState<S>
167where
168 S: TableEditorState,
169{
170 fn build(&self, builder: &mut FocusBuilder) {
171 builder.leaf_widget(self);
172 }
173
174 fn focus(&self) -> FocusFlag {
175 self.table.focus()
176 }
177
178 fn area(&self) -> Rect {
179 self.table.area()
180 }
181
182 fn navigable(&self) -> Navigation {
183 match self.mode {
184 Mode::View => self.table.navigable(),
185 Mode::Edit | Mode::Insert => Navigation::Lock,
186 }
187 }
188
189 fn is_focused(&self) -> bool {
190 self.table.is_focused()
191 }
192
193 fn lost_focus(&self) -> bool {
194 self.table.lost_focus()
195 }
196
197 fn gained_focus(&self) -> bool {
198 self.table.gained_focus()
199 }
200}
201
202impl<S> HasScreenCursor for EditableTableVecState<S>
203where
204 S: TableEditorState + HasScreenCursor,
205{
206 fn screen_cursor(&self) -> Option<(u16, u16)> {
207 match self.mode {
208 Mode::View => None,
209 Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
210 }
211 }
212}
213
214impl<S> RelocatableState for EditableTableVecState<S>
215where
216 S: TableEditorState + RelocatableState,
217{
218 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
219 match self.mode {
220 Mode::View => {}
221 Mode::Edit | Mode::Insert => {
222 self.editor.relocate(shift, clip);
223 }
224 }
225 }
226}
227
228impl<S> EditableTableVecState<S>
229where
230 S: TableEditorState,
231{
232 pub fn new(editor: S) -> Self {
233 Self {
234 mode: Mode::View,
235 table: TableState::new(),
236 editor,
237 data: Default::default(),
238 mouse: Default::default(),
239 }
240 }
241
242 pub fn named(name: &str, editor: S) -> Self {
243 Self {
244 mode: Mode::View,
245 table: TableState::named(name),
246 editor,
247 data: Default::default(),
248 mouse: Default::default(),
249 }
250 }
251}
252
253impl<S> EditableTableVecState<S>
254where
255 S: TableEditorState,
256{
257 pub fn set_value(&mut self, data: Vec<S::Value>) {
259 self.data = data;
260 }
261
262 pub fn value(&self) -> Vec<S::Value> {
268 self.data.clone()
269 }
270
271 pub fn clear(&mut self) {
273 self.mode = Mode::View;
274 self.table.clear_offset();
275 self.table.clear_selection();
276 self.data.clear();
277 }
279
280 pub fn is_editing(&self) -> bool {
282 self.mode == Mode::Edit || self.mode == Mode::Insert
283 }
284
285 pub fn is_insert(&self) -> bool {
287 self.mode == Mode::Insert
288 }
289
290 pub fn remove(&mut self, row: usize) {
292 if self.mode != Mode::View {
293 return;
294 }
295 if row < self.data.len() {
296 self.data.remove(row);
297 self.table.items_removed(row, 1);
298 if !self.table.scroll_to_row(row) {
299 self.table.scroll_to_row(row.saturating_sub(1));
300 }
301 }
302 }
303
304 pub fn edit_new<'a>(&mut self, row: usize, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
306 if self.mode != Mode::View {
307 return Ok(());
308 }
309 let value = self.editor.create_value(ctx)?;
310 self.editor.set_value(value.clone(), ctx)?;
311 self.data.insert(row, value);
312 self._start(0, row, Mode::Insert);
313 Ok(())
314 }
315
316 pub fn edit<'a>(
318 &mut self,
319 col: usize,
320 row: usize,
321 ctx: &'a S::Context<'a>,
322 ) -> Result<(), S::Err> {
323 if self.mode != Mode::View {
324 return Ok(());
325 }
326 {
327 let value = &self.data[row];
328 self.editor.set_value(value.clone(), ctx)?;
329 }
330 self._start(col, row, Mode::Edit);
331 Ok(())
332 }
333
334 fn _start(&mut self, col: usize, row: usize, mode: Mode) {
335 if self.table.is_focused() {
336 FocusBuilder::build_for(&self.editor).first();
338 }
339
340 self.mode = mode;
341 if self.mode == Mode::Insert {
342 self.table.items_added(row, 1);
343 }
344 self.table.move_to(row);
345 self.table.scroll_to_col(col);
346 self.editor.set_focused_col(col);
347 }
348
349 pub fn cancel(&mut self) {
353 if self.mode == Mode::View {
354 return;
355 }
356 let Some(row) = self.table.selected_checked() else {
357 return;
358 };
359 if self.mode == Mode::Insert {
360 self.data.remove(row);
361 self.table.items_removed(row, 1);
362 }
363 self._stop();
364 }
365
366 pub fn commit<'a>(&mut self, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
368 if self.mode == Mode::View {
369 return Ok(());
370 }
371 let Some(row) = self.table.selected_checked() else {
372 return Ok(());
373 };
374 {
375 let value = self.editor.value(ctx)?;
376 if let Some(value) = value {
377 self.data[row] = value;
378 } else {
379 self.data.remove(row);
380 self.table.items_removed(row, 1);
381 }
382 }
383 self._stop();
384 Ok(())
385 }
386
387 pub fn commit_and_append<'a>(&mut self, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
388 self.commit(ctx)?;
389 if let Some(row) = self.table.selected_checked() {
390 self.edit_new(row + 1, ctx)?;
391 }
392 Ok(())
393 }
394
395 pub fn commit_and_edit<'a>(&mut self, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
396 let Some(row) = self.table.selected_checked() else {
397 return Ok(());
398 };
399
400 self.commit(ctx)?;
401 if row + 1 < self.data.len() {
402 self.table.select(Some(row + 1));
403 self.edit(0, row + 1, ctx)?;
404 }
405 Ok(())
406 }
407
408 fn _stop(&mut self) {
409 self.mode = Mode::View;
410 self.table.scroll_to_col(0);
411 }
412}
413
414impl<'a, S> HandleEvent<crossterm::event::Event, &'a S::Context<'a>, Result<Outcome, S::Err>>
415 for EditableTableVecState<S>
416where
417 S: HandleEvent<crossterm::event::Event, &'a S::Context<'a>, Result<Outcome, S::Err>>,
418 S: TableEditorState,
419{
420 #[allow(clippy::collapsible_if)]
421 fn handle(
422 &mut self,
423 event: &crossterm::event::Event,
424 ctx: &'a S::Context<'a>,
425 ) -> Result<Outcome, S::Err> {
426 if self.mode == Mode::Edit || self.mode == Mode::Insert {
427 if self.is_focused() {
428 event_flow!({
429 let r = self.editor.handle(event, ctx)?;
430 if let Some(col) = self.editor.focused_col() {
431 if self.table.scroll_to_col(col) {
432 Outcome::Changed
433 } else {
434 r
435 }
436 } else {
437 r
438 }
439 });
440
441 try_flow!(match event {
442 ct_event!(keycode press Esc) => {
443 self.cancel();
444 Outcome::Changed
445 }
446 ct_event!(keycode press Enter) => {
447 if self.table.selected_checked() < Some(self.table.rows().saturating_sub(1))
448 {
449 self.commit_and_edit(ctx)?;
450 Outcome::Changed
451 } else {
452 self.commit_and_append(ctx)?;
453 Outcome::Changed
454 }
455 }
456 ct_event!(keycode press Up) => {
457 self.commit(ctx)?;
458 if self.data.is_empty() {
459 self.edit_new(0, ctx)?;
460 } else if let Some(row) = self.table.selected_checked()
461 && row > 0
462 {
463 self.table.select(Some(row));
464 }
465 Outcome::Changed
466 }
467 ct_event!(keycode press Down) => {
468 self.commit(ctx)?;
469 if self.data.is_empty() {
470 self.edit_new(0, ctx)?;
471 } else if let Some(row) = self.table.selected_checked()
472 && row + 1 < self.data.len()
473 {
474 self.table.select(Some(row + 1));
475 }
476 Outcome::Changed
477 }
478 _ => Outcome::Continue,
479 });
480 }
481
482 Ok(Outcome::Continue)
483 } else {
484 if self.table.gained_focus() {
485 if self.data.is_empty() {
486 self.edit_new(0, ctx)?;
487 }
488 }
489
490 try_flow!(match event {
491 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table.table_area, m) => {
492 if let Some((col, row)) = self.table.cell_at_clicked((m.column, m.row)) {
493 self.edit(col, row, ctx)?;
494 Outcome::Changed
495 } else {
496 Outcome::Continue
497 }
498 }
499 _ => Outcome::Continue,
500 });
501
502 if self.is_focused() {
503 try_flow!(match event {
504 ct_event!(keycode press Insert) => {
505 if let Some(row) = self.table.selected_checked() {
506 self.edit_new(row, ctx)?;
507 }
508 Outcome::Changed
509 }
510 ct_event!(keycode press Delete) => {
511 if let Some(row) = self.table.selected_checked() {
512 self.remove(row);
513 if self.data.is_empty() {
514 self.edit_new(0, ctx)?;
515 }
516 }
517 Outcome::Changed
518 }
519 ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
520 if let Some(row) = self.table.selected_checked() {
521 self.edit(0, row, ctx)?;
522 Outcome::Changed
523 } else if self.table.rows() == 0 {
524 self.edit_new(0, ctx)?;
525 Outcome::Changed
526 } else {
527 Outcome::Continue
528 }
529 }
530 ct_event!(keycode press Down) => {
531 if let Some(row) = self.table.selected_checked() {
532 if row == self.table.rows().saturating_sub(1) {
533 self.edit_new(row + 1, ctx)?;
534 Outcome::Changed
535 } else {
536 Outcome::Continue
537 }
538 } else if self.table.rows() == 0 {
539 self.edit_new(0, ctx)?;
540 Outcome::Changed
541 } else {
542 Outcome::Continue
543 }
544 }
545 _ => {
546 Outcome::Continue
547 }
548 });
549 }
550
551 try_flow!(self.table.handle(event, Regular));
552
553 Ok(Outcome::Continue)
554 }
555 }
556}