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