rust_kanban/ui/rendering/view/
new_card_form.rs

1use crate::{
2    app::{
3        state::{AppStatus, Focus, KeyBindingEnum},
4        App,
5    },
6    ui::{
7        rendering::{
8            common::render_close_button,
9            utils::{
10                calculate_viewport_corrected_cursor_position, check_if_active_and_get_style,
11                get_mouse_focusable_field_style,
12            },
13            view::NewCardForm,
14        },
15        PopUp, Renderable,
16    },
17};
18use ratatui::{
19    layout::{Alignment, Constraint, Direction, Layout},
20    text::{Line, Span},
21    widgets::{Block, BorderType, Borders, Paragraph},
22    Frame,
23};
24
25impl Renderable for NewCardForm {
26    fn render(rect: &mut Frame, app: &mut App, is_active: bool) {
27        let chunks = Layout::default()
28            .direction(Direction::Vertical)
29            .constraints(
30                [
31                    Constraint::Length(3),
32                    Constraint::Length(5),
33                    Constraint::Fill(1),
34                    Constraint::Length(3),
35                    Constraint::Length(4),
36                    Constraint::Length(3),
37                ]
38                .as_ref(),
39            )
40            .split(rect.area());
41
42        let card_due_date = app
43            .widgets
44            .date_time_picker
45            .get_date_time_as_string(app.config.date_time_format);
46
47        if app.state.z_stack.last() == Some(&PopUp::DateTimePicker) {
48            if app.widgets.date_time_picker.anchor.is_none() {
49                app.widgets.date_time_picker.anchor = Some((
50                    chunks[3].x + card_due_date.len() as u16 + 2,
51                    chunks[3].y + 3,
52                )); // offsets to make sure date is visible
53            }
54            app.widgets.date_time_picker.current_viewport = Some(rect.area());
55        }
56
57        let general_style = check_if_active_and_get_style(
58            is_active,
59            app.current_theme.inactive_text_style,
60            app.current_theme.general_style,
61        );
62        let name_style =
63            get_mouse_focusable_field_style(app, Focus::CardName, &chunks[1], is_active, false);
64        let description_style = get_mouse_focusable_field_style(
65            app,
66            Focus::CardDescription,
67            &chunks[2],
68            is_active,
69            false,
70        );
71        let due_date_style =
72            get_mouse_focusable_field_style(app, Focus::CardDueDate, &chunks[3], is_active, false);
73        let help_key_style = check_if_active_and_get_style(
74            is_active,
75            app.current_theme.inactive_text_style,
76            app.current_theme.help_key_style,
77        );
78        let help_text_style = check_if_active_and_get_style(
79            is_active,
80            app.current_theme.inactive_text_style,
81            app.current_theme.help_text_style,
82        );
83        let submit_style =
84            get_mouse_focusable_field_style(app, Focus::SubmitButton, &chunks[5], is_active, false);
85
86        let title_paragraph = Paragraph::new("Create a new Card")
87            .alignment(Alignment::Center)
88            .block(
89                Block::default()
90                    .borders(Borders::ALL)
91                    .border_type(BorderType::Rounded)
92                    .style(general_style),
93            );
94        rect.render_widget(title_paragraph, chunks[0]);
95
96        let card_name_block = Block::default()
97            .borders(Borders::ALL)
98            .style(name_style)
99            .border_type(BorderType::Rounded)
100            .title("Card Name (required)");
101        app.state.text_buffers.card_name.set_block(card_name_block);
102        rect.render_widget(app.state.text_buffers.card_name.widget(), chunks[1]);
103        let description_length = app.state.text_buffers.card_description.get_num_lines();
104        let description_block = Block::default()
105            .title(format!("Description ({} line(s))", description_length))
106            .borders(Borders::ALL)
107            .border_type(BorderType::Rounded)
108            .border_style(description_style);
109
110        if app.config.show_line_numbers {
111            app.state
112                .text_buffers
113                .card_description
114                .set_line_number_style(general_style)
115        } else {
116            app.state.text_buffers.card_description.remove_line_number()
117        }
118        app.state
119            .text_buffers
120            .card_description
121            .set_block(description_block.clone());
122        rect.render_widget(app.state.text_buffers.card_description.widget(), chunks[2]);
123
124        let card_due_date = app
125            .widgets
126            .date_time_picker
127            .get_date_time_as_string(app.config.date_time_format);
128        let card_due_date_paragraph = Paragraph::new(card_due_date).block(
129            Block::default()
130                .title("Due Date")
131                .borders(Borders::ALL)
132                .style(due_date_style)
133                .border_type(BorderType::Rounded),
134        );
135        rect.render_widget(card_due_date_paragraph, chunks[3]);
136
137        let input_mode_key = app
138            .get_first_keybinding(KeyBindingEnum::TakeUserInput)
139            .unwrap_or("".to_string());
140        let next_focus_key = app
141            .get_first_keybinding(KeyBindingEnum::NextFocus)
142            .unwrap_or("".to_string());
143        let prv_focus_key = app
144            .get_first_keybinding(KeyBindingEnum::PrvFocus)
145            .unwrap_or("".to_string());
146        let accept_key = app
147            .get_first_keybinding(KeyBindingEnum::Accept)
148            .unwrap_or("".to_string());
149        let cancel_key = app
150            .get_first_keybinding(KeyBindingEnum::GoToPreviousViewOrCancel)
151            .unwrap_or("".to_string());
152        let stop_user_input_key = app
153            .get_first_keybinding(KeyBindingEnum::StopUserInput)
154            .unwrap_or("".to_string());
155
156        let help_spans = Line::from(vec![
157            Span::styled("Press ", help_text_style),
158            Span::styled(input_mode_key, help_key_style),
159            Span::styled(" or ", help_text_style),
160            Span::styled(accept_key.clone(), help_key_style),
161            Span::styled(" to start typing. Press ", help_text_style),
162            Span::styled(stop_user_input_key, help_key_style),
163            Span::styled(" to stop typing. Press ", help_text_style),
164            Span::styled(next_focus_key, help_key_style),
165            Span::styled(" or ", help_text_style),
166            Span::styled(prv_focus_key, help_key_style),
167            Span::styled(" to switch focus. Press ", help_text_style),
168            Span::styled(accept_key, help_key_style),
169            Span::styled(" to submit. Press ", help_text_style),
170            Span::styled(cancel_key, help_key_style),
171            Span::styled(" to cancel", help_text_style),
172        ]);
173
174        let help_paragraph = Paragraph::new(help_spans)
175            .alignment(Alignment::Center)
176            .block(
177                Block::default()
178                    .borders(Borders::ALL)
179                    .border_type(BorderType::Rounded)
180                    .border_style(general_style),
181            )
182            .wrap(ratatui::widgets::Wrap { trim: true });
183        rect.render_widget(help_paragraph, chunks[4]);
184
185        let submit_button = Paragraph::new("Submit").alignment(Alignment::Center).block(
186            Block::default()
187                .borders(Borders::ALL)
188                .style(submit_style)
189                .border_type(BorderType::Rounded),
190        );
191        rect.render_widget(submit_button, chunks[5]);
192
193        if app.state.app_status == AppStatus::UserInput {
194            match app.state.focus {
195                Focus::CardName => {
196                    let (x_pos, y_pos) = calculate_viewport_corrected_cursor_position(
197                        &app.state.text_buffers.card_name,
198                        &app.config.show_line_numbers,
199                        &chunks[1],
200                    );
201                    rect.set_cursor_position((x_pos, y_pos));
202                }
203                Focus::CardDescription => {
204                    let (x_pos, y_pos) = calculate_viewport_corrected_cursor_position(
205                        &app.state.text_buffers.card_description,
206                        &app.config.show_line_numbers,
207                        &chunks[2],
208                    );
209                    rect.set_cursor_position((x_pos, y_pos));
210                }
211                _ => {}
212            }
213        }
214
215        if app.config.enable_mouse_support {
216            render_close_button(rect, app, is_active);
217        }
218    }
219}