From 942e3953e29e364ab6789eeb5b8a438e56bb9ae0 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Mon, 10 Mar 2025 18:04:11 +0100 Subject: [PATCH] feat(eckhart): implement bootloader loader --- .../src/ui/layout_eckhart/cshape/loader.rs | 112 +++++++++++++----- .../rust/src/ui/layout_eckhart/cshape/mod.rs | 2 +- .../ui/layout_eckhart/cshape/screen_border.rs | 2 +- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/core/embed/rust/src/ui/layout_eckhart/cshape/loader.rs b/core/embed/rust/src/ui/layout_eckhart/cshape/loader.rs index b81575dc09..37dffd024d 100644 --- a/core/embed/rust/src/ui/layout_eckhart/cshape/loader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/cshape/loader.rs @@ -1,40 +1,96 @@ -use crate::ui::{display::Color, geometry::Point, shape, shape::Renderer}; +use crate::ui::{ + constant::SCREEN, + geometry::{Offset, Rect}, + lerp::Lerp, + shape::{self, Renderer}, +}; -use super::super::constant; - -pub enum LoaderRange { - Full, - FromTo(f32, f32), -} +use super::{super::theme::BG, ScreenBorder}; pub fn render_loader<'s>( - center: Point, - inactive_color: Color, - active_color: Color, - background_color: Color, - range: LoaderRange, + progress: u16, + border: &'static ScreenBorder, target: &mut impl Renderer<'s>, ) { - shape::Circle::new(center, constant::LOADER_OUTER) - .with_bg(inactive_color) - .render(target); + // convert to ration from 0.0 to 1.0 + let progress_ratio = (progress as f32 / 1000.0).clamp(0.0, 1.0); + let (clip, top_gap) = get_clips(progress_ratio); + render_clipped_border(border, clip, top_gap, u8::MAX, target); +} - match range { - LoaderRange::Full => { - shape::Circle::new(center, constant::LOADER_OUTER) - .with_bg(active_color) - .render(target); +fn get_clips(progress_ratio: f32) -> (Rect, Rect) { + /// Ratio of total_duration for the bottom part of the border + const BOTTOM_DURATION_RATIO: f32 = 0.125; + /// Ratio of total_duration for the side parts of the border + const SIDES_DURATION_RATIO: f32 = 0.5; + /// Ratio of total_duration for the top part of the border + const TOP_DURATION_RATIO: f32 = 0.375; + + const TOP_GAP_ZERO: Rect = Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::zero(), + ); + const TOP_GAP_FULL: Rect = Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::new(SCREEN.width(), ScreenBorder::WIDTH), + ); + + match progress_ratio { + // Bottom phase growing linearly + p if p < BOTTOM_DURATION_RATIO => { + let bottom_progress = (p / BOTTOM_DURATION_RATIO).clamp(0.0, 1.0); + let width = i16::lerp(0, SCREEN.width(), bottom_progress); + let clip = Rect::from_center_and_size( + SCREEN + .bottom_center() + .ofs(Offset::y(-ScreenBorder::WIDTH / 2)), + Offset::new(width, ScreenBorder::WIDTH), + ); + (clip, TOP_GAP_FULL) } - LoaderRange::FromTo(start, end) => { - shape::Circle::new(center, constant::LOADER_OUTER) - .with_bg(active_color) - .with_start_angle(start) - .with_end_angle(end) - .render(target); + + // Sides phase growing up linearly + p if p < (BOTTOM_DURATION_RATIO + SIDES_DURATION_RATIO) => { + let sides_progress = + ((p - BOTTOM_DURATION_RATIO) / SIDES_DURATION_RATIO).clamp(0.0, 1.0); + let height = i16::lerp(ScreenBorder::WIDTH, SCREEN.height(), sides_progress); + let clip = Rect::from_bottom_left_and_size( + SCREEN.bottom_left(), + Offset::new(SCREEN.width(), height), + ); + (clip, TOP_GAP_FULL) } + + // Top gap shrinking linearly + p if p < 1.0 => { + let top_progress = ((p - BOTTOM_DURATION_RATIO - SIDES_DURATION_RATIO) + / TOP_DURATION_RATIO) + .clamp(0.0, 1.0); + let width = i16::lerp(SCREEN.width(), 0, top_progress); + let top_gap = Rect::from_center_and_size( + SCREEN.top_center().ofs(Offset::y(ScreenBorder::WIDTH / 2)), + Offset::new(width, ScreenBorder::WIDTH), + ); + (SCREEN, top_gap) + } + + // Animation complete + _ => (SCREEN, TOP_GAP_ZERO), } +} - shape::Circle::new(center, constant::LOADER_INNER + 2) - .with_bg(background_color) +fn render_clipped_border<'s>( + border: &'static ScreenBorder, + clip: Rect, + top_gap: Rect, + alpha: u8, + target: &mut impl Renderer<'s>, +) { + target.in_clip(clip, &|target| { + border.render(alpha, target); + }); + shape::Bar::new(top_gap) + .with_bg(BG) + .with_fg(BG) .render(target); } diff --git a/core/embed/rust/src/ui/layout_eckhart/cshape/mod.rs b/core/embed/rust/src/ui/layout_eckhart/cshape/mod.rs index b7101f15b5..b07738658d 100644 --- a/core/embed/rust/src/ui/layout_eckhart/cshape/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/cshape/mod.rs @@ -1,5 +1,5 @@ mod loader; mod screen_border; -pub use loader::{render_loader, LoaderRange}; +pub use loader::render_loader; pub use screen_border::ScreenBorder; diff --git a/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs b/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs index b36a9836ab..d4e2b42f51 100644 --- a/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs +++ b/core/embed/rust/src/ui/layout_eckhart/cshape/screen_border.rs @@ -17,7 +17,7 @@ pub struct ScreenBorder { impl ScreenBorder { pub const WIDTH: i16 = 4; - pub fn new(color: Color) -> Self { + pub const fn new(color: Color) -> Self { let screen = constant::screen(); // Top bar: from the right edge of top-left icon to the left edge of top-right