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 let mut r = match event {
334 ct_event!(mouse any for m)
335 if self.mouse.drag(
336 &[self.area_cal, self.area_weeknum], m,
338 ) =>
339 {
340 if self.mouse.drag.get() == Some(0) {
341 if let Some(sel) = item_at(&self.area_days, m.column, m.row) {
342 self.select_day(sel, true)
343 } else {
344 let mut r = CalOutcome::Continue;
345
346 let mut above = self.area_cal;
347 above.y -= 2;
348 above.height = 3;
349 if above.contains((m.column, m.row).into()) {
350 r = self.select_day(0, true);
351 } else {
352 let mut below = self.area_cal;
353 below.y = below.bottom().saturating_sub(1);
354 below.height = 2;
355 if below.contains((m.column, m.row).into()) {
356 r = self.select_day(self.end_date().day0() as usize, true);
357 }
358 }
359 r
360 }
361 } else if self.mouse.drag.get() == Some(1) {
362 if let Some(sel) = item_at(&self.area_weeks, m.column, m.row) {
363 self.select_week(sel, true)
364 } else {
365 let mut r = CalOutcome::Continue;
366
367 let mut above = self.area_weeknum;
368 above.y -= 2;
369 above.height = 3;
370 if above.contains((m.column, m.row).into()) {
371 r = self.select_week(0, true);
372 } else {
373 let mut below = self.area_cal;
374 below.y = below.bottom().saturating_sub(1);
375 below.height = 2;
376 if below.contains((m.column, m.row).into()) {
377 r = self.select_week(self.end_date().day0() as usize, true);
378 }
379 }
380 r
381 }
382 } else {
383 CalOutcome::Continue
384 }
385 }
386 _ => CalOutcome::Continue,
387 };
388
389 r = r.or_else(|| match event {
390 ct_event!(mouse down Left for x, y) => {
391 if let Some(sel) = item_at(&self.area_weeks, *x, *y) {
392 self.select_week(sel, false)
393 } else if let Some(sel) = item_at(&self.area_days, *x, *y) {
394 self.select_day(sel, false)
395 } else {
396 CalOutcome::Continue
397 }
398 }
399 ct_event!(mouse down CONTROL-Left for x, y) => {
400 if let Some(sel) = item_at(&self.area_weeks, *x, *y) {
401 self.select_week(sel, true)
402 } else if let Some(sel) = item_at(&self.area_days, *x, *y) {
403 self.select_day(sel, true)
404 } else {
405 CalOutcome::Continue
406 }
407 }
408 _ => CalOutcome::Continue,
409 });
410
411 r
412 }
413}
414
415impl<const N: usize> HandleEvent<Event, Regular, CalOutcome> for CalendarState<N, RangeSelection> {
416 fn handle(&mut self, event: &Event, _qualifier: Regular) -> CalOutcome {
417 let mut r = 'f: {
418 for month in &mut self.months {
419 let r = month.handle(event, Regular);
420 if r == CalOutcome::Selected {
421 self.focus_lead();
422 break 'f r;
423 }
424 }
425 CalOutcome::Continue
426 };
427 if r.is_consumed() {
429 let mut drag = None;
430 for m in &self.months {
431 if let Some(d) = m.mouse.drag.get() {
432 drag = Some(d);
433 break;
434 }
435 }
436 if drag.is_some() {
437 for m in &self.months {
438 m.mouse.drag.set(drag);
439 }
440 }
441 }
442
443 r = r.or_else(|| {
444 if self.is_focused() {
445 match event {
446 ct_event!(key press CONTROL-'a') => {
447 if self.select_month(self.months[self.primary_idx()].start_date(), false) {
448 CalOutcome::Selected
449 } else {
450 CalOutcome::Continue
451 }
452 }
453 ct_event!(keycode press CONTROL-Home) => self.move_to_today(),
454 ct_event!(keycode press PageUp) => {
455 self.move_to_prev(Months::new(1), Days::new(0))
456 }
457 ct_event!(keycode press PageDown) => {
458 self.move_to_next(Months::new(1), Days::new(0))
459 }
460 ct_event!(keycode press SHIFT-PageUp) => self.prev_month(1, true),
461 ct_event!(keycode press SHIFT-PageDown) => self.next_month(1, true),
462
463 ct_event!(keycode press Up) => self.prev_day(7, false),
464 ct_event!(keycode press Down) => self.next_day(7, false),
465 ct_event!(keycode press Left) => self.prev_day(1, false),
466 ct_event!(keycode press Right) => self.next_day(1, false),
467 ct_event!(keycode press SHIFT-Up) => self.prev_day(7, true),
468 ct_event!(keycode press SHIFT-Down) => self.next_day(7, true),
469 ct_event!(keycode press SHIFT-Left) => self.prev_day(1, true),
470 ct_event!(keycode press SHIFT-Right) => self.next_day(1, true),
471
472 ct_event!(keycode press ALT-Up) => self.prev_week(1, false),
473 ct_event!(keycode press ALT-Down) => self.next_week(1, false),
474 ct_event!(keycode press ALT_SHIFT-Up) => self.prev_week(1, true),
475 ct_event!(keycode press ALT_SHIFT-Down) => self.next_week(1, true),
476
477 _ => CalOutcome::Continue,
478 }
479 } else {
480 CalOutcome::Continue
481 }
482 });
483
484 r.or_else(|| self.handle(event, MouseOnly))
485 }
486}
487
488impl<const N: usize> HandleEvent<Event, MouseOnly, CalOutcome>
489 for CalendarState<N, RangeSelection>
490{
491 fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> CalOutcome {
492 for i in 0..self.months.len() {
493 if self.months[i].gained_focus() {
494 self.set_primary_idx(i);
495 break;
496 }
497 }
498
499 let all_areas = self
500 .months
501 .iter()
502 .map(|v| v.area)
503 .reduce(|v, w| v.union(w))
504 .unwrap_or_default();
505 match event {
506 ct_event!(scroll up for x,y) if all_areas.contains((*x, *y).into()) => {
507 self.scroll_back(self.step())
508 }
509 ct_event!(scroll down for x,y) if all_areas.contains((*x, *y).into()) => {
510 self.scroll_forward(self.step())
511 }
512 _ => CalOutcome::Continue,
513 }
514 }
515}