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