From d600feb3f1473e33cff50e605436f5068a3e5771 Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 19 Apr 2024 09:40:19 +0200 Subject: [PATCH] WIP - drawlib animation demo --- core/embed/firmware/main.c | 4 + core/embed/rust/rust_ui.h | 2 + core/embed/rust/src/ui/demo/anim_timer.rs | 43 +++++++ core/embed/rust/src/ui/demo/demo_core.rs | 103 +++++++++++++++++ core/embed/rust/src/ui/demo/mod.rs | 4 + core/embed/rust/src/ui/demo/screen1.rs | 129 +++++++++++++++++++++ core/embed/rust/src/ui/demo/screen2.rs | 134 ++++++++++++++++++++++ core/embed/rust/src/ui/layout/obj.rs | 34 +++++- core/embed/rust/src/ui/mod.rs | 4 +- core/embed/unix/main.c | 6 + 10 files changed, 458 insertions(+), 5 deletions(-) create mode 100644 core/embed/rust/src/ui/demo/anim_timer.rs create mode 100644 core/embed/rust/src/ui/demo/demo_core.rs create mode 100644 core/embed/rust/src/ui/demo/mod.rs create mode 100644 core/embed/rust/src/ui/demo/screen1.rs create mode 100644 core/embed/rust/src/ui/demo/screen2.rs diff --git a/core/embed/firmware/main.c b/core/embed/firmware/main.c index 64e42da49..11bdc1a28 100644 --- a/core/embed/firmware/main.c +++ b/core/embed/firmware/main.c @@ -237,6 +237,10 @@ int main(void) { mp_obj_list_init(mp_sys_path, 0); mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen)); + drawlib_demo(); // TODO: !!! remove + while (1) + ; + // Execute the main script printf("CORE: Executing main script\n"); pyexec_frozen_module("main.py"); diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h index 897c6a331..68fa1f1c1 100644 --- a/core/embed/rust/rust_ui.h +++ b/core/embed/rust/rust_ui.h @@ -4,3 +4,5 @@ #include "rust_ui_bootloader.h" #include "rust_ui_common.h" + +void drawlib_demo(); diff --git a/core/embed/rust/src/ui/demo/anim_timer.rs b/core/embed/rust/src/ui/demo/anim_timer.rs new file mode 100644 index 000000000..217bef79f --- /dev/null +++ b/core/embed/rust/src/ui/demo/anim_timer.rs @@ -0,0 +1,43 @@ +use crate::time; + +pub struct AnimTimer { + base: Option, + limit: Option, +} + +impl AnimTimer { + pub fn new() -> Self { + Self { + base: None, + limit: None, + } + } + + pub fn reset(&mut self) { + self.base = None; + } + + pub fn start(&mut self) { + self.base = Some(time::Instant::now()); + } + + pub fn stop(&mut self) { + self.base = None; + } + + pub fn is_running(&self) -> bool { + self.base.is_some() + } + + pub fn elapsed(&self) -> f32 { + if let Some(t) = self.base { + time::Instant::now() + .checked_duration_since(t) + .unwrap() + .to_millis() as f32 + / 1000.0 + } else { + 0.0 + } + } +} diff --git a/core/embed/rust/src/ui/demo/demo_core.rs b/core/embed/rust/src/ui/demo/demo_core.rs new file mode 100644 index 000000000..c94747a6c --- /dev/null +++ b/core/embed/rust/src/ui/demo/demo_core.rs @@ -0,0 +1,103 @@ +use super::anim_timer::AnimTimer; +use crate::ui::{ + component::Event, + display::{self, Color, Font}, + event::TouchEvent, + geometry::{Offset, Point, Rect}, + shape::{self, Viewport}, +}; + +use crate::{time, trezorhal::io::io_touch_read}; +use core::fmt::Write; +use heapless::String; + +use super::{screen1::build_screen1, screen2::build_screen2}; + +fn render_time_overlay(duration: time::Duration) { + shape::render_on_display( + Some(Viewport::new(Rect::new( + Point::new(200, 0), + 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, "{}", duration.to_millis() as f32 / 1.0).unwrap(); + + let font = Font::NORMAL; + let pt = Point::new(200, font.vert_center(0, 20, "A")); + shape::Text::new(pt, info.as_str()) + .with_fg(text_color) + .with_font(font) + .render(target); + }, + ); +} + +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() +} + +// Returns the split point between two screens +// (ranging from -240 to 0) +fn anim_screen_transition(timer: &AnimTimer) -> i16 { + let anim = pareen::constant(0.0) + .seq_ease_in_out( + 0.0, + pareen::easer::functions::Back, + 1.5, + pareen::constant(-240.0), + ) + .seq_ease_in_out( + 5.0, + pareen::easer::functions::Back, + 0.75, + pareen::constant(0.0), + ) + .repeat(10.0); + + anim.eval(timer.elapsed()) as i16 +} + +#[no_mangle] +extern "C" fn drawlib_demo() { + let screen1 = build_screen1().unwrap(); + let screen2 = build_screen2().unwrap(); + + let mut timer = AnimTimer::new(); + timer.start(); + + loop { + if let Some(e) = touch_event() { + screen1.obj_event(Event::Touch(e)).unwrap(); + screen2.obj_event(Event::Touch(e)).unwrap(); + } + + let split = anim_screen_transition(&timer); + + display::sync(); + + let start_time = time::Instant::now(); + + screen1.obj_render(Offset::x(split)); + screen2.obj_render(Offset::x(240 + split)); + + let duration = time::Instant::now() + .checked_duration_since(start_time) + .unwrap(); + + render_time_overlay(duration); + + display::refresh(); + } +} diff --git a/core/embed/rust/src/ui/demo/mod.rs b/core/embed/rust/src/ui/demo/mod.rs new file mode 100644 index 000000000..66ccc4725 --- /dev/null +++ b/core/embed/rust/src/ui/demo/mod.rs @@ -0,0 +1,4 @@ +pub mod anim_timer; +pub mod demo_core; +pub mod screen1; +pub mod screen2; diff --git a/core/embed/rust/src/ui/demo/screen1.rs b/core/embed/rust/src/ui/demo/screen1.rs new file mode 100644 index 000000000..06d9d44c8 --- /dev/null +++ b/core/embed/rust/src/ui/demo/screen1.rs @@ -0,0 +1,129 @@ +use crate::ui::{ + component::{Component, Event, EventCtx, Never}, + display::{font::Font, Color}, + event::TouchEvent, + geometry::{Point, Rect}, + layout::obj::{ComponentMsgObj, LayoutObj}, + model_tt::{component::Frame, theme}, + shape, + shape::Renderer, +}; + +use crate::{ + error::Error, + micropython::{gc::Gc, obj::Obj}, +}; +use pareen; + +use super::anim_timer::AnimTimer; + +pub struct Screen1 { + jump_time: AnimTimer, +} + +impl Screen1 { + pub fn new() -> Self { + Self { + jump_time: AnimTimer::new(), + } + } +} + +impl Screen1 { + fn anim_ball_coords(&self) -> Point { + let x_anim = pareen::constant(30.0).seq_ease_out( + 0.0, + pareen::easer::functions::Cubic, + 1.5, + pareen::constant(210.0), + ); + + let y_anim = pareen::constant(30.0).seq_ease_out( + 0.0, + pareen::easer::functions::Bounce, + 1.5, + pareen::constant(210.0), + ); + + let t = self.jump_time.elapsed(); + + Point::new(x_anim.eval(t) as i16, y_anim.eval(t) as i16) + } + + fn anim_text_coords(&self) -> Point { + let x_anim = pareen::constant(30.0) + .seq_ease_in( + 0.0, + pareen::easer::functions::Cubic, + 0.3, + pareen::constant(240.0), + ) + .seq_ease_out( + 2.5, + pareen::easer::functions::Elastic, + 0.9, + pareen::constant(30.0), + ); + + let t = self.jump_time.elapsed(); + + Point::new(x_anim.eval(t) as i16, 120) + } +} + +impl Component for Screen1 { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + bounds + } + + fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option { + match event { + Event::Touch(TouchEvent::TouchStart(_)) => { + self.jump_time.start(); + } + _ => {} + } + + None + } + + fn paint(&mut self) {} + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let r = Rect::new(Point::new(10, 30), Point::new(230, 230)); + shape::Bar::new(r) + .with_bg(Color::rgb(0, 20, 40)) + .render(target); + + shape::Text::new(self.anim_text_coords(), "Touch to start") + .with_font(Font::BOLD) + .render(target); + + shape::Circle::new(self.anim_ball_coords(), 16) + .with_bg(Color::rgb(96, 128, 128)) + .render(target); + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Screen1 { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("Demo screen"); + } +} + +impl ComponentMsgObj for Screen1 { + fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { + unreachable!(); + } +} + +pub fn build_screen1() -> Result, Error> { + LayoutObj::new(Frame::left_aligned( + theme::label_title(), + "Animation Demo".into(), + Screen1::new(), + )) +} diff --git a/core/embed/rust/src/ui/demo/screen2.rs b/core/embed/rust/src/ui/demo/screen2.rs new file mode 100644 index 000000000..f7a4efb65 --- /dev/null +++ b/core/embed/rust/src/ui/demo/screen2.rs @@ -0,0 +1,134 @@ +use crate::ui::{ + component::{Component, Event, EventCtx, Never}, + display::Color, + event::TouchEvent, + geometry::{Offset, Point, Rect}, + layout::obj::{ComponentMsgObj, LayoutObj}, + model_tt::{component::Frame, theme}, + shape, + shape::Renderer, +}; + +use crate::{ + error::Error, + micropython::{gc::Gc, obj::Obj}, +}; +use pareen; + +use super::anim_timer::AnimTimer; + +pub struct Screen1 { + jump_time: AnimTimer, +} + +impl Screen1 { + pub fn new() -> Self { + Self { + jump_time: { + let mut timer = AnimTimer::new(); + timer.start(); + timer + }, + } + } +} + +impl Screen1 { + fn anim_radius(&self) -> (i16, i16, i16) { + let r1_anim = pareen::circle() + .cos() + .scale_min_max(60f32, 64f32) + .repeat(1.0f32); + + let r2_anim = pareen::circle() + .cos() + .scale_min_max(52f32, 58f32) + .squeeze(0f32..=0.5f32) + .repeat(0.5f32); + + let r3_anim = pareen::circle() + .cos() + .scale_min_max(40f32, 50f32) + .squeeze(0f32..=2f32) + .repeat(2f32); + + let t = self.jump_time.elapsed(); + + ( + r1_anim.eval(t) as i16, + r2_anim.eval(t) as i16, + r3_anim.eval(t) as i16, + ) + } +} + +impl Component for Screen1 { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + bounds + } + + fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option { + match event { + Event::Touch(TouchEvent::TouchStart(_)) => { + self.jump_time.start(); + } + _ => {} + } + + None + } + + fn paint(&mut self) {} + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let bg_color = Color::rgb(40, 20, 0); + + let r = Rect::new(Point::new(10, 30), Point::new(230, 230)); + shape::Bar::new(r).with_bg(bg_color).render(target); + + let center = Point::new(120, 120); + + let (r1, r2, r3) = self.anim_radius(); + + shape::Circle::new(center, r1) + .with_thickness(2) + .with_bg(bg_color) + .with_fg(Color::rgb(0, 120, 0)) + .render(target); + + shape::Circle::new(center, r2) + .with_thickness(3) + .with_bg(bg_color) + .with_fg(Color::rgb(0, 120, 30)) + .render(target); + + shape::Circle::new(center, r3) + .with_thickness(4) + .with_bg(bg_color) + .with_fg(Color::rgb(0, 120, 30)) + .render(target); + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Screen1 { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("Demo screen"); + } +} + +impl ComponentMsgObj for Screen1 { + fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { + unreachable!(); + } +} + +pub fn build_screen2() -> Result, Error> { + LayoutObj::new(Frame::left_aligned( + theme::label_title(), + "Animation Demo 2".into(), + Screen1::new(), + )) +} diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 6c00316ad..8f7f5f633 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -20,8 +20,8 @@ use crate::{ component::{Component, Event, EventCtx, Never, Root, TimerToken}, constant, display::{sync, Color}, - geometry::Rect, - shape::render_on_display, + geometry::{Offset, Rect}, + shape::{render_on_display, Viewport}, }, }; @@ -48,6 +48,8 @@ pub trait ObjComponent: MaybeTrace { fn obj_place(&mut self, bounds: Rect) -> Rect; fn obj_event(&mut self, ctx: &mut EventCtx, event: Event) -> Result; fn obj_paint(&mut self) -> bool; + #[cfg(feature = "new_rendering")] + fn obj_render(&mut self, offset: Offset); fn obj_bounds(&self, _sink: &mut dyn FnMut(Rect)) {} fn obj_skip_paint(&mut self) {} fn obj_request_clear(&mut self) {} @@ -92,6 +94,15 @@ where } } + #[cfg(feature = "new_rendering")] + fn obj_render(&mut self, offset: Offset) { + let screen = crate::ui::constant::screen(); + let vp = Viewport::new(screen).translate(offset); + render_on_display(Some(vp), Some(Color::black()), |target| { + self.render(target); + }); + } + #[cfg(feature = "ui_bounds")] fn obj_bounds(&self, sink: &mut dyn FnMut(Rect)) { self.bounds(sink) @@ -160,7 +171,7 @@ impl LayoutObj { /// pending timers are drained into `self.timer_callback`. Returns `Err` /// in case the timer callback raises or one of the components returns /// an error, `Ok` with the message otherwise. - fn obj_event(&self, event: Event) -> Result { + pub fn obj_event(&self, event: Event) -> Result { let inner = &mut *self.inner.borrow_mut(); // Place the root component on the screen in case it was previously requested. @@ -205,7 +216,7 @@ impl LayoutObj { /// Run a paint pass over the component tree. Returns true if any component /// actually requested painting since last invocation of the function. - fn obj_paint_if_requested(&self) -> bool { + pub fn obj_paint_if_requested(&self) -> bool { let mut inner = self.inner.borrow_mut(); // Place the root component on the screen in case it was previously requested. @@ -220,6 +231,21 @@ impl LayoutObj { unsafe { Gc::as_mut(&mut inner.root) }.obj_paint() } + /// Run a paint pass over the component tree. Returns true if any component + /// actually requested painting since last invocation of the function. + pub fn obj_render(&self, offset: Offset) { + let mut inner = self.inner.borrow_mut(); + + // Place the root component on the screen in case it was previously requested. + if inner.event_ctx.needs_place_before_next_event_or_paint() { + // SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`. + unsafe { Gc::as_mut(&mut inner.root) }.obj_place(constant::screen()); + } + + // SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`. + unsafe { Gc::as_mut(&mut inner.root) }.obj_render(offset) + } + /// Run a tracing pass over the component tree. Passed `callback` is called /// with each piece of tracing information. Panics in case the callback /// raises an exception. diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index b08f77789..ab29227b7 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -23,5 +23,7 @@ pub mod model_tr; #[cfg(feature = "model_tt")] pub mod model_tt; pub mod ui_features; - pub use ui_features::UIFeaturesCommon; + +#[cfg(any(feature = "model_tt", feature = "model_mercury"))] +pub mod demo; // !!! REMOVE diff --git a/core/embed/unix/main.c b/core/embed/unix/main.c index 2acd5b528..a14acef66 100644 --- a/core/embed/unix/main.c +++ b/core/embed/unix/main.c @@ -56,6 +56,8 @@ #include "common.h" +#include "rust_ui.h" + // Command line options, with their defaults STATIC bool compile_only = false; STATIC uint emit_opt = MP_EMIT_OPT_NONE; @@ -547,6 +549,10 @@ MP_NOINLINE int main_(int argc, char **argv) { mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); + display_backlight(255); + drawlib_demo(); // TODO: !!! remove + while (1) ; + // Here is some example code to create a class and instance of that class. // First is the Python, then the C code. //