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