CalendarMonthView lays a month in week rows. An event across days lies over them as a bar in the first free lane, cut at the week's edge; the rest sit by the hour as chips, and a crowded day keeps the rest behind "N more". Arrows move a day, Page keys turn the month, and Enter or a double press opens the day; the cursor holds through the owner's echo of a turn. Days sit at sevenths of their row, as the bars do, so text never widens a column. YearView shows twelve small months; a day with something on it wears a dot in its first event's hue. EventChip is an event in a line and EventCard a block with its hours and place. MiniCalendar points to forms::Calendar. Palette::hue is now the one home for a chart color by index, for mail labels and events alike.
147 lines
4.6 KiB
Rust
147 lines
4.6 KiB
Rust
use std::rc::Rc;
|
|
|
|
use gpui::{
|
|
App, ElementId, FontWeight, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement,
|
|
RenderOnce, StatefulInteractiveElement, Styled, Window, div, prelude::*, relative,
|
|
};
|
|
use jiff::tz::TimeZone;
|
|
|
|
use super::event::{Event, When};
|
|
use crate::{
|
|
forms::Run,
|
|
primitives::{FocusRing, tab_stop},
|
|
theme::{ActiveTheme, Radius, TextSize},
|
|
typography::{Ellipsis, format},
|
|
};
|
|
|
|
/// Event `event`'s color: the chart color at its hue.
|
|
pub(crate) fn tint(event: &Event, cx: &App) -> Hsla {
|
|
cx.theme()
|
|
.colors
|
|
.hue(event.hue, format_args!("event {}", event.key))
|
|
}
|
|
|
|
/// An event in a line: a dot, the hour it starts and its title; or, for whole days, its title on a wash of its color. Pressable with `on_click`.
|
|
#[derive(IntoElement)]
|
|
pub struct EventChip {
|
|
id: ElementId,
|
|
event: Event,
|
|
zone: Option<TimeZone>,
|
|
before: bool,
|
|
after: bool,
|
|
on_click: Option<Run>,
|
|
}
|
|
|
|
impl EventChip {
|
|
pub fn new(id: impl Into<ElementId>, event: Event) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
event,
|
|
zone: None,
|
|
before: false,
|
|
after: false,
|
|
on_click: None,
|
|
}
|
|
}
|
|
|
|
/// The zone its hour reads in; the system's otherwise.
|
|
pub fn zone(mut self, zone: TimeZone) -> Self {
|
|
self.zone = Some(zone);
|
|
self
|
|
}
|
|
|
|
/// Squares the ends where the event runs on past the chip, as a bar cut at a week's edge.
|
|
pub(crate) fn open(mut self, before: bool, after: bool) -> Self {
|
|
self.before = before;
|
|
self.after = after;
|
|
self
|
|
}
|
|
|
|
pub fn on_click(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
|
|
self.on_click = Some(Rc::new(handler));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for EventChip {
|
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let zone = self
|
|
.zone
|
|
.unwrap_or_else(|| format::system_zone("event chip"));
|
|
let focus = tab_stop(
|
|
(self.id.clone(), "focus").into(),
|
|
self.on_click.is_some(),
|
|
window,
|
|
cx,
|
|
);
|
|
let theme = cx.theme();
|
|
let colors = &theme.colors;
|
|
let tint = tint(&self.event, cx);
|
|
let round = theme.radius(Radius::Sm);
|
|
let spans = self.event.spans(&zone);
|
|
let hour = match self.event.when {
|
|
When::Timed { start, .. } if !spans => {
|
|
Some(format::datetime(start, &zone, "%H:%M").expect("a fixed pattern formats"))
|
|
}
|
|
_ => None,
|
|
};
|
|
let key = self.event.key.clone();
|
|
div()
|
|
.id(self.id.clone())
|
|
.debug_selector(move || format!("event-chip {key}"))
|
|
.track_focus(&focus)
|
|
.flex()
|
|
.items_center()
|
|
.gap_1()
|
|
.w_full()
|
|
.h(theme.calendar().chip)
|
|
.px_1p5()
|
|
.rounded(round)
|
|
.when(self.before, |chip| chip.rounded_l_none())
|
|
.when(self.after, |chip| chip.rounded_r_none())
|
|
.border_1()
|
|
.border_color(gpui::transparent_black())
|
|
.focus_ring(cx)
|
|
.text_size(theme.text_size(TextSize::Xs))
|
|
.line_height(relative(1.0))
|
|
.text_color(colors.fg)
|
|
.map(|chip| match &hour {
|
|
Some(_) => chip.hover(|style| style.bg(colors.hover)),
|
|
None => chip.bg(tint.alpha(0.18)).font_weight(FontWeight::MEDIUM),
|
|
})
|
|
.when_some(self.on_click, |chip, on_click| {
|
|
let key = self.event.key.clone();
|
|
chip.cursor_pointer()
|
|
.on_mouse_down(MouseButton::Left, |_, window, cx| {
|
|
window.prevent_default();
|
|
cx.stop_propagation();
|
|
})
|
|
.on_click(move |_, window, cx| {
|
|
log::info!("event chip: {key}");
|
|
on_click(window, cx)
|
|
})
|
|
})
|
|
.when(hour.is_some(), |chip| {
|
|
chip.child(
|
|
div()
|
|
.flex_none()
|
|
.size(theme.status_dot())
|
|
.rounded_full()
|
|
.bg(tint),
|
|
)
|
|
})
|
|
.children(hour.map(|hour| {
|
|
div()
|
|
.min_w_0()
|
|
.text_color(colors.fg_muted)
|
|
.child(Ellipsis::new(hour))
|
|
}))
|
|
.child(
|
|
div()
|
|
.flex_1()
|
|
.min_w_0()
|
|
.child(Ellipsis::new(self.event.title.clone())),
|
|
)
|
|
}
|
|
}
|