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_core::buffer::Buffer;
22use ratatui_core::layout::Rect;
23use ratatui_core::widgets::StatefulWidget;
24use ratatui_crossterm::crossterm::event::Event;
25use std::fmt::{Debug, Formatter};
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!(feature = "perf_warnings") {
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: Default::default(),
145 data: Default::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
191impl<S> HasScreenCursor for EditableTableVecState<S>
192where
193 S: TableEditorState + HasScreenCursor,
194{
195 fn screen_cursor(&self) -> Option<(u16, u16)> {
196 match self.mode {
197 Mode::View => None,
198 Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
199 }
200 }
201}
202
203impl<S> RelocatableState for EditableTableVecState<S>
204where
205 S: TableEditorState + RelocatableState,
206{
207 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
208 match self.mode {
209 Mode::View => {}
210 Mode::Edit | Mode::Insert => {
211 self.editor.relocate(shift, clip);
212 }
213 }
214 }
215}
216
217impl<S> EditableTableVecState<S>
218where
219 S: TableEditorState,
220{
221 pub fn new(editor: S) -> Self {
222 Self {
223 mode: Mode::View,
224 table: Default::default(),
225 editor,
226 data: Default::default(),
227 mouse: Default::default(),
228 }
229 }
230
231 pub fn named(name: &str, editor: S) -> Self {
232 Self {
233 mode: Mode::View,
234 table: TableState::named(name),
235 editor,
236 data: Default::default(),
237 mouse: Default::default(),
238 }
239 }
240}
241
242impl<S> EditableTableVecState<S>
243where
244 S: TableEditorState,
245{
246 pub fn set_value(&mut self, data: Vec<S::Value>) {
248 self.data = data;
249 }
250
251 pub fn value(&self) -> Vec<S::Value> {
257 self.data.clone()
258 }
259
260 pub fn clear(&mut self) {
262 self.mode = Mode::View;
263 self.table.clear_offset();
264 self.table.clear_selection();
265 self.data.clear();
266 }
268
269 pub fn is_editing(&self) -> bool {
271 self.mode == Mode::Edit || self.mode == Mode::Insert
272 }
273
274 pub fn is_insert(&self) -> bool {
276 self.mode == Mode::Insert
277 }
278
279 pub fn remove(&mut self, row: usize) {
281 if self.mode != Mode::View {
282 return;
283 }
284 if row < self.data.len() {
285 self.data.remove(row);
286 self.table.items_removed(row, 1);
287 if !self.table.scroll_to_row(row) {
288 self.table.scroll_to_row(row.saturating_sub(1));
289 }
290 }
291 }
292
293 pub fn edit_new<'a>(&mut self, row: usize, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
295 if self.mode != Mode::View {
296 return Ok(());
297 }
298 let value = self.editor.create_value(ctx)?;
299 self.editor.set_value(value.clone(), ctx)?;
300 self.data.insert(row, value);
301 self._start(0, row, Mode::Insert);
302 Ok(())
303 }
304
305 pub fn edit<'a>(
307 &mut self,
308 col: usize,
309 row: usize,
310 ctx: &'a S::Context<'a>,
311 ) -> Result<(), S::Err> {
312 if self.mode != Mode::View {
313 return Ok(());
314 }
315 {
316 let value = &self.data[row];
317 self.editor.set_value(value.clone(), ctx)?;
318 }
319 self._start(col, row, Mode::Edit);
320 Ok(())
321 }
322
323 fn _start(&mut self, col: usize, row: usize, mode: Mode) {
324 if self.table.is_focused() {
325 FocusBuilder::build_for(&self.editor).first();
327 }
328
329 self.mode = mode;
330 if self.mode == Mode::Insert {
331 self.table.items_added(row, 1);
332 }
333 self.table.move_to(row);
334 self.table.scroll_to_col(col);
335 self.editor.set_focused_col(col);
336 }
337
338 pub fn cancel(&mut self) {
342 if self.mode == Mode::View {
343 return;
344 }
345 let Some(row) = self.table.selected_checked() else {
346 return;
347 };
348 if self.mode == Mode::Insert {
349 self.data.remove(row);
350 self.table.items_removed(row, 1);
351 }
352 self._stop();
353 }
354
355 pub fn commit<'a>(&mut self, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
357 if self.mode == Mode::View {
358 return Ok(());
359 }
360 let Some(row) = self.table.selected_checked() else {
361 return Ok(());
362 };
363 {
364 let value = self.editor.value(ctx)?;
365 if let Some(value) = value {
366 self.data[row] = value;
367 } else {
368 self.data.remove(row);
369 self.table.items_removed(row, 1);
370 }
371 }
372 self._stop();
373 Ok(())
374 }
375
376 pub fn commit_and_append<'a>(&mut self, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
377 self.commit(ctx)?;
378 if let Some(row) = self.table.selected_checked() {
379 self.edit_new(row + 1, ctx)?;
380 }
381 Ok(())
382 }
383
384 pub fn commit_and_edit<'a>(&mut self, ctx: &'a S::Context<'a>) -> Result<(), S::Err> {
385 let Some(row) = self.table.selected_checked() else {
386 return Ok(());
387 };
388
389 self.commit(ctx)?;
390 if row + 1 < self.data.len() {
391 self.table.select(Some(row + 1));
392 self.edit(0, row + 1, ctx)?;
393 }
394 Ok(())
395 }
396
397 fn _stop(&mut self) {
398 self.mode = Mode::View;
399 self.table.scroll_to_col(0);
400 }
401}
402
403impl<'a, S> HandleEvent<Event, &'a S::Context<'a>, Result<Outcome, S::Err>>
404 for EditableTableVecState<S>
405where
406 S: HandleEvent<Event, &'a S::Context<'a>, Result<Outcome, S::Err>>,
407 S: TableEditorState,
408{
409 #[allow(clippy::collapsible_if)]
410 fn handle(&mut self, event: &Event, ctx: &'a S::Context<'a>) -> Result<Outcome, S::Err> {
411 if self.mode == Mode::Edit || self.mode == Mode::Insert {
412 if self.is_focused() {
413 event_flow!({
414 let r = self.editor.handle(event, ctx)?;
415 if let Some(col) = self.editor.focused_col() {
416 if self.table.scroll_to_col(col) {
417 Outcome::Changed
418 } else {
419 r
420 }
421 } else {
422 r
423 }
424 });
425
426 event_flow!(match event {
427 ct_event!(keycode press Esc) => {
428 self.cancel();
429 Outcome::Changed
430 }
431 ct_event!(keycode press Enter) => {
432 if self.table.selected_checked() < Some(self.table.rows().saturating_sub(1))
433 {
434 self.commit_and_edit(ctx)?;
435 Outcome::Changed
436 } else {
437 self.commit_and_append(ctx)?;
438 Outcome::Changed
439 }
440 }
441 ct_event!(keycode press Up) => {
442 self.commit(ctx)?;
443 if self.data.is_empty() {
444 self.edit_new(0, ctx)?;
445 } else if let Some(row) = self.table.selected_checked()
446 && row > 0
447 {
448 self.table.select(Some(row));
449 }
450 Outcome::Changed
451 }
452 ct_event!(keycode press Down) => {
453 self.commit(ctx)?;
454 if self.data.is_empty() {
455 self.edit_new(0, ctx)?;
456 } else if let Some(row) = self.table.selected_checked()
457 && row + 1 < self.data.len()
458 {
459 self.table.select(Some(row + 1));
460 }
461 Outcome::Changed
462 }
463 _ => Outcome::Continue,
464 });
465 }
466
467 Ok(Outcome::Continue)
468 } else {
469 if self.table.gained_focus() {
470 if self.data.is_empty() {
471 self.edit_new(0, ctx)?;
472 }
473 }
474
475 event_flow!(match event {
476 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table.table_area, m) => {
477 if let Some((col, row)) = self.table.cell_at_clicked((m.column, m.row)) {
478 self.edit(col, row, ctx)?;
479 Outcome::Changed
480 } else {
481 Outcome::Continue
482 }
483 }
484 _ => Outcome::Continue,
485 });
486
487 if self.is_focused() {
488 event_flow!(match event {
489 ct_event!(keycode press Insert) => {
490 if let Some(row) = self.table.selected_checked() {
491 self.edit_new(row, ctx)?;
492 }
493 Outcome::Changed
494 }
495 ct_event!(keycode press Delete) => {
496 if let Some(row) = self.table.selected_checked() {
497 self.remove(row);
498 if self.data.is_empty() {
499 self.edit_new(0, ctx)?;
500 }
501 }
502 Outcome::Changed
503 }
504 ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
505 if let Some(row) = self.table.selected_checked() {
506 self.edit(0, row, ctx)?;
507 Outcome::Changed
508 } else if self.table.rows() == 0 {
509 self.edit_new(0, ctx)?;
510 Outcome::Changed
511 } else {
512 Outcome::Continue
513 }
514 }
515 ct_event!(keycode press Down) => {
516 if let Some(row) = self.table.selected_checked() {
517 if row == self.table.rows().saturating_sub(1) {
518 self.edit_new(row + 1, ctx)?;
519 Outcome::Changed
520 } else {
521 Outcome::Continue
522 }
523 } else if self.table.rows() == 0 {
524 self.edit_new(0, ctx)?;
525 Outcome::Changed
526 } else {
527 Outcome::Continue
528 }
529 }
530 _ => {
531 Outcome::Continue
532 }
533 });
534 }
535
536 try_flow!(self.table.handle(event, Regular));
537
538 Ok(Outcome::Continue)
539 }
540 }
541}