1use crate::_private::NonExhaustive;
11use crate::edit::{Mode, TableEditor, TableEditorState};
12use crate::event::{EditOutcome, TableOutcome};
13use crate::rowselection::RowSelection;
14use crate::{Table, TableSelection, TableState};
15use log::warn;
16use rat_cursor::HasScreenCursor;
17use rat_event::util::MouseFlags;
18use rat_event::{ct_event, flow, HandleEvent, Regular};
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#[derive(Debug)]
32pub struct EditableTable<'a, E>
33where
34 E: TableEditor + 'a,
35{
36 table: Table<'a, RowSelection>,
37 editor: E,
38}
39
40pub struct EditableTableState<S> {
46 pub mode: Mode,
48
49 pub table: TableState<RowSelection>,
51 pub editor: S,
53
54 pub mouse: MouseFlags,
55
56 pub non_exhaustive: NonExhaustive,
57}
58
59impl<'a, E> EditableTable<'a, E>
60where
61 E: TableEditor + 'a,
62{
63 pub fn new(table: Table<'a, RowSelection>, editor: E) -> Self {
64 Self { table, editor }
65 }
66}
67
68impl<'a, E> StatefulWidget for &EditableTable<'a, E>
69where
70 E: TableEditor + 'a,
71{
72 type State = EditableTableState<E::State>;
73
74 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
75 (&self.table).render(area, buf, &mut state.table);
76
77 if state.mode == Mode::Edit || state.mode == Mode::Insert {
78 if let Some(row) = state.table.selected_checked() {
79 if let Some((row_area, cell_areas)) = state.table.row_cells(row) {
81 self.editor
82 .render(row_area, &cell_areas, buf, &mut state.editor);
83 }
84 } else {
85 if cfg!(debug_assertions) {
86 warn!("no row selection, not rendering editor");
87 }
88 }
89 }
90 }
91}
92
93impl<'a, E> StatefulWidget for EditableTable<'a, E>
94where
95 E: TableEditor + 'a,
96{
97 type State = EditableTableState<E::State>;
98
99 #[allow(clippy::collapsible_else_if)]
100 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
101 self.table.render(area, buf, &mut state.table);
102
103 if state.mode == Mode::Insert || state.mode == Mode::Edit {
104 if let Some(row) = state.table.selected_checked() {
105 if let Some((row_area, cell_areas)) = state.table.row_cells(row) {
107 self.editor
108 .render(row_area, &cell_areas, buf, &mut state.editor);
109 }
110 } else {
111 if cfg!(debug_assertions) {
112 warn!("no row selection, not rendering editor");
113 }
114 }
115 }
116 }
117}
118
119impl<S> Default for EditableTableState<S>
120where
121 S: Default,
122{
123 fn default() -> Self {
124 Self {
125 mode: Mode::View,
126 table: Default::default(),
127 editor: S::default(),
128 mouse: Default::default(),
129 non_exhaustive: NonExhaustive,
130 }
131 }
132}
133
134impl<S> Debug for EditableTableState<S>
135where
136 S: Debug,
137{
138 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
139 f.debug_struct("EditTableState")
140 .field("mode", &self.mode)
141 .field("table", &self.table)
142 .field("editor", &self.editor)
143 .field("mouse", &self.mouse)
144 .finish()
145 }
146}
147
148impl<S> HasFocus for EditableTableState<S> {
149 fn build(&self, builder: &mut FocusBuilder) {
150 builder.leaf_widget(self);
151 }
152
153 fn focus(&self) -> FocusFlag {
154 self.table.focus()
155 }
156
157 fn area(&self) -> Rect {
158 self.table.area()
159 }
160
161 fn navigable(&self) -> Navigation {
162 match self.mode {
163 Mode::View => self.table.navigable(),
164 Mode::Edit | Mode::Insert => Navigation::Lock,
165 }
166 }
167
168 fn is_focused(&self) -> bool {
169 self.table.is_focused()
170 }
171
172 fn lost_focus(&self) -> bool {
173 self.table.lost_focus()
174 }
175
176 fn gained_focus(&self) -> bool {
177 self.table.gained_focus()
178 }
179}
180
181impl<S> HasScreenCursor for EditableTableState<S>
182where
183 S: HasScreenCursor,
184{
185 fn screen_cursor(&self) -> Option<(u16, u16)> {
186 match self.mode {
187 Mode::View => None,
188 Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
189 }
190 }
191}
192
193impl<S> RelocatableState for EditableTableState<S>
194where
195 S: TableEditorState + RelocatableState,
196{
197 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
198 match self.mode {
199 Mode::View => {}
200 Mode::Edit | Mode::Insert => {
201 self.editor.relocate(shift, clip);
202 }
203 }
204 }
205}
206
207impl<S> EditableTableState<S> {
208 pub fn new(editor: S) -> Self {
210 Self {
211 mode: Mode::View,
212 table: TableState::new(),
213 editor,
214 mouse: Default::default(),
215 non_exhaustive: NonExhaustive,
216 }
217 }
218
219 pub fn named(name: &str, editor: S) -> Self {
221 Self {
222 mode: Mode::View,
223 table: TableState::named(name),
224 editor,
225 mouse: Default::default(),
226 non_exhaustive: NonExhaustive,
227 }
228 }
229}
230
231impl<S> EditableTableState<S>
232where
233 S: TableEditorState,
234{
235 pub fn is_editing(&self) -> bool {
237 self.mode == Mode::Edit || self.mode == Mode::Insert
238 }
239
240 pub fn is_insert(&self) -> bool {
242 self.mode == Mode::Insert
243 }
244
245 pub fn remove(&mut self, row: usize) {
250 if self.mode != Mode::View {
251 return;
252 }
253 self.table.items_removed(row, 1);
254 if !self.table.scroll_to_row(row) {
255 self.table.scroll_to_row(row.saturating_sub(1));
256 }
257 }
258
259 pub fn edit_new(&mut self, row: usize) {
270 if self.mode != Mode::View {
271 return;
272 }
273 self._start(row, Mode::Insert);
274 }
275
276 pub fn edit(&mut self, row: usize) {
287 if self.mode != Mode::View {
288 return;
289 }
290 self._start(row, Mode::Edit);
291 }
292
293 fn _start(&mut self, pos: usize, mode: Mode) {
294 if self.table.is_focused() {
295 FocusBuilder::build_for(&self.editor).first();
296 }
297
298 self.mode = mode;
299 if self.mode == Mode::Insert {
300 self.table.items_added(pos, 1);
301 }
302 self.table.move_to(pos);
303 self.table.scroll_to_col(0);
304 }
305
306 pub fn cancel(&mut self) {
316 if self.mode == Mode::View {
317 return;
318 }
319 let Some(row) = self.table.selected_checked() else {
320 return;
321 };
322 if self.mode == Mode::Insert {
323 self.table.items_removed(row, 1);
324 }
325 self._stop();
326 }
327
328 pub fn commit(&mut self) {
339 if self.mode == Mode::View {
340 return;
341 }
342 self._stop();
343 }
344
345 fn _stop(&mut self) {
346 self.mode = Mode::View;
347 self.table.scroll_to_col(0);
348 }
349}
350
351impl<'a, S> HandleEvent<crossterm::event::Event, S::Context<'a>, EditOutcome>
352 for EditableTableState<S>
353where
354 S: HandleEvent<crossterm::event::Event, S::Context<'a>, EditOutcome>,
355 S: TableEditorState,
356{
357 fn handle(&mut self, event: &crossterm::event::Event, ctx: S::Context<'a>) -> EditOutcome {
358 if self.mode == Mode::Edit || self.mode == Mode::Insert {
359 if self.table.is_focused() {
360 flow!(match self.editor.handle(event, ctx.clone()) {
361 EditOutcome::Continue => EditOutcome::Continue,
362 EditOutcome::Unchanged => EditOutcome::Unchanged,
363 r => {
364 if let Some(col) = self.editor.focused_col() {
365 self.table.scroll_to_col(col);
366 }
367 r
368 }
369 });
370
371 flow!(match event {
372 ct_event!(keycode press Esc) => {
373 EditOutcome::Cancel
374 }
375 ct_event!(keycode press Enter) => {
376 if self.table.selected_checked() < Some(self.table.rows().saturating_sub(1))
377 {
378 EditOutcome::CommitAndEdit
379 } else {
380 EditOutcome::CommitAndAppend
381 }
382 }
383 ct_event!(keycode press Up) => {
384 EditOutcome::Commit
385 }
386 ct_event!(keycode press Down) => {
387 EditOutcome::Commit
388 }
389 _ => EditOutcome::Continue,
390 });
391 }
392 EditOutcome::Continue
393 } else {
394 flow!(match event {
395 ct_event!(mouse any for m) if self.mouse.doubleclick(self.table.table_area, m) => {
396 if self.table.cell_at_clicked((m.column, m.row)).is_some() {
397 EditOutcome::Edit
398 } else {
399 EditOutcome::Continue
400 }
401 }
402 _ => EditOutcome::Continue,
403 });
404
405 if self.table.is_focused() {
406 flow!(match event {
407 ct_event!(keycode press Insert) => {
408 EditOutcome::Insert
409 }
410 ct_event!(keycode press Delete) => {
411 EditOutcome::Remove
412 }
413 ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
414 EditOutcome::Edit
415 }
416 ct_event!(keycode press Down) => {
417 if let Some((_column, row)) = self.table.selection.lead_selection() {
418 if row == self.table.rows().saturating_sub(1) {
419 EditOutcome::Append
420 } else {
421 EditOutcome::Continue
422 }
423 } else {
424 EditOutcome::Continue
425 }
426 }
427 _ => {
428 EditOutcome::Continue
429 }
430 });
431 }
432
433 match self.table.handle(event, Regular) {
434 TableOutcome::Continue => EditOutcome::Continue,
435 TableOutcome::Unchanged => EditOutcome::Unchanged,
436 TableOutcome::Changed => EditOutcome::Changed,
437 TableOutcome::Selected => EditOutcome::Changed,
438 }
439 }
440 }
441}