1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-08 22:40:59 +00:00

feat(core): animated lockscreen for mercury UI

[no changelog]
This commit is contained in:
tychovrahe 2024-05-22 22:19:50 +02:00 committed by TychoVrahe
parent b178c10e8b
commit a34c2cb9dd
6 changed files with 112 additions and 61 deletions

View File

@ -87,11 +87,6 @@ SECTIONS {
. = ALIGN(4);
} >SRAM1
.buf : ALIGN(4) {
*(.buf*);
. = ALIGN(4);
} >SRAM1
.stack : ALIGN(8) {
. = 16K; /* Overflow causes UsageFault */
} >SRAM2
@ -110,6 +105,11 @@ SECTIONS {
. = ALIGN(4);
} >SRAM3
.buf : ALIGN(4) {
*(.buf*);
. = ALIGN(4);
} >SRAM3
.heap : ALIGN(4) {
. = 37K; /* this acts as a build time assertion that at least this much memory is available for heap use */
. = ABSOLUTE(sram3_end); /* this explicitly sets the end of the heap */

View File

@ -1,16 +1,12 @@
use crate::{
io::BinaryData,
strutil::TString,
time::{Duration, Instant},
time::{Duration, Instant, Stopwatch},
translations::TR,
trezorhal::usb::usb_configured,
ui::{
component::{Component, Event, EventCtx, TimerToken},
display::{
image::{ImageInfo, ToifFormat},
toif::Icon,
Color, Font,
},
display::{image::ImageInfo, toif::Icon, Color, Font},
event::{TouchEvent, USBEvent},
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
layout::util::get_user_custom_image,
@ -18,12 +14,16 @@ use crate::{
shape::{self, Renderer},
},
};
use core::mem;
use crate::ui::{
constant::{screen, HEIGHT, WIDTH},
model_mercury::theme::{
GREEN_LIGHT, GREY_LIGHT, ICON_CENTRAL_CIRCLE, ICON_KEY, ICON_LOCKSCREEN_FILTER,
model_mercury::{
cshape,
theme::{GREY_LIGHT, ICON_KEY},
},
shape::{render_on_canvas, ImageBuffer, Rgb565Canvas},
util::animation_disabled,
};
use super::{theme, Loader, LoaderMsg};
@ -194,9 +194,6 @@ impl Component for Homescreen {
ImageInfo::Jpeg(_) => shape::JpegImage::new_image(AREA.center(), self.image)
.with_align(Alignment2D::CENTER)
.render(target),
ImageInfo::Toif(_) => shape::ToifImage::new_image(AREA.center(), self.image)
.with_align(Alignment2D::CENTER)
.render(target),
_ => {}
}
@ -273,20 +270,54 @@ impl crate::trace::Trace for Homescreen {
}
}
#[derive(Default)]
struct LockscreenAnim {
pub timer: Stopwatch,
}
impl LockscreenAnim {
const DURATION_MS: u32 = 1500;
pub fn is_active(&self) -> bool {
true
}
pub fn eval(&self) -> f32 {
if animation_disabled() {
return 0.0;
}
let anim = pareen::prop(30.0f32);
let t: f32 = self.timer.elapsed().to_millis() as f32 / 1000.0;
anim.eval(t)
}
}
pub struct Lockscreen<'a> {
anim: LockscreenAnim,
label: TString<'a>,
image: BinaryData<'static>,
bootscreen: bool,
coinjoin_authorized: bool,
bg_image: ImageBuffer<Rgb565Canvas<'static>>,
}
impl<'a> Lockscreen<'a> {
pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
let image = get_homescreen_image();
let mut buf = unwrap!(ImageBuffer::new(AREA.size()), "no image buf");
render_on_canvas(buf.canvas(), None, |target| {
shape::JpegImage::new_image(Point::zero(), image).render(target);
});
Lockscreen {
anim: LockscreenAnim::default(),
label,
image: get_homescreen_image(),
image,
bootscreen,
coinjoin_authorized,
bg_image: buf,
}
}
}
@ -298,10 +329,29 @@ impl Component for Lockscreen<'_> {
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if event == Event::Attach {
ctx.request_anim_frame();
}
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
if (!animation_disabled()) {
if !self.anim.timer.is_running() {
self.anim.timer.start();
}
ctx.request_anim_frame();
ctx.request_paint();
}
}
if let Event::Touch(TouchEvent::TouchEnd(_)) = event {
let bg_img = mem::replace(&mut self.bg_image, None);
if let Some(bg_img) = bg_img {
drop(bg_img);
}
return Some(HomescreenMsg::Dismissed);
}
None
}
@ -310,43 +360,46 @@ impl Component for Lockscreen<'_> {
}
fn render<'s>(&self, target: &mut impl Renderer<'s>) {
const OVERLAY_SIZE: i16 = 170;
const OVERLAY_BORDER: i16 = (AREA.height() - OVERLAY_SIZE) / 2;
const OVERLAY_OFFSET: i16 = 9;
let center = AREA.center();
match ImageInfo::parse(self.image) {
ImageInfo::Jpeg(_) => shape::JpegImage::new_image(center, self.image)
.with_align(Alignment2D::CENTER)
.with_blur(4)
.with_dim(102)
.render(target),
ImageInfo::Toif(_) => shape::ToifImage::new_image(center, self.image)
.with_align(Alignment2D::CENTER)
//.with_blur(5)
.render(target),
_ => {}
}
// shape::RawImage::new(AREA, self.bg_image.view())
// .render(target);
shape::ToifImage::new(center, ICON_LOCKSCREEN_FILTER.toif)
.with_align(Alignment2D::CENTER)
.with_fg(Color::black())
cshape::UnlockOverlay::new(center + Offset::y(OVERLAY_OFFSET), self.anim.eval())
.render(target);
shape::ToifImage::new(center + Offset::y(12), ICON_CENTRAL_CIRCLE.toif)
.with_align(Alignment2D::CENTER)
.with_fg(GREEN_LIGHT)
shape::Bar::new(AREA.split_top(OVERLAY_BORDER + OVERLAY_OFFSET).0)
.with_bg(Color::black())
.render(target);
shape::ToifImage::new(center + Offset::y(12), ICON_KEY.toif)
shape::Bar::new(AREA.split_bottom(OVERLAY_BORDER - OVERLAY_OFFSET).1)
.with_bg(Color::black())
.render(target);
shape::Bar::new(AREA.split_left(OVERLAY_BORDER).0)
.with_bg(Color::black())
.render(target);
shape::Bar::new(AREA.split_right(OVERLAY_BORDER).1)
.with_bg(Color::black())
.render(target);
shape::ToifImage::new(center + Offset::y(OVERLAY_OFFSET), ICON_KEY.toif)
.with_align(Alignment2D::CENTER)
.with_fg(GREY_LIGHT)
.render(target);
let (locked, tap) = if self.bootscreen {
(
TR::lockscreen__title_not_connected,
Some(TR::lockscreen__title_not_connected),
TR::lockscreen__tap_to_connect,
)
} else {
(TR::lockscreen__title_locked, TR::lockscreen__tap_to_unlock)
(None, TR::lockscreen__tap_to_unlock)
};
let mut label_style = theme::TEXT_DEMIBOLD;
@ -367,16 +420,18 @@ impl Component for Lockscreen<'_> {
offset += 6;
locked.map_translated(|t| {
offset += theme::TEXT_SUB_GREY.text_font.visible_text_height(t);
if let Some(t) = locked {
t.map_translated(|t| {
offset += theme::TEXT_SUB_GREY.text_font.visible_text_height(t);
let text_pos = Point::new(0, offset);
let text_pos = Point::new(0, offset);
shape::Text::new(text_pos, t)
.with_font(theme::TEXT_SUB_GREY.text_font)
.with_fg(theme::TEXT_SUB_GREY.text_color)
.render(target);
});
shape::Text::new(text_pos, t)
.with_font(theme::TEXT_SUB_GREY.text_font)
.with_fg(theme::TEXT_SUB_GREY.text_color)
.render(target);
})
};
tap.map_translated(|t| {
offset = theme::TEXT_SUB_GREY.text_font.text_baseline();
@ -398,26 +453,20 @@ impl Component for Lockscreen<'_> {
}
}
pub fn check_homescreen_format(image: BinaryData, accept_toif: bool) -> bool {
pub fn check_homescreen_format(image: BinaryData) -> bool {
match ImageInfo::parse(image) {
ImageInfo::Jpeg(info) => {
info.width() == HOMESCREEN_IMAGE_WIDTH
&& info.height() == HOMESCREEN_IMAGE_HEIGHT
&& info.mcu_height() <= 16
}
ImageInfo::Toif(info) => {
accept_toif
&& info.width() == HOMESCREEN_TOIF_SIZE
&& info.height() == HOMESCREEN_TOIF_SIZE
&& info.format() == ToifFormat::FullColorBE
}
_ => false,
}
}
fn get_homescreen_image() -> BinaryData<'static> {
if let Ok(image) = get_user_custom_image() {
if check_homescreen_format(image, true) {
if check_homescreen_format(image) {
return image;
}
}

View File

@ -592,7 +592,7 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
jpeg = theme::IMAGE_HOMESCREEN.into();
}
if !check_homescreen_format(jpeg, false) {
if !check_homescreen_format(jpeg) {
return Err(value_error!("Invalid image."));
};
@ -1418,7 +1418,7 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut
pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj {
let block = || {
let buffer = data.try_into()?;
Ok(check_homescreen_format(buffer, false).into())
Ok(check_homescreen_format(buffer).into())
};
unsafe { util::try_or_raise(block) }

View File

@ -7,6 +7,7 @@ pub mod component;
pub mod constant;
pub mod theme;
pub mod cshape;
#[cfg(feature = "micropython")]
pub mod flow;
#[cfg(feature = "micropython")]

View File

@ -4,8 +4,8 @@ use crate::ui::{
};
/// Size of image buffer in bytes
/// (up to 180x180 pixel, 16-bit RGB565 image)
const IMAGE_BUFFER_SIZE: usize = 64 * 1024;
/// (up to 240x240 pixel, 16-bit RGB565 image)
const IMAGE_BUFFER_SIZE: usize = 240 * 240 * 2;
#[repr(align(16))]
struct AlignedBuffer {

View File

@ -49,7 +49,6 @@ async def bootscreen() -> None:
Any non-PIN loaders are ignored during this function.
Allowing all of them before returning.
"""
lockscreen = Lockscreen(label=storage.device.get_label(), bootscreen=True)
while True:
try:
@ -59,7 +58,9 @@ async def bootscreen() -> None:
ui.display.orientation(storage.device.get_rotation())
if utils.USE_HAPTIC:
io.haptic.haptic_set_enabled(storage.device.get_haptic_feedback())
lockscreen = Lockscreen(
label=storage.device.get_label(), bootscreen=True
)
await lockscreen
await verify_user_pin()
storage.init_unlocked()