Calendar · Time grid: week and day views, drag to make and to resize
TimeGrid sets days side by side under a column of hours. Each event is a card by its hours, clipped to each day it touches, and events that overlap share the width in lanes. A drag on empty time makes an event by quarter hours through forms::pen; a drag on an event's lower edge, on the day it ends, moves its end, each drag checked against its owner. The grid opens at half past seven, or an hour and a half before now when today is shown and that is later. CurrentTimeIndicator marks the minute now on today and moves with the clock. TimezoneOverlay adds another zone's hours beside the grid's. AllDayRow lays whole days across their columns in lanes. CalendarWeekView and CalendarDayView are one view of seven days or one: a header that steps and comes back to today, the days' names stacked so they fit narrow columns, the whole days, then the hours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f316ab56ba
commit
a41cb2f9c2
18 files changed
+1577
-36
No files matched your search
@@ -2,13 +2,16 @@ use ely_gpui_component::{
|
||||
calendar::Event,
|
||||
theme::{ActiveTheme, TextSize},
|
||||
};
|
||||
use gpui::{AnyElement, App, IntoElement, ParentElement, SharedString, Styled, Window, div};
|
||||
use gpui::{
|
||||
AnyElement, App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div,
|
||||
};
|
||||
use jiff::{Timestamp, ToSpan, civil::Date, tz::TimeZone};
|
||||
|
||||
mod months;
|
||||
mod times;
|
||||
|
||||
use super::Page;
|
||||
use crate::script::Step;
|
||||
use crate::{script::Step, ui::keep};
|
||||
|
||||
pub const PAGE: Page = Page {
|
||||
number: 30,
|
||||
@@ -35,6 +38,13 @@ const SCRIPT: &[Step] = &[
|
||||
Step::Key("right"),
|
||||
Step::Wait(150),
|
||||
Step::Shot("year-cursor"),
|
||||
Step::DownAt("calendar-week", 560.0, 300.0),
|
||||
Step::DragTo("calendar-week", 560.0, 380.0),
|
||||
Step::Wait(150),
|
||||
Step::Shot("week-drafting"),
|
||||
Step::UpAt("calendar-week", 560.0, 380.0),
|
||||
Step::Wait(200),
|
||||
Step::Shot("week-made"),
|
||||
];
|
||||
|
||||
/// What a demo says it did, under it.
|
||||
@@ -53,6 +63,12 @@ fn here() -> (TimeZone, Date) {
|
||||
(zone, today)
|
||||
}
|
||||
|
||||
/// The page's events, which a drag on the week or day adds to.
|
||||
fn plan(window: &mut Window, cx: &mut App) -> Entity<Vec<Event>> {
|
||||
let (zone, today) = here();
|
||||
keep("calendar-events", || events(today, &zone), window, cx)
|
||||
}
|
||||
|
||||
/// A studio's weeks around today: meetings, a crowded day, an offsite, a trip past a weekend, a night shoot past midnight.
|
||||
fn events(today: Date, zone: &TimeZone) -> Vec<Event> {
|
||||
let day = |offset: i64| today.checked_add(offset.days()).expect("a day nearby");
|
||||
@@ -83,6 +99,8 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
div()
|
||||
.child(months::month(window, cx))
|
||||
.child(months::mini(window, cx))
|
||||
.child(times::week(window, cx))
|
||||
.child(times::day(window, cx))
|
||||
.child(months::year(window, cx))
|
||||
.child(months::marks(cx))
|
||||
.into_any_element()
|
||||
|
||||
@@ -7,7 +7,7 @@ use gpui::{
|
||||
};
|
||||
use jiff::civil::Date;
|
||||
|
||||
use super::{events, here, quiet};
|
||||
use super::{events, here, plan, quiet};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{change, keep, section, set},
|
||||
@@ -37,11 +37,12 @@ fn viewing(window: &mut Window, cx: &mut App) -> Entity<Viewing> {
|
||||
}
|
||||
|
||||
pub fn month(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let (zone, _) = here();
|
||||
let viewing = viewing(window, cx);
|
||||
let now = viewing.read(cx).clone();
|
||||
let (turned, opened, pressed) = (viewing.clone(), viewing.clone(), viewing);
|
||||
let view = CalendarMonthView::new("calendar-month", now.month, events(today, &zone))
|
||||
let events = plan(window, cx).read(cx).clone();
|
||||
let view = CalendarMonthView::new("calendar-month", now.month, events)
|
||||
.zone(zone)
|
||||
.on_month(move |first, _, cx| change(&turned, cx, |viewing| viewing.month = first))
|
||||
.on_day(move |day, _, cx| {
|
||||
@@ -92,6 +93,7 @@ pub fn mini(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
|
||||
pub fn year(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let events = plan(window, cx).read(cx).clone();
|
||||
let said = keep("calendar-year-said", || None::<SharedString>, window, cx);
|
||||
let shown = said.read(cx).clone();
|
||||
section(
|
||||
@@ -107,7 +109,7 @@ pub fn year(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(
|
||||
YearView::new("calendar-year", today.year(), events(today, &zone))
|
||||
YearView::new("calendar-year", today.year(), events)
|
||||
.zone(zone)
|
||||
.on_day(move |day, _, cx| {
|
||||
let opened = format!("Opened {}.", day.strftime("%A, %B %-d"));
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
use ely_gpui_component::calendar::{
|
||||
CalendarDayView, CalendarWeekView, Event, TimezoneOverlay, When,
|
||||
};
|
||||
use gpui::{App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use jiff::{Timestamp, tz::TimeZone};
|
||||
|
||||
use super::{here, plan, quiet};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{change, keep, section, set},
|
||||
};
|
||||
|
||||
/// Adds an event from a drag and says so.
|
||||
fn creating(
|
||||
events: &Entity<Vec<Event>>,
|
||||
said: &Entity<Option<SharedString>>,
|
||||
) -> impl Fn(Timestamp, Timestamp, &mut Window, &mut App) + 'static {
|
||||
let (events, said) = (events.clone(), said.clone());
|
||||
move |start, end, _, cx| {
|
||||
change(&events, cx, |events| {
|
||||
let key = format!("made-{}", events.len());
|
||||
events.push(Event::timed(key, "New event", start, end, 4));
|
||||
});
|
||||
set(&said, Some("Made a new event.".into()), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves an event's end to where its edge was dragged.
|
||||
fn resizing(
|
||||
events: &Entity<Vec<Event>>,
|
||||
) -> impl Fn(&SharedString, Timestamp, &mut Window, &mut App) + 'static {
|
||||
let events = events.clone();
|
||||
move |key, end, _, cx| {
|
||||
change(&events, cx, |events| {
|
||||
let event = events
|
||||
.iter_mut()
|
||||
.find(|event| event.key == *key)
|
||||
.expect("a resized event is listed");
|
||||
let When::Timed { start, .. } = event.when else {
|
||||
panic!("event {key}: only timed events resize");
|
||||
};
|
||||
event.when = When::Timed { start, end };
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn week(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let events = plan(window, cx);
|
||||
let said = keep("calendar-week-said", || None::<SharedString>, window, cx);
|
||||
let (shown, listed) = (said.read(cx).clone(), events.read(cx).clone());
|
||||
let (create, resize) = (creating(&events, &said), resizing(&events));
|
||||
let pressed = said;
|
||||
section(
|
||||
"CalendarWeekView / TimeGrid / AllDayRow / CurrentTimeIndicator",
|
||||
"A week under its days' names: whole days lie above, the rest by their hours, and events that overlap share the width. A drag on empty time makes an event of a quarter hour or more, and a drag on an event's lower edge moves its end. Today carries a line at the minute now.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"calendar-week",
|
||||
div()
|
||||
.w(px(800.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(
|
||||
div().h(px(560.)).child(
|
||||
CalendarWeekView::new("calendar-week", today, listed)
|
||||
.zone(zone)
|
||||
.on_create(create)
|
||||
.on_resize(resize)
|
||||
.on_event(move |key, _, cx| {
|
||||
set(&pressed, Some(format!("Pressed {key}.").into()), cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.children(shown.map(|shown| quiet(shown, cx))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn day(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let (zone, today) = here();
|
||||
let events = plan(window, cx);
|
||||
let said = keep("calendar-day-said", || None::<SharedString>, window, cx);
|
||||
let listed = events.read(cx).clone();
|
||||
let (create, resize) = (creating(&events, &said), resizing(&events));
|
||||
let tokyo = TimeZone::get("Asia/Tokyo").expect("the system knows Tokyo");
|
||||
section(
|
||||
"CalendarDayView / TimezoneOverlay",
|
||||
"One day, with another zone's hours beside its own, so a call across zones reads in both.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"calendar-day",
|
||||
div().w(px(420.)).h(px(480.)).child(
|
||||
CalendarDayView::new("calendar-day", today, listed)
|
||||
.zone(zone)
|
||||
.overlay(TimezoneOverlay::new(tokyo))
|
||||
.on_create(create)
|
||||
.on_resize(resize),
|
||||
),
|
||||
))
|
||||
}
|
||||
Reference in new issue
Block a user