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::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, Clone)]
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 E::State: Sized,
70{
71 type State = EditListState<E::State>;
72
73 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
74 self.list.render(area, buf, &mut state.list);
75
76 if state.mode == Mode::Insert || state.mode == Mode::Edit {
77 if let Some(row) = state.list.selected() {
78 if let Some(row_area) = state.list.row_area(row) {
80 self.editor.render(row_area, buf, &mut state.editor);
81 }
82 } else {
83 if cfg!(debug_assertions) {
84 warn!("no row selection, not rendering editor");
85 }
86 }
87 }
88 }
89}
90
91impl<S> Default for EditListState<S>
92where
93 S: Default,
94{
95 fn default() -> Self {
96 Self {
97 mode: Mode::View,
98 list: Default::default(),
99 editor: S::default(),
100 mouse: Default::default(),
101 }
102 }
103}
104
105impl<S> HasFocus for EditListState<S>
106where
107 S: HasFocus,
108{
109 fn build(&self, builder: &mut FocusBuilder) {
110 builder.leaf_widget(self);
111 }
112
113 fn focus(&self) -> FocusFlag {
114 match self.mode {
115 Mode::View => self.list.focus(),
116 Mode::Edit => self.editor.focus(),
117 Mode::Insert => self.editor.focus(),
118 }
119 }
120
121 fn area(&self) -> Rect {
122 self.list.area()
123 }
124}
125
126impl<S> RelocatableState for EditListState<S>
127where
128 S: RelocatableState,
129{
130 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
131 self.editor.relocate(shift, clip);
132 self.list.relocate(shift, clip);
133 }
134}
135
136impl<S> HasScreenCursor for EditListState<S>
137where
138 S: HasScreenCursor,
139{
140 fn screen_cursor(&self) -> Option<(u16, u16)> {
141 match self.mode {
142 Mode::View => None,
143 Mode::Edit | Mode::Insert => self.editor.screen_cursor(),
144 }
145 }
146}
147
148impl<S> EditListState<S> {
149 pub fn new(editor: S) -> Self {
151 Self {
152 mode: Mode::View,
153 list: Default::default(),
154 editor,
155 mouse: Default::default(),
156 }
157 }
158
159 pub fn named(name: &str, editor: S) -> Self {
161 Self {
162 mode: Mode::View,
163 list: ListState::named(name),
164 editor,
165 mouse: Default::default(),
166 }
167 }
168}
169
170impl<S> EditListState<S>
171where
172 S: HasFocus,
173{
174 pub fn is_editing(&self) -> bool {
176 self.mode == Mode::Edit || self.mode == Mode::Insert
177 }
178
179 pub fn is_insert(&self) -> bool {
181 self.mode == Mode::Insert
182 }
183
184 pub fn remove(&mut self, row: usize) {
189 if self.mode != Mode::View {
190 return;
191 }
192 self.list.items_removed(row, 1);
193 if !self.list.scroll_to(row) {
194 self.list.scroll_to(row.saturating_sub(1));
195 }
196 }
197
198 pub fn edit_new(&mut self, row: usize) {
206 if self.mode != Mode::View {
207 return;
208 }
209 self._start(row, Mode::Insert);
210 }
211
212 pub fn edit(&mut self, row: usize) {
220 if self.mode != Mode::View {
221 return;
222 }
223 self._start(row, Mode::Edit);
224 }
225
226 fn _start(&mut self, pos: usize, mode: Mode) {
227 if self.list.is_focused() {
228 self.list.focus().set(false);
229 self.editor.focus().set(true);
230 }
231
232 self.mode = mode;
233 if self.mode == Mode::Insert {
234 self.list.items_added(pos, 1);
235 }
236 self.list.move_to(pos);
237 }
238
239 pub fn cancel(&mut self) {
246 if self.mode == Mode::View {
247 return;
248 }
249 let Some(row) = self.list.selected() else {
250 return;
251 };
252 if self.mode == Mode::Insert {
253 self.list.items_removed(row, 1);
254 }
255 self._stop();
256 }
257
258 pub fn commit(&mut self) {
266 if self.mode == Mode::View {
267 return;
268 }
269 self._stop();
270 }
271
272 fn _stop(&mut self) {
273 self.mode = Mode::View;
274 if self.editor.is_focused() {
275 self.list.focus.set(true);
276 self.editor.focus().set(false);
277 }
278 }
279}
280
281impl<S, C> HandleEvent<crossterm::event::Event, C, EditOutcome> for EditListState<S>
282where
283 S: HandleEvent<crossterm::event::Event, C, EditOutcome>,
284 S: HandleEvent<crossterm::event::Event, MouseOnly, EditOutcome>,
285 S: HasFocus,
286{
287 fn handle(&mut self, event: &crossterm::event::Event, ctx: C) -> EditOutcome {
288 if self.mode == Mode::Edit || self.mode == Mode::Insert {
289 if self.editor.is_focused() {
290 event_flow!(return self.editor.handle(event, ctx));
291
292 event_flow!(
293 return match event {
294 ct_event!(keycode press Esc) => {
295 EditOutcome::Cancel
296 }
297 ct_event!(keycode press Enter) => {
298 EditOutcome::Commit
299 }
300 ct_event!(keycode press Tab) => {
301 if self.list.selected() < Some(self.list.rows().saturating_sub(1)) {
302 EditOutcome::CommitAndEdit
303 } else {
304 EditOutcome::CommitAndAppend
305 }
306 }
307 ct_event!(keycode press Up) => {
308 EditOutcome::Commit
309 }
310 ct_event!(keycode press Down) => {
311 EditOutcome::Commit
312 }
313 _ => EditOutcome::Continue,
314 }
315 );
316 } else {
317 event_flow!(return self.editor.handle(event, MouseOnly));
318 }
319 } else {
320 event_flow!(
321 return match event {
322 ct_event!(mouse any for m) if self.mouse.doubleclick(self.list.inner, m) => {
323 if self.list.row_at_clicked((m.column, m.row)).is_some() {
324 EditOutcome::Edit
325 } else {
326 EditOutcome::Continue
327 }
328 }
329 _ => EditOutcome::Continue,
330 }
331 );
332
333 if self.list.is_focused() {
334 event_flow!(
335 return match event {
336 ct_event!(keycode press Insert) => {
337 EditOutcome::Insert
338 }
339 ct_event!(keycode press Delete) => {
340 EditOutcome::Remove
341 }
342 ct_event!(keycode press Enter) | ct_event!(keycode press F(2)) => {
343 EditOutcome::Edit
344 }
345 ct_event!(keycode press Down) => {
346 if let Some(row) = self.list.selection.lead_selection() {
347 if row == self.list.rows().saturating_sub(1) {
348 EditOutcome::Append
349 } else {
350 EditOutcome::Continue
351 }
352 } else {
353 EditOutcome::Continue
354 }
355 }
356 _ => {
357 EditOutcome::Continue
358 }
359 }
360 );
361 event_flow!(
362 return match self.list.handle(event, Regular) {
363 Outcome::Continue => EditOutcome::Continue,
364 Outcome::Unchanged => EditOutcome::Unchanged,
365 Outcome::Changed => EditOutcome::Changed,
366 }
367 );
368 }
369
370 event_flow!(return self.list.handle(event, MouseOnly));
371 }
372
373 EditOutcome::Continue
374 }
375}
376
377pub fn handle_edit_events<S, C>(
385 state: &mut EditListState<S>,
386 focus: bool,
387 event: &crossterm::event::Event,
388 qualifier: C,
389) -> EditOutcome
390where
391 S: HandleEvent<crossterm::event::Event, C, EditOutcome>,
392 S: HandleEvent<crossterm::event::Event, MouseOnly, EditOutcome>,
393 S: HasFocus,
394{
395 state.list.focus.set(focus);
396 state.handle(event, qualifier)
397}