1use crate::calendar::calendar::CalendarState;
2use crate::calendar::event::CalOutcome;
3use crate::calendar::{
4 CalendarSelection, MonthState, first_day_of_month, is_first_day_of_month, is_last_day_of_month,
5 is_same_month, is_same_week, last_day_of_month,
6};
7use chrono::{Datelike, Days, Months, NaiveDate, Weekday};
8use rat_event::util::item_at;
9use rat_event::{ConsumedEvent, event_flow};
10use rat_event::{HandleEvent, MouseOnly, Regular, ct_event};
11use rat_focus::HasFocus;
12use ratatui_crossterm::crossterm::event::Event;
13use std::ops::RangeInclusive;
14
15#[derive(Debug, Default, Clone)]
26pub struct RangeSelection {
27 anchor: Option<NaiveDate>,
28 lead: Option<NaiveDate>,
29}
30
31impl CalendarSelection for RangeSelection {
32 fn count(&self) -> usize {
34 if let Some(anchor) = self.anchor {
35 if let Some(lead) = self.lead {
36 (lead - anchor).num_days().unsigned_abs() as usize + 1
37 } else {
38 unreachable!()
39 }
40 } else {
41 0
42 }
43 }
44
45 fn is_selected(&self, date: NaiveDate) -> bool {
46 if let Some(lead) = self.lead {
47 if let Some(anchor) = self.anchor {
48 if lead > anchor {
49 date >= anchor && date <= lead
50 } else {
51 date >= lead && date <= anchor
52 }
53 } else {
54 unreachable!()
55 }
56 } else {
57 false
58 }
59 }
60
61 fn lead_selection(&self) -> Option<NaiveDate> {
62 self.lead
63 }
64}
65
66impl RangeSelection {
67 pub fn clear(&mut self) {
69 self.anchor = None;
70 self.lead = None;
71 }
72
73 pub fn select_month(&mut self, date: NaiveDate, extend: bool) -> bool {
80 let old = (self.anchor, self.lead);
81
82 let new_start = first_day_of_month(date);
83 let new_end = last_day_of_month(date);
84
85 if extend {
86 if let Some(mut lead) = self.lead {
87 let Some(mut anchor) = self.anchor else {
88 unreachable!();
89 };
90
91 if lead <= anchor {
93 if !is_first_day_of_month(lead) || !is_last_day_of_month(anchor) {
94 lead = first_day_of_month(lead);
95 anchor = last_day_of_month(anchor);
96 self.lead = Some(lead);
97 self.anchor = Some(anchor);
98 }
99 } else {
100 if !is_last_day_of_month(lead) || !is_first_day_of_month(anchor) {
101 lead = last_day_of_month(lead);
102 anchor = first_day_of_month(anchor);
103 self.lead = Some(lead);
104 self.anchor = Some(anchor);
105 }
106 }
107
108 if is_same_month(lead, anchor) {
109 if lead <= anchor {
111 if new_start < anchor {
112 self.lead = Some(new_start);
113 } else {
114 self.lead = Some(new_end);
115 self.anchor = Some(lead);
116 }
117 } else {
118 if new_start > anchor {
119 self.lead = Some(new_end);
120 } else {
121 self.lead = Some(new_start);
122 self.anchor = Some(lead);
123 }
124 }
125 } else {
126 if lead < anchor {
128 if new_start <= lead {
129 self.lead = Some(new_start);
130 } else if new_end <= anchor {
131 self.lead = Some(new_start);
132 } else {
133 self.lead = Some(new_end);
134 }
135 } else {
136 if new_end <= lead {
137 self.lead = Some(new_end);
138 } else if new_start >= anchor {
139 self.lead = Some(new_end);
140 } else {
141 self.lead = Some(new_start);
142 }
143 }
144 }
145 } else {
146 self.lead = Some(new_start);
147 self.anchor = Some(new_end);
148 }
149 } else {
150 self.lead = Some(new_start);
151 self.anchor = Some(new_end);
152 }
153
154 old != (self.anchor, self.lead)
155 }
156
157 pub fn select_week(&mut self, date: NaiveDate, extend: bool) -> bool {
164 let old = (self.anchor, self.lead);
165
166 let new_start = date.week(Weekday::Mon).first_day();
167 let new_end = date.week(Weekday::Mon).last_day();
168
169 if extend {
170 if let Some(mut lead) = self.lead {
171 let Some(mut anchor) = self.anchor else {
172 unreachable!();
173 };
174
175 if lead <= anchor {
177 if lead.weekday() != Weekday::Mon || anchor.weekday() != Weekday::Sun {
178 lead = lead.week(Weekday::Mon).first_day();
179 anchor = anchor.week(Weekday::Mon).last_day();
180 self.lead = Some(lead);
181 self.anchor = Some(anchor);
182 }
183 } else {
184 if lead.weekday() != Weekday::Sun || anchor.weekday() != Weekday::Mon {
185 lead = lead.week(Weekday::Mon).last_day();
186 anchor = anchor.week(Weekday::Mon).first_day();
187 self.lead = Some(lead);
188 self.anchor = Some(anchor);
189 }
190 }
191
192 if is_same_week(lead, anchor) {
193 if lead <= anchor {
195 if new_start < anchor {
196 self.lead = Some(new_start);
197 } else {
198 self.lead = Some(new_end);
199 self.anchor = Some(lead);
200 }
201 } else {
202 if new_start > anchor {
203 self.lead = Some(new_end);
204 } else {
205 self.lead = Some(new_start);
206 self.anchor = Some(lead);
207 }
208 }
209 } else {
210 if lead < anchor {
212 if new_start <= lead {
213 self.lead = Some(new_start);
214 } else if new_end <= anchor {
215 self.lead = Some(new_start);
216 } else {
217 self.lead = Some(new_end);
218 }
219 } else {
220 if new_end <= lead {
221 self.lead = Some(new_end);
222 } else if new_start >= anchor {
223 self.lead = Some(new_end);
224 } else {
225 self.lead = Some(new_start);
226 }
227 }
228 }
229 } else {
230 self.lead = Some(new_start);
231 self.anchor = Some(new_end);
232 }
233 } else {
234 self.lead = Some(new_start);
235 self.anchor = Some(new_end);
236 }
237
238 old != (self.anchor, self.lead)
239 }
240
241 pub fn select_day(&mut self, date: NaiveDate, extend: bool) -> bool {
243 let old = (self.anchor, self.lead);
244
245 if extend {
246 self.lead = Some(date);
247 if self.anchor.is_none() {
248 self.anchor = Some(date);
249 }
250 } else {
251 self.anchor = Some(date);
252 self.lead = Some(date);
253 }
254
255 old != (self.anchor, self.lead)
256 }
257
258 pub fn select(&mut self, selection: (NaiveDate, NaiveDate)) -> bool {
260 let old = (self.anchor, self.lead);
261
262 self.anchor = Some(selection.0);
263 self.lead = Some(selection.1);
264
265 old != (self.anchor, self.lead)
266 }
267
268 pub fn selected(&self) -> Option<(NaiveDate, NaiveDate)> {
270 if let Some(anchor) = self.anchor {
271 if let Some(lead) = self.lead {
272 Some((anchor, lead))
273 } else {
274 unreachable!()
275 }
276 } else {
277 None
278 }
279 }
280
281 pub fn selected_range(&self) -> Option<RangeInclusive<NaiveDate>> {
283 if let Some(anchor) = self.anchor {
284 if let Some(lead) = self.lead {
285 if lead > anchor {
286 Some(anchor..=lead)
287 } else {
288 Some(lead..=anchor)
289 }
290 } else {
291 unreachable!()
292 }
293 } else {
294 None
295 }
296 }
297}
298
299impl HandleEvent<Event, Regular, CalOutcome> for MonthState<RangeSelection> {
300 fn handle(&mut self, event: &Event, _qualifier: Regular) -> CalOutcome {
301 if self.is_focused() {
302 event_flow!(
303 return match event {
304 ct_event!(keycode press Home) => self.select_day(0, false),
305 ct_event!(keycode press End) => self.select_last(false),
306 ct_event!(keycode press SHIFT-Home) => self.select_day(0, true),
307 ct_event!(keycode press SHIFT-End) => self.select_last(true),
308
309 ct_event!(keycode press Up) => self.prev_day(7, false),
310 ct_event!(keycode press Down) => self.next_day(7, false),
311 ct_event!(keycode press Left) => self.prev_day(1, false),
312 ct_event!(keycode press Right) => self.next_day(1, false),
313 ct_event!(keycode press SHIFT-Up) => self.prev_day(7, true),
314 ct_event!(keycode press SHIFT-Down) => self.next_day(7, true),
315 ct_event!(keycode press SHIFT-Left) => self.prev_day(1, true),
316 ct_event!(keycode press SHIFT-Right) => self.next_day(1, true),
317
318 ct_event!(keycode press ALT-Up) => self.prev_week(1, false),
319 ct_event!(keycode press ALT-Down) => self.next_week(1, false),
320 ct_event!(keycode press ALT_SHIFT-Up) => self.prev_week(1, true),
321 ct_event!(keycode press ALT_SHIFT-Down) => self.next_week(1, true),
322 _ => CalOutcome::Continue,
323 }
324 )
325 }
326
327 self.handle(event, MouseOnly)
328 }
329}
330
331impl HandleEvent<Event, MouseOnly, CalOutcome> for MonthState<RangeSelection> {
332 fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> CalOutcome {
333 if !self.has_mouse_focus() {
334 return CalOutcome::Continue
335 }
336 let mut r = match event {
337 ct_event!(mouse any for m)
338 if self.mouse.drag(
339 &[self.area_cal, self.area_weeknum], m,
341 ) =>
342 {
343 if self.mouse.drag.get() == Some(0) {
344 if let Some(sel) = item_at(&self.area_days, m.column, m.row) {
345 self.select_day(sel, true)
346 } else {
347 let mut r = CalOutcome::Continue;
348
349 let mut above = self.area_cal;
350 above.y -= 2;
351 above.height = 3;
352 if above.contains((m.column, m.row).into()) {
353 r = self.select_day(0, true);
354 } else {
355 let mut below = self.area_cal;
356 below.y = below.bottom().saturating_sub(1);
357 below.height = 2;
358 if below.contains((m.column, m.row).into()) {
359 r = self.select_day(self.end_date().day0() as usize, true);
360 }
361 }
362 r
363 }
364 } else if self.mouse.drag.get() == Some(1) {
365 if let Some(sel) = item_at(&self.area_weeks, m.column, m.row) {
366 self.select_week(sel, true)
367 } else {
368 let mut r = CalOutcome::Continue;
369
370 let mut above = self.area_weeknum;
371 above.y -= 2;
372 above.height = 3;
373 if above.contains((m.column, m.row).into()) {
374 r = self.select_week(0, true);
375 } else {
376 let mut below = self.area_cal;
377 below.y = below.bottom().saturating_sub(1);
378 below.height = 2;
379 if below.contains((m.column, m.row).into()) {
380 r = self.select_week(self.end_date().day0() as usize, true);
381 }
382 }
383 r
384 }
385 } else {
386 CalOutcome::Continue
387 }
388 }
389 _ => CalOutcome::Continue,
390 };
391
392 r = r.or_else(|| match event {
393 ct_event!(mouse down Left for x, y) => {
394 if let Some(sel) = item_at(&self.area_weeks, *x, *y) {
395 self.select_week(sel, false)
396 } else if let Some(sel) = item_at(&self.area_days, *x, *y) {
397 self.select_day(sel, false)
398 } else {
399 CalOutcome::Continue
400 }
401 }
402 ct_event!(mouse down CONTROL-Left for x, y) => {
403 if let Some(sel) = item_at(&self.area_weeks, *x, *y) {
404 self.select_week(sel, true)
405 } else if let Some(sel) = item_at(&self.area_days, *x, *y) {
406 self.select_day(sel, true)
407 } else {
408 CalOutcome::Continue
409 }
410 }
411 _ => CalOutcome::Continue,
412 });
413
414 r
415 }
416}
417
418impl<const N: usize> HandleEvent<Event, Regular, CalOutcome> for CalendarState<N, RangeSelection> {
419 fn handle(&mut self, event: &Event, _qualifier: Regular) -> CalOutcome {
420 let mut r = 'f: {
421 for month in &mut self.months {
422 let r = month.handle(event, Regular);
423 if r == CalOutcome::Selected {
424 self.focus_lead();
425 break 'f r;
426 }
427 }
428 CalOutcome::Continue
429 };
430 if r.is_consumed() {
432 let mut drag = None;
433 for m in &self.months {
434 if let Some(d) = m.mouse.drag.get() {
435 drag = Some(d);
436 break;
437 }
438 }
439 if drag.is_some() {
440 for m in &self.months {
441 m.mouse.drag.set(drag);
442 }
443 }
444 }
445
446 r = r.or_else(|| {
447 if self.is_focused() {
448 match event {
449 ct_event!(key press CONTROL-'a') => {
450 if self.select_month(self.months[self.primary_idx()].start_date(), false) {
451 CalOutcome::Selected
452 } else {
453 CalOutcome::Continue
454 }
455 }
456 ct_event!(keycode press CONTROL-Home) => self.move_to_today(),
457 ct_event!(keycode press PageUp) => {
458 self.move_to_prev(Months::new(1), Days::new(0))
459 }
460 ct_event!(keycode press PageDown) => {
461 self.move_to_next(Months::new(1), Days::new(0))
462 }
463 ct_event!(keycode press SHIFT-PageUp) => self.prev_month(1, true),
464 ct_event!(keycode press SHIFT-PageDown) => self.next_month(1, true),
465
466 ct_event!(keycode press Up) => self.prev_day(7, false),
467 ct_event!(keycode press Down) => self.next_day(7, false),
468 ct_event!(keycode press Left) => self.prev_day(1, false),
469 ct_event!(keycode press Right) => self.next_day(1, false),
470 ct_event!(keycode press SHIFT-Up) => self.prev_day(7, true),
471 ct_event!(keycode press SHIFT-Down) => self.next_day(7, true),
472 ct_event!(keycode press SHIFT-Left) => self.prev_day(1, true),
473 ct_event!(keycode press SHIFT-Right) => self.next_day(1, true),
474
475 ct_event!(keycode press ALT-Up) => self.prev_week(1, false),
476 ct_event!(keycode press ALT-Down) => self.next_week(1, false),
477 ct_event!(keycode press ALT_SHIFT-Up) => self.prev_week(1, true),
478 ct_event!(keycode press ALT_SHIFT-Down) => self.next_week(1, true),
479
480 _ => CalOutcome::Continue,
481 }
482 } else {
483 CalOutcome::Continue
484 }
485 });
486
487 r.or_else(|| self.handle(event, MouseOnly))
488 }
489}
490
491impl<const N: usize> HandleEvent<Event, MouseOnly, CalOutcome>
492 for CalendarState<N, RangeSelection>
493{
494 fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> CalOutcome {
495 if !self.has_mouse_focus() {
496 return CalOutcome::Continue
497 }
498
499 for i in 0..self.months.len() {
500 if self.months[i].gained_focus() {
501 self.set_primary_idx(i);
502 break;
503 }
504 }
505
506 let all_areas = self
507 .months
508 .iter()
509 .map(|v| v.area)
510 .reduce(|v, w| v.union(w))
511 .unwrap_or_default();
512 match event {
513 ct_event!(scroll up for x,y) if all_areas.contains((*x, *y).into()) => {
514 self.scroll_back(self.step())
515 }
516 ct_event!(scroll down for x,y) if all_areas.contains((*x, *y).into()) => {
517 self.scroll_forward(self.step())
518 }
519 _ => CalOutcome::Continue,
520 }
521 }
522}