firmware for my Touchscreen E-Paper Input Module for Framework Laptop 16
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

eepy-launcher: refactor UI code eepy-derive: new macro to automate header generation

+454 -381
+1
.idea/epd_firmware.iml
··· 16 16 <sourceFolder url="file://$MODULE_DIR$/eepy/src" isTestSource="false" /> 17 17 <sourceFolder url="file://$MODULE_DIR$/eepy-launcher/src" isTestSource="false" /> 18 18 <sourceFolder url="file://$MODULE_DIR$/eepytool/src" isTestSource="false" /> 19 + <sourceFolder url="file://$MODULE_DIR$/eepy-derive/src" isTestSource="false" /> 19 20 <excludeFolder url="file://$MODULE_DIR$/fw16_epd_bsp/target" /> 20 21 <excludeFolder url="file://$MODULE_DIR$/target" /> 21 22 <excludeFolder url="file://$MODULE_DIR$/fw16-epd-bsp/target" />
+2 -1
Cargo.toml
··· 13 13 "tp370pgh01", 14 14 "eepy-launcher", 15 15 "eepytool", 16 + "eepy-derive", 16 17 ] 17 18 18 19 [workspace.dependencies] ··· 43 44 strum = { version = "0.27", default-features = false, features = ["derive"] } 44 45 tickv = "2.0" 45 46 siphasher = "1.0" 46 - fugit = "0.3" 47 + fugit = "0.3"
+11
eepy-derive/Cargo.toml
··· 1 + [package] 2 + name = "eepy-derive" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [lib] 7 + proc-macro = true 8 + 9 + [dependencies] 10 + syn = { version = "2.0", features = ["full"] } 11 + quote = "1.0"
+56
eepy-derive/src/lib.rs
··· 1 + extern crate proc_macro; 2 + 3 + use proc_macro::TokenStream; 4 + use quote::quote; 5 + use syn::{parse_macro_input, ItemFn, LitStr}; 6 + 7 + #[proc_macro_attribute] 8 + pub fn eepy_app(attr: TokenStream, item: TokenStream) -> TokenStream { 9 + let mut name: Option<LitStr> = None; 10 + let mut version: Option<LitStr> = None; 11 + let args_parser = syn::meta::parser(|meta| { 12 + if meta.path.is_ident("name") { 13 + name = Some(meta.value()?.parse()?); 14 + Ok(()) 15 + } else if meta.path.is_ident("version") { 16 + version = Some(meta.value()?.parse()?); 17 + Ok(()) 18 + } else { 19 + Err(meta.error("unsupported property")) 20 + } 21 + }); 22 + 23 + parse_macro_input!(attr with args_parser); 24 + 25 + let fn_item = parse_macro_input!(item as ItemFn); 26 + let fn_name = fn_item.sig.ident.clone(); 27 + 28 + let name = if name.is_none() { 29 + quote! { env!("CARGO_PKG_NAME") } 30 + } else { 31 + quote! { #name } 32 + }; 33 + let version = if version.is_none() { 34 + quote! { env!("CARGO_PKG_VERSION") } 35 + } else { 36 + quote! { #version } 37 + }; 38 + 39 + let output = quote! { 40 + #[link_section = ".header"] 41 + #[used] 42 + static HEADER: ::eepy_sys::header::ProgramSlotHeader = ::eepy_sys::header::ProgramSlotHeader::partial( 43 + #name, 44 + #version, 45 + __eepy_sys_entry, 46 + ); 47 + 48 + extern "C" fn __eepy_sys_entry() { 49 + #fn_name() 50 + } 51 + 52 + #fn_item 53 + }; 54 + 55 + output.into() 56 + }
+3 -11
eepy-example-app/src/main.rs
··· 14 14 use eepy_gui::draw_target::EpdDrawTarget; 15 15 use eepy_gui::element::button::Button; 16 16 use eepy_gui::element::{Gui, DEFAULT_TEXT_STYLE}; 17 - use eepy_sys::header::ProgramSlotHeader; 18 17 use eepy_sys::input::{has_event, next_event, set_touch_enabled}; 19 - use eepy_sys::kv_store; 20 - 21 - #[link_section = ".header"] 22 - #[used] 23 - static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( 24 - "ExampleApp", 25 - env!("CARGO_PKG_VERSION"), 26 - main, 27 - ); 18 + use eepy_sys::{eepy_app, kv_store}; 28 19 29 20 fn load_counter() -> u32 { 30 21 let mut buf = [0u8; size_of::<i32>()]; ··· 46 37 .unwrap(); 47 38 } 48 39 49 - extern "C" fn main() { 40 + #[eepy_app(name = "ExampleApp")] 41 + fn main() { 50 42 set_touch_enabled(true); 51 43 52 44 let mut draw_target = EpdDrawTarget::default();
+7 -369
eepy-launcher/src/main.rs
··· 2 2 #![no_main] 3 3 4 4 mod serial; 5 + mod ui; 5 6 6 7 extern crate panic_halt; 7 8 8 9 use core::arch::asm; 9 - use core::mem::offset_of; 10 10 use core::sync::atomic::Ordering; 11 - use embedded_graphics::geometry::AnchorPoint; 12 - use embedded_graphics::pixelcolor::BinaryColor; 13 - use embedded_graphics::prelude::*; 14 - use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle, Rectangle}; 15 - use embedded_graphics::text::Text; 16 11 use usb_device::bus::UsbBusAllocator; 17 12 use eepy_gui::draw_target::EpdDrawTarget; 18 - use eepy_gui::element::button::Button; 19 - use eepy_gui::element::{Gui, DEFAULT_TEXT_STYLE}; 20 - use eepy_gui::element::slider::Slider; 21 - use eepy_sys::exec::exec; 13 + use eepy_gui::element::Gui; 22 14 use eepy_sys::input::{has_event, next_event, set_touch_enabled}; 23 - use eepy_sys::input_common::{Event, TouchEventType}; 24 - use eepy_sys::header::{slot, slot_ptr, ProgramSlotHeader, Programs}; 25 15 use eepy_sys::misc::get_serial; 26 - use eepy_sys::{flash, usb}; 27 - use eepy_sys::usb::UsbBus; 16 + use eepy_sys::usb::{self, UsbBus}; 28 17 use usb_device::prelude::*; 29 18 use usbd_serial::SerialPort; 19 + use eepy_sys::eepy_app; 30 20 use crate::serial::{HOST_APP, NEEDS_REFRESH, NEEDS_REFRESH_PROGRAMS}; 31 - 32 - #[link_section = ".header"] 33 - #[used] 34 - static HEADER: ProgramSlotHeader = ProgramSlotHeader::partial( 35 - "Launcher", 36 - env!("CARGO_PKG_VERSION"), 37 - main, 38 - ); 39 - 40 - struct MainPage { 41 - scratchpad_button: Button<'static>, 42 - app_buttons: [Option<(Button<'static>, u8)>; 32], 43 - } 44 - 45 - impl MainPage { 46 - fn refresh_buttons(&mut self) { 47 - let mut programs = Programs::new(); 48 - 49 - for y in 0..16 { 50 - for x in 0..2 { 51 - if let Some(prog) = programs.next() { 52 - let bi = y * 2 + x; 53 - let x_coord = if x == 0 { 10 } else { 125 }; 54 - let y_coord = 35 + 23 * y as i32; 55 - let button = Button::with_default_style( 56 - Rectangle::new(Point::new(x_coord, y_coord), Size::new(105, 20)), 57 - unsafe { (*prog).name().unwrap() }, 58 - false, 59 - ); 60 - let slot_num = unsafe { (&*prog).slot() }; 61 - self.app_buttons[bi] = Some((button, slot_num)) 62 - } 63 - } 64 - } 65 - } 66 - 67 - fn new() -> Self { 68 - let mut res = Self { 69 - scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), 70 - app_buttons: [const { None }; 32], 71 - }; 72 - res.refresh_buttons(); 73 - res 74 - } 75 - } 76 - 77 - impl Gui for MainPage { 78 - type Output = Option<PageType>; 79 - 80 - fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 81 - self.scratchpad_button.draw_init(draw_target); 82 - for b in &self.app_buttons { 83 - if let Some((button, _)) = b { 84 - button.draw_init(draw_target); 85 - } 86 - } 87 - } 88 - 89 - fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 90 - let mut needs_refresh = false; 91 - 92 - let s = self.scratchpad_button.tick(draw_target, ev); 93 - if s.clicked { 94 - return Some(PageType::ScratchpadPage); 95 - } else if s.needs_refresh { 96 - draw_target.refresh(true); 97 - } 98 - 99 - for b in &mut self.app_buttons { 100 - if let Some((button, s)) = b { 101 - let response = button.tick(draw_target, ev); 102 - 103 - if response.long_clicked { 104 - return Some(PageType::AppInfoPage(*s)); 105 - } else if response.clicked { 106 - unsafe { cleanup_usb() }; 107 - exec(*s); 108 - } 109 - 110 - needs_refresh |= response.needs_refresh; 111 - } 112 - } 113 - 114 - if needs_refresh { 115 - draw_target.refresh(true); 116 - } 117 - 118 - None 119 - } 120 - } 121 - 122 - struct ScratchpadPage { 123 - exit_button: Button<'static>, 124 - clear_button: Button<'static>, 125 - toggle_button: Button<'static>, 126 - slider: Slider, 127 - eraser: bool, 128 - prev_pos: Option<Point>, 129 - } 130 - 131 - impl ScratchpadPage { 132 - fn new() -> Self { 133 - let exit_button = Button::with_default_style_auto_sized(Point::new(10, 416 - 10 - 20), "Exit", true); 134 - 135 - let next_pos = exit_button 136 - .bounding_box() 137 - .translate(Point::new(10, 0)) 138 - .anchor_point(AnchorPoint::TopRight); 139 - let clear_button = Button::with_default_style_auto_sized(next_pos, "Clear", true); 140 - 141 - let next_pos = clear_button 142 - .bounding_box() 143 - .translate(Point::new(10, 0)) 144 - .anchor_point(AnchorPoint::TopRight); 145 - let toggle_button = Button::with_default_style_auto_sized(next_pos, "Eraser", true); 146 - 147 - let slider = Slider::with_default_style(Point::new(20, 20), 100, 1, 10, 2); 148 - 149 - Self { 150 - exit_button, 151 - clear_button, 152 - toggle_button, 153 - slider, 154 - eraser: false, 155 - prev_pos: None, 156 - } 157 - } 158 - } 159 - 160 - impl Gui for ScratchpadPage { 161 - type Output = Option<PageType>; 162 - 163 - fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 164 - self.exit_button.draw_init(draw_target); 165 - self.clear_button.draw_init(draw_target); 166 - self.toggle_button.draw_init(draw_target); 167 - self.slider.draw_init(draw_target); 168 - } 169 - 170 - fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 171 - let mut refresh = false; 172 - let mut maybe_refresh = false; 173 - let mut handle_drawing: bool = true; 174 - 175 - let e = self.exit_button.tick(draw_target, ev); 176 - if e.clicked { 177 - return Some(PageType::MainPage); 178 - } 179 - refresh |= e.needs_refresh; 180 - 181 - let c = self.clear_button.tick(draw_target, ev); 182 - if c.clicked { 183 - draw_target.clear(BinaryColor::Off).unwrap(); 184 - self.draw_init(draw_target); 185 - draw_target.refresh(false); 186 - return None; 187 - } 188 - refresh |= c.needs_refresh; 189 - 190 - let t = self.toggle_button.tick(draw_target, ev); 191 - if t.clicked { 192 - self.eraser = !self.eraser; 193 - self.toggle_button.label = if self.eraser { "Pen" } else { "Eraser" }; 194 - } 195 - maybe_refresh |= t.clicked; 196 - refresh |= t.needs_refresh; 197 - 198 - if self.slider.tick(draw_target, ev) { 199 - maybe_refresh = true; 200 - handle_drawing = false; 201 - } 202 - 203 - if let Event::Touch(ev) = ev { 204 - if handle_drawing && matches!(ev.ev_type, TouchEventType::Move | TouchEventType::Up) { 205 - if let Some(prev) = self.prev_pos { 206 - let style = PrimitiveStyle::with_stroke(BinaryColor::from(!self.eraser), self.slider.value as u32); 207 - Line::new(prev, ev.eg_point()) 208 - .into_styled(style) 209 - .draw(draw_target) 210 - .unwrap(); 211 - // Draw a circle at each end of the line 212 - let circle_style = PrimitiveStyle::with_fill(BinaryColor::from(!self.eraser)); 213 - Circle::with_center(prev, self.slider.value as u32 - 1) 214 - .into_styled(circle_style) 215 - .draw(draw_target) 216 - .unwrap(); 217 - Circle::with_center(ev.eg_point(), self.slider.value as u32 - 1) 218 - .into_styled(circle_style) 219 - .draw(draw_target) 220 - .unwrap(); 221 - 222 - self.draw_init(draw_target); 223 - 224 - maybe_refresh = true; 225 - } 226 - } 227 - 228 - if matches!(ev.ev_type, TouchEventType::Down | TouchEventType::Move) { 229 - self.prev_pos = Some(ev.eg_point()); 230 - } 231 - } 232 - 233 - if refresh { 234 - draw_target.refresh(true); 235 - } else if maybe_refresh { 236 - draw_target.maybe_refresh(true); 237 - } 238 - 239 - None 240 - } 241 - } 242 - 243 - struct AppInfoPage { 244 - slot: u8, 245 - back_button: Button<'static>, 246 - delete_button: Button<'static>, 247 - } 248 - 249 - impl AppInfoPage { 250 - fn new(slot: u8) -> Self { 251 - Self { 252 - slot, 253 - back_button: Button::with_default_style_auto_sized(Point::new(10, 386), "Back", true), 254 - delete_button: Button::with_default_style_auto_sized(Point::new(10, 60), "Delete program", true), 255 - } 256 - } 257 - } 258 - 259 - impl Gui for AppInfoPage { 260 - type Output = Option<PageType>; 261 - 262 - fn draw_init(&self, target: &mut EpdDrawTarget) { 263 - let header = unsafe { slot(self.slot) }; 264 - 265 - Text::new("Program: ", Point::new(10, 20), DEFAULT_TEXT_STYLE) 266 - .draw(target) 267 - .unwrap(); 268 - 269 - if let Ok(name) = unsafe { (*header).name() } { 270 - Text::new(name, Point::new(120, 20), DEFAULT_TEXT_STYLE) 271 - .draw(target) 272 - .unwrap(); 273 - } else { 274 - Text::new("<invalid>", Point::new(120, 20), DEFAULT_TEXT_STYLE) 275 - .draw(target) 276 - .unwrap(); 277 - } 278 - 279 - 280 - Text::new("Version: ", Point::new(10, 40), DEFAULT_TEXT_STYLE) 281 - .draw(target) 282 - .unwrap(); 283 - 284 - if let Ok(vers) = unsafe { (*header).version() } { 285 - Text::new(vers, Point::new(120, 40), DEFAULT_TEXT_STYLE) 286 - .draw(target) 287 - .unwrap(); 288 - } else { 289 - Text::new("<invalid>", Point::new(120, 40), DEFAULT_TEXT_STYLE) 290 - .draw(target) 291 - .unwrap(); 292 - } 293 - 294 - self.back_button.draw_init(target); 295 - self.delete_button.draw_init(target); 296 - } 297 - 298 - fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 299 - let b = self.back_button.tick(target, ev); 300 - if b.clicked { 301 - return Some(PageType::MainPage); 302 - } 303 - 304 - let d = self.delete_button.tick(target, ev); 305 - if d.clicked { 306 - let ptr = unsafe { slot_ptr(self.slot) }; 307 - let mut buf = [0u8; 256]; 308 - unsafe { buf.copy_from_slice(core::slice::from_raw_parts(ptr, 256)) }; 309 - let offset = offset_of!(ProgramSlotHeader, len); 310 - buf[offset..offset + 4].copy_from_slice(&[0; 4]); 311 - 312 - unsafe { flash::program(self.slot as u32 * 512 * 1024, &buf) }; 313 - return Some(PageType::MainPage); 314 - } 315 - 316 - None 317 - } 318 - } 319 - 320 - #[derive(Copy, Clone, Debug, Eq, PartialEq)] 321 - enum PageType { 322 - MainPage, 323 - ScratchpadPage, 324 - AppInfoPage(u8), 325 - } 326 - 327 - enum MainGui { 328 - MainPage(MainPage), 329 - ScratchpadPage(ScratchpadPage), 330 - AppInfoPage(AppInfoPage), 331 - } 332 - 333 - impl MainGui { 334 - fn new() -> Self { 335 - Self::MainPage(MainPage::new()) 336 - } 337 - 338 - fn get_current_page(&self) -> &dyn Gui<Output = Option<PageType>> { 339 - match self { 340 - MainGui::MainPage(page) => page, 341 - MainGui::ScratchpadPage(page) => page, 342 - MainGui::AppInfoPage(page) => page, 343 - } 344 - } 345 - 346 - fn get_current_page_mut(&mut self) -> &mut dyn Gui<Output = Option<PageType>> { 347 - match self { 348 - MainGui::MainPage(page) => page, 349 - MainGui::ScratchpadPage(page) => page, 350 - MainGui::AppInfoPage(page) => page, 351 - } 352 - } 353 - } 354 - 355 - impl Gui for MainGui { 356 - type Output = (); 357 - 358 - fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 359 - self.get_current_page().draw_init(draw_target); 360 - } 361 - 362 - fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 363 - if let Some(page) = self.get_current_page_mut().tick(draw_target, ev) { 364 - *self = match page { 365 - PageType::MainPage => MainGui::MainPage(MainPage::new()), 366 - PageType::ScratchpadPage => MainGui::ScratchpadPage(ScratchpadPage::new()), 367 - PageType::AppInfoPage(slot) => MainGui::AppInfoPage(AppInfoPage::new(slot)), 368 - }; 369 - 370 - draw_target.clear(BinaryColor::Off).unwrap(); 371 - self.draw_init(draw_target); 372 - draw_target.refresh(false); 373 - } 374 - } 375 - } 21 + use crate::ui::MainGui; 376 22 377 23 static mut USB: Option<UsbBusAllocator<UsbBus>> = None; 378 24 static mut USB_DEVICE: Option<UsbDevice<UsbBus>> = None; 379 25 static mut USB_SERIAL: Option<SerialPort<UsbBus>> = None; 380 26 381 - unsafe fn cleanup_usb() { 382 - #[allow(static_mut_refs)] 383 - unsafe { 384 - let _ = USB.take(); 385 - let _ = USB_DEVICE.take(); 386 - let _ = USB_SERIAL.take(); 387 - } 388 - } 389 - 390 - extern "C" fn main() { 27 + #[eepy_app(name = "Launcher")] 28 + fn main() { 391 29 #[allow(static_mut_refs)] 392 30 unsafe { 393 31 let bus = UsbBusAllocator::new(UsbBus::init());
+56
eepy-launcher/src/ui/mod.rs
··· 1 + use embedded_graphics::pixelcolor::BinaryColor; 2 + use embedded_graphics::prelude::*; 3 + use eepy_gui::draw_target::EpdDrawTarget; 4 + use eepy_gui::element::Gui; 5 + use eepy_sys::input_common::Event; 6 + use crate::ui::page::app_info::AppInfoPage; 7 + use crate::ui::page::main::MainPage; 8 + use crate::ui::page::scratchpad::ScratchpadPage; 9 + 10 + mod page; 11 + 12 + pub(crate) enum MainGui { 13 + MainPage(MainPage), 14 + ScratchpadPage(ScratchpadPage), 15 + AppInfoPage(AppInfoPage), 16 + } 17 + 18 + impl MainGui { 19 + pub(crate) fn new() -> Self { 20 + Self::MainPage(MainPage::new()) 21 + } 22 + 23 + fn get_current_page(&self) -> &dyn Gui<Output = Option<Self>> { 24 + match self { 25 + MainGui::MainPage(page) => page, 26 + MainGui::ScratchpadPage(page) => page, 27 + MainGui::AppInfoPage(page) => page, 28 + } 29 + } 30 + 31 + fn get_current_page_mut(&mut self) -> &mut dyn Gui<Output = Option<Self>> { 32 + match self { 33 + MainGui::MainPage(page) => page, 34 + MainGui::ScratchpadPage(page) => page, 35 + MainGui::AppInfoPage(page) => page, 36 + } 37 + } 38 + } 39 + 40 + impl Gui for MainGui { 41 + type Output = (); 42 + 43 + fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 44 + self.get_current_page().draw_init(draw_target); 45 + } 46 + 47 + fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 48 + if let Some(gui) = self.get_current_page_mut().tick(draw_target, ev) { 49 + *self = gui; 50 + 51 + draw_target.clear(BinaryColor::Off).unwrap(); 52 + self.draw_init(draw_target); 53 + draw_target.refresh(false); 54 + } 55 + } 56 + }
+88
eepy-launcher/src/ui/page/app_info.rs
··· 1 + use core::mem::offset_of; 2 + use eepy_gui::element::button::Button; 3 + use embedded_graphics::prelude::*; 4 + use embedded_graphics::text::Text; 5 + use eepy_gui::draw_target::EpdDrawTarget; 6 + use eepy_gui::element::{Gui, DEFAULT_TEXT_STYLE}; 7 + use eepy_sys::flash; 8 + use eepy_sys::header::{slot, slot_ptr, ProgramSlotHeader}; 9 + use eepy_sys::input_common::Event; 10 + use crate::ui::MainGui; 11 + use crate::ui::page::main::MainPage; 12 + 13 + pub(crate) struct AppInfoPage { 14 + slot: u8, 15 + back_button: Button<'static>, 16 + delete_button: Button<'static>, 17 + } 18 + 19 + impl AppInfoPage { 20 + pub(crate) fn new(slot: u8) -> Self { 21 + Self { 22 + slot, 23 + back_button: Button::with_default_style_auto_sized(Point::new(10, 386), "Back", true), 24 + delete_button: Button::with_default_style_auto_sized(Point::new(10, 60), "Delete program", true), 25 + } 26 + } 27 + } 28 + 29 + impl Gui for AppInfoPage { 30 + type Output = Option<MainGui>; 31 + 32 + fn draw_init(&self, target: &mut EpdDrawTarget) { 33 + let header = unsafe { slot(self.slot) }; 34 + 35 + Text::new("Program: ", Point::new(10, 20), DEFAULT_TEXT_STYLE) 36 + .draw(target) 37 + .unwrap(); 38 + 39 + if let Ok(name) = unsafe { (*header).name() } { 40 + Text::new(name, Point::new(120, 20), DEFAULT_TEXT_STYLE) 41 + .draw(target) 42 + .unwrap(); 43 + } else { 44 + Text::new("<invalid>", Point::new(120, 20), DEFAULT_TEXT_STYLE) 45 + .draw(target) 46 + .unwrap(); 47 + } 48 + 49 + 50 + Text::new("Version: ", Point::new(10, 40), DEFAULT_TEXT_STYLE) 51 + .draw(target) 52 + .unwrap(); 53 + 54 + if let Ok(vers) = unsafe { (*header).version() } { 55 + Text::new(vers, Point::new(120, 40), DEFAULT_TEXT_STYLE) 56 + .draw(target) 57 + .unwrap(); 58 + } else { 59 + Text::new("<invalid>", Point::new(120, 40), DEFAULT_TEXT_STYLE) 60 + .draw(target) 61 + .unwrap(); 62 + } 63 + 64 + self.back_button.draw_init(target); 65 + self.delete_button.draw_init(target); 66 + } 67 + 68 + fn tick(&mut self, target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 69 + let b = self.back_button.tick(target, ev); 70 + if b.clicked { 71 + return Some(MainGui::MainPage(MainPage::new())); 72 + } 73 + 74 + let d = self.delete_button.tick(target, ev); 75 + if d.clicked { 76 + let ptr = unsafe { slot_ptr(self.slot) }; 77 + let mut buf = [0u8; 256]; 78 + unsafe { buf.copy_from_slice(core::slice::from_raw_parts(ptr, 256)) }; 79 + let offset = offset_of!(ProgramSlotHeader, len); 80 + buf[offset..offset + 4].copy_from_slice(&[0; 4]); 81 + 82 + unsafe { flash::program(self.slot as u32 * 512 * 1024, &buf) }; 83 + return Some(MainGui::MainPage(MainPage::new())); 84 + } 85 + 86 + None 87 + } 88 + }
+92
eepy-launcher/src/ui/page/main.rs
··· 1 + use embedded_graphics::prelude::*; 2 + use embedded_graphics::primitives::Rectangle; 3 + use eepy_gui::draw_target::EpdDrawTarget; 4 + use eepy_gui::element::button::Button; 5 + use eepy_gui::element::Gui; 6 + use eepy_sys::exec::exec; 7 + use eepy_sys::header::Programs; 8 + use eepy_sys::input_common::Event; 9 + use crate::ui::MainGui; 10 + use crate::ui::page::app_info::AppInfoPage; 11 + use crate::ui::page::scratchpad::ScratchpadPage; 12 + 13 + pub(crate) struct MainPage { 14 + scratchpad_button: Button<'static>, 15 + app_buttons: [Option<(Button<'static>, u8)>; 32], 16 + } 17 + 18 + impl MainPage { 19 + pub(crate) fn refresh_buttons(&mut self) { 20 + let mut programs = Programs::new(); 21 + 22 + for y in 0..16 { 23 + for x in 0..2 { 24 + if let Some(prog) = programs.next() { 25 + let bi = y * 2 + x; 26 + let x_coord = if x == 0 { 10 } else { 125 }; 27 + let y_coord = 35 + 23 * y as i32; 28 + let button = Button::with_default_style( 29 + Rectangle::new(Point::new(x_coord, y_coord), Size::new(105, 20)), 30 + unsafe { (*prog).name().unwrap() }, 31 + false, 32 + ); 33 + let slot_num = unsafe { (&*prog).slot() }; 34 + self.app_buttons[bi] = Some((button, slot_num)) 35 + } 36 + } 37 + } 38 + } 39 + 40 + pub(crate) fn new() -> Self { 41 + let mut res = Self { 42 + scratchpad_button: Button::with_default_style_auto_sized(Point::new(10, 10), "Scratchpad", true), 43 + app_buttons: [const { None }; 32], 44 + }; 45 + res.refresh_buttons(); 46 + res 47 + } 48 + } 49 + 50 + impl Gui for MainPage { 51 + type Output = Option<MainGui>; 52 + 53 + fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 54 + self.scratchpad_button.draw_init(draw_target); 55 + for b in &self.app_buttons { 56 + if let Some((button, _)) = b { 57 + button.draw_init(draw_target); 58 + } 59 + } 60 + } 61 + 62 + fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 63 + let mut needs_refresh = false; 64 + 65 + let s = self.scratchpad_button.tick(draw_target, ev); 66 + if s.clicked { 67 + return Some(MainGui::ScratchpadPage(ScratchpadPage::new())); 68 + } else if s.needs_refresh { 69 + draw_target.refresh(true); 70 + } 71 + 72 + for b in &mut self.app_buttons { 73 + if let Some((button, s)) = b { 74 + let response = button.tick(draw_target, ev); 75 + 76 + if response.long_clicked { 77 + return Some(MainGui::AppInfoPage(AppInfoPage::new(*s))); 78 + } else if response.clicked { 79 + exec(*s); 80 + } 81 + 82 + needs_refresh |= response.needs_refresh; 83 + } 84 + } 85 + 86 + if needs_refresh { 87 + draw_target.refresh(true); 88 + } 89 + 90 + None 91 + } 92 + }
+3
eepy-launcher/src/ui/page/mod.rs
··· 1 + pub(super) mod main; 2 + pub(super) mod scratchpad; 3 + pub(super) mod app_info;
+132
eepy-launcher/src/ui/page/scratchpad.rs
··· 1 + use embedded_graphics::geometry::AnchorPoint; 2 + use embedded_graphics::pixelcolor::BinaryColor; 3 + use embedded_graphics::prelude::*; 4 + use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle}; 5 + use eepy_gui::draw_target::EpdDrawTarget; 6 + use eepy_gui::element::button::Button; 7 + use eepy_gui::element::Gui; 8 + use eepy_gui::element::slider::Slider; 9 + use eepy_sys::input_common::{Event, TouchEventType}; 10 + use crate::ui::MainGui; 11 + use crate::ui::page::main::MainPage; 12 + 13 + pub(crate) struct ScratchpadPage { 14 + exit_button: Button<'static>, 15 + clear_button: Button<'static>, 16 + toggle_button: Button<'static>, 17 + slider: Slider, 18 + eraser: bool, 19 + prev_pos: Option<Point>, 20 + } 21 + 22 + impl ScratchpadPage { 23 + pub(crate) fn new() -> Self { 24 + let exit_button = Button::with_default_style_auto_sized(Point::new(10, 416 - 10 - 20), "Exit", true); 25 + 26 + let next_pos = exit_button 27 + .bounding_box() 28 + .translate(Point::new(10, 0)) 29 + .anchor_point(AnchorPoint::TopRight); 30 + let clear_button = Button::with_default_style_auto_sized(next_pos, "Clear", true); 31 + 32 + let next_pos = clear_button 33 + .bounding_box() 34 + .translate(Point::new(10, 0)) 35 + .anchor_point(AnchorPoint::TopRight); 36 + let toggle_button = Button::with_default_style_auto_sized(next_pos, "Eraser", true); 37 + 38 + let slider = Slider::with_default_style(Point::new(20, 20), 100, 1, 10, 2); 39 + 40 + Self { 41 + exit_button, 42 + clear_button, 43 + toggle_button, 44 + slider, 45 + eraser: false, 46 + prev_pos: None, 47 + } 48 + } 49 + } 50 + 51 + impl Gui for ScratchpadPage { 52 + type Output = Option<MainGui>; 53 + 54 + fn draw_init(&self, draw_target: &mut EpdDrawTarget) { 55 + self.exit_button.draw_init(draw_target); 56 + self.clear_button.draw_init(draw_target); 57 + self.toggle_button.draw_init(draw_target); 58 + self.slider.draw_init(draw_target); 59 + } 60 + 61 + fn tick(&mut self, draw_target: &mut EpdDrawTarget, ev: Event) -> Self::Output { 62 + let mut refresh = false; 63 + let mut maybe_refresh = false; 64 + let mut handle_drawing: bool = true; 65 + 66 + let e = self.exit_button.tick(draw_target, ev); 67 + if e.clicked { 68 + return Some(MainGui::MainPage(MainPage::new())); 69 + } 70 + refresh |= e.needs_refresh; 71 + 72 + let c = self.clear_button.tick(draw_target, ev); 73 + if c.clicked { 74 + draw_target.clear(BinaryColor::Off).unwrap(); 75 + self.draw_init(draw_target); 76 + draw_target.refresh(false); 77 + return None; 78 + } 79 + refresh |= c.needs_refresh; 80 + 81 + let t = self.toggle_button.tick(draw_target, ev); 82 + if t.clicked { 83 + self.eraser = !self.eraser; 84 + self.toggle_button.label = if self.eraser { "Pen" } else { "Eraser" }; 85 + } 86 + maybe_refresh |= t.clicked; 87 + refresh |= t.needs_refresh; 88 + 89 + if self.slider.tick(draw_target, ev) { 90 + maybe_refresh = true; 91 + handle_drawing = false; 92 + } 93 + 94 + if let Event::Touch(ev) = ev { 95 + if handle_drawing && matches!(ev.ev_type, TouchEventType::Move | TouchEventType::Up) { 96 + if let Some(prev) = self.prev_pos { 97 + let style = PrimitiveStyle::with_stroke(BinaryColor::from(!self.eraser), self.slider.value as u32); 98 + Line::new(prev, ev.eg_point()) 99 + .into_styled(style) 100 + .draw(draw_target) 101 + .unwrap(); 102 + // Draw a circle at each end of the line 103 + let circle_style = PrimitiveStyle::with_fill(BinaryColor::from(!self.eraser)); 104 + Circle::with_center(prev, self.slider.value as u32 - 1) 105 + .into_styled(circle_style) 106 + .draw(draw_target) 107 + .unwrap(); 108 + Circle::with_center(ev.eg_point(), self.slider.value as u32 - 1) 109 + .into_styled(circle_style) 110 + .draw(draw_target) 111 + .unwrap(); 112 + 113 + self.draw_init(draw_target); 114 + 115 + maybe_refresh = true; 116 + } 117 + } 118 + 119 + if matches!(ev.ev_type, TouchEventType::Down | TouchEventType::Move) { 120 + self.prev_pos = Some(ev.eg_point()); 121 + } 122 + } 123 + 124 + if refresh { 125 + draw_target.refresh(true); 126 + } else if maybe_refresh { 127 + draw_target.maybe_refresh(true); 128 + } 129 + 130 + None 131 + } 132 + }
+1
eepy-sys/Cargo.toml
··· 4 4 edition = "2021" 5 5 6 6 [dependencies] 7 + eepy-derive = { path = "../eepy-derive" } 7 8 tp370pgh01 = { path = "../tp370pgh01" } 8 9 defmt = { workspace = true, optional = true } 9 10 embedded-graphics = { workspace = true, optional = true }
+2
eepy-sys/src/lib.rs
··· 1 1 #![no_std] 2 2 3 + pub use eepy_derive::eepy_app; 4 + 3 5 #[cfg(all(target_os = "none", target_arch = "arm"))] 4 6 pub mod header; 5 7 #[cfg(all(target_os = "none", target_arch = "arm"))]