rust_kanban/ui/rendering/popup/widgets/
date_time_picker.rs1use crate::{
2 app::{state::Focus, App},
3 constants::MOUSE_OUT_OF_BOUNDS_COORDINATES,
4 ui::{
5 rendering::{
6 common::render_blank_styled_canvas,
7 popup::widgets::DateTimePicker,
8 utils::{check_if_active_and_get_style, check_if_mouse_is_in_area, get_button_style},
9 },
10 widgets::SelfViewportCorrection,
11 Renderable,
12 },
13};
14use ratatui::{
15 layout::{Alignment, Constraint, Direction, Layout, Rect},
16 widgets::{Block, BorderType, Borders, Paragraph},
17 Frame,
18};
19
20impl Renderable for DateTimePicker {
21 fn render(rect: &mut Frame, app: &mut App, is_active: bool) {
22 let anchor = app
23 .widgets
24 .date_time_picker
25 .viewport_corrected_anchor
26 .unwrap_or_default();
27 let (current_month, current_year) = app
28 .widgets
29 .date_time_picker
30 .calculate_styled_lines_of_dates(is_active, &app.current_theme);
31 let render_area = Rect {
32 x: anchor.0,
33 y: anchor.1,
34 width: app.widgets.date_time_picker.widget_width,
35 height: app.widgets.date_time_picker.widget_height,
36 };
37
38 app.widgets
39 .date_time_picker
40 .set_current_viewport(Some(rect.area()));
41
42 let title_length = (current_month.len() + 3 + current_year.len() + 4) as u16;
44 let padding = (render_area
45 .width
46 .min(app.widgets.date_time_picker.date_target_width)
47 - 3
48 - 2)
49 .saturating_sub(title_length); let month_length = current_month.len() as u16 + (padding / 2) + 2;
51 let year_length = current_year.len() as u16 + (padding / 2) + 2;
52
53 let (date_picker_render_area, time_picker_render_area) =
54 if app.widgets.date_time_picker.widget_width
55 > app.widgets.date_time_picker.date_target_width
56 {
57 let chunks = Layout::default()
58 .direction(Direction::Horizontal)
59 .constraints(
60 [
61 Constraint::Length(app.widgets.date_time_picker.date_target_width),
62 Constraint::Fill(1),
63 ]
64 .as_ref(),
65 )
66 .split(render_area);
67 let time_picker_render_area = Layout::default()
69 .direction(Direction::Vertical)
70 .constraints([Constraint::Length(1), Constraint::Fill(1)].as_ref())
71 .margin(1)
72 .split(chunks[1]);
73 (chunks[0], Some(time_picker_render_area[1]))
74 } else {
75 (render_area, None)
76 };
77
78 let chunks = Layout::default()
79 .direction(Direction::Vertical)
80 .constraints(
81 [
82 Constraint::Length(1),
83 Constraint::Length(1),
84 Constraint::Fill(1),
85 ]
86 .as_ref(),
87 )
88 .margin(1)
89 .split(date_picker_render_area);
90 let header_chunks = Layout::default()
91 .direction(Direction::Horizontal)
92 .constraints(
93 [
94 Constraint::Length(month_length),
95 Constraint::Length(1),
96 Constraint::Length(year_length),
97 Constraint::Fill(1),
98 Constraint::Length(1),
99 ]
100 .as_ref(),
101 )
102 .split(chunks[0]);
103
104 let time_picker_toggle_style = get_button_style(
105 app,
106 Focus::DTPToggleTimePicker,
107 Some(&header_chunks[3]),
108 is_active,
109 false,
110 );
111 let month_style = get_button_style(
112 app,
113 Focus::DTPMonth,
114 Some(&header_chunks[0]),
115 is_active,
116 false,
117 );
118 let year_style = get_button_style(
119 app,
120 Focus::DTPYear,
121 Some(&header_chunks[2]),
122 is_active,
123 false,
124 );
125 let general_style = check_if_active_and_get_style(
126 is_active,
127 app.current_theme.inactive_text_style,
128 app.current_theme.general_style,
129 );
130
131 let month_block = if app.state.focus == Focus::DTPMonth {
132 Block::default()
133 .borders(Borders::LEFT | Borders::RIGHT)
134 .border_type(BorderType::Rounded)
135 .border_style(month_style)
136 } else {
137 Block::default()
138 };
139
140 let year_block = if app.state.focus == Focus::DTPYear {
141 Block::default()
142 .borders(Borders::LEFT | Borders::RIGHT)
143 .border_type(BorderType::Rounded)
144 .border_style(year_style)
145 } else {
146 Block::default()
147 };
148
149 let border_block = if app.widgets.date_time_picker.time_picker_active {
150 Block::default()
151 .borders(Borders::ALL)
152 .border_type(BorderType::Rounded)
153 .border_style(general_style)
154 .title("Date Time Picker")
155 } else {
156 Block::default()
157 .borders(Borders::ALL)
158 .border_type(BorderType::Rounded)
159 .border_style(general_style)
160 .title("Date Picker")
161 };
162
163 let time_picker_toggle_button = if app.widgets.date_time_picker.time_picker_active {
164 "<"
165 } else {
166 ">"
167 };
168
169 let main_paragraph =
170 Paragraph::new(app.widgets.date_time_picker.styled_date_lines.0.clone())
171 .block(Block::default())
172 .wrap(ratatui::widgets::Wrap { trim: true })
173 .alignment(Alignment::Center);
174 let month_paragraph = Paragraph::new(current_month)
175 .style(month_style)
176 .block(month_block)
177 .alignment(Alignment::Center);
178 let separator_paragraph = Paragraph::new("-")
179 .style(general_style)
180 .block(Block::default())
181 .alignment(Alignment::Center);
182 let year_paragraph = Paragraph::new(current_year)
183 .style(year_style)
184 .block(year_block)
185 .alignment(Alignment::Center);
186 let toggle_time_panel_paragraph = Paragraph::new(time_picker_toggle_button)
187 .style(time_picker_toggle_style)
188 .block(Block::default())
189 .alignment(Alignment::Right);
190
191 if !check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &render_area)
192 && (app.state.current_mouse_coordinates != MOUSE_OUT_OF_BOUNDS_COORDINATES)
193 {
194 app.state.set_focus(Focus::NoFocus);
195 }
196
197 if check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &chunks[2]) {
198 app.state.set_focus(Focus::DTPCalender);
199 let maybe_date_to_select = if let Some((calculated_pos, _, _)) =
200 &app.widgets.date_time_picker.calculated_mouse_coords
201 {
202 calculated_pos.iter().find_map(|(rect, date)| {
203 if check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, rect) {
204 Some(*date)
205 } else {
206 None
207 }
208 })
209 } else {
210 None
211 };
212
213 if let Some(date_to_select) = maybe_date_to_select {
214 app.widgets
215 .date_time_picker
216 .select_date_in_current_month(date_to_select);
217 }
218 }
219
220 render_blank_styled_canvas(rect, &app.current_theme, render_area, is_active);
221 rect.render_widget(border_block, render_area);
222 rect.render_widget(month_paragraph, header_chunks[0]);
223 rect.render_widget(separator_paragraph, header_chunks[1]);
224 rect.render_widget(year_paragraph, header_chunks[2]);
225 rect.render_widget(toggle_time_panel_paragraph, header_chunks[3]);
226 rect.render_widget(main_paragraph, chunks[2]);
227
228 if app.widgets.date_time_picker.time_picker_active && time_picker_render_area.is_some() {
229 let render_area = time_picker_render_area.unwrap();
230 let time_picker_chunks = Layout::default()
232 .direction(Direction::Horizontal)
233 .constraints(
234 [
235 Constraint::Length(2),
236 Constraint::Length(3),
237 Constraint::Length(2),
238 ]
239 .as_ref(),
240 )
241 .margin(1)
242 .split(render_area);
243 if check_if_mouse_is_in_area(
244 &app.state.current_mouse_coordinates,
245 &time_picker_chunks[0],
246 ) {
247 app.state.set_focus(Focus::DTPHour);
248 }
249 if check_if_mouse_is_in_area(
250 &app.state.current_mouse_coordinates,
251 &time_picker_chunks[1],
252 ) {
253 app.state.set_focus(Focus::DTPMinute);
254 }
255 if check_if_mouse_is_in_area(
256 &app.state.current_mouse_coordinates,
257 &time_picker_chunks[2],
258 ) {
259 app.state.set_focus(Focus::DTPSecond);
260 }
261 let time_picker_lines = app.widgets.date_time_picker.get_styled_lines_of_time(
262 is_active,
263 &app.current_theme,
264 &app.state.focus,
265 );
266 let time_picker_paragraph = Paragraph::new(time_picker_lines)
267 .block(Block::default())
268 .wrap(ratatui::widgets::Wrap { trim: true });
269 rect.render_widget(time_picker_paragraph, render_area);
270 }
271 }
272}