From 563cd69eac9426ee80ff95bae69e94767e1d181f Mon Sep 17 00:00:00 2001 From: Real Root Date: Mon, 24 Feb 2025 17:54:29 +0100 Subject: [PATCH] add setting to change time --- firmware/src/state.rs | 61 ++++++++++++- watchful-ui/src/lib.rs | 193 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 250 insertions(+), 4 deletions(-) diff --git a/firmware/src/state.rs b/firmware/src/state.rs index 1dfea51..18746c1 100644 --- a/firmware/src/state.rs +++ b/firmware/src/state.rs @@ -2,7 +2,7 @@ use defmt::info; use embassy_futures::select::{select, select3, Either, Either3}; use embassy_time::{Duration, Instant, Ticker, Timer}; use embedded_graphics::prelude::*; -use watchful_ui::{FirmwareDetails, MenuAction, MenuView, TimerView, TimeView, WorkoutView}; +use watchful_ui::{FirmwareDetails, MenuAction, MenuView, TimeDetails, TimerView, TimeView, WorkoutView}; use crate::device::Device; @@ -223,6 +223,11 @@ impl MenuState { device.screen.change_brightness(); WatchState::Menu(MenuState::new(MenuView::settings())) } + MenuAction::TimeSettings => { + WatchState::Menu(MenuState::new(MenuView::time_settings( + time_details(device).await, + ))) + } MenuAction::Reset => { cortex_m::peripheral::SCB::sys_reset(); } @@ -238,6 +243,54 @@ impl MenuState { info!("Firmware marked as valid"); WatchState::Menu(MenuState::new(MenuView::main())) } + MenuAction::ChangeTimeMinInc => { + let new_time = device.clock.get() + .checked_add(time::Duration::new(60, 0)); + match new_time { + Some(time) => device.clock.set(time), + None => { info!("Incremented time 1 minute too many"); } + } + + WatchState::Menu(MenuState::new(MenuView::time_settings( + time_details(device).await, + ))) + } + MenuAction::ChangeTimeHourInc => { + let new_time = device.clock.get() + .checked_add(time::Duration::new(3600, 0)); + match new_time { + Some(time) => device.clock.set(time), + None => { info!("Incremented time 1 hour too many"); } + } + + WatchState::Menu(MenuState::new(MenuView::time_settings( + time_details(device).await, + ))) + } + MenuAction::ChangeTimeMinDec => { + let new_time = device.clock.get() + .checked_sub(time::Duration::new(60, 0)); + match new_time { + Some(time) => device.clock.set(time), + None => { info!("Decremented time 1 minute too many"); } + } + + WatchState::Menu(MenuState::new(MenuView::time_settings( + time_details(device).await, + ))) + } + MenuAction::ChangeTimeHourDec => { + let new_time = device.clock.get() + .checked_sub(time::Duration::new(3600, 0)); + match new_time { + Some(time) => device.clock.set(time), + None => { info!("Decremented time 1 hour too many"); } + } + + WatchState::Menu(MenuState::new(MenuView::time_settings( + time_details(device).await, + ))) + } }, } } @@ -344,3 +397,9 @@ async fn firmware_details(battery: &crate::device::Battery<'_>, validated: bool) validated, ) } + +async fn time_details(device: &mut Device<'_>) -> TimeDetails { + TimeDetails::new( + device.clock.get(), + ) +} \ No newline at end of file diff --git a/watchful-ui/src/lib.rs b/watchful-ui/src/lib.rs index 72699de..7c0f71e 100644 --- a/watchful-ui/src/lib.rs +++ b/watchful-ui/src/lib.rs @@ -13,6 +13,7 @@ use embedded_layout::prelude::*; use embedded_text::style::TextBoxStyleBuilder; use embedded_text::TextBox; use u8g2_fonts::{fonts, U8g2TextStyle}; +use time::PrimitiveDateTime; const WIDTH: u32 = 240; const HEIGHT: u32 = 240; @@ -244,6 +245,11 @@ pub enum MenuAction { FirmwareSettings, ValidateFirmware, Brightness, + TimeSettings, + ChangeTimeMinInc, + ChangeTimeHourInc, + ChangeTimeMinDec, + ChangeTimeHourDec, Reset, } @@ -258,12 +264,20 @@ pub enum MenuView { Settings { firmware: MenuItem, brightness: MenuItem, + time_settings: MenuItem, reset: MenuItem, }, Firmware { details: FirmwareDetails, item: MenuItem, }, + TimeSettings { + details: TimeDetails, + min_inc: MenuControl, + hour_inc: MenuControl, + min_dec: MenuControl, + hour_dec: MenuControl, + }, } impl MenuView { @@ -280,7 +294,8 @@ impl MenuView { Self::Settings { firmware: MenuItem::new("Firmware", 0), brightness: MenuItem::new("Brightness", 1), - reset: MenuItem::new("Reset", 2), + time_settings: MenuItem::new("Time", 2), + reset: MenuItem::new("Reset", 3), } } @@ -292,6 +307,16 @@ impl MenuView { } } + pub fn time_settings(details: TimeDetails) -> Self { + Self::TimeSettings { + details, + hour_inc: MenuControl::new("+", 0), + min_inc: MenuControl::new("+", 1), + hour_dec: MenuControl::new("-", 2), + min_dec: MenuControl::new("-", 3), + } + } + pub fn draw>(&self, display: &mut D) -> Result<(), D::Error> { display.clear(Rgb::BLACK)?; @@ -308,15 +333,24 @@ impl MenuView { settings.draw(display)?; } - Self::Settings { firmware, brightness, reset } => { + Self::Settings { firmware, brightness, time_settings, reset } => { firmware.draw(display)?; brightness.draw(display)?; + time_settings.draw(display)?; reset.draw(display)?; } Self::Firmware { details, item } => { details.draw(display)?; item.draw(display)?; + } + + Self::TimeSettings { details, min_inc, hour_inc, min_dec, hour_dec } => { + details.draw(display)?; + min_inc.draw(display)?; + hour_inc.draw(display)?; + min_dec.draw(display)?; + hour_dec.draw(display)?; } } @@ -343,11 +377,13 @@ impl MenuView { None } } - Self::Settings { firmware, brightness, reset } => { + Self::Settings { firmware, brightness, time_settings, reset } => { if firmware.is_clicked(input) { Some(MenuAction::FirmwareSettings) } else if brightness.is_clicked(input) { Some(MenuAction::Brightness) + } else if time_settings.is_clicked(input) { + Some(MenuAction::TimeSettings) } else if reset.is_clicked(input) { Some(MenuAction::Reset) } else { @@ -361,6 +397,19 @@ impl MenuView { None } } + Self::TimeSettings { details: _, min_inc, hour_inc, min_dec, hour_dec } => { + if min_inc.is_clicked(input) { + Some(MenuAction::ChangeTimeMinInc) + } else if hour_inc.is_clicked(input) { + Some(MenuAction::ChangeTimeHourInc) + } else if min_dec.is_clicked(input) { + Some(MenuAction::ChangeTimeMinDec) + } else if hour_dec.is_clicked(input) { + Some(MenuAction::ChangeTimeHourDec) + } else { + None + } + } } } } @@ -422,6 +471,98 @@ impl MenuItem { } } +#[derive(Clone, Copy, PartialEq)] +pub struct MenuControl { + text: &'static str, + idx: u32, +} + +impl MenuControl { + pub fn new(text: &'static str, idx: u32) -> Self { + Self { text, idx } + } + + pub fn draw>(&self, display: &mut D) -> Result<(), D::Error> { + let line_style = PrimitiveStyleBuilder::new() + .stroke_color(Rgb::CSS_DARK_CYAN) + .stroke_width(1) + .fill_color(Rgb::CSS_DARK_CYAN) + .build(); + let (start, end) = self.placement(); + Rectangle::with_corners(start, end) + .into_styled(line_style) + .draw(display)?; + + Text::with_text_style( + self.text, + Point::new( + (start.x + end.x) / 2, + (start.y + end.y) / 2 + 8, + ), + menu_text_style(Rgb::CSS_CORNSILK), + TextStyleBuilder::new() + .alignment(embedded_graphics::text::Alignment::Center) + .build(), + ) + .draw(display)?; + Ok(()) + } + + fn placement(&self) -> (Point, Point) { + match self.idx { + 0 => { + let start = Point::new(20, 20); + let end = Point::new( + WIDTH as i32 / 2 - 10, + start.y + HEIGHT as i32 / 4 - 10 + ); + (start, end) + } + 1 => { + let start = Point::new(WIDTH as i32 / 2 + 10, 20); + let end = Point::new( + WIDTH as i32 - 20, + start.y + HEIGHT as i32 / 4 - 10 + ); + (start, end) + } + 2 => { + let start = Point::new( + 20, + HEIGHT as i32 / 2 + 50 + ); + let end = Point::new( + WIDTH as i32 / 2 - 10, + start.y + HEIGHT as i32 / 4 - 10 + ); + (start, end) + } + 3 => { + let start = Point::new( + WIDTH as i32 / 2 + 10, + HEIGHT as i32 / 2 + 50 + ); + let end = Point::new( + WIDTH as i32 - 20, + start.y + HEIGHT as i32 / 4 - 10 + ); + (start, end) + } + _ => (Point::new(0, 0), Point::new(0, 0)) + } + } + + // Check if point is within our range + pub fn is_clicked(&self, event: InputEvent) -> bool { + if let InputEvent::Touch(TouchGesture::SingleTap(pos)) = event { + let (c1, c2) = self.placement(); + c1.x <= pos.x && c1.y <= pos.y && c2.x >= pos.x && c2.y >= pos.y + } else { + false + } + } +} + #[derive(Clone, Copy, PartialEq)] pub struct FirmwareDetails { name: &'static str, @@ -485,3 +626,49 @@ impl FirmwareDetails { Ok(()) } } + +#[derive(Clone, Copy, PartialEq)] +pub struct TimeDetails { + time: PrimitiveDateTime, +} + +impl TimeDetails { + pub const fn new( + time: PrimitiveDateTime, + ) -> Self { + Self { + time, + } + } + + pub fn draw>(&self, display: &mut D) -> Result<(), D::Error> { + let mut buf: heapless::String<16> = heapless::String::new(); + write!( + buf, + "{:02}:{:02}", + self.time.hour(), + self.time.minute(), + ) + .unwrap(); + + let cd = Text::with_text_style( + &buf, + display.bounding_box().center(), + watch_text_style(Rgb::CSS_DARK_CYAN), + TextStyleBuilder::new() + .alignment(embedded_graphics::text::Alignment::Center) + .baseline(embedded_graphics::text::Baseline::Alphabetic) + .build(), + ); + + let display_area = display.bounding_box(); + LinearLayout::vertical(Chain::new(cd)) + .with_spacing(spacing::FixedMargin(10)) + .with_alignment(horizontal::Center) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center) + .draw(display)?; + + Ok(()) + } +} \ No newline at end of file