You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs

327 lines
9.1 KiB

mod render;
use crate::{
micropython::gc::Gc,
storage::{get_avatar, get_avatar_len},
time::{Duration, Instant},
trezorhal::usb::usb_configured,
ui::{
component::{Component, Event, EventCtx, Pad, TimerToken},
display::{self, tjpgd::jpeg_info, toif::Icon, Color, Font},
event::{TouchEvent, USBEvent},
geometry::{Offset, Point, Rect},
model_tt::{constant, theme::IMAGE_HOMESCREEN},
},
};
use render::{
homescreen, homescreen_blurred, HomescreenNotification, HomescreenText, HOMESCREEN_IMAGE_SIZE,
};
use super::{theme, Loader, LoaderMsg};
const AREA: Rect = constant::screen();
const TOP_CENTER: Point = AREA.top_center();
const LABEL_Y: i16 = 222;
const LOCKED_Y: i16 = 107;
const TAP_Y: i16 = 134;
const HOLD_Y: i16 = 35;
const LOADER_OFFSET: Offset = Offset::y(-10);
const LOADER_DELAY: Duration = Duration::from_millis(500);
const LOADER_DURATION: Duration = Duration::from_millis(2000);
pub struct Homescreen<T> {
label: T,
notification: Option<(T, u8)>,
hold_to_lock: bool,
loader: Loader,
pad: Pad,
paint_notification_only: bool,
delay: Option<TimerToken>,
}
pub enum HomescreenMsg {
Dismissed,
}
impl<T> Homescreen<T>
where
T: AsRef<str>,
{
pub fn new(label: T, notification: Option<(T, u8)>, hold_to_lock: bool) -> Self {
Self {
label,
notification,
hold_to_lock,
loader: Loader::new().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
pad: Pad::with_background(theme::BG),
paint_notification_only: false,
delay: None,
}
}
fn level_to_style(level: u8) -> (Color, Icon) {
match level {
2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)),
1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)),
_ => (theme::RED, Icon::new(theme::ICON_WARN)),
}
}
fn get_notification(&self) -> Option<HomescreenNotification> {
if !usb_configured() {
let (color, icon) = Self::level_to_style(0);
Some(HomescreenNotification {
text: "NO USB CONNECTION",
icon,
color,
})
} else if let Some((notification, level)) = &self.notification {
let (color, icon) = Self::level_to_style(*level);
Some(HomescreenNotification {
text: notification.as_ref(),
icon,
color,
})
} else {
None
}
}
fn paint_loader(&mut self) {
display::text_center(
TOP_CENTER + Offset::y(HOLD_Y),
"HOLD TO LOCK",
Font::BOLD,
theme::FG,
theme::BG,
);
self.loader.paint()
}
pub fn set_paint_notification(&mut self) {
self.paint_notification_only = true;
}
fn event_usb(&mut self, ctx: &mut EventCtx, event: Event) {
if let Event::USB(USBEvent::Connected(_)) = event {
self.paint_notification_only = true;
ctx.request_paint();
}
}
fn event_hold(&mut self, ctx: &mut EventCtx, event: Event) -> bool {
match event {
Event::Touch(TouchEvent::TouchStart(_)) => {
if self.loader.is_animating() {
self.loader.start_growing(ctx, Instant::now());
} else {
self.delay = Some(ctx.request_timer(LOADER_DELAY));
}
}
Event::Touch(TouchEvent::TouchEnd(_)) => {
self.delay = None;
let now = Instant::now();
if self.loader.is_completely_grown(now) {
return true;
}
if self.loader.is_animating() {
self.loader.start_shrinking(ctx, now);
}
}
Event::Timer(token) if Some(token) == self.delay => {
self.delay = None;
self.pad.clear();
self.paint_notification_only = false;
self.loader.start_growing(ctx, Instant::now());
}
_ => {}
}
match self.loader.event(ctx, event) {
Some(LoaderMsg::GrownCompletely) => {
// Wait for TouchEnd before returning.
}
Some(LoaderMsg::ShrunkCompletely) => {
self.loader.reset();
self.pad.clear();
self.paint_notification_only = false;
ctx.request_paint()
}
None => {}
}
false
}
}
impl<T> Component for Homescreen<T>
where
T: AsRef<str>,
{
type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.pad.place(AREA);
self.loader.place(AREA.translate(LOADER_OFFSET));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
Self::event_usb(self, ctx, event);
if self.hold_to_lock {
Self::event_hold(self, ctx, event).then_some(HomescreenMsg::Dismissed)
} else {
None
}
}
fn paint(&mut self) {
self.pad.paint();
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
self.paint_loader();
} else {
let mut label_style = theme::TEXT_DEMIBOLD;
label_style.text_color = theme::FG;
let text = HomescreenText {
text: self.label.as_ref(),
style: label_style,
offset: Offset::new(10, LABEL_Y),
icon: None,
};
let notification = self.get_notification();
let res = get_image();
if let Ok(data) = res {
homescreen(
data.as_ref(),
&[text],
notification,
self.paint_notification_only,
);
} else {
homescreen(
IMAGE_HOMESCREEN,
&[text],
notification,
self.paint_notification_only,
);
}
}
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.loader.bounds(sink);
sink(self.pad.area);
}
}
#[cfg(feature = "ui_debug")]
impl<T: AsRef<str>> crate::trace::Trace for Homescreen<T> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Homescreen");
d.field("label", &self.label.as_ref());
d.close();
}
}
pub struct Lockscreen<T> {
label: T,
bootscreen: bool,
}
impl<T> Lockscreen<T> {
pub fn new(label: T, bootscreen: bool) -> Self {
Lockscreen { label, bootscreen }
}
}
impl<T> Component for Lockscreen<T>
where
T: AsRef<str>,
{
type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Touch(TouchEvent::TouchEnd(_)) = event {
return Some(HomescreenMsg::Dismissed);
}
None
}
fn paint(&mut self) {
let (locked, tap) = if self.bootscreen {
("NOT CONNECTED", "Tap to connect")
} else {
("LOCKED", "Tap to unlock")
};
let mut label_style = theme::TEXT_DEMIBOLD;
label_style.text_color = theme::GREY_LIGHT;
let texts: [HomescreenText; 3] = [
HomescreenText {
text: locked,
style: theme::TEXT_BOLD,
offset: Offset::new(10, LOCKED_Y),
icon: Some(Icon::new(theme::ICON_LOCK)),
},
HomescreenText {
text: tap,
style: theme::TEXT_NORMAL,
offset: Offset::new(10, TAP_Y),
icon: None,
},
HomescreenText {
text: self.label.as_ref(),
style: label_style,
offset: Offset::new(10, LABEL_Y),
icon: None,
},
];
let res = get_image();
if let Ok(data) = res {
homescreen_blurred(data.as_ref(), &texts);
} else {
homescreen_blurred(IMAGE_HOMESCREEN, &texts);
}
}
}
fn get_image() -> Result<Gc<[u8]>, ()> {
if let Ok(len) = get_avatar_len() {
let result = Gc::<[u8]>::new_slice(len);
if let Ok(mut buffer) = result {
let buf = unsafe { Gc::<[u8]>::as_mut(&mut buffer) };
if get_avatar(buf).is_ok() {
let jpeg = jpeg_info(buffer.as_ref());
if let Some((size, mcu_height)) = jpeg {
if size.x == HOMESCREEN_IMAGE_SIZE
&& size.y == HOMESCREEN_IMAGE_SIZE
&& mcu_height <= 16
{
return Ok(buffer);
}
}
}
}
};
Err(())
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Lockscreen<T> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Lockscreen");
d.close();
}
}