1use super::{events::EventData, Flags};
2use crate::input::KeyCode;
3use crate::prelude::*;
4use crate::ui::common::{ControlEvent, ControlEventData};
5use chrono::{NaiveTime, Timelike};
6
7#[derive(Clone, Copy, PartialEq)]
8enum TimeComponent {
9 Hour,
10 Minute,
11 Second,
12 AmPm,
13}
14
15#[CustomControl(overwrite=OnPaint+OnKeyPressed+OnMouseEvent+OnFocus, internal=true)]
16pub struct TimePicker {
17 hour: u8,
18 minute: u8,
19 second: u8,
20 flags: Flags,
21 selected_component: TimeComponent,
22 editable_digit_is_first: bool,
23 hovered_component: Option<TimeComponent>,
24}
25
26impl TimePicker {
27 pub fn new(time_str: &str, layout: Layout, flags: Flags) -> Self {
44 let time = time_str.parse::<NaiveTime>().unwrap();
45 Self::with_time(time, layout, flags)
46 }
47
48 pub fn with_time(time: NaiveTime, layout: Layout, flags: Flags) -> Self {
67 let hour = time.hour().min(23) as u8;
68 let minute = time.minute().min(59) as u8;
69 let second = time.second().min(59) as u8;
70
71 let mut obj = Self {
72 base: ControlBase::with_status_flags(layout, StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput),
73 hour,
74 minute,
75 second,
76 flags,
77 selected_component: TimeComponent::Hour,
78 editable_digit_is_first: true,
79 hovered_component: None,
80 };
81
82 let min_width = 7 + if flags.contains(Flags::AMPM) { 3 } else { 0 } + if flags.contains(Flags::Seconds) { 3 } else { 0 };
83 obj.set_size_bounds(min_width, 1, u16::MAX, 1);
84 obj
85 }
86
87 fn raise_time_changed_event(&mut self) {
88 self.raise_event(ControlEvent {
89 emitter: self.handle,
90 receiver: self.event_processor,
91 data: ControlEventData::TimePicker(EventData {
92 time: self.time(),
93 }),
94 });
95 }
96
97 #[inline(always)]
99 pub fn time(&self) -> NaiveTime {
100 NaiveTime::from_hms_opt(self.hour as u32, self.minute as u32, self.second as u32).unwrap()
101 }
102
103 pub fn set_time(&mut self, time: NaiveTime) {
105 self.hour = time.hour().min(23) as u8;
106 self.minute = time.minute().min(59) as u8;
107 self.second = time.second().min(59) as u8;
108 }
109
110 fn mouse_pos_to_component(&self, x: i32, y: i32) -> Option<TimeComponent> {
111 if y != 0 {
112 return None;
113 }
114 if !self.is_enabled() {
115 return None;
116 }
117 match x {
118 1 | 2 => Some(TimeComponent::Hour),
119 4 | 5 => Some(TimeComponent::Minute),
120 7 | 8 => {
121 if self.flags.contains_one(Flags::Seconds) {
122 Some(TimeComponent::Second)
123 } else if self.flags.contains_one(Flags::AMPM) {
124 Some(TimeComponent::AmPm)
125 } else {
126 None
127 }
128 }
129 10 | 11 => {
130 if self.flags & (Flags::Seconds | Flags::AMPM) == (Flags::Seconds | Flags::AMPM) {
131 Some(TimeComponent::AmPm)
132 } else {
133 None
134 }
135 }
136 _ => None,
137 }
138 }
139 fn update_value(value: u8, max: u8, increment: bool) -> u8 {
140 let value = value % max;
141 if increment {
142 (value + 1) % max
143 } else {
144 (value + max - 1) % max
145 }
146 }
147 fn update_value_withdigit(value: u8, max: u8, digit: u8, first_digit: bool) -> Option<u8> {
148 if first_digit {
149 if digit > max / 10 {
150 None
151 } else {
152 Some((digit * 10 + (value % 10)).min(max))
153 }
154 } else {
155 let new_value = digit + ((value / 10) * 10);
156 if new_value > max {
157 None
158 } else {
159 Some(new_value)
160 }
161 }
162 }
163
164 fn update_digit(&mut self, digit: u8) {
165 let (value, max) = match self.selected_component {
166 TimeComponent::Hour => (self.hour, 23),
167 TimeComponent::Minute => (self.minute, 59),
168 TimeComponent::Second => (self.second, 59),
169 TimeComponent::AmPm => {
170 return;
171 }
172 };
173 if let Some(v) = Self::update_value_withdigit(value, max, digit, self.editable_digit_is_first) {
174 match self.selected_component {
175 TimeComponent::Hour => self.hour = v,
176 TimeComponent::Minute => self.minute = v,
177 TimeComponent::Second => self.second = v,
178 TimeComponent::AmPm => {}
179 }
180 self.editable_digit_is_first = !self.editable_digit_is_first;
181 if self.editable_digit_is_first {
182 self.move_to_next_component();
183 }
184 self.raise_time_changed_event();
185 }
186 }
187 fn increment_decrement_selected_component(&mut self, increment: bool) {
188 match self.selected_component {
189 TimeComponent::Hour => {
190 self.hour = Self::update_value(self.hour, 24, increment);
191 }
192 TimeComponent::Minute => {
193 self.minute = Self::update_value(self.minute, 60, increment);
194 }
195 TimeComponent::Second => {
196 self.second = Self::update_value(self.second, 60, increment);
197 }
198 TimeComponent::AmPm => {
199 if self.hour >= 12 {
201 self.hour -= 12;
202 } else {
203 self.hour += 12;
204 }
205 self.raise_time_changed_event();
206 }
207 }
208 self.raise_time_changed_event();
209 }
210
211 fn move_to_next_component(&mut self) {
212 self.selected_component = match self.selected_component {
213 TimeComponent::Hour => TimeComponent::Minute,
214 TimeComponent::Minute => {
215 if self.flags.contains(Flags::Seconds) {
216 TimeComponent::Second
217 } else if self.flags.contains(Flags::AMPM) {
218 TimeComponent::AmPm
219 } else {
220 TimeComponent::Hour
221 }
222 }
223 TimeComponent::Second => {
224 if self.flags.contains(Flags::AMPM) {
225 TimeComponent::AmPm
226 } else {
227 TimeComponent::Hour
228 }
229 }
230 TimeComponent::AmPm => TimeComponent::Hour,
231 };
232 self.editable_digit_is_first = true;
233 }
234
235 fn move_to_prev_component(&mut self) {
236 self.selected_component = match self.selected_component {
237 TimeComponent::Hour => {
238 if self.flags.contains(Flags::AMPM) {
239 TimeComponent::AmPm
240 } else if self.flags.contains(Flags::Seconds) {
241 TimeComponent::Second
242 } else {
243 TimeComponent::Minute
244 }
245 }
246 TimeComponent::Minute => TimeComponent::Hour,
247 TimeComponent::Second => TimeComponent::Minute,
248 TimeComponent::AmPm => {
249 if self.flags.contains(Flags::Seconds) {
250 TimeComponent::Second
251 } else {
252 TimeComponent::Minute
253 }
254 }
255 };
256 self.editable_digit_is_first = true;
257 }
258 #[inline(always)]
259 fn paint_number(&self, surface: &mut Surface, theme: &Theme, x: i32, comp: TimeComponent, attr: CharAttribute, has_focus: bool) {
260 let num = match comp {
261 TimeComponent::Hour => {
262 if self.flags.contains(Flags::AMPM) {
263 match self.hour % 12 {
264 0 => 12,
265 n => n,
266 }
267 } else {
268 self.hour
269 }
270 }
271 TimeComponent::Minute => self.minute,
272 TimeComponent::Second => self.second,
273 TimeComponent::AmPm => 0,
274 };
275 let buf: [u8; 2] = [48 + num / 10, 48 + num % 10];
276 let (attr, show_cursor) = if has_focus {
277 if comp == self.selected_component {
278 (theme.editor.pressed_or_selected, true)
279 } else if Some(comp) == self.hovered_component {
280 (theme.editor.hovered, false)
281 } else {
282 (attr, false)
283 }
284 } else if Some(comp) == self.hovered_component {
285 (theme.editor.hovered, false)
286 } else {
287 (attr, false)
288 };
289 surface.write_ascii(x, 0, &buf, attr, false);
290 if show_cursor {
291 surface.set_cursor(x + if self.editable_digit_is_first { 0 } else { 1 }, 0);
292 }
293 }
294}
295
296impl OnPaint for TimePicker {
297 fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
298 let attr = match () {
299 _ if !self.is_enabled() => theme.editor.inactive,
300 _ if self.has_focus() => theme.editor.focused,
301 _ => theme.editor.normal,
302 };
303 surface.clear(Character::with_attributes(' ', attr));
304
305 let has_focus = self.has_focus();
306 let sep = Character::with_attributes(':', theme.editor.inactive);
307
308 let mut x = 1;
309
310 self.paint_number(surface, theme, x, TimeComponent::Hour, attr, has_focus);
312 surface.write_char(x + 2, 0, sep);
313 x += 3;
314
315 self.paint_number(surface, theme, x, TimeComponent::Minute, attr, has_focus);
317 x += 2;
318
319 if self.flags.contains(Flags::Seconds) {
321 surface.write_char(x, 0, sep);
322 x += 1;
323 self.paint_number(surface, theme, x, TimeComponent::Second, attr, has_focus);
324 x += 2;
325 }
326 if self.flags.contains(Flags::AMPM) {
327 x += 1;
328 let attr = if has_focus {
329 if self.selected_component == TimeComponent::AmPm {
330 theme.editor.pressed_or_selected
331 } else if Some(TimeComponent::AmPm) == self.hovered_component {
332 theme.editor.hovered
333 } else {
334 attr
335 }
336 } else if Some(TimeComponent::AmPm) == self.hovered_component {
337 theme.editor.hovered
338 } else {
339 attr
340 };
341 if self.hour >= 12 {
342 surface.write_ascii(x, 0, b"PM", attr, false);
343 } else {
344 surface.write_ascii(x, 0, b"AM", attr, false);
345 }
346 }
347 }
348}
349
350impl OnKeyPressed for TimePicker {
351 fn on_key_pressed(&mut self, key: Key, character: char) -> EventProcessStatus {
352 match key.code {
353 KeyCode::Up => {
354 self.increment_decrement_selected_component(true);
355 EventProcessStatus::Processed
356 }
357 KeyCode::Down => {
358 self.increment_decrement_selected_component(false);
359 EventProcessStatus::Processed
360 }
361 KeyCode::Left => {
362 self.move_to_prev_component();
363 EventProcessStatus::Processed
364 }
365 KeyCode::Right => {
366 self.move_to_next_component();
367 EventProcessStatus::Processed
368 }
369 KeyCode::Backspace => {
370 self.editable_digit_is_first = true;
371 EventProcessStatus::Processed
372 }
373 _ => {
374 if character.is_ascii_digit() {
375 self.update_digit((character as u8) - b'0');
376 EventProcessStatus::Processed
377 } else {
378 EventProcessStatus::Ignored
379 }
380 }
381 }
382 }
383}
384
385impl OnMouseEvent for TimePicker {
386 fn on_mouse_event(&mut self, mouse_event: &MouseEvent) -> EventProcessStatus {
387 if !self.is_enabled() {
388 return EventProcessStatus::Ignored;
389 }
390
391 match mouse_event {
392 MouseEvent::Pressed(data) => {
393 if let Some(new_pos) = self.mouse_pos_to_component(data.x, data.y) {
394 self.selected_component = new_pos;
395 }
396 EventProcessStatus::Processed
397 }
398 MouseEvent::Enter | MouseEvent::Leave => {
399 self.hovered_component = None;
400 EventProcessStatus::Processed
401 }
402 MouseEvent::Over(point) => {
403 let new_pos = self.mouse_pos_to_component(point.x, point.y);
404 if new_pos != self.hovered_component {
405 self.hovered_component = new_pos;
406 EventProcessStatus::Processed
407 } else {
408 EventProcessStatus::Ignored
409 }
410 }
411 MouseEvent::Wheel(dir) => {
412 match dir {
413 MouseWheelDirection::Left => self.move_to_prev_component(),
414 MouseWheelDirection::Right => self.move_to_next_component(),
415 MouseWheelDirection::Up => self.increment_decrement_selected_component(true),
416 MouseWheelDirection::Down => self.increment_decrement_selected_component(false),
417 };
418 EventProcessStatus::Processed
419 }
420 _ => EventProcessStatus::Ignored,
421 }
422 }
423}
424
425impl OnFocus for TimePicker {
426 fn on_focus(&mut self) {
427 self.selected_component = TimeComponent::Hour;
428 self.editable_digit_is_first = true;
429 }
430}