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