use super::core::RangePicker;
use super::types::RangeFocus;
use crate::render::{Cell, Modifier};
use crate::style::Color;
use crate::utils::unicode::char_width;
use crate::widget::data::calendar::{days_in_month, Date};
use crate::widget::theme::{DISABLED_FG, LIGHT_GRAY, SECONDARY_TEXT};
use crate::widget::traits::{RenderContext, View};
use crate::{impl_styled_view, impl_widget_builders};
impl View for RangePicker {
fn render(&self, ctx: &mut RenderContext) {
let area = ctx.area;
if area.width < 50 || area.height < 10 {
return;
}
let x: u16 = 0;
let y: u16 = 0;
let start_focused = self.focus == RangeFocus::Start;
self.render_calendar(
ctx,
x,
y,
&self.start.date,
self.start_cursor_day,
true,
start_focused,
);
let end_x = x + 24;
let end_focused = self.focus == RangeFocus::End;
self.render_calendar(
ctx,
end_x,
y,
&self.end.date,
self.end_cursor_day,
false,
end_focused,
);
if self.show_presets {
let presets_x = end_x + 24;
let presets_focused = self.focus == RangeFocus::Presets;
self.render_presets(ctx, presets_x, y, presets_focused);
}
let summary_y = y + 9;
let range_str = format!(
"Range: {}-{:02}-{:02} to {}-{:02}-{:02}",
self.start.date.year,
self.start.date.month,
self.start.date.day,
self.end.date.year,
self.end.date.month,
self.end.date.day,
);
self.draw_text(ctx, x, summary_y, &range_str, SECONDARY_TEXT, false);
let help = "Tab: switch | ←→↑↓: navigate | [/]: month | Enter: select";
self.draw_text(ctx, x, summary_y + 1, help, DISABLED_FG, false);
}
}
impl_styled_view!(RangePicker);
impl_widget_builders!(RangePicker);
impl RangePicker {
fn first_weekday(&self, date: &Date) -> u32 {
let m = if date.month < 3 {
date.month as i32 + 12
} else {
date.month as i32
};
let y = if date.month < 3 {
date.year - 1
} else {
date.year
};
let k = y % 100;
let j = y / 100;
let h = (1 + (13 * (m + 1)) / 5 + k + k / 4 + j / 4 - 2 * j) % 7;
let h = ((h + 6) % 7 + 7) % 7;
match self.first_day {
crate::widget::data::calendar::FirstDayOfWeek::Sunday => h as u32,
crate::widget::data::calendar::FirstDayOfWeek::Monday => ((h + 6) % 7) as u32,
}
}
#[allow(clippy::too_many_arguments)]
fn render_calendar(
&self,
ctx: &mut RenderContext,
x: u16,
y: u16,
date: &Date,
cursor_day: u32,
is_start: bool,
is_focused: bool,
) {
let days = days_in_month(date.year, date.month);
let first_weekday = self.first_weekday(date);
let title = if is_start { "Start" } else { "End" };
let header = format!(
"{}: {} {}",
title,
super::impls::month_name(date.month),
date.year
);
let header_color = if is_focused {
self.header_fg
} else {
DISABLED_FG
};
self.draw_text(ctx, x, y, &header, header_color, true);
let day_headers = match self.first_day {
crate::widget::data::calendar::FirstDayOfWeek::Sunday => "Su Mo Tu We Th Fr Sa",
crate::widget::data::calendar::FirstDayOfWeek::Monday => "Mo Tu We Th Fr Sa Su",
};
self.draw_text(ctx, x, y + 1, day_headers, LIGHT_GRAY, false);
let mut row = 0u16;
let mut col = first_weekday as u16;
let selected_day = if is_start {
self.start.date.day
} else {
self.end.date.day
};
for day in 1..=days {
let day_x = x + col * 3;
let day_y = y + 2 + row;
let day_str = format!("{:2}", day);
let check_date = Date::new(date.year, date.month, day);
let in_range = self.is_in_range(&check_date);
let is_selected = day == selected_day;
let is_cursor = day == cursor_day && is_focused;
let (fg, bg, bold) = if is_cursor {
(Color::BLACK, Some(Color::WHITE), true)
} else if is_selected {
(self.selected_fg, Some(self.selected_bg), true)
} else if in_range {
(Color::WHITE, Some(self.range_bg), false)
} else {
(Color::WHITE, None, false)
};
if let Some(bg_color) = bg {
for i in 0..2 {
let mut cell = Cell::new(' ');
cell.bg = Some(bg_color);
ctx.set(day_x + i, day_y, cell);
}
}
self.draw_text(ctx, day_x, day_y, &day_str, fg, bold);
col += 1;
if col > 6 {
col = 0;
row += 1;
}
}
}
fn render_presets(&self, ctx: &mut RenderContext, x: u16, y: u16, is_focused: bool) {
let title_color = if is_focused {
self.header_fg
} else {
DISABLED_FG
};
self.draw_text(ctx, x, y, "Presets", title_color, true);
for (i, preset) in self.presets.iter().enumerate() {
let preset_y = y + 1 + i as u16;
let is_cursor = i == self.preset_cursor && is_focused;
let is_active = self.active_preset == Some(*preset);
let (fg, bg) = if is_cursor {
(self.preset_selected_fg, Some(self.preset_selected_bg))
} else if is_active {
(Color::CYAN, None)
} else {
(self.preset_fg, None)
};
let marker = if is_active { "● " } else { " " };
let text = format!("{}{}", marker, preset.name());
if let Some(bg_color) = bg {
for dx in 0..16 {
let mut cell = Cell::new(' ');
cell.bg = Some(bg_color);
ctx.set(x + dx, preset_y, cell);
}
}
self.draw_text(ctx, x, preset_y, &text, fg, is_cursor);
}
}
fn draw_text(
&self,
ctx: &mut RenderContext,
x: u16,
y: u16,
text: &str,
color: Color,
bold: bool,
) {
let mut offset = 0u16;
for ch in text.chars() {
let ch_width = char_width(ch) as u16;
if ch_width == 0 {
continue;
}
let mut cell = Cell::new(ch);
cell.fg = Some(color);
if bold {
cell.modifier |= Modifier::BOLD;
}
ctx.set(x + offset, y, cell);
for i in 1..ch_width {
ctx.set(x + offset + i, y, Cell::continuation());
}
offset += ch_width;
}
}
}