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