use std::sync::Arc;
use std::time::Duration;
use masonry::properties::types::AsUnit;
use time::error::IndeterminateOffset;
use time::macros::format_description;
use time::{OffsetDateTime, UtcOffset};
use winit::error::EventLoopError;
use xilem::core::fork;
use xilem::style::Style as _;
use xilem::view::{
FlexExt, FlexSpacer, flex_col, flex_row, inline_prose, label, portal, prose, sized_box, task,
text_button, variable_label,
};
use xilem::{
Blob, EventLoop, EventLoopBuilder, FontWeight, WidgetView, WindowOptions, Xilem, palette,
};
struct Clocks {
weight: f32,
local_offset: Result<UtcOffset, IndeterminateOffset>,
now_utc: OffsetDateTime,
}
struct TimeZone {
region: &'static str,
offset: UtcOffset,
}
fn app_logic(data: &mut Clocks) -> impl WidgetView<Clocks> + use<> {
let view = flex_col((
FlexSpacer::Fixed(40.px()),
local_time(data),
controls(),
portal(flex_col(
TIMEZONES.iter().map(|it| it.view(data)).collect::<Vec<_>>(),
))
.flex(1.),
))
.padding(10.0);
fork(
view,
task(
|proxy| async move {
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
let Ok(()) = proxy.message(()) else {
break;
};
}
},
|data: &mut Clocks, ()| data.now_utc = OffsetDateTime::now_utc(),
),
)
}
fn local_time(data: &mut Clocks) -> impl WidgetView<Clocks> + use<> {
let (error_view, offset) = if let Ok(offset) = data.local_offset {
(None, offset)
} else {
(
Some(
prose("Could not determine local UTC offset, using UTC")
.text_color(palette::css::ORANGE_RED),
),
UtcOffset::UTC,
)
};
flex_col((
TimeZone {
region: "Here",
offset,
}
.view(data),
error_view,
))
}
fn controls() -> impl WidgetView<Clocks> {
flex_row((
text_button("Increase", |data: &mut Clocks| {
data.weight = (data.weight + 100.).clamp(1., 1000.);
}),
text_button("Decrease", |data: &mut Clocks| {
data.weight = (data.weight - 100.).clamp(1., 1000.);
}),
text_button("Minimum", |data: &mut Clocks| {
data.weight = 1.;
}),
text_button("Maximum", |data: &mut Clocks| {
data.weight = 1000.;
}),
))
}
impl TimeZone {
fn view(&self, data: &mut Clocks) -> impl WidgetView<Clocks> + use<> {
let date_time_in_self = data.now_utc.to_offset(self.offset);
sized_box(flex_col((
flex_row((
inline_prose(self.region),
FlexSpacer::Flex(1.),
label(format!("UTC{}", self.offset)).color(
if data.local_offset.is_ok_and(|it| it == self.offset) {
palette::css::ORANGE
} else {
masonry::theme::TEXT_COLOR
},
),
))
.must_fill_major_axis(true)
.flex(1.),
flex_row((
variable_label(
date_time_in_self
.format(format_description!("[hour repr:24]:[minute]:[second]"))
.unwrap()
.to_string(),
)
.text_size(48.)
.font("Roboto Flex")
.target_weight(data.weight, 400.),
FlexSpacer::Flex(1.0),
(data.local_now().date() != date_time_in_self.date()).then(|| {
label(
date_time_in_self
.format(format_description!("([day] [month repr:short])"))
.unwrap(),
)
}),
)),
)))
.expand_width()
.height(72.px())
}
}
impl Clocks {
fn local_now(&self) -> OffsetDateTime {
match self.local_offset {
Ok(offset) => self.now_utc.to_offset(offset),
Err(_) => self.now_utc,
}
}
}
const ROBOTO_FLEX: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/resources/fonts/roboto_flex/",
"RobotoFlex-Subset.ttf"
));
fn run(event_loop: EventLoopBuilder) -> Result<(), EventLoopError> {
let data = Clocks {
weight: FontWeight::BLACK.value(),
local_offset: UtcOffset::current_local_offset(),
now_utc: OffsetDateTime::now_utc(),
};
let app = Xilem::new_simple(data, app_logic, WindowOptions::new("Clocks"))
.with_font(Blob::new(Arc::new(ROBOTO_FLEX)));
app.run_in(event_loop)?;
Ok(())
}
const fn tz(region: &'static str, offset: i8) -> TimeZone {
TimeZone {
region,
offset: match UtcOffset::from_hms(offset, 0, 0) {
Ok(it) => it,
Err(_) => {
panic!("Component out of range.");
}
},
}
}
const TIMEZONES: &[TimeZone] = &[
tz("Hawaii", -10),
tz("Pitcairn Islands", -8),
tz("Arizona", -7),
tz("Saskatchewan", -6),
tz("Peru", -5),
tz("Barbados", -4),
tz("Martinique", -4),
tz("Uruguay", -3),
tz("Iceland", 0),
tz("Tunisia", 1),
tz("Mozambique", 2),
tz("Qatar", 3),
tz("Azerbaijan", 4),
tz("Pakistan", 5),
tz("Bangladesh", 6),
tz("Thailand", 7),
tz("Singapore", 8),
tz("Japan", 9),
tz("Queensland", 10),
tz("Tonga", 13),
];
#[expect(clippy::allow_attributes, reason = "No way to specify the condition")]
#[allow(dead_code, reason = "False positive: needed in not-_android version")]
fn main() -> Result<(), EventLoopError> {
run(EventLoop::with_user_event())
}
#[cfg(target_os = "android")]
#[expect(
unsafe_code,
reason = "We believe that there are no other declarations using this name in the compiled objects here"
)]
#[unsafe(no_mangle)]
fn android_main(app: winit::platform::android::activity::AndroidApp) {
use winit::platform::android::EventLoopBuilderExtAndroid;
let mut event_loop = EventLoop::with_user_event();
event_loop.with_android_app(app);
run(event_loop).expect("Can create app");
}