diff --git a/.idea/epd_firmware.iml b/.idea/epd_firmware.iml index eb5cd81..99b4861 100644 --- a/.idea/epd_firmware.iml +++ b/.idea/epd_firmware.iml @@ -16,6 +16,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index 361bf62..16f7481 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "tp370pgh01", "eepy-launcher", "eepytool", + "eepy-derive", ] [workspace.dependencies] @@ -43,4 +44,4 @@ zstd = "0.13" strum = { version = "0.27", default-features = false, features = ["derive"] } tickv = "2.0" siphasher = "1.0" -fugit = "0.3" \ No newline at end of file +fugit = "0.3" diff --git a/eepy-derive/Cargo.toml b/eepy-derive/Cargo.toml new file mode 100644 index 0000000..4762670 --- /dev/null +++ b/eepy-derive/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "eepy-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full"] } +quote = "1.0" \ No newline at end of file diff --git a/eepy-derive/src/lib.rs b/eepy-derive/src/lib.rs new file mode 100644 index 0000000..e244460 --- /dev/null +++ b/eepy-derive/src/lib.rs @@ -0,0 +1,56 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn, LitStr}; + +#[proc_macro_attribute] +pub fn eepy_app(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut name: Option = None; + let mut version: Option = None; + let args_parser = syn::meta::parser(|meta| { + if meta.path.is_ident("name") { + name = Some(meta.value()?.parse()?); + Ok(()) + } else if meta.path.is_ident("version") { + version = Some(meta.value()?.parse()?); + Ok(()) + } else { + Err(meta.error("unsupported property")) + } + }); + + parse_macro_input!(attr with args_parser); + + let fn_item = parse_macro_input!(item as ItemFn); + let fn_name = fn_item.sig.ident.clone(); + + let name = if name.is_none() { + quote! { env!("CARGO_PKG_NAME") } + } else { + quote! { #name } + }; + let version = if version.is_none() { + quote! { env!("CARGO_PKG_VERSION") } + } else { + quote! { #version } + }; + + let output = quote! { + #[link_section = ".header"] + #[used] + static HEADER: ::eepy_sys::header::ProgramSlotHeader = ::eepy_sys::header::ProgramSlotHeader::partial( + #name, + #version, + __eepy_sys_entry, + ); + + extern "C" fn __eepy_sys_entry() { + #fn_name() + } + + #fn_item + }; + + output.into() +} \ No newline at end of file diff --git a/eepy-example-app/src/main.rs b/eepy-example-app/src/main.rs index 81ee305..876d51c 100644 --- a/eepy-example-app/src/main.rs +++ b/eepy-example-app/src/main.rs @@ -14,17 +14,8 @@ use heapless::String; use eepy_gui::draw_target::EpdDrawTarget; use eepy_gui::element::button::Button; use eepy_gui::element::{Gui, DEFAULT_TEXT_STYLE}; -use eepy_sys::header::ProgramSlotHeader; use eepy_sys::input::{has_event, next_event, set_touch_enabled}; -use eepy_sys::kv_store; - -#[link_section = ".header"] -#[used] -static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( - "ExampleApp", - env!("CARGO_PKG_VERSION"), - main, -); +use eepy_sys::{eepy_app, kv_store}; fn load_counter() -> u32 { let mut buf = [0u8; size_of::()]; @@ -46,7 +37,8 @@ fn render_counter(draw_target: &mut EpdDrawTarget, counter: u32) { .unwrap(); } -extern "C" fn main() { +#[eepy_app(name = "ExampleApp")] +fn main() { set_touch_enabled(true); let mut draw_target = EpdDrawTarget::default(); diff --git a/eepy-launcher/src/main.rs b/eepy-launcher/src/main.rs index b1fb304..c828c64 100644 --- a/eepy-launcher/src/main.rs +++ b/eepy-launcher/src/main.rs @@ -2,392 +2,30 @@ #![no_main] mod serial; +mod ui; extern crate panic_halt; use core::arch::asm; -use core::mem::offset_of; use core::sync::atomic::Ordering; -use embedded_graphics::geometry::AnchorPoint; -use embedded_graphics::pixelcolor::BinaryColor; -use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle, Rectangle}; -use embedded_graphics::text::Text; use usb_device::bus::UsbBusAllocator; use eepy_gui::draw_target::EpdDrawTarget; -use eepy_gui::element::button::Button; -use eepy_gui::element::{Gui, DEFAULT_TEXT_STYLE}; -use eepy_gui::element::slider::Slider; -use eepy_sys::exec::exec; +use eepy_gui::element::Gui; use eepy_sys::input::{has_event, next_event, set_touch_enabled}; -use eepy_sys::input_common::{Event, TouchEventType}; -use eepy_sys::header::{slot, slot_ptr, ProgramSlotHeader, Programs}; use eepy_sys::misc::get_serial; -use eepy_sys::{flash, usb}; -use eepy_sys::usb::UsbBus; +use eepy_sys::usb::{self, UsbBus}; use usb_device::prelude::*; use usbd_serial::SerialPort; +use eepy_sys::eepy_app; use crate::serial::{HOST_APP, NEEDS_REFRESH, NEEDS_REFRESH_PROGRAMS}; - -#[link_section = ".header"] -#[used] -static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( - "Launcher", - env!("CARGO_PKG_VERSION"), - main, -); - -struct MainPage { - scratchpad_button: Button<'static>, - app_buttons: [Option<(Button<'static>, u8)>; 32], -} - -impl MainPage { - fn refresh_buttons(&mut self) { - let mut programs = Programs::new(); - - for y in 0..16 { - for x in 0..2 { - if let Some(prog) = programs.next() { - let bi = y * 2 + x; - let x_coord = if x == 0 { 10 } else { 125 }; - let y_coord = 35 + 23 * y as i32; - let button = Button::with_default_style( - Rectangle::new(Point::new(x_coord, y_coord), Size::new(105, 20)), - unsafe { (*prog).name().unwrap() }, - false, - ); - let slot_num = unsafe { (&*prog).slot() }; - self.app_buttons[bi] = Some((button, slot_num)) - } - } - } - } - - fn new() -> Self { - let mut res = Self { - scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), - app_buttons: [const { None }; 32], - }; - res.refresh_buttons(); - res - } -} - -impl Gui for MainPage { - type Output = Option; - - fn draw_init(&self, draw_target: &mut EpdDrawTarget) { - self.scratchpad_button.draw_init(draw_target); - for b in &self.app_buttons { - if let Some((button, _)) = b { - button.draw_init(draw_target); - } - } - } - - fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { - let mut needs_refresh = false; - - let s = self.scratchpad_button.tick(draw_target, ev); - if s.clicked { - return Some(PageType::ScratchpadPage); - } else if s.needs_refresh { - draw_target.refresh(true); - } - - for b in &mut self.app_buttons { - if let Some((button, s)) = b { - let response = button.tick(draw_target, ev); - - if response.long_clicked { - return Some(PageType::AppInfoPage(*s)); - } else if response.clicked { - unsafe { cleanup_usb() }; - exec(*s); - } - - needs_refresh |= response.needs_refresh; - } - } - - if needs_refresh { - draw_target.refresh(true); - } - - None - } -} - -struct ScratchpadPage { - exit_button: Button<'static>, - clear_button: Button<'static>, - toggle_button: Button<'static>, - slider: Slider, - eraser: bool, - prev_pos: Option, -} - -impl ScratchpadPage { - fn new() -> Self { - let exit_button = Button::with_default_style_auto_sized(Point::new(10, 416 - 10 - 20), "Exit", true); - - let next_pos = exit_button - .bounding_box() - .translate(Point::new(10, 0)) - .anchor_point(AnchorPoint::TopRight); - let clear_button = Button::with_default_style_auto_sized(next_pos, "Clear", true); - - let next_pos = clear_button - .bounding_box() - .translate(Point::new(10, 0)) - .anchor_point(AnchorPoint::TopRight); - let toggle_button = Button::with_default_style_auto_sized(next_pos, "Eraser", true); - - let slider = Slider::with_default_style(Point::new(20, 20), 100, 1, 10, 2); - - Self { - exit_button, - clear_button, - toggle_button, - slider, - eraser: false, - prev_pos: None, - } - } -} - -impl Gui for ScratchpadPage { - type Output = Option; - - fn draw_init(&self, draw_target: &mut EpdDrawTarget) { - self.exit_button.draw_init(draw_target); - self.clear_button.draw_init(draw_target); - self.toggle_button.draw_init(draw_target); - self.slider.draw_init(draw_target); - } - - fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { - let mut refresh = false; - let mut maybe_refresh = false; - let mut handle_drawing: bool = true; - - let e = self.exit_button.tick(draw_target, ev); - if e.clicked { - return Some(PageType::MainPage); - } - refresh |= e.needs_refresh; - - let c = self.clear_button.tick(draw_target, ev); - if c.clicked { - draw_target.clear(BinaryColor::Off).unwrap(); - self.draw_init(draw_target); - draw_target.refresh(false); - return None; - } - refresh |= c.needs_refresh; - - let t = self.toggle_button.tick(draw_target, ev); - if t.clicked { - self.eraser = !self.eraser; - self.toggle_button.label = if self.eraser { "Pen" } else { "Eraser" }; - } - maybe_refresh |= t.clicked; - refresh |= t.needs_refresh; - - if self.slider.tick(draw_target, ev) { - maybe_refresh = true; - handle_drawing = false; - } - - if let Event::Touch(ev) = ev { - if handle_drawing && matches!(ev.ev_type, TouchEventType::Move | TouchEventType::Up) { - if let Some(prev) = self.prev_pos { - let style = PrimitiveStyle::with_stroke(BinaryColor::from(!self.eraser), self.slider.value as u32); - Line::new(prev, ev.eg_point()) - .into_styled(style) - .draw(draw_target) - .unwrap(); - // Draw a circle at each end of the line - let circle_style = PrimitiveStyle::with_fill(BinaryColor::from(!self.eraser)); - Circle::with_center(prev, self.slider.value as u32 - 1) - .into_styled(circle_style) - .draw(draw_target) - .unwrap(); - Circle::with_center(ev.eg_point(), self.slider.value as u32 - 1) - .into_styled(circle_style) - .draw(draw_target) - .unwrap(); - - self.draw_init(draw_target); - - maybe_refresh = true; - } - } - - if matches!(ev.ev_type, TouchEventType::Down | TouchEventType::Move) { - self.prev_pos = Some(ev.eg_point()); - } - } - - if refresh { - draw_target.refresh(true); - } else if maybe_refresh { - draw_target.maybe_refresh(true); - } - - None - } -} - -struct AppInfoPage { - slot: u8, - back_button: Button<'static>, - delete_button: Button<'static>, -} - -impl AppInfoPage { - fn new(slot: u8) -> Self { - Self { - slot, - back_button: Button::with_default_style_auto_sized(Point::new(10, 386), "Back", true), - delete_button: Button::with_default_style_auto_sized(Point::new(10, 60), "Delete program", true), - } - } -} - -impl Gui for AppInfoPage { - type Output = Option; - - fn draw_init(&self, target: &mut EpdDrawTarget) { - let header = unsafe { slot(self.slot) }; - - Text::new("Program: ", Point::new(10, 20), DEFAULT_TEXT_STYLE) - .draw(target) - .unwrap(); - - if let Ok(name) = unsafe { (*header).name() } { - Text::new(name, Point::new(120, 20), DEFAULT_TEXT_STYLE) - .draw(target) - .unwrap(); - } else { - Text::new("", Point::new(120, 20), DEFAULT_TEXT_STYLE) - .draw(target) - .unwrap(); - } - - - Text::new("Version: ", Point::new(10, 40), DEFAULT_TEXT_STYLE) - .draw(target) - .unwrap(); - - if let Ok(vers) = unsafe { (*header).version() } { - Text::new(vers, Point::new(120, 40), DEFAULT_TEXT_STYLE) - .draw(target) - .unwrap(); - } else { - Text::new("", Point::new(120, 40), DEFAULT_TEXT_STYLE) - .draw(target) - .unwrap(); - } - - self.back_button.draw_init(target); - self.delete_button.draw_init(target); - } - - fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> Self::Output { - let b = self.back_button.tick(target, ev); - if b.clicked { - return Some(PageType::MainPage); - } - - let d = self.delete_button.tick(target, ev); - if d.clicked { - let ptr = unsafe { slot_ptr(self.slot) }; - let mut buf = [0u8; 256]; - unsafe { buf.copy_from_slice(core::slice::from_raw_parts(ptr, 256)) }; - let offset = offset_of!(ProgramSlotHeader, len); - buf[offset..offset + 4].copy_from_slice(&[0; 4]); - - unsafe { flash::program(self.slot as u32 * 512 * 1024, &buf) }; - return Some(PageType::MainPage); - } - - None - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum PageType { - MainPage, - ScratchpadPage, - AppInfoPage(u8), -} - -enum MainGui { - MainPage(MainPage), - ScratchpadPage(ScratchpadPage), - AppInfoPage(AppInfoPage), -} - -impl MainGui { - fn new() -> Self { - Self::MainPage(MainPage::new()) - } - - fn get_current_page(&self) -> &dyn Gui> { - match self { - MainGui::MainPage(page) => page, - MainGui::ScratchpadPage(page) => page, - MainGui::AppInfoPage(page) => page, - } - } - - fn get_current_page_mut(&mut self) -> &mut dyn Gui> { - match self { - MainGui::MainPage(page) => page, - MainGui::ScratchpadPage(page) => page, - MainGui::AppInfoPage(page) => page, - } - } -} - -impl Gui for MainGui { - type Output = (); - - fn draw_init(&self, draw_target: &mut EpdDrawTarget) { - self.get_current_page().draw_init(draw_target); - } - - fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { - if let Some(page) = self.get_current_page_mut().tick(draw_target, ev) { - *self = match page { - PageType::MainPage => MainGui::MainPage(MainPage::new()), - PageType::ScratchpadPage => MainGui::ScratchpadPage(ScratchpadPage::new()), - PageType::AppInfoPage(slot) => MainGui::AppInfoPage(AppInfoPage::new(slot)), - }; - - draw_target.clear(BinaryColor::Off).unwrap(); - self.draw_init(draw_target); - draw_target.refresh(false); - } - } -} +use crate::ui::MainGui; static mut USB: Option> = None; static mut USB_DEVICE: Option> = None; static mut USB_SERIAL: Option> = None; -unsafe fn cleanup_usb() { - #[allow(static_mut_refs)] - unsafe { - let _ = USB.take(); - let _ = USB_DEVICE.take(); - let _ = USB_SERIAL.take(); - } -} - -extern "C" fn main() { +#[eepy_app(name = "Launcher")] +fn main() { #[allow(static_mut_refs)] unsafe { let bus = UsbBusAllocator::new(UsbBus::init()); diff --git a/eepy-launcher/src/ui/mod.rs b/eepy-launcher/src/ui/mod.rs new file mode 100644 index 0000000..0ce96e8 --- /dev/null +++ b/eepy-launcher/src/ui/mod.rs @@ -0,0 +1,56 @@ +use embedded_graphics::pixelcolor::BinaryColor; +use embedded_graphics::prelude::*; +use eepy_gui::draw_target::EpdDrawTarget; +use eepy_gui::element::Gui; +use eepy_sys::input_common::Event; +use crate::ui::page::app_info::AppInfoPage; +use crate::ui::page::main::MainPage; +use crate::ui::page::scratchpad::ScratchpadPage; + +mod page; + +pub(crate) enum MainGui { + MainPage(MainPage), + ScratchpadPage(ScratchpadPage), + AppInfoPage(AppInfoPage), +} + +impl MainGui { + pub(crate) fn new() -> Self { + Self::MainPage(MainPage::new()) + } + + fn get_current_page(&self) -> &dyn Gui> { + match self { + MainGui::MainPage(page) => page, + MainGui::ScratchpadPage(page) => page, + MainGui::AppInfoPage(page) => page, + } + } + + fn get_current_page_mut(&mut self) -> &mut dyn Gui> { + match self { + MainGui::MainPage(page) => page, + MainGui::ScratchpadPage(page) => page, + MainGui::AppInfoPage(page) => page, + } + } +} + +impl Gui for MainGui { + type Output = (); + + fn draw_init(&self, draw_target: &mut EpdDrawTarget) { + self.get_current_page().draw_init(draw_target); + } + + fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { + if let Some(gui) = self.get_current_page_mut().tick(draw_target, ev) { + *self = gui; + + draw_target.clear(BinaryColor::Off).unwrap(); + self.draw_init(draw_target); + draw_target.refresh(false); + } + } +} \ No newline at end of file diff --git a/eepy-launcher/src/ui/page/app_info.rs b/eepy-launcher/src/ui/page/app_info.rs new file mode 100644 index 0000000..dc674da --- /dev/null +++ b/eepy-launcher/src/ui/page/app_info.rs @@ -0,0 +1,88 @@ +use core::mem::offset_of; +use eepy_gui::element::button::Button; +use embedded_graphics::prelude::*; +use embedded_graphics::text::Text; +use eepy_gui::draw_target::EpdDrawTarget; +use eepy_gui::element::{Gui, DEFAULT_TEXT_STYLE}; +use eepy_sys::flash; +use eepy_sys::header::{slot, slot_ptr, ProgramSlotHeader}; +use eepy_sys::input_common::Event; +use crate::ui::MainGui; +use crate::ui::page::main::MainPage; + +pub(crate) struct AppInfoPage { + slot: u8, + back_button: Button<'static>, + delete_button: Button<'static>, +} + +impl AppInfoPage { + pub(crate) fn new(slot: u8) -> Self { + Self { + slot, + back_button: Button::with_default_style_auto_sized(Point::new(10, 386), "Back", true), + delete_button: Button::with_default_style_auto_sized(Point::new(10, 60), "Delete program", true), + } + } +} + +impl Gui for AppInfoPage { + type Output = Option; + + fn draw_init(&self, target: &mut EpdDrawTarget) { + let header = unsafe { slot(self.slot) }; + + Text::new("Program: ", Point::new(10, 20), DEFAULT_TEXT_STYLE) + .draw(target) + .unwrap(); + + if let Ok(name) = unsafe { (*header).name() } { + Text::new(name, Point::new(120, 20), DEFAULT_TEXT_STYLE) + .draw(target) + .unwrap(); + } else { + Text::new("", Point::new(120, 20), DEFAULT_TEXT_STYLE) + .draw(target) + .unwrap(); + } + + + Text::new("Version: ", Point::new(10, 40), DEFAULT_TEXT_STYLE) + .draw(target) + .unwrap(); + + if let Ok(vers) = unsafe { (*header).version() } { + Text::new(vers, Point::new(120, 40), DEFAULT_TEXT_STYLE) + .draw(target) + .unwrap(); + } else { + Text::new("", Point::new(120, 40), DEFAULT_TEXT_STYLE) + .draw(target) + .unwrap(); + } + + self.back_button.draw_init(target); + self.delete_button.draw_init(target); + } + + fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> Self::Output { + let b = self.back_button.tick(target, ev); + if b.clicked { + return Some(MainGui::MainPage(MainPage::new())); + } + + let d = self.delete_button.tick(target, ev); + if d.clicked { + let ptr = unsafe { slot_ptr(self.slot) }; + let mut buf = [0u8; 256]; + unsafe { buf.copy_from_slice(core::slice::from_raw_parts(ptr, 256)) }; + let offset = offset_of!(ProgramSlotHeader, len); + buf[offset..offset + 4].copy_from_slice(&[0; 4]); + + unsafe { flash::program(self.slot as u32 * 512 * 1024, &buf) }; + return Some(MainGui::MainPage(MainPage::new())); + } + + None + } +} \ No newline at end of file diff --git a/eepy-launcher/src/ui/page/main.rs b/eepy-launcher/src/ui/page/main.rs new file mode 100644 index 0000000..8aed496 --- /dev/null +++ b/eepy-launcher/src/ui/page/main.rs @@ -0,0 +1,92 @@ +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use eepy_gui::draw_target::EpdDrawTarget; +use eepy_gui::element::button::Button; +use eepy_gui::element::Gui; +use eepy_sys::exec::exec; +use eepy_sys::header::Programs; +use eepy_sys::input_common::Event; +use crate::ui::MainGui; +use crate::ui::page::app_info::AppInfoPage; +use crate::ui::page::scratchpad::ScratchpadPage; + +pub(crate) struct MainPage { + scratchpad_button: Button<'static>, + app_buttons: [Option<(Button<'static>, u8)>; 32], +} + +impl MainPage { + pub(crate) fn refresh_buttons(&mut self) { + let mut programs = Programs::new(); + + for y in 0..16 { + for x in 0..2 { + if let Some(prog) = programs.next() { + let bi = y * 2 + x; + let x_coord = if x == 0 { 10 } else { 125 }; + let y_coord = 35 + 23 * y as i32; + let button = Button::with_default_style( + Rectangle::new(Point::new(x_coord, y_coord), Size::new(105, 20)), + unsafe { (*prog).name().unwrap() }, + false, + ); + let slot_num = unsafe { (&*prog).slot() }; + self.app_buttons[bi] = Some((button, slot_num)) + } + } + } + } + + pub(crate) fn new() -> Self { + let mut res = Self { + scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), + app_buttons: [const { None }; 32], + }; + res.refresh_buttons(); + res + } +} + +impl Gui for MainPage { + type Output = Option; + + fn draw_init(&self, draw_target: &mut EpdDrawTarget) { + self.scratchpad_button.draw_init(draw_target); + for b in &self.app_buttons { + if let Some((button, _)) = b { + button.draw_init(draw_target); + } + } + } + + fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { + let mut needs_refresh = false; + + let s = self.scratchpad_button.tick(draw_target, ev); + if s.clicked { + return Some(MainGui::ScratchpadPage(ScratchpadPage::new())); + } else if s.needs_refresh { + draw_target.refresh(true); + } + + for b in &mut self.app_buttons { + if let Some((button, s)) = b { + let response = button.tick(draw_target, ev); + + if response.long_clicked { + return Some(MainGui::AppInfoPage(AppInfoPage::new(*s))); + } else if response.clicked { + exec(*s); + } + + needs_refresh |= response.needs_refresh; + } + } + + if needs_refresh { + draw_target.refresh(true); + } + + None + } +} \ No newline at end of file diff --git a/eepy-launcher/src/ui/page/mod.rs b/eepy-launcher/src/ui/page/mod.rs new file mode 100644 index 0000000..bb7aab6 --- /dev/null +++ b/eepy-launcher/src/ui/page/mod.rs @@ -0,0 +1,3 @@ +pub(super) mod main; +pub(super) mod scratchpad; +pub(super) mod app_info; \ No newline at end of file diff --git a/eepy-launcher/src/ui/page/scratchpad.rs b/eepy-launcher/src/ui/page/scratchpad.rs new file mode 100644 index 0000000..5159b30 --- /dev/null +++ b/eepy-launcher/src/ui/page/scratchpad.rs @@ -0,0 +1,132 @@ +use embedded_graphics::geometry::AnchorPoint; +use embedded_graphics::pixelcolor::BinaryColor; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle}; +use eepy_gui::draw_target::EpdDrawTarget; +use eepy_gui::element::button::Button; +use eepy_gui::element::Gui; +use eepy_gui::element::slider::Slider; +use eepy_sys::input_common::{Event, TouchEventType}; +use crate::ui::MainGui; +use crate::ui::page::main::MainPage; + +pub(crate) struct ScratchpadPage { + exit_button: Button<'static>, + clear_button: Button<'static>, + toggle_button: Button<'static>, + slider: Slider, + eraser: bool, + prev_pos: Option, +} + +impl ScratchpadPage { + pub(crate) fn new() -> Self { + let exit_button = Button::with_default_style_auto_sized(Point::new(10, 416 - 10 - 20), "Exit", true); + + let next_pos = exit_button + .bounding_box() + .translate(Point::new(10, 0)) + .anchor_point(AnchorPoint::TopRight); + let clear_button = Button::with_default_style_auto_sized(next_pos, "Clear", true); + + let next_pos = clear_button + .bounding_box() + .translate(Point::new(10, 0)) + .anchor_point(AnchorPoint::TopRight); + let toggle_button = Button::with_default_style_auto_sized(next_pos, "Eraser", true); + + let slider = Slider::with_default_style(Point::new(20, 20), 100, 1, 10, 2); + + Self { + exit_button, + clear_button, + toggle_button, + slider, + eraser: false, + prev_pos: None, + } + } +} + +impl Gui for ScratchpadPage { + type Output = Option; + + fn draw_init(&self, draw_target: &mut EpdDrawTarget) { + self.exit_button.draw_init(draw_target); + self.clear_button.draw_init(draw_target); + self.toggle_button.draw_init(draw_target); + self.slider.draw_init(draw_target); + } + + fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { + let mut refresh = false; + let mut maybe_refresh = false; + let mut handle_drawing: bool = true; + + let e = self.exit_button.tick(draw_target, ev); + if e.clicked { + return Some(MainGui::MainPage(MainPage::new())); + } + refresh |= e.needs_refresh; + + let c = self.clear_button.tick(draw_target, ev); + if c.clicked { + draw_target.clear(BinaryColor::Off).unwrap(); + self.draw_init(draw_target); + draw_target.refresh(false); + return None; + } + refresh |= c.needs_refresh; + + let t = self.toggle_button.tick(draw_target, ev); + if t.clicked { + self.eraser = !self.eraser; + self.toggle_button.label = if self.eraser { "Pen" } else { "Eraser" }; + } + maybe_refresh |= t.clicked; + refresh |= t.needs_refresh; + + if self.slider.tick(draw_target, ev) { + maybe_refresh = true; + handle_drawing = false; + } + + if let Event::Touch(ev) = ev { + if handle_drawing && matches!(ev.ev_type, TouchEventType::Move | TouchEventType::Up) { + if let Some(prev) = self.prev_pos { + let style = PrimitiveStyle::with_stroke(BinaryColor::from(!self.eraser), self.slider.value as u32); + Line::new(prev, ev.eg_point()) + .into_styled(style) + .draw(draw_target) + .unwrap(); + // Draw a circle at each end of the line + let circle_style = PrimitiveStyle::with_fill(BinaryColor::from(!self.eraser)); + Circle::with_center(prev, self.slider.value as u32 - 1) + .into_styled(circle_style) + .draw(draw_target) + .unwrap(); + Circle::with_center(ev.eg_point(), self.slider.value as u32 - 1) + .into_styled(circle_style) + .draw(draw_target) + .unwrap(); + + self.draw_init(draw_target); + + maybe_refresh = true; + } + } + + if matches!(ev.ev_type, TouchEventType::Down | TouchEventType::Move) { + self.prev_pos = Some(ev.eg_point()); + } + } + + if refresh { + draw_target.refresh(true); + } else if maybe_refresh { + draw_target.maybe_refresh(true); + } + + None + } +} diff --git a/eepy-sys/Cargo.toml b/eepy-sys/Cargo.toml index eaf6778..8ae1085 100644 --- a/eepy-sys/Cargo.toml +++ b/eepy-sys/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +eepy-derive = { path = "../eepy-derive" } tp370pgh01 = { path = "../tp370pgh01" } defmt = { workspace = true, optional = true } embedded-graphics = { workspace = true, optional = true } diff --git a/eepy-sys/src/lib.rs b/eepy-sys/src/lib.rs index 7758f7f..87426be 100644 --- a/eepy-sys/src/lib.rs +++ b/eepy-sys/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] +pub use eepy_derive::eepy_app; + #[cfg(all(target_os = "none", target_arch = "arm"))] pub mod header; #[cfg(all(target_os = "none", target_arch = "arm"))]