From 99b9865edee05bb49ae447187a6db746593114cf Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 22 Feb 2024 07:46:25 +0100 Subject: [PATCH] WIP - new drawing library demo/test --- core/embed/bootloader/main.c | 2 + core/embed/firmware/main.c | 4 + core/embed/rust/rust_ui.h | 2 + core/embed/rust/src/ui/drawlib_demo_r.rs | 252 +++++++++++++++ core/embed/rust/src/ui/drawlib_demo_t.rs | 373 +++++++++++++++++++++++ core/embed/rust/src/ui/minion.jpg | Bin 0 -> 23500 bytes core/embed/rust/src/ui/mod.rs | 6 + core/embed/unix/main_main.c | 9 + 8 files changed, 648 insertions(+) create mode 100644 core/embed/rust/src/ui/drawlib_demo_r.rs create mode 100644 core/embed/rust/src/ui/drawlib_demo_t.rs create mode 100644 core/embed/rust/src/ui/minion.jpg diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index f9fed5cc0..b644313c7 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -652,6 +652,8 @@ int bootloader_main(void) { firmware_jump_fn = real_jump_to_firmware; } + drawlib_demo(); + firmware_jump_fn(); return 0; diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index f21fdeac8..78f03fc36 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -189,6 +189,10 @@ int main(void) { ensure(sectrue * (zkp_context_init() == 0), NULL); #endif + drawlib_demo(); // TODO: !!! remove + while (1) + ; + printf("CORE: Preparing stack\n"); // Stack limit should be less than real stack size, so we have a chance // to recover from limit hit. diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h index 5f2ff97e1..c84bc0bd2 100644 --- a/core/embed/rust/rust_ui.h +++ b/core/embed/rust/rust_ui.h @@ -32,3 +32,5 @@ void display_image(int16_t x, int16_t y, const uint8_t* data, uint32_t datalen); void display_icon(int16_t x, int16_t y, const uint8_t* data, uint32_t datalen, uint16_t fg_color, uint16_t bg_color); void bld_continue_label(uint16_t bg_color); + +void drawlib_demo(); \ No newline at end of file diff --git a/core/embed/rust/src/ui/drawlib_demo_r.rs b/core/embed/rust/src/ui/drawlib_demo_r.rs new file mode 100644 index 000000000..efb606a40 --- /dev/null +++ b/core/embed/rust/src/ui/drawlib_demo_r.rs @@ -0,0 +1,252 @@ +use crate::ui::{ + display::{Color, Font}, + event::{ButtonEvent, PhysicalButton}, + geometry::{Insets, Offset, Point, Rect}, + model_tr::theme::bootloader::{ICON_ALERT, ICON_SPINNER, ICON_TRASH}, + shape, + shape::Renderer, +}; + +use qrcodegen::{QrCode, QrCodeEcc, Version}; + +use crate::time; +use core::fmt::Write; +use heapless::String; + +use crate::trezorhal::io::io_button_read; + +const ICON_GOOGLE: &[u8] = include_res!("model_tt/res/fido/icon_google.toif"); + +fn render_screen_1(target: &mut impl Renderer) { + let pt = Point::new(0, 0); + shape::Text::new(pt, "TREZOR!!!") + .with_fg(Color::rgb(255, 0, 0)) + .render(target); + + let pt = Point::new(40, 0); + shape::Text::new(pt, "TREZOR!!!") + .with_fg(Color::rgb(0, 255, 0)) + .render(target); + + let pt = Point::new(100, 0); + shape::Text::new(pt, "TREZOR!!!") + .with_fg(Color::rgb(0, 0, 255)) + .render(target); + + let pt = Point::new(80, 30); + shape::Text::new(pt, "BITCOIN!") + .with_font(Font::BOLD) + .render(target); + + let pt = Point::new(80, 40); + let s = "SatoshiLabs"; + shape::Text::new(pt, s) + .with_fg(Color::rgb(0, 255, 255)) + .render(target); + + shape::Text::new(pt + Offset::new(1, 1), s) + .with_fg(Color::rgb(255, 0, 0)) + .render(target); + + let pt = Point::new(-1, 25); + shape::ToifImage::new(pt, ICON_TRASH.toif) + .with_fg(Color::black()) + .with_bg(Color::white()) + .render(target); + + let pt = Point::new(80, 35); + shape::ToifImage::new(pt, ICON_ALERT.toif).render(target); + + let pt = Point::new(95, 50); + shape::ToifImage::new(pt, ICON_SPINNER.toif) + .with_fg(Color::rgb(64, 192, 200)) + .render(target); +} + +const QR_MAX_VERSION: Version = Version::new(10); + +fn render_screen_2(target: &mut impl Renderer, ctx: &DemoContext) { + let r = Rect::new(Point::new(4, 4), Point::new(64, 64)); + shape::Bar::new(r) + .with_bg(Color::white()) + .with_radius(1) + .render(target); + + if let Some(qr) = ctx.qr { + shape::QrImage::new(r.inset(Insets::uniform(2)), qr) + .with_fg(Color::white()) + .with_bg(Color::black()) + .render(target); + } +} + +fn render_screen_3(target: &mut impl Renderer, ctx: &DemoContext) { + let pt = Point::new(64, 32); + + shape::Circle::new(pt, 20) + .with_start_angle(ctx.counter - 360) + .with_end_angle(ctx.counter) + .with_bg(Color::white()) + .render(target); + + shape::Circle::new(pt, 20) + .with_fg(Color::white()) + .with_thickness(1) + .render(target); + + shape::Circle::new(pt, 13) + .with_bg(Color::black()) + .render(target); + + let toif = ICON_ALERT.toif; + let icon_tl = Point::new(pt.x - toif.width() / 2, pt.y - toif.height() / 2); + shape::ToifImage::new(icon_tl, toif).render(target); + + let pt = Point::new(20, 55); + shape::Text::new(pt, "Installing firmware") + .with_fg(Color::white()) + .render(target); +} + +fn render_screen_4(target: &mut impl Renderer, _ctx: &DemoContext) { + let r = Rect::from_size(Offset::new(24, 18)).translate(Offset::new(4, 12)); + + for y in 0..3 { + for x in 0..4 { + let radius = x + y * 4; + let ofs = Offset::new(x * 32, y * 20); + shape::Bar::new(r.translate(ofs)) + .with_radius(radius) + .with_bg(Color::white()) + .render(target); + } + } +} + +fn render_demo(ctx: &DemoContext) -> time::Duration { + let split = ctx.split; + let start_time = time::Instant::now(); + + shape::render_on_display( + Some(Rect::new(Point::new(0, 0), Point::new(128, 64))), + Some(Color::black()), + |target| { + target.with_origin(Offset::new(split.x, split.y), &|target| { + render_screen_4(target, ctx); + }); + + target.with_origin(Offset::new(split.x - 128, split.y), &|target| { + render_screen_3(target, ctx); + }); + + target.with_origin(Offset::new(split.x - 256, split.y), &|target| { + render_screen_2(target, ctx); + }); + + target.with_origin(Offset::new(split.x - 384, split.y), &|target| { + render_screen_1(target); + }); + }, + ); + + time::Instant::now() + .checked_duration_since(start_time) + .unwrap() +} + +fn render_info(duration: time::Duration) { + shape::render_on_display( + Some(Rect::new(Point::new(96, 0), Point::new(128, 10))), + Some(Color::white()), + |target| { + let text_color = Color::black(); + + let mut info = String::<128>::new(); + write!(info, "{}ms", duration.to_millis() as f32 / 1.0).unwrap(); + + let pt = Point::new(97, 1); + shape::Text::new(pt, info.as_str()) + .with_offset(false) + .with_fg(text_color) + .render(target); + }, + ); +} + +struct DemoContext<'a> { + qr: Option<&'a QrCode<'a>>, + split: Point, + counter: i16, +} + +impl<'a> DemoContext<'a> { + fn new(qr: Option<&'a QrCode>) -> Self { + Self { + qr, + split: Point::zero(), + counter: 0, + } + } + + fn update(&mut self, event: Option) { + match event { + Some(ButtonEvent::ButtonPressed(PhysicalButton::Left)) => { + self.split.x -= 32; + } + Some(ButtonEvent::ButtonPressed(PhysicalButton::Right)) => { + self.split.x += 32; + } + + _ => {} + } + + self.counter += 1; + if self.counter == 720 { + self.counter = 0; + } + } +} + +fn button_eval() -> Option { + let event = io_button_read(); + if event == 0 { + return None; + } + + let event_type = event >> 24; + let event_btn = event & 0xFFFFFF; + + let event = ButtonEvent::new(event_type, event_btn); + + if let Ok(event) = event { + return Some(event); + } + None +} + +static mut FB: [u8; 128 * 64] = [0u8; 128 * 64]; + +#[no_mangle] +extern "C" fn drawlib_demo() { + let mut outbuffer = [0u8; QR_MAX_VERSION.buffer_len()]; + let mut tempbuffer = [0u8; QR_MAX_VERSION.buffer_len()]; + + let qr = unwrap!(QrCode::encode_text( + "https://satoshilabs.com", + &mut tempbuffer, + &mut outbuffer, + QrCodeEcc::Medium, + Version::MIN, + QR_MAX_VERSION, + None, + true, + )); + + let mut ctx = DemoContext::new(Some(&qr)); + + loop { + ctx.update(button_eval()); + let duration = render_demo(&ctx); + render_info(duration); + } +} diff --git a/core/embed/rust/src/ui/drawlib_demo_t.rs b/core/embed/rust/src/ui/drawlib_demo_t.rs new file mode 100644 index 000000000..580b84665 --- /dev/null +++ b/core/embed/rust/src/ui/drawlib_demo_t.rs @@ -0,0 +1,373 @@ +use crate::ui::{ + display::{refresh, toif::Toif, Color, Font}, + event::TouchEvent, + geometry::{Insets, Offset, Point, Rect}, + model_tt::theme::bootloader::{FIRE40, REFRESH24, WARNING40}, + shape, + shape::Renderer, +}; + +use qrcodegen::{QrCode, QrCodeEcc, Version}; + +use crate::{time, trezorhal::io::io_touch_read}; +use core::fmt::Write; +use heapless::String; + +fn render_screen_1(target: &mut impl Renderer) { + let r = Rect::from_top_left_and_size(Point::new(30, 120), Offset::new(180, 60)); + shape::Bar::new(r) + .with_radius(16) + .with_bg(Color::rgb(96, 128, 128)) + .render(target); + + let r = Rect::from_top_left_and_size(Point::new(50, 50), Offset::new(50, 50)); + shape::Bar::new(r) + .with_fg(Color::rgb(128, 0, 192)) + .with_bg(Color::rgb(192, 0, 0)) + .with_thickness(4) + .render(target); + + let r = Rect::new(Point::zero(), Point::new(16, 160)); + shape::Bar::new(r.translate(Offset::new(140, 40))) + .with_bg(Color::rgb(0, 160, 0)) + .render(target); + + let pt = Point::new(0, 0); + shape::Text::new(pt, "TREZOR!!!") + .with_fg(Color::rgb(255, 0, 0)) + .render(target); + + let pt = Point::new(80, 0); + shape::Text::new(pt, "TREZOR!!!") + .with_fg(Color::rgb(0, 255, 0)) + .render(target); + + let pt = Point::new(160, 0); + shape::Text::new(pt, "TREZOR!!!") + .with_fg(Color::rgb(0, 0, 255)) + .render(target); + + let pt = Point::new(80, 80); + shape::Text::new(pt, "BITCOIN!") + .with_font(Font::BOLD) + .render(target); + + let pt = Point::new(80, 140); + let s = "SatoshiLabs"; + shape::Text::new(pt, s) + .with_fg(Color::rgb(0, 255, 255)) + .render(target); + + shape::Text::new(pt + Offset::new(1, 1), s) + .with_fg(Color::rgb(255, 0, 0)) + .render(target); + + let pt = Point::new(-1, 40); + let toif = Toif::new(REFRESH24).unwrap(); + shape::ToifImage::new(pt, toif) + .with_fg(Color::black()) + .with_bg(Color::white()) + .render(target); + + let pt = Point::new(80, 40); + let toif = Toif::new(FIRE40).unwrap(); + shape::ToifImage::new(pt, toif).render(target); + + let pt = Point::new(95, 50); + let toif = Toif::new(WARNING40).unwrap(); + shape::ToifImage::new(pt, toif) + .with_fg(Color::rgb(64, 192, 200)) + .render(target); + + const ICON_GOOGLE: &[u8] = include_res!("model_tt/res/fido/icon_google.toif"); + + let pt = Point::new(0, 70); + let toif = Toif::new(ICON_GOOGLE).unwrap(); + shape::ToifImage::new(pt, toif).render(target); + + let pt = Point::new(120, 120); + shape::Circle::new(pt, 20) + .with_bg(Color::white()) + .render(target); +} + +fn draw_screen_2(target: &mut impl Renderer) { + let pt = Point::new(120, 110); + shape::Circle::new(pt, 60) + .with_bg(Color::rgb(80, 80, 80)) + .render(target); + shape::Circle::new(pt, 42) + .with_bg(Color::rgb(0, 0, 0)) + .with_fg(Color::white()) + .with_thickness(2) + .render(target); + + let toif = Toif::new(FIRE40).unwrap(); + let icon_tl = Point::new(pt.x - toif.width() / 2, pt.y - toif.height() / 2); + shape::ToifImage::new(icon_tl, toif).render(target); + + let pt = Point::new(35, 190); + shape::Text::new(pt, "Installing firmware") + .with_fg(Color::white()) + .render(target); +} + +fn render_screen_3(target: &mut impl Renderer) { + const IMAGE_HOMESCREEN: &[u8] = include_res!("minion.jpg"); + + shape::JpegImage::new(Point::new(0, 0), IMAGE_HOMESCREEN) + .with_scale(0) + .with_blur(2) + .render(target); +} + +fn render_screen_4(target: &mut impl Renderer, ctx: &DemoContext) { + let r = Rect::new(Point::new(30, 30), Point::new(210, 210)); + shape::Bar::new(r) + .with_bg(Color::white()) + .with_radius(8) + .render(target); + + if let Some(qr) = ctx.qr { + shape::QrImage::new(r.inset(Insets::uniform(4)), qr) + .with_fg(Color::white()) + .with_bg(Color::rgb(80, 0, 0)) + .render(target); + } +} + +fn render_screen_5(target: &mut impl Renderer, ctx: &DemoContext) { + let pt = Point::new(120, 110); + shape::Circle::new(pt, 60) + .with_bg(Color::rgb(80, 80, 80)) + .render(target); + + shape::Circle::new(pt, 60) + .with_start_angle(ctx.counter - 360) + .with_end_angle(ctx.counter) + .with_bg(Color::white()) + .render(target); + + shape::Circle::new(pt, 42) + .with_bg(Color::rgb(0, 0, 0)) + .with_fg(Color::white()) + .with_thickness(2) + .render(target); + + let toif = Toif::new(FIRE40).unwrap(); + let icon_tl = Point::new(pt.x - toif.width() / 2, pt.y - toif.height() / 2); + shape::ToifImage::new(icon_tl, toif).render(target); + + let pt = Point::new(35, 190); + shape::Text::new(pt, "Installing firmware") + .with_fg(Color::white()) + .render(target); +} + +fn render_screen_6(target: &mut impl Renderer, _ctx: &DemoContext) { + let r = Rect::from_size(Offset::new(40, 40)).translate(Offset::new(10, 10)); + + for y in 0..4 { + for x in 0..4 { + let radius = x + y * 4; + let ofs = Offset::new(x * 50, y * 50); + shape::Bar::new(r.translate(ofs)) + .with_radius(radius) + .with_bg(Color::white()) + .render(target); + } + } +} + +fn render_demo(ctx: &DemoContext) -> time::Duration { + let split = ctx.split + ctx.delta; + let start_time = time::Instant::now(); + + shape::render_on_display( + Some(Rect::new(Point::new(0, 20), Point::new(240, 240))), + Some(Color::rgb(0, 0, 48)), + |target| { + target.with_origin(Offset::new(split.x, split.y), &|target| { + render_screen_6(target, ctx); + }); + + target.with_origin(Offset::new(split.x - 240, split.y), &|target| { + render_screen_5(target, ctx); + }); + + target.with_origin(Offset::new(split.x - 480, split.y), &|target| { + render_screen_4(target, ctx); + }); + + target.with_origin(Offset::new(split.x - 720, split.y), &|target| { + render_screen_3(target); + }); + + target.with_origin(Offset::new(split.x - 960, split.y), &|target| { + draw_screen_2(target); + }); + + target.with_origin(Offset::new(split.x - 1200, split.y), &|target| { + render_screen_1(target); + }); + + /*let r = Rect::new(Point::new(60, 60), Point::new(180, 180)); + //let r = Rect::new(Point::new(0, 0), Point::new(240, 240)); + //shape::Blurring::new(r, 1).render(&mut target); + //shape::Blurring::new(r, 2).render(&mut target); + //shape::Blurring::new(r, 3).render(&mut target); + shape::Blurring::new(r, 4).render(&mut target); + shape::Bar::new(r) + .with_fg(Color::white()) + .render(&mut target);*/ + }, + ); + + time::Instant::now() + .checked_duration_since(start_time) + .unwrap() +} + +fn render_info(duration: time::Duration, evstr: &str) { + shape::render_on_display( + Some(Rect::new(Point::zero(), Point::new(240, 20))), + Some(Color::rgb(0, 0, 255)), + |target| { + let text_color = Color::rgb(255, 255, 0); + + let mut info = String::<128>::new(); + write!(info, "{}ms", duration.to_millis() as f32 / 1.0).unwrap(); + + let pt = Point::new(0, 0); + shape::Text::new(pt, info.as_str()) + .with_offset(false) + .with_fg(text_color) + .render(target); + + let pt = Point::new(60, 0); + shape::Text::new(pt, evstr) + .with_offset(false) + .with_fg(text_color) + .render(target); + }, + ); +} + +struct DemoContext<'a> { + split: Point, + origin: Point, + delta: Offset, + pressed: bool, + evstr: String<128>, + sevstr: String<128>, + qr: Option<&'a QrCode<'a>>, + + pub counter: i16, +} + +impl<'a> DemoContext<'a> { + fn new(qr: Option<&'a QrCode>) -> Self { + Self { + split: Point::zero(), + origin: Point::zero(), + delta: Offset::zero(), + pressed: false, + evstr: String::<128>::new(), + sevstr: String::<128>::new(), + counter: 0, + qr: qr, + } + } + + fn update(&mut self, event: Option) { + match event { + Some(TouchEvent::TouchStart(pt)) => { + self.origin = pt; + + self.evstr.clear(); + self.sevstr.clear(); + write!(self.evstr, "S[{},{}]", pt.x, pt.y).unwrap(); + write!(self.sevstr, "{} ", self.evstr).unwrap(); + } + + Some(TouchEvent::TouchMove(pt)) => { + self.evstr.clear(); + write!(self.evstr, "{}", self.sevstr).unwrap(); + write!(self.evstr, "M[{},{}]", pt.x, pt.y).unwrap(); + + let delta = pt - self.origin; + let k = 0; + self.delta.x = (self.delta.x * k + delta.x * (10 - k)) / 10; + self.delta.y = (self.delta.y * k + delta.y * (10 - k)) / 10; + } + Some(TouchEvent::TouchEnd(pt)) => { + self.evstr.clear(); + write!(self.evstr, "{}", self.sevstr).unwrap(); + write!(self.evstr, "E[{},{}]", pt.x, pt.y).unwrap(); + + self.split = self.split + self.delta; + self.pressed = false; + self.delta = Offset::zero(); + } + None => { + if self.split.x < 0 { + self.split.x -= self.split.x / 4; + } else if self.split.x > 960 { + self.split.x -= (self.split.x - 960) / 4; + } + + if self.split.y < -120 { + self.split.y = -120; + } else if self.split.y > 120 { + self.split.y = 120; + } + } + } + self.counter += 1; + if self.counter == 720 { + self.counter = 0; + } + } +} + +fn touch_event() -> Option { + let event = io_touch_read(); + if event == 0 { + return None; + } + let event_type = event >> 24; + let ex = ((event >> 12) & 0xFFF) as i16; + let ey = (event & 0xFFF) as i16; + + TouchEvent::new(event_type, ex as _, ey as _).ok() +} + +#[no_mangle] +extern "C" fn drawlib_demo() { + const QR_MAX_VERSION: Version = Version::new(10); + + let mut outbuffer = [0u8; QR_MAX_VERSION.buffer_len()]; + let mut tempbuffer = [0u8; QR_MAX_VERSION.buffer_len()]; + + let qr = unwrap!(QrCode::encode_text( + "https://satoshilabs.com", + &mut tempbuffer, + &mut outbuffer, + QrCodeEcc::Medium, + Version::MIN, + QR_MAX_VERSION, + None, + true, + )); + + let mut ctx = DemoContext::new(Some(&qr)); + + loop { + ctx.update(touch_event()); + + let duration = render_demo(&ctx); + render_info(duration, ctx.evstr.as_str()); + + refresh(); + } +} diff --git a/core/embed/rust/src/ui/minion.jpg b/core/embed/rust/src/ui/minion.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9fabb371853bf91f283f7f4cbaea59d73d76f5c8 GIT binary patch literal 23500 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 x-MP=1KmX8LLN)`(wz#Hh{0-{tI*t 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 literal 0 HcmV?d00001 diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index 98891d08e..2fd8bbd1a 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -21,3 +21,9 @@ pub mod layout; pub mod model_tr; #[cfg(feature = "model_tt")] pub mod model_tt; + +#[cfg(feature = "model_tt")] +pub mod drawlib_demo_t; // !!! REMOVE + +#[cfg(feature = "model_tr")] +pub mod drawlib_demo_r; // !!! REMOVE diff --git a/core/embed/unix/main_main.c b/core/embed/unix/main_main.c index f1e289312..e5272618b 100644 --- a/core/embed/unix/main_main.c +++ b/core/embed/unix/main_main.c @@ -7,6 +7,10 @@ #include "common.h" +#include "display.h" // TODO:just for testing, remove later !!! +#include "rust_ui.h" // TODO:just for testing, remove later !!! +#include "touch.h" // TODO:just for testing, remove later !!! + MP_NOINLINE int main_(int argc, char **argv); int main(int argc, char **argv) { @@ -16,6 +20,11 @@ int main(int argc, char **argv) { ensure(sectrue * (zkp_context_init() == 0), NULL); #endif + display_refresh(); + display_backlight(255); + + drawlib_demo(); // TODO: !!! remove + #if MICROPY_PY_THREAD mp_thread_init(); #endif