1use crate::event::EditOutcome;
6use crate::list::selection::RowSelection;
7use crate::list::{List, ListSelection, ListState};
8use log::warn;
9use rat_event::util::MouseFlags;
10use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, event_flow};
11use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
12use rat_reloc::RelocatableState;
13use rat_text::HasScreenCursor;
14use ratatui_core::buffer::Buffer;
15use ratatui_core::layout::Rect;
16use ratatui_core::widgets::StatefulWidget;
17use ratatui_crossterm::crossterm::event::Event;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Mode {
22 View,
23 Edit,
24 Insert,
25}
26
27#[derive(Debug, Default)]
31pub struct EditList<'a, E>
32where
33 E: StatefulWidget + 'a,
34{
35 list: List<'a, RowSelection>,
36 editor: E,
37}
38
39#[derive(Debug, Clone)]
45pub struct EditListState<S> {
46 pub mode: Mode,
48
49 pub list: ListState<RowSelection>,
51 pub editor: S,
53
54 pub mouse: MouseFlags,
56}
57
58impl<'a, E> EditList<'a, E>
59where
60 E: StatefulWidget + 'a,
61{
62 pub fn new(list: List<'a, RowSelection>, edit: E) -> Self {
63 Self { list, editor: edit }
64 }
65}
66
67impl<'a, E> StatefulWidget for EditList<'a, E>
68where
69 E: StatefulWidget + 'a,
70 E::State: Sized,
71{
72 type State = EditListState<E::State>;
73
74 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
75 self.list.render(area, buf, &mut state.list);
76
77 if state.mode == Mode::Insert || state.mode == Mode::Edit {
78 if let Some(row) = state.list.selected() {
79 if let Some(row_area) = state.list.row_area(row) {
81 self.editor.render(row_area, buf, &mut state.editor);
82 }
83 } else {
84 if cfg!(debug_assertions) {
85 warn!("no row selection, not rendering editor");
86 }
87 }
88 }
89 }
90}
91
92impl<S> Default for EditListState<S>
93where
94 S: Default,
95{
96 fn default() -> Self {
97 Self {
98 mode: Mode::View,
99 list: Default::default(),
100 editor: S::default(),
101 mouse: Default::default(),
102 }
103 }
104}
105
106impl<S> HasFocus for EditListState<S>
107where
108 S: HasFocus,
109{
110 fn build(&self, builder: &mut FocusBuilder) {
111 builder.leaf_widget(self);
112 }
113
114 fn focus(&self) -> FocusFlag {
115 match self.mode {
116 Mode::View => self.list.focus(),
117 Mode::Edit => self.editor.focus(),
118 Mode::Insert => self.editor.focus(),
119 }
120 }
121
122 fn area(&self) -> Rect {
123 self.list.area()
124 }
125}
126
127impl<S> RelocatableState for EditListState<S>
128where
129 S: RelocatableState,
130{
131 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
132 self.editor.relocate(shift, clip);
133 self.list.relocate(shift, clip);
134 }
135}
136
137impl<S> HasScreenCursor for EditListState<S>
138where
139 S: HasScreenCursor,
140{
141 fn screen_cursor(&self) -> Option<(u16, u16)> {
142 match self.mode {
143 Mode::View => None,
144 Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
145 }
146 }
147}
148
149impl<S> EditListState<S> {
150 pub fn new(editor: S) -> Self {
152 Self {
153 mode: Mode::View,
154 list: Default::default(),
155 editor,
156 mouse: Default::default(),
157 }
158 }
159
160 pub fn named(name: &str, editor: S) -> Self {
162 Self {
163 mode: Mode::View,
164 list: ListState::named(name),
165 editor,
166 mouse: Default::default(),
167 }
168 }
169}
170
171impl<S> EditListState<S>
172where
173 S: HasFocus,
174{
175 pub fn is_editing(&self) -> bool {
177 self.mode == Mode::Edit || self.mode == Mode::Insert
178 }
179
180 pub fn is_insert(&self) -> bool {
182 self.mode == Mode::Insert
183 }
184
185 pub fn remove(&mut self, row: usize) {
190 if self.mode != Mode::View {
191 return;
192 }
193 self.list.items_removed(row, 1);
194 if !self.list.scroll_to(row) {
195 self.list.scroll_to(row.saturating_sub(1));
196 }
197 }
198
199 pub fn edit_new(&mut self, row: usize) {
207 if self.mode != Mode::View {
208 return;
209 }
210 self._start(row, Mode::Insert);
211 }
212
213 pub fn edit(&mut self, row: usize) {
221 if self.mode != Mode::View {
222 return;
223 }
224 self._start(row, Mode::Edit);
225 }
226
227 fn _start(&mut self, pos: usize, mode: Mode) {
228 if self.list.is_focused() {
229 self.list.focus().set(false);
230 self.editor.focus().set(true);
231 }
232
233 self.mode = mode;
234 if self.mode == Mode::Insert {
235 self.list.items_added(pos, 1);
236 }
237 self.list.move_to(pos);
238 }
239
240 pub fn cancel(&mut self) {
247 if self.mode == Mode::View {
248 return;
249 }
250 let Some(row) = self.list.selected() else {
251 return;
252 };
253 if self.mode == Mode::Insert {
254 self.list.items_removed(row, 1);
255 }
256 self._stop();
257 }
258
259 pub fn commit(&mut self) {
267 if self.mode == Mode::View {
268 return;
269 }
270 self._stop();
271 }
272
273 fn _stop(&mut self) {
274 self.mode = Mode::View;
275 if self.editor.is_focused() {
276 self.list.focus.set(true);
277 self.editor.focus().set(false);
278 }
279 }
280}
281
282impl<S, C> HandleEvent<Event, C, EditOutcome> for EditListState<S>
283where
284 S: HandleEvent<Event, C, EditOutcome>,
285 S: HandleEvent<Event, MouseOnly, EditOutcome>,
286 S: HasFocus,
287{
288 fn handle(&mut self, event: &Event, ctx: C) -> EditOutcome {
289 if self.mode == Mode::Edit || self.mode == Mode::Insert {
290 if self.editor.is_focused() {
291 event_flow!(return self.editor.handle(event, ctx));
292
293 event_flow!(
294 return match event {
295 ct_event!(keycode press Esc) => {
296 EditOutcome::Cancel
297 }
298 ct_event!(keycode press Enter) => {
299 EditOutcome::Commit
300 }
301 ct_event!(keycode press Tab) => {
302 if self.list.selected() < Some(self.list.rows().saturating_sub(1)) {
303 EditOutcome::CommitAndEdit
304 } else {
305 EditOutcome::CommitAndAppend
306 }
307 }
308 ct_event!(keycode press Up) => {
309 EditOutcome::Commit
310 }
311 ct_event!(keycode press Down) => {
312 EditOutcome::Commit
313 }
314 _ => EditOutcome::Continue,
315 }
316 );
317 } else {
318 event_flow!(return self.editor.handle(event, MouseOnly));
319 }
320 } else {
321 event_flow!(
322 return match event {
323 ct_event!(mouse any for m) if self.mouse.doubleclick(self.list.inner, m) => {
324 if self.list.row_at_clicked((m.column, m.row)).is_some() {
325 EditOutcome::Edit
326 } else {
327 EditOutcome::Continue
328 }
329 }
330 _ => EditOutcome::Continue,
331 }
332 );
333
334 if self.list.is_focused() {
335 event_flow!(
336 return match event {
337 ct_event!(keycode press Insert) => {
338 EditOutcome::Insert
339 }
340 ct_event!(keycode press Delete) => {
341 EditOutcome::Remove
342 }
343 ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
344 EditOutcome::Edit
345 }
346 ct_event!(keycode press Down) => {
347 if let Some(row) = self.list.selection.lead_selection() {
348 if row == self.list.rows().saturating_sub(1) {
349 EditOutcome::Append
350 } else {
351 EditOutcome::Continue
352 }
353 } else {
354 EditOutcome::Continue
355 }
356 }
357 _ => {
358 EditOutcome::Continue
359 }
360 }
361 );
362 event_flow!(
363 return match self.list.handle(event, Regular) {
364 Outcome::Continue => EditOutcome::Continue,
365 Outcome::Unchanged => EditOutcome::Unchanged,
366 Outcome::Changed => EditOutcome::Changed,
367 }
368 );
369 }
370
371 event_flow!(return self.list.handle(event, MouseOnly));
372 }
373
374 EditOutcome::Continue
375 }
376}
377
378pub fn handle_edit_events<S, C>(
386 state: &mut EditListState<S>,
387 focus: bool,
388 event: &Event,
389 qualifier: C,
390) -> EditOutcome
391where
392 S: HandleEvent<Event, C, EditOutcome>,
393 S: HandleEvent<Event, MouseOnly, EditOutcome>,
394 S: HasFocus,
395{
396 state.list.focus.set(focus);
397 state.handle(event, qualifier)
398}