diff --git a/xilem/Cargo.toml b/xilem/Cargo.toml index 3d28144f5..e1dce10d8 100644 --- a/xilem/Cargo.toml +++ b/xilem/Cargo.toml @@ -30,6 +30,9 @@ crate-type = ["cdylib"] [[example]] name = "calc" +[[example]] +name = "calendar" + [[example]] name = "calc_android" path = "examples/calc.rs" @@ -66,11 +69,9 @@ vello.workspace = true smallvec.workspace = true accesskit.workspace = true tokio = { version = "1.39.1", features = ["rt", "rt-multi-thread", "time"] } - -[dev-dependencies] -# Used for `variable_clock` time = { workspace = true, features = ["local-offset"] } +[dev-dependencies] # Make wgpu use tracing for its spans. profiling = { version = "1.0.15", features = ["profile-with-tracing"] } diff --git a/xilem/examples/calendar.rs b/xilem/examples/calendar.rs new file mode 100644 index 000000000..d71118bcc --- /dev/null +++ b/xilem/examples/calendar.rs @@ -0,0 +1,70 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 +use masonry::widget::{CrossAxisAlignment, MainAxisAlignment}; +use time::{Date, OffsetDateTime}; +use winit::error::EventLoopError; +use xilem::{ + view::{button, flex, label, Axis, DatePicker, DatePickerMessage}, + EventLoop, WidgetView, Xilem, +}; +use xilem_core::{adapt, MessageResult}; + +struct Calendar { + selected_date: Date, + date: DatePicker, +} + +impl Calendar { + fn new() -> Self { + let now = OffsetDateTime::now_utc(); + Self { + selected_date: now.date(), + date: DatePicker::new(now.month(), now.year()), + } + } +} +fn selected_date(selected_date: Date) -> impl WidgetView { + flex((label("Selected date:"), label(format!("{selected_date}")))).direction(Axis::Horizontal) +} + +/// A component to make a bigger than usual button +fn external_controls() -> impl WidgetView { + flex(( + button("Today", |data: &mut Calendar| { + data.selected_date = OffsetDateTime::now_utc().date(); + }), + button("Tomorrow", |data: &mut Calendar| { + data.selected_date = OffsetDateTime::now_utc().date().next_day().unwrap(); + }), + )) + .direction(Axis::Horizontal) +} + +fn app_logic(data: &mut Calendar) -> impl WidgetView { + flex(( + selected_date(data.selected_date), + external_controls(), + adapt( + data.date.view(&mut data.selected_date), + |state: &mut Calendar, thunk| match thunk.call(&mut state.date) { + MessageResult::Action(DatePickerMessage::Select(date)) => { + state.selected_date = date; + MessageResult::Action(()) + } + MessageResult::Action(DatePickerMessage::Nop) => MessageResult::Nop, + MessageResult::Action(DatePickerMessage::ChangeView) => MessageResult::Action(()), + message_result => message_result.map(|_| ()), + }, + ), + )) + .direction(Axis::Vertical) + .cross_axis_alignment(CrossAxisAlignment::Center) + .main_axis_alignment(MainAxisAlignment::Center) +} + +fn main() -> Result<(), EventLoopError> { + let data = Calendar::new(); + let app = Xilem::new(data, app_logic); + app.run_windowed(EventLoop::with_user_event(), "Calendar".into())?; + Ok(()) +} diff --git a/xilem/src/view/date_picker.rs b/xilem/src/view/date_picker.rs new file mode 100644 index 000000000..0494b0d14 --- /dev/null +++ b/xilem/src/view/date_picker.rs @@ -0,0 +1,120 @@ +use masonry::widget::{Axis, CrossAxisAlignment, MainAxisAlignment}; +use time::{Date, Month, OffsetDateTime}; + +use crate::{view::sized_box, WidgetView}; + +use super::{button, flex, FlexSpacer}; + +pub enum DatePickerMessage { + Select(Date), + ChangeView, + Nop, +} + +pub struct DatePicker { + previous_date: Date, + month: Month, + year: i32, +} + +impl DatePicker { + pub fn new(month: Month, year: i32) -> Self { + let previous_date = OffsetDateTime::now_utc().date(); + Self { + previous_date, + month, + year, + } + } + + pub fn view( + &mut self, + selected_date: &mut Date, + ) -> impl WidgetView { + if self.previous_date != *selected_date { + self.month = selected_date.month(); + self.year = selected_date.year(); + self.previous_date = selected_date.clone(); + } + flex((self.date_controls(), self.date_grid(selected_date))) + .direction(Axis::Vertical) + .cross_axis_alignment(CrossAxisAlignment::Center) + .main_axis_alignment(MainAxisAlignment::Center) + } + + fn date_controls(&self) -> impl WidgetView { + let month = self.month; + let year = self.year; + + flex(( + button("<", |data: &mut DatePicker| { + data.month = data.month.previous(); + DatePickerMessage::ChangeView + }), + sized_box(button(format!("{month}"), |_| DatePickerMessage::Nop)).width(100.), + button(">", |data: &mut DatePicker| { + data.month = data.month.next(); + DatePickerMessage::ChangeView + }), + FlexSpacer::Fixed(40.), + button("<", |data: &mut DatePicker| { + data.year -= 1; + DatePickerMessage::ChangeView + }), + button(format!("{year}"), |_| DatePickerMessage::Nop), + button(">", |data: &mut DatePicker| { + data.year += 1; + DatePickerMessage::ChangeView + }), + )) + .direction(Axis::Horizontal) + .main_axis_alignment(MainAxisAlignment::Center) + } + + // The selected_date in the interface is needed to highlight the currently selected date + // It is currently not implemented + fn date_grid( + &self, + _selected_date: &mut Date, + ) -> impl WidgetView { + const COLUMNS: u8 = 7; + const ROWS: u8 = 5; + let mut date = Date::from_calendar_date(self.year, self.month, 1).unwrap(); + let days_from_monday = date.weekday().number_days_from_monday(); + + for _day in 0..days_from_monday { + date = date.previous_day().unwrap(); + } + + let mut rows = Vec::new(); + for _row in 0..ROWS { + let mut columns = Vec::new(); + for _column in 0..COLUMNS { + // Add buttons of each row into columns vec + let day_number = date.day(); + columns.push( + sized_box(button( + format!("{day_number}"), + move |data: &mut DatePicker| { + // Set the selected_date + data.previous_date = date; + DatePickerMessage::Select(date) + }, + )) + .width(50.), + ); + date = date.next_day().unwrap(); + } + // Add column vec into flex with horizontal axis + // Add flex into rows vec + rows.push( + flex(columns) + .direction(Axis::Horizontal) + .main_axis_alignment(MainAxisAlignment::Center) + .gap(10.), + ); + } + // Add row vec into flex with vertical axis + flex(rows).direction(Axis::Vertical) + } +} diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index 9cafb2016..40b52f67f 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -33,3 +33,6 @@ pub use textbox::*; mod portal; pub use portal::*; + +mod date_picker; +pub use date_picker::*;