mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-03 03:50:58 +00:00
feat(lincoln): initial commit
- build variables, lincoln UI selected for compilation only with `UI_LINCOLN_DEV=1` - lincoln directory structure - bootloader UI and assets copied from mercury - FirmwareUI trait functions are empty - Python layout functions are copied from mercury except some of more complicated ones which raise NotImplemented for now
This commit is contained in:
parent
7eb0b4d689
commit
7f240247a4
@ -33,6 +33,7 @@ BOOTLOADER_QA ?= 0
|
||||
BOOTLOADER_DEVEL ?= 0
|
||||
DISABLE_OPTIGA ?= 0
|
||||
TREZOR_MODEL ?= T
|
||||
UI_LINCOLN_DEV ?= 0
|
||||
TREZOR_MEMPERF ?= 0
|
||||
ADDRESS_SANITIZER ?= 0
|
||||
CMAKELISTS ?= 0
|
||||
@ -69,7 +70,12 @@ MODEL_FEATURE = model_tr
|
||||
else ifeq ($(TREZOR_MODEL),$(filter $(TREZOR_MODEL),T3W1))
|
||||
MCU = STM32U5
|
||||
OPENOCD_TARGET = target/stm32u5x.cfg
|
||||
# model_tt by default so far, lincoln if requested
|
||||
ifeq ($(UI_LINCOLN_DEV),1)
|
||||
MODEL_FEATURE = model_lincoln
|
||||
else
|
||||
MODEL_FEATURE = model_tt
|
||||
endif
|
||||
else ifeq ($(TREZOR_MODEL),$(filter $(TREZOR_MODEL),DISC1))
|
||||
MCU = STM32F4
|
||||
OPENOCD_TARGET = target/stm32f4x.cfg
|
||||
@ -151,7 +157,8 @@ SCONS_VARS = \
|
||||
TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
|
||||
TREZOR_EMULATOR_DEBUGGABLE=$(TREZOR_EMULATOR_DEBUGGABLE) \
|
||||
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" \
|
||||
TREZOR_MODEL="$(TREZOR_MODEL)"
|
||||
TREZOR_MODEL="$(TREZOR_MODEL)" \
|
||||
UI_LINCOLN_DEV="$(UI_LINCOLN_DEV)"
|
||||
|
||||
SCONS_OPTS = -Q -j $(JOBS)
|
||||
ifeq ($(QUIET_MODE),1)
|
||||
|
@ -11,6 +11,7 @@ crypto = ["zeroize"]
|
||||
model_tt = ["jpeg"]
|
||||
model_tr = []
|
||||
model_mercury = ["jpeg", "dma2d"]
|
||||
model_lincoln = ["jpeg", "dma2d"]
|
||||
micropython = []
|
||||
protobuf = ["micropython"]
|
||||
ui = []
|
||||
|
@ -86,12 +86,24 @@ const DEFAULT_BINDGEN_MACROS_T3T1: &[&str] = &[
|
||||
#[cfg(not(feature = "model_mercury"))]
|
||||
const DEFAULT_BINDGEN_MACROS_T3T1: &[&str] = &[];
|
||||
|
||||
#[cfg(feature = "model_lincoln")]
|
||||
const DEFAULT_BINDGEN_MACROS_T3W1: &[&str] = &[
|
||||
"-DSTM32U5",
|
||||
"-DTREZOR_MODEL_T3W1",
|
||||
"-DFLASH_BIT_ACCESS=0", // FIXME: fill in correct value
|
||||
"-DFLASH_BLOCK_WORDS=4",
|
||||
"-DTREZOR_BOARD=\"T3W1/boards/t3w1-unix.h\"",
|
||||
];
|
||||
#[cfg(not(feature = "model_lincoln"))]
|
||||
const DEFAULT_BINDGEN_MACROS_T3W1: &[&str] = &[];
|
||||
|
||||
fn add_bindgen_macros<'a>(clang_args: &mut Vec<&'a str>, envvar: Option<&'a str>) {
|
||||
let default_macros = DEFAULT_BINDGEN_MACROS_COMMON
|
||||
.iter()
|
||||
.chain(DEFAULT_BINDGEN_MACROS_T2T1)
|
||||
.chain(DEFAULT_BINDGEN_MACROS_T2B1)
|
||||
.chain(DEFAULT_BINDGEN_MACROS_T3T1);
|
||||
.chain(DEFAULT_BINDGEN_MACROS_T3T1)
|
||||
.chain(DEFAULT_BINDGEN_MACROS_T3W1);
|
||||
|
||||
match envvar {
|
||||
Some(envvar) => clang_args.extend(envvar.split(',')),
|
||||
|
@ -1,10 +1,14 @@
|
||||
//! Reexporting the `constant` module according to the
|
||||
//! current feature (Trezor model)
|
||||
|
||||
#[cfg(all(
|
||||
feature = "model_lincoln",
|
||||
not(any(feature = "model_mercury", feature = "model_tr", feature = "model_tt"))
|
||||
))]
|
||||
pub use super::model_lincoln::constant::*;
|
||||
#[cfg(all(
|
||||
feature = "model_mercury",
|
||||
not(feature = "model_tr"),
|
||||
not(feature = "model_tt")
|
||||
not(any(feature = "model_tr", feature = "model_tt"),)
|
||||
))]
|
||||
pub use super::model_mercury::constant::*;
|
||||
#[cfg(all(feature = "model_tr", not(feature = "model_tt")))]
|
||||
|
@ -30,6 +30,10 @@ const fn clamp(x: i16, min: i16, max: i16) -> i16 {
|
||||
/// Relative offset in 2D space, used for representing translation and
|
||||
/// dimensions of objects. Absolute positions on the screen are represented by
|
||||
/// the `Point` type.
|
||||
///
|
||||
/// Coordinate system orientation:
|
||||
/// * x-axis: negative values go left, positive values go right
|
||||
/// * y-axis: negative values go up, positive values go down
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Offset {
|
||||
pub x: i16,
|
||||
|
@ -17,6 +17,8 @@ pub mod layout;
|
||||
|
||||
mod api;
|
||||
|
||||
#[cfg(feature = "model_lincoln")]
|
||||
pub mod model_lincoln;
|
||||
#[cfg(feature = "model_mercury")]
|
||||
pub mod model_mercury;
|
||||
#[cfg(feature = "model_tr")]
|
||||
@ -32,10 +34,15 @@ pub mod ui_firmware;
|
||||
|
||||
pub use ui_common::CommonUI;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "model_lincoln",
|
||||
not(any(feature = "model_mercury", feature = "model_tr", feature = "model_tt"))
|
||||
))]
|
||||
pub type ModelUI = crate::ui::model_lincoln::UILincoln;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "model_mercury",
|
||||
not(feature = "model_tr"),
|
||||
not(feature = "model_tt")
|
||||
not(any(feature = "model_tr", feature = "model_tt"))
|
||||
))]
|
||||
pub type ModelUI = crate::ui::model_mercury::UIMercury;
|
||||
|
||||
|
113
core/embed/rust/src/ui/model_lincoln/bootloader/intro.rs
Normal file
113
core/embed/rust/src/ui/model_lincoln/bootloader/intro.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label, Pad},
|
||||
constant::screen,
|
||||
display::Icon,
|
||||
geometry::{Alignment, Insets, Point, Rect},
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{Button, ButtonMsg::Clicked},
|
||||
constant::WIDTH,
|
||||
theme::bootloader::{
|
||||
button_bld, button_bld_menu, text_title, BLD_BG, BUTTON_AREA_START, BUTTON_HEIGHT,
|
||||
CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_WARNING, TITLE_AREA,
|
||||
},
|
||||
};
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, ToPrimitive)]
|
||||
pub enum IntroMsg {
|
||||
Menu = 1,
|
||||
Host = 2,
|
||||
}
|
||||
|
||||
pub struct Intro<'a> {
|
||||
bg: Pad,
|
||||
title: Child<Label<'a>>,
|
||||
menu: Child<Button>,
|
||||
host: Child<Button>,
|
||||
text: Child<Label<'a>>,
|
||||
warn: Option<Child<Label<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> Intro<'a> {
|
||||
pub fn new(title: TString<'a>, content: TString<'a>, fw_ok: bool) -> Self {
|
||||
Self {
|
||||
bg: Pad::with_background(BLD_BG).with_clear(),
|
||||
title: Child::new(Label::left_aligned(title, text_title(BLD_BG)).vertically_centered()),
|
||||
menu: Child::new(
|
||||
Button::with_icon(Icon::new(MENU32))
|
||||
.styled(button_bld_menu())
|
||||
.with_expanded_touch_area(Insets::uniform(13)),
|
||||
),
|
||||
host: Child::new(
|
||||
Button::with_text("INSTALL FIRMWARE".into())
|
||||
.styled(button_bld())
|
||||
.with_text_align(Alignment::Center),
|
||||
),
|
||||
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
|
||||
warn: (!fw_ok).then_some(Child::new(
|
||||
Label::new("FIRMWARE CORRUPTED".into(), Alignment::Start, TEXT_WARNING)
|
||||
.vertically_centered(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Component for Intro<'a> {
|
||||
type Msg = IntroMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.bg.place(screen());
|
||||
|
||||
self.title.place(TITLE_AREA);
|
||||
self.menu.place(CORNER_BUTTON_AREA);
|
||||
self.host.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START + BUTTON_HEIGHT),
|
||||
));
|
||||
if self.warn.is_some() {
|
||||
self.warn.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING),
|
||||
Point::new(
|
||||
WIDTH - CONTENT_PADDING,
|
||||
TITLE_AREA.y1 + CONTENT_PADDING + 30,
|
||||
),
|
||||
));
|
||||
self.text.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING + 30),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START - CONTENT_PADDING),
|
||||
));
|
||||
} else {
|
||||
self.text.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, TITLE_AREA.y1 + CONTENT_PADDING),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START - CONTENT_PADDING),
|
||||
));
|
||||
}
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Some(Clicked) = self.menu.event(ctx, event) {
|
||||
return Some(Self::Msg::Menu);
|
||||
};
|
||||
if let Some(Clicked) = self.host.event(ctx, event) {
|
||||
return Some(Self::Msg::Host);
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.bg.render(target);
|
||||
self.title.render(target);
|
||||
self.text.render(target);
|
||||
self.warn.render(target);
|
||||
self.host.render(target);
|
||||
self.menu.render(target);
|
||||
}
|
||||
}
|
111
core/embed/rust/src/ui/model_lincoln/bootloader/menu.rs
Normal file
111
core/embed/rust/src/ui/model_lincoln/bootloader/menu.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::{
|
||||
trezorhal::secbool::{secbool, sectrue},
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Label, Pad},
|
||||
constant::{screen, WIDTH},
|
||||
display::Icon,
|
||||
geometry::{Insets, Point, Rect},
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{Button, ButtonMsg::Clicked, IconText},
|
||||
theme::bootloader::{
|
||||
button_bld, button_bld_menu, text_title, BLD_BG, BUTTON_HEIGHT, CONTENT_PADDING,
|
||||
CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, FIRE24, REFRESH24, TITLE_AREA, X32,
|
||||
},
|
||||
};
|
||||
|
||||
const BUTTON_AREA_START: i16 = 56;
|
||||
const BUTTON_SPACING: i16 = 8;
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, ToPrimitive)]
|
||||
pub enum MenuMsg {
|
||||
Close = 0xAABBCCDD,
|
||||
Reboot = 0x11223344,
|
||||
FactoryReset = 0x55667788,
|
||||
}
|
||||
|
||||
pub struct Menu {
|
||||
bg: Pad,
|
||||
title: Child<Label<'static>>,
|
||||
close: Child<Button>,
|
||||
reboot: Child<Button>,
|
||||
reset: Child<Button>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn new(firmware_present: secbool) -> Self {
|
||||
let content_reboot = IconText::new("REBOOT TREZOR", Icon::new(REFRESH24));
|
||||
let content_reset = IconText::new("FACTORY RESET", Icon::new(FIRE24));
|
||||
|
||||
let mut instance = Self {
|
||||
bg: Pad::with_background(BLD_BG),
|
||||
title: Child::new(
|
||||
Label::left_aligned("BOOTLOADER".into(), text_title(BLD_BG)).vertically_centered(),
|
||||
),
|
||||
close: Child::new(
|
||||
Button::with_icon(Icon::new(X32))
|
||||
.styled(button_bld_menu())
|
||||
.with_expanded_touch_area(Insets::uniform(CORNER_BUTTON_TOUCH_EXPANSION)),
|
||||
),
|
||||
reboot: Child::new(
|
||||
Button::with_icon_and_text(content_reboot)
|
||||
.styled(button_bld())
|
||||
.initially_enabled(sectrue == firmware_present),
|
||||
),
|
||||
reset: Child::new(Button::with_icon_and_text(content_reset).styled(button_bld())),
|
||||
};
|
||||
instance.bg.clear();
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Menu {
|
||||
type Msg = MenuMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.bg.place(screen());
|
||||
self.title.place(TITLE_AREA);
|
||||
self.close.place(CORNER_BUTTON_AREA);
|
||||
self.reboot.place(Rect::new(
|
||||
Point::new(CONTENT_PADDING, BUTTON_AREA_START),
|
||||
Point::new(WIDTH - CONTENT_PADDING, BUTTON_AREA_START + BUTTON_HEIGHT),
|
||||
));
|
||||
self.reset.place(Rect::new(
|
||||
Point::new(
|
||||
CONTENT_PADDING,
|
||||
BUTTON_AREA_START + BUTTON_HEIGHT + BUTTON_SPACING,
|
||||
),
|
||||
Point::new(
|
||||
WIDTH - CONTENT_PADDING,
|
||||
BUTTON_AREA_START + 2 * BUTTON_HEIGHT + BUTTON_SPACING,
|
||||
),
|
||||
));
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Some(Clicked) = self.close.event(ctx, event) {
|
||||
return Some(Self::Msg::Close);
|
||||
}
|
||||
if let Some(Clicked) = self.reboot.event(ctx, event) {
|
||||
return Some(Self::Msg::Reboot);
|
||||
}
|
||||
if let Some(Clicked) = self.reset.event(ctx, event) {
|
||||
return Some(Self::Msg::FactoryReset);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.bg.render(target);
|
||||
self.title.render(target);
|
||||
self.close.render(target);
|
||||
self.reboot.render(target);
|
||||
self.reset.render(target);
|
||||
}
|
||||
}
|
473
core/embed/rust/src/ui/model_lincoln/bootloader/mod.rs
Normal file
473
core/embed/rust/src/ui/model_lincoln/bootloader/mod.rs
Normal file
@ -0,0 +1,473 @@
|
||||
use heapless::String;
|
||||
|
||||
use crate::{
|
||||
trezorhal::secbool::secbool,
|
||||
ui::{
|
||||
component::{connect::Connect, Label},
|
||||
display::{self, Color, Font, Icon},
|
||||
geometry::{Alignment, Offset, Point, Rect},
|
||||
layout::simplified::{run, show},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
bootloader::welcome::Welcome,
|
||||
component::{
|
||||
bl_confirm::{Confirm, ConfirmTitle},
|
||||
Button, ResultScreen, WelcomeScreen,
|
||||
},
|
||||
theme,
|
||||
theme::{
|
||||
bootloader::{
|
||||
button_bld, button_bld_menu, button_confirm, button_wipe_cancel, button_wipe_confirm,
|
||||
BLD_BG, BLD_FG, BLD_TITLE_COLOR, BLD_WIPE_COLOR, CHECK24, CHECK40, DOWNLOAD24, FIRE32,
|
||||
FIRE40, RESULT_FW_INSTALL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL, TEXT_WIPE_BOLD,
|
||||
TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24,
|
||||
},
|
||||
GREEN_LIGHT, GREY,
|
||||
},
|
||||
UILincoln,
|
||||
};
|
||||
|
||||
use crate::ui::{ui_bootloader::BootloaderUI, CommonUI};
|
||||
|
||||
use crate::ui::{
|
||||
display::{toif::Toif, LOADER_MAX},
|
||||
geometry::Alignment2D,
|
||||
model_mercury::cshape::{render_loader, LoaderRange},
|
||||
shape,
|
||||
shape::render_on_display,
|
||||
};
|
||||
|
||||
use ufmt::uwrite;
|
||||
|
||||
use super::theme::bootloader::BLD_WARN_COLOR;
|
||||
use intro::Intro;
|
||||
use menu::Menu;
|
||||
|
||||
pub mod intro;
|
||||
pub mod menu;
|
||||
pub mod welcome;
|
||||
|
||||
pub type BootloaderString = String<128>;
|
||||
|
||||
const RECONNECT_MESSAGE: &str = "PLEASE RECONNECT\nTHE DEVICE";
|
||||
|
||||
const SCREEN: Rect = UILincoln::SCREEN;
|
||||
const PROGRESS_TEXT_ORIGIN: Point = Point::new(2, 28);
|
||||
|
||||
impl UILincoln {
|
||||
fn screen_progress(
|
||||
text: &str,
|
||||
progress: u16,
|
||||
initialize: bool,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
icon: Option<(Icon, Color)>,
|
||||
center_text: Option<&str>,
|
||||
) {
|
||||
if initialize {
|
||||
Self::fadeout();
|
||||
}
|
||||
display::sync();
|
||||
|
||||
render_on_display(None, Some(bg_color), |target| {
|
||||
shape::Text::new(PROGRESS_TEXT_ORIGIN, text)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(BLD_FG)
|
||||
.render(target);
|
||||
|
||||
let loader_offset: i16 = 19;
|
||||
let center_text_offset: i16 = 10;
|
||||
let center = SCREEN.center() + Offset::y(loader_offset);
|
||||
let inactive_color = bg_color.blend(fg_color, 85);
|
||||
let end = 360.0 * progress as f32 / 1000.0;
|
||||
|
||||
render_loader(
|
||||
center,
|
||||
inactive_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
if progress >= LOADER_MAX {
|
||||
LoaderRange::Full
|
||||
} else {
|
||||
LoaderRange::FromTo(0.0, end)
|
||||
},
|
||||
target,
|
||||
);
|
||||
|
||||
if let Some((icon, color)) = icon {
|
||||
shape::ToifImage::new(center, icon.toif)
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.with_fg(color)
|
||||
.render(target);
|
||||
}
|
||||
|
||||
if let Some(center_text) = center_text {
|
||||
shape::Text::new(
|
||||
SCREEN.center() + Offset::y(loader_offset + center_text_offset),
|
||||
center_text,
|
||||
)
|
||||
.with_align(Alignment::Center)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(GREY)
|
||||
.render(target);
|
||||
}
|
||||
});
|
||||
|
||||
display::refresh();
|
||||
if initialize {
|
||||
Self::fadein();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BootloaderUI for UILincoln {
|
||||
fn screen_welcome() {
|
||||
let mut frame = Welcome::new();
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool) {
|
||||
let mut reboot_msg = BootloaderString::new();
|
||||
|
||||
let bg_color = if initial_setup { WELCOME_COLOR } else { BLD_BG };
|
||||
let fg_color = if initial_setup { GREEN_LIGHT } else { BLD_FG };
|
||||
|
||||
if restart_seconds >= 1 {
|
||||
// in practice, restart_seconds is 5 or less so this is fine
|
||||
let seconds_char = b'0' + restart_seconds % 10;
|
||||
unwrap!(reboot_msg.push(seconds_char as char));
|
||||
let progress = (5 - (restart_seconds as u16)).clamp(0, 5) * 200;
|
||||
|
||||
Self::screen_progress(
|
||||
"Restarting device",
|
||||
progress,
|
||||
complete_draw,
|
||||
fg_color,
|
||||
bg_color,
|
||||
None,
|
||||
Some(reboot_msg.as_str()),
|
||||
);
|
||||
} else {
|
||||
Self::screen_progress(
|
||||
"Firmware installed",
|
||||
1000,
|
||||
complete_draw,
|
||||
fg_color,
|
||||
bg_color,
|
||||
Some((Icon::new(CHECK24), BLD_FG)),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn screen_install_fail() {
|
||||
let mut frame = ResultScreen::new(
|
||||
&RESULT_FW_INSTALL,
|
||||
Icon::new(WARNING40),
|
||||
"Firmware installation was not successful".into(),
|
||||
Label::centered(RECONNECT_MESSAGE.into(), RESULT_FW_INSTALL.title_style())
|
||||
.vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_install_confirm(
|
||||
vendor: &str,
|
||||
version: &str,
|
||||
fingerprint: &str,
|
||||
should_keep_seed: bool,
|
||||
is_newvendor: bool,
|
||||
is_newinstall: bool,
|
||||
version_cmp: i32,
|
||||
) -> u32 {
|
||||
let mut version_str: BootloaderString = String::new();
|
||||
unwrap!(version_str.push_str("Firmware version "));
|
||||
unwrap!(version_str.push_str(version));
|
||||
unwrap!(version_str.push_str("\nby "));
|
||||
unwrap!(version_str.push_str(vendor));
|
||||
|
||||
let title_str = if is_newinstall {
|
||||
"INSTALL FIRMWARE"
|
||||
} else if is_newvendor {
|
||||
"CHANGE FW\nVENDOR"
|
||||
} else if version_cmp > 0 {
|
||||
"UPDATE FIRMWARE"
|
||||
} else if version_cmp == 0 {
|
||||
"REINSTALL FW"
|
||||
} else {
|
||||
"DOWNGRADE FW"
|
||||
};
|
||||
let title = Label::left_aligned(title_str.into(), TEXT_BOLD).vertically_centered();
|
||||
let msg = Label::left_aligned(version_str.as_str().into(), TEXT_NORMAL);
|
||||
let alert = (!should_keep_seed).then_some(Label::left_aligned(
|
||||
"SEED WILL BE ERASED!".into(),
|
||||
TEXT_BOLD,
|
||||
));
|
||||
|
||||
let (left, right) = if should_keep_seed {
|
||||
let l = Button::with_text("CANCEL".into())
|
||||
.styled(button_bld())
|
||||
.with_text_align(Alignment::Center);
|
||||
let r = Button::with_text("INSTALL".into())
|
||||
.styled(button_confirm())
|
||||
.with_text_align(Alignment::Center);
|
||||
(l, r)
|
||||
} else {
|
||||
let l = Button::with_icon(Icon::new(X24))
|
||||
.styled(button_bld())
|
||||
.with_text_align(Alignment::Center);
|
||||
let r = Button::with_icon(Icon::new(CHECK24))
|
||||
.styled(button_confirm())
|
||||
.with_text_align(Alignment::Center);
|
||||
(l, r)
|
||||
};
|
||||
|
||||
let mut frame = Confirm::new(BLD_BG, left, right, ConfirmTitle::Text(title), msg)
|
||||
.with_info(
|
||||
"FW FINGERPRINT".into(),
|
||||
fingerprint.into(),
|
||||
button_bld_menu(),
|
||||
);
|
||||
|
||||
if let Some(alert) = alert {
|
||||
frame = frame.with_alert(alert);
|
||||
}
|
||||
|
||||
run(&mut frame)
|
||||
}
|
||||
|
||||
fn screen_wipe_confirm() -> u32 {
|
||||
let icon = Icon::new(FIRE40);
|
||||
|
||||
let msg = Label::centered(
|
||||
"Are you sure you want to factory reset the device?".into(),
|
||||
TEXT_WIPE_NORMAL,
|
||||
);
|
||||
let alert = Label::centered("SEED AND FIRMWARE\nWILL BE ERASED!".into(), TEXT_WIPE_BOLD);
|
||||
|
||||
let right = Button::with_text("RESET".into())
|
||||
.styled(button_wipe_confirm())
|
||||
.with_text_align(Alignment::Center);
|
||||
let left = Button::with_text("CANCEL".into())
|
||||
.styled(button_wipe_cancel())
|
||||
.with_text_align(Alignment::Center);
|
||||
|
||||
let mut frame = Confirm::new(BLD_WIPE_COLOR, left, right, ConfirmTitle::Icon(icon), msg)
|
||||
.with_alert(alert);
|
||||
|
||||
run(&mut frame)
|
||||
}
|
||||
|
||||
fn screen_unlock_bootloader_confirm() -> u32 {
|
||||
let title =
|
||||
Label::left_aligned("UNLOCK BOOTLOADER".into(), TEXT_BOLD).vertically_centered();
|
||||
let msg = Label::centered("This action cannot be undone!".into(), TEXT_NORMAL);
|
||||
|
||||
let right = Button::with_text("UNLOCK".into())
|
||||
.styled(button_confirm())
|
||||
.with_text_align(Alignment::Center);
|
||||
let left = Button::with_text("CANCEL".into())
|
||||
.styled(button_bld())
|
||||
.with_text_align(Alignment::Center);
|
||||
|
||||
let mut frame = Confirm::new(BLD_BG, left, right, ConfirmTitle::Text(title), msg);
|
||||
|
||||
run(&mut frame)
|
||||
}
|
||||
|
||||
fn screen_unlock_bootloader_success() {
|
||||
let mut frame = ResultScreen::new(
|
||||
&RESULT_FW_INSTALL,
|
||||
Icon::new(CHECK40),
|
||||
"Bootloader unlocked".into(),
|
||||
Label::centered(RECONNECT_MESSAGE.into(), RESULT_FW_INSTALL.title_style())
|
||||
.vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_menu(firmware_present: secbool) -> u32 {
|
||||
run(&mut Menu::new(firmware_present))
|
||||
}
|
||||
|
||||
fn screen_intro(bld_version: &str, vendor: &str, version: &str, fw_ok: bool) -> u32 {
|
||||
let mut title_str: BootloaderString = String::new();
|
||||
unwrap!(title_str.push_str("BOOTLOADER "));
|
||||
unwrap!(title_str.push_str(bld_version));
|
||||
|
||||
let mut version_str: BootloaderString = String::new();
|
||||
unwrap!(version_str.push_str("Firmware version "));
|
||||
unwrap!(version_str.push_str(version));
|
||||
unwrap!(version_str.push_str("\nby "));
|
||||
unwrap!(version_str.push_str(vendor));
|
||||
|
||||
let mut frame = Intro::new(
|
||||
title_str.as_str().into(),
|
||||
version_str.as_str().into(),
|
||||
fw_ok,
|
||||
);
|
||||
|
||||
run(&mut frame)
|
||||
}
|
||||
|
||||
fn screen_boot_stage_1(fading: bool) {
|
||||
if fading {
|
||||
Self::fadeout();
|
||||
}
|
||||
|
||||
let mut frame = WelcomeScreen::new();
|
||||
show(&mut frame, false);
|
||||
|
||||
if fading {
|
||||
Self::fadein();
|
||||
} else {
|
||||
display::set_backlight(theme::backlight::get_backlight_normal());
|
||||
}
|
||||
}
|
||||
|
||||
fn screen_wipe_progress(progress: u16, initialize: bool) {
|
||||
Self::screen_progress(
|
||||
"Resetting Trezor",
|
||||
progress,
|
||||
initialize,
|
||||
BLD_FG,
|
||||
BLD_WIPE_COLOR,
|
||||
Some((Icon::new(FIRE32), BLD_FG)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn screen_install_progress(progress: u16, initialize: bool, initial_setup: bool) {
|
||||
let bg_color = if initial_setup { WELCOME_COLOR } else { BLD_BG };
|
||||
let fg_color = if initial_setup { GREEN_LIGHT } else { BLD_FG };
|
||||
let icon_color = BLD_FG;
|
||||
|
||||
Self::screen_progress(
|
||||
"Installing firmware",
|
||||
progress,
|
||||
initialize,
|
||||
fg_color,
|
||||
bg_color,
|
||||
Some((Icon::new(DOWNLOAD24), icon_color)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn screen_connect(initial_setup: bool) {
|
||||
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
|
||||
let mut frame = Connect::new("Waiting for host...", BLD_TITLE_COLOR, bg);
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_wipe_success() {
|
||||
let mut frame = ResultScreen::new(
|
||||
&RESULT_WIPE,
|
||||
Icon::new(CHECK40),
|
||||
"Trezor reset\nsuccessfully".into(),
|
||||
Label::centered(RECONNECT_MESSAGE.into(), RESULT_WIPE.title_style())
|
||||
.vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_wipe_fail() {
|
||||
let mut frame = ResultScreen::new(
|
||||
&RESULT_WIPE,
|
||||
Icon::new(WARNING40),
|
||||
"Trezor reset was\nnot successful".into(),
|
||||
Label::centered(RECONNECT_MESSAGE.into(), RESULT_WIPE.title_style())
|
||||
.vertically_centered(),
|
||||
true,
|
||||
);
|
||||
show(&mut frame, true);
|
||||
}
|
||||
|
||||
fn screen_boot(
|
||||
warning: bool,
|
||||
vendor_str: Option<&str>,
|
||||
version: [u8; 4],
|
||||
vendor_img: &'static [u8],
|
||||
wait: i32,
|
||||
) {
|
||||
let bg_color = if warning { BLD_WARN_COLOR } else { BLD_BG };
|
||||
|
||||
display::sync();
|
||||
|
||||
render_on_display(None, Some(bg_color), |target| {
|
||||
// Draw vendor image if it's valid and has size of 120x120
|
||||
if let Ok(toif) = Toif::new(vendor_img) {
|
||||
if (toif.width() == 120) && (toif.height() == 120) {
|
||||
// Image position depends on the vendor string presence
|
||||
let pos = if vendor_str.is_some() {
|
||||
Point::new(SCREEN.width() / 2, 30)
|
||||
} else {
|
||||
Point::new(SCREEN.width() / 2, 60)
|
||||
};
|
||||
|
||||
shape::ToifImage::new(pos, toif)
|
||||
.with_align(Alignment2D::TOP_CENTER)
|
||||
.with_fg(BLD_FG)
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw vendor string if present
|
||||
if let Some(text) = vendor_str {
|
||||
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 50);
|
||||
shape::Text::new(pos, text)
|
||||
.with_align(Alignment::Center)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(BLD_FG) //COLOR_BL_BG
|
||||
.render(target);
|
||||
|
||||
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 25);
|
||||
|
||||
let mut version_text: BootloaderString = String::new();
|
||||
unwrap!(uwrite!(
|
||||
version_text,
|
||||
"{}.{}.{}",
|
||||
version[0],
|
||||
version[1],
|
||||
version[2]
|
||||
));
|
||||
|
||||
shape::Text::new(pos, version_text.as_str())
|
||||
.with_align(Alignment::Center)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(BLD_FG)
|
||||
.render(target);
|
||||
}
|
||||
|
||||
// Draw a message
|
||||
match wait.cmp(&0) {
|
||||
core::cmp::Ordering::Equal => {}
|
||||
core::cmp::Ordering::Greater => {
|
||||
let mut text: BootloaderString = String::new();
|
||||
unwrap!(uwrite!(text, "starting in {} s", wait));
|
||||
|
||||
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5);
|
||||
shape::Text::new(pos, text.as_str())
|
||||
.with_align(Alignment::Center)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(BLD_FG)
|
||||
.render(target);
|
||||
}
|
||||
core::cmp::Ordering::Less => {
|
||||
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5);
|
||||
shape::Text::new(pos, "click to continue ...")
|
||||
.with_align(Alignment::Center)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(BLD_FG)
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
display::refresh();
|
||||
}
|
||||
}
|
67
core/embed/rust/src/ui/model_lincoln/bootloader/welcome.rs
Normal file
67
core/embed/rust/src/ui/model_lincoln/bootloader/welcome.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx, Never, Pad},
|
||||
constant::screen,
|
||||
display::Font,
|
||||
geometry::{Offset, Point, Rect},
|
||||
shape,
|
||||
shape::Renderer,
|
||||
};
|
||||
|
||||
use super::super::theme::{BLACK, GREY, WHITE};
|
||||
|
||||
const TEXT_ORIGIN: Point = Point::new(0, 105);
|
||||
const STRIDE: i16 = 22;
|
||||
|
||||
pub struct Welcome {
|
||||
bg: Pad,
|
||||
}
|
||||
|
||||
impl Welcome {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bg: Pad::with_background(BLACK).with_clear(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Welcome {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.bg.place(screen());
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.bg.render(target);
|
||||
|
||||
shape::Text::new(TEXT_ORIGIN, "Get started")
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(GREY)
|
||||
.render(target);
|
||||
|
||||
shape::Text::new(TEXT_ORIGIN + Offset::y(STRIDE), "with your Trezor")
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(GREY)
|
||||
.render(target);
|
||||
|
||||
shape::Text::new(TEXT_ORIGIN + Offset::y(2 * STRIDE), "at")
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(GREY)
|
||||
.render(target);
|
||||
|
||||
let at_width = Font::NORMAL.text_width("at ");
|
||||
|
||||
shape::Text::new(
|
||||
TEXT_ORIGIN + Offset::new(at_width, 2 * STRIDE),
|
||||
"trezor.io/start",
|
||||
)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(WHITE)
|
||||
.render(target);
|
||||
}
|
||||
}
|
470
core/embed/rust/src/ui/model_lincoln/component/button.rs
Normal file
470
core/embed/rust/src/ui/model_lincoln/component/button.rs
Normal file
@ -0,0 +1,470 @@
|
||||
#[cfg(feature = "haptic")]
|
||||
use crate::trezorhal::haptic::{play, HapticEffect};
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Timer},
|
||||
display::{toif::Icon, Color, Font},
|
||||
event::TouchEvent,
|
||||
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
|
||||
shape::{self, Renderer},
|
||||
util::split_two_lines,
|
||||
},
|
||||
};
|
||||
|
||||
use super::theme;
|
||||
|
||||
pub enum ButtonMsg {
|
||||
Pressed,
|
||||
Released,
|
||||
Clicked,
|
||||
LongPressed,
|
||||
}
|
||||
|
||||
pub struct Button {
|
||||
area: Rect,
|
||||
touch_expand: Option<Insets>,
|
||||
content: ButtonContent,
|
||||
styles: ButtonStyleSheet,
|
||||
text_align: Alignment,
|
||||
radius: Option<u8>,
|
||||
state: State,
|
||||
long_press: Option<Duration>,
|
||||
long_timer: Timer,
|
||||
haptic: bool,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub const BASELINE_OFFSET: Offset = Offset::new(2, 6);
|
||||
|
||||
pub const fn new(content: ButtonContent) -> Self {
|
||||
Self {
|
||||
content,
|
||||
area: Rect::zero(),
|
||||
touch_expand: None,
|
||||
styles: theme::button_default(),
|
||||
text_align: Alignment::Start,
|
||||
radius: None,
|
||||
state: State::Initial,
|
||||
long_press: None,
|
||||
long_timer: Timer::new(),
|
||||
haptic: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn with_text(text: TString<'static>) -> Self {
|
||||
Self::new(ButtonContent::Text(text))
|
||||
}
|
||||
|
||||
pub const fn with_icon(icon: Icon) -> Self {
|
||||
Self::new(ButtonContent::Icon(icon))
|
||||
}
|
||||
|
||||
pub const fn with_icon_and_text(content: IconText) -> Self {
|
||||
Self::new(ButtonContent::IconAndText(content))
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self::new(ButtonContent::Empty)
|
||||
}
|
||||
|
||||
pub const fn styled(mut self, styles: ButtonStyleSheet) -> Self {
|
||||
self.styles = styles;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_text_align(mut self, align: Alignment) -> Self {
|
||||
self.text_align = align;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn with_expanded_touch_area(mut self, expand: Insets) -> Self {
|
||||
self.touch_expand = Some(expand);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_long_press(mut self, duration: Duration) -> Self {
|
||||
self.long_press = Some(duration);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_radius(mut self, radius: u8) -> Self {
|
||||
self.radius = Some(radius);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn without_haptics(mut self) -> Self {
|
||||
self.haptic = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn enable_if(&mut self, ctx: &mut EventCtx, enabled: bool) {
|
||||
if enabled {
|
||||
self.enable(ctx);
|
||||
} else {
|
||||
self.disable(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initially_enabled(mut self, enabled: bool) -> Self {
|
||||
if !enabled {
|
||||
self.state = State::Disabled;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn enable(&mut self, ctx: &mut EventCtx) {
|
||||
self.set(ctx, State::Initial)
|
||||
}
|
||||
|
||||
pub fn disable(&mut self, ctx: &mut EventCtx) {
|
||||
self.set(ctx, State::Disabled)
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
matches!(
|
||||
self.state,
|
||||
State::Initial | State::Pressed | State::Released
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_disabled(&self) -> bool {
|
||||
matches!(self.state, State::Disabled)
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, content: ButtonContent) {
|
||||
if self.content != content {
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &ButtonContent {
|
||||
&self.content
|
||||
}
|
||||
|
||||
pub fn set_stylesheet(&mut self, ctx: &mut EventCtx, styles: ButtonStyleSheet) {
|
||||
if self.styles != styles {
|
||||
self.styles = styles;
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(&self) -> &ButtonStyle {
|
||||
match self.state {
|
||||
State::Initial | State::Released => self.styles.normal,
|
||||
State::Pressed => self.styles.active,
|
||||
State::Disabled => self.styles.disabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn area(&self) -> Rect {
|
||||
self.area
|
||||
}
|
||||
|
||||
fn set(&mut self, ctx: &mut EventCtx, state: State) {
|
||||
if self.state != state {
|
||||
self.state = state;
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_background<'s>(
|
||||
&self,
|
||||
target: &mut impl Renderer<'s>,
|
||||
style: &ButtonStyle,
|
||||
alpha: u8,
|
||||
) {
|
||||
if self.radius.is_some() {
|
||||
shape::Bar::new(self.area)
|
||||
.with_bg(style.background_color)
|
||||
.with_radius(self.radius.unwrap() as i16)
|
||||
.with_thickness(2)
|
||||
.with_fg(style.button_color)
|
||||
.with_alpha(alpha)
|
||||
.render(target);
|
||||
} else {
|
||||
shape::Bar::new(self.area)
|
||||
.with_bg(style.button_color)
|
||||
.with_fg(style.button_color)
|
||||
.with_alpha(alpha)
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_content<'s>(
|
||||
&self,
|
||||
target: &mut impl Renderer<'s>,
|
||||
style: &ButtonStyle,
|
||||
alpha: u8,
|
||||
) {
|
||||
match &self.content {
|
||||
ButtonContent::Empty => {}
|
||||
ButtonContent::Text(text) => {
|
||||
let y_offset = Offset::y(self.style().font.allcase_text_height() / 2);
|
||||
let start_of_baseline = match self.text_align {
|
||||
Alignment::Start => {
|
||||
self.area.left_center() + Offset::x(Self::BASELINE_OFFSET.x)
|
||||
}
|
||||
Alignment::Center => self.area.center(),
|
||||
Alignment::End => self.area.right_center() - Offset::x(Self::BASELINE_OFFSET.x),
|
||||
} + y_offset;
|
||||
text.map(|text| {
|
||||
shape::Text::new(start_of_baseline, text)
|
||||
.with_font(style.font)
|
||||
.with_fg(style.text_color)
|
||||
.with_align(self.text_align)
|
||||
.with_alpha(alpha)
|
||||
.render(target);
|
||||
});
|
||||
}
|
||||
ButtonContent::Icon(icon) => {
|
||||
shape::ToifImage::new(self.area.center(), icon.toif)
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.with_fg(style.icon_color)
|
||||
.with_alpha(alpha)
|
||||
.render(target);
|
||||
}
|
||||
ButtonContent::IconAndText(child) => {
|
||||
child.render(
|
||||
target,
|
||||
self.area,
|
||||
self.style(),
|
||||
Self::BASELINE_OFFSET,
|
||||
alpha,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_with_alpha<'s>(&self, target: &mut impl Renderer<'s>, alpha: u8) {
|
||||
let style = self.style();
|
||||
self.render_background(target, style, alpha);
|
||||
self.render_content(target, style, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Button {
|
||||
type Msg = ButtonMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
self.area
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let touch_area = if let Some(expand) = self.touch_expand {
|
||||
self.area.outset(expand)
|
||||
} else {
|
||||
self.area
|
||||
};
|
||||
|
||||
match event {
|
||||
Event::Touch(TouchEvent::TouchStart(pos)) => {
|
||||
match self.state {
|
||||
State::Disabled => {
|
||||
// Do nothing.
|
||||
}
|
||||
_ => {
|
||||
// Touch started in our area, transform to `Pressed` state.
|
||||
if touch_area.contains(pos) {
|
||||
#[cfg(feature = "haptic")]
|
||||
if self.haptic {
|
||||
play(HapticEffect::ButtonPress);
|
||||
}
|
||||
self.set(ctx, State::Pressed);
|
||||
if let Some(duration) = self.long_press {
|
||||
self.long_timer.start(ctx, duration);
|
||||
}
|
||||
return Some(ButtonMsg::Pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Touch(TouchEvent::TouchMove(pos)) => {
|
||||
match self.state {
|
||||
State::Pressed if !touch_area.contains(pos) => {
|
||||
// Touch is leaving our area, transform to `Released` state.
|
||||
self.set(ctx, State::Released);
|
||||
return Some(ButtonMsg::Released);
|
||||
}
|
||||
_ => {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Touch(TouchEvent::TouchEnd(pos)) => {
|
||||
match self.state {
|
||||
State::Initial | State::Disabled => {
|
||||
// Do nothing.
|
||||
}
|
||||
State::Pressed if touch_area.contains(pos) => {
|
||||
// Touch finished in our area, we got clicked.
|
||||
self.set(ctx, State::Initial);
|
||||
return Some(ButtonMsg::Clicked);
|
||||
}
|
||||
State::Pressed => {
|
||||
// Touch finished outside our area.
|
||||
self.set(ctx, State::Initial);
|
||||
self.long_timer.stop();
|
||||
return Some(ButtonMsg::Released);
|
||||
}
|
||||
_ => {
|
||||
// Touch finished outside our area.
|
||||
self.set(ctx, State::Initial);
|
||||
self.long_timer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Swipe(_) => {
|
||||
// When a swipe is detected, abort any ongoing touch.
|
||||
match self.state {
|
||||
State::Initial | State::Disabled => {
|
||||
// Do nothing.
|
||||
}
|
||||
State::Pressed => {
|
||||
// Touch aborted
|
||||
self.set(ctx, State::Initial);
|
||||
self.long_timer.stop();
|
||||
return Some(ButtonMsg::Released);
|
||||
}
|
||||
_ => {
|
||||
// Irrelevant touch abort
|
||||
self.set(ctx, State::Initial);
|
||||
self.long_timer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::Timer(_) if self.long_timer.expire(event) => {
|
||||
if matches!(self.state, State::Pressed) {
|
||||
#[cfg(feature = "haptic")]
|
||||
if self.haptic {
|
||||
play(HapticEffect::ButtonPress);
|
||||
}
|
||||
self.set(ctx, State::Initial);
|
||||
return Some(ButtonMsg::LongPressed);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let style = self.style();
|
||||
self.render_background(target, style, 0xFF);
|
||||
self.render_content(target, style, 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for Button {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("Button");
|
||||
match &self.content {
|
||||
ButtonContent::Empty => {}
|
||||
ButtonContent::Text(text) => t.string("text", *text),
|
||||
ButtonContent::Icon(_) => t.bool("icon", true),
|
||||
ButtonContent::IconAndText(content) => {
|
||||
t.string("text", content.text);
|
||||
t.bool("icon", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
enum State {
|
||||
Initial,
|
||||
Pressed,
|
||||
Released,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub enum ButtonContent {
|
||||
Empty,
|
||||
Text(TString<'static>),
|
||||
Icon(Icon),
|
||||
IconAndText(IconText),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub struct ButtonStyleSheet {
|
||||
pub normal: &'static ButtonStyle,
|
||||
pub active: &'static ButtonStyle,
|
||||
pub disabled: &'static ButtonStyle,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct ButtonStyle {
|
||||
pub font: Font,
|
||||
pub text_color: Color,
|
||||
pub button_color: Color,
|
||||
pub icon_color: Color,
|
||||
pub background_color: Color,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct IconText {
|
||||
text: TString<'static>,
|
||||
icon: Icon,
|
||||
}
|
||||
|
||||
impl IconText {
|
||||
const ICON_SPACE: i16 = 46;
|
||||
const ICON_MARGIN: i16 = 4;
|
||||
const TEXT_MARGIN: i16 = 6;
|
||||
|
||||
pub fn new(text: impl Into<TString<'static>>, icon: Icon) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
icon,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<'s>(
|
||||
&self,
|
||||
target: &mut impl Renderer<'s>,
|
||||
area: Rect,
|
||||
style: &ButtonStyle,
|
||||
baseline_offset: Offset,
|
||||
alpha: u8,
|
||||
) {
|
||||
let mut show_text = |text: &str, rect: Rect| {
|
||||
let text_pos = rect.left_center() + baseline_offset;
|
||||
let text_pos = Point::new(rect.top_left().x + Self::ICON_SPACE, text_pos.y);
|
||||
shape::Text::new(text_pos, text)
|
||||
.with_font(style.font)
|
||||
.with_fg(style.text_color)
|
||||
.with_alpha(alpha)
|
||||
.render(target)
|
||||
};
|
||||
|
||||
self.text.map(|t| {
|
||||
let (t1, t2) = split_two_lines(
|
||||
t,
|
||||
style.font,
|
||||
area.width() - Self::ICON_SPACE - Self::TEXT_MARGIN,
|
||||
);
|
||||
|
||||
if t1.is_empty() || t2.is_empty() {
|
||||
show_text(t, area);
|
||||
} else {
|
||||
show_text(t1, Rect::new(area.top_left(), area.right_center()));
|
||||
show_text(t2, Rect::new(area.left_center(), area.bottom_right()));
|
||||
}
|
||||
});
|
||||
|
||||
let icon_pos = Point::new(
|
||||
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
|
||||
area.center().y,
|
||||
);
|
||||
shape::ToifImage::new(icon_pos, self.icon.toif)
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.with_fg(style.icon_color)
|
||||
.with_alpha(alpha)
|
||||
.render(target);
|
||||
}
|
||||
}
|
94
core/embed/rust/src/ui/model_lincoln/component/error.rs
Normal file
94
core/embed/rust/src/ui/model_lincoln/component/error.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Label, Never, Pad},
|
||||
constant::screen,
|
||||
geometry::{Alignment2D, Point, Rect},
|
||||
shape,
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
constant::WIDTH,
|
||||
theme::{FATAL_ERROR_COLOR, ICON_WARNING40, RESULT_FOOTER_START, RESULT_PADDING, WHITE},
|
||||
ResultFooter, ResultStyle,
|
||||
};
|
||||
|
||||
const ICON_TOP: i16 = 23;
|
||||
const TITLE_AREA_START: i16 = 70;
|
||||
const MESSAGE_AREA_START: i16 = 90;
|
||||
|
||||
#[cfg(feature = "bootloader")]
|
||||
const STYLE: &ResultStyle = &crate::ui::model_mercury::theme::bootloader::RESULT_WIPE;
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
const STYLE: &ResultStyle = &super::theme::RESULT_ERROR;
|
||||
|
||||
pub struct ErrorScreen<'a> {
|
||||
bg: Pad,
|
||||
title: Label<'a>,
|
||||
message: Label<'a>,
|
||||
footer: ResultFooter<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ErrorScreen<'a> {
|
||||
pub fn new(title: TString<'a>, message: TString<'a>, footer: TString<'a>) -> Self {
|
||||
let title = Label::centered(title, STYLE.title_style());
|
||||
let message = Label::centered(message, STYLE.message_style()).vertically_centered();
|
||||
let footer = ResultFooter::new(
|
||||
Label::centered(footer, STYLE.title_style()).vertically_centered(),
|
||||
STYLE,
|
||||
);
|
||||
|
||||
Self {
|
||||
bg: Pad::with_background(FATAL_ERROR_COLOR).with_clear(),
|
||||
title,
|
||||
message,
|
||||
footer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Component for ErrorScreen<'a> {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, _bounds: Rect) -> Rect {
|
||||
self.bg.place(screen());
|
||||
|
||||
let title_area = Rect::new(
|
||||
Point::new(RESULT_PADDING, TITLE_AREA_START),
|
||||
Point::new(WIDTH - RESULT_PADDING, MESSAGE_AREA_START),
|
||||
);
|
||||
self.title.place(title_area);
|
||||
|
||||
let message_area = Rect::new(
|
||||
Point::new(RESULT_PADDING, MESSAGE_AREA_START),
|
||||
Point::new(WIDTH - RESULT_PADDING, RESULT_FOOTER_START),
|
||||
);
|
||||
self.message.place(message_area);
|
||||
|
||||
let (_, bottom_area) = ResultFooter::<'a>::split_bounds();
|
||||
self.footer.place(bottom_area);
|
||||
|
||||
screen()
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.bg.render(target);
|
||||
|
||||
let icon = ICON_WARNING40;
|
||||
shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif)
|
||||
.with_fg(WHITE)
|
||||
.with_bg(FATAL_ERROR_COLOR)
|
||||
.with_align(Alignment2D::TOP_CENTER)
|
||||
.render(target);
|
||||
|
||||
self.title.render(target);
|
||||
self.message.render(target);
|
||||
self.footer.render(target);
|
||||
}
|
||||
}
|
11
core/embed/rust/src/ui/model_lincoln/component/mod.rs
Normal file
11
core/embed/rust/src/ui/model_lincoln/component/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
mod button;
|
||||
mod error;
|
||||
mod result;
|
||||
mod welcome_screen;
|
||||
|
||||
pub use button::{ButtonStyle, ButtonStyleSheet};
|
||||
pub use error::ErrorScreen;
|
||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||
pub use welcome_screen::WelcomeScreen;
|
||||
|
||||
use super::{constant, theme};
|
175
core/embed/rust/src/ui/model_lincoln/component/result.rs
Normal file
175
core/embed/rust/src/ui/model_lincoln/component/result.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{text::TextStyle, Component, Event, EventCtx, Label, Never, Pad},
|
||||
constant::screen,
|
||||
display::{Color, Font, Icon},
|
||||
geometry::{Alignment2D, Insets, Offset, Point, Rect},
|
||||
shape,
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
constant::WIDTH,
|
||||
theme::{FG, RESULT_FOOTER_START, RESULT_PADDING},
|
||||
};
|
||||
|
||||
const MESSAGE_AREA_START: i16 = 97;
|
||||
const ICON_CENTER_Y: i16 = 62;
|
||||
|
||||
pub struct ResultStyle {
|
||||
pub fg_color: Color,
|
||||
pub bg_color: Color,
|
||||
pub divider_color: Color,
|
||||
}
|
||||
|
||||
impl ResultStyle {
|
||||
pub const fn new(fg_color: Color, bg_color: Color, divider_color: Color) -> Self {
|
||||
Self {
|
||||
fg_color,
|
||||
bg_color,
|
||||
divider_color,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn message_style(&self) -> TextStyle {
|
||||
TextStyle::new(Font::NORMAL, self.fg_color, self.bg_color, FG, FG)
|
||||
}
|
||||
|
||||
pub const fn title_style(&self) -> TextStyle {
|
||||
TextStyle::new(Font::BOLD, self.fg_color, self.bg_color, FG, FG)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResultFooter<'a> {
|
||||
style: &'a ResultStyle,
|
||||
text: Label<'a>,
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
impl<'a> ResultFooter<'a> {
|
||||
pub fn new(text: Label<'a>, style: &'a ResultStyle) -> Self {
|
||||
Self {
|
||||
style,
|
||||
text,
|
||||
area: Rect::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResultFooter<'_> {
|
||||
pub const fn split_bounds() -> (Rect, Rect) {
|
||||
let main_area = Rect::new(
|
||||
Point::new(RESULT_PADDING, 0),
|
||||
Point::new(WIDTH - RESULT_PADDING, RESULT_FOOTER_START),
|
||||
);
|
||||
let footer_area = Rect::new(
|
||||
Point::new(RESULT_PADDING, RESULT_FOOTER_START),
|
||||
Point::new(WIDTH - RESULT_PADDING, screen().height()),
|
||||
);
|
||||
(main_area, footer_area)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ResultFooter<'_> {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
self.text.place(bounds);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
// divider line
|
||||
let bar = Rect::from_center_and_size(
|
||||
Point::new(self.area.center().x, self.area.y0),
|
||||
Offset::new(self.area.width(), 1),
|
||||
);
|
||||
shape::Bar::new(bar)
|
||||
.with_fg(self.style.divider_color)
|
||||
.render(target);
|
||||
|
||||
// footer text
|
||||
self.text.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResultScreen<'a> {
|
||||
bg: Pad,
|
||||
footer_pad: Pad,
|
||||
style: &'a ResultStyle,
|
||||
icon: Icon,
|
||||
message: Label<'a>,
|
||||
footer: ResultFooter<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ResultScreen<'a> {
|
||||
pub fn new(
|
||||
style: &'a ResultStyle,
|
||||
icon: Icon,
|
||||
message: TString<'a>,
|
||||
footer: Label<'a>,
|
||||
complete_draw: bool,
|
||||
) -> Self {
|
||||
let mut instance = Self {
|
||||
bg: Pad::with_background(style.bg_color),
|
||||
footer_pad: Pad::with_background(style.bg_color),
|
||||
style,
|
||||
icon,
|
||||
message: Label::centered(message, style.message_style()),
|
||||
footer: ResultFooter::new(footer, style),
|
||||
};
|
||||
|
||||
if complete_draw {
|
||||
instance.bg.clear();
|
||||
} else {
|
||||
instance.footer_pad.clear();
|
||||
}
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Component for ResultScreen<'a> {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, _bounds: Rect) -> Rect {
|
||||
self.bg.place(screen());
|
||||
|
||||
let (main_area, footer_area) = ResultFooter::split_bounds();
|
||||
|
||||
self.footer_pad.place(footer_area);
|
||||
self.footer.place(footer_area);
|
||||
|
||||
let message_area = main_area.inset(Insets::top(MESSAGE_AREA_START));
|
||||
self.message.place(message_area);
|
||||
|
||||
screen()
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.bg.render(target);
|
||||
self.footer_pad.render(target);
|
||||
|
||||
shape::ToifImage::new(
|
||||
Point::new(screen().center().x, ICON_CENTER_Y),
|
||||
self.icon.toif,
|
||||
)
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.with_fg(self.style.fg_color)
|
||||
.with_bg(self.style.bg_color)
|
||||
.render(target);
|
||||
|
||||
self.message.render(target);
|
||||
self.footer.render(target);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx, Never},
|
||||
display::font::Font,
|
||||
geometry::{Alignment, Alignment2D, Offset, Rect},
|
||||
shape,
|
||||
shape::Renderer,
|
||||
};
|
||||
|
||||
use super::theme;
|
||||
|
||||
const TEXT_BOTTOM_MARGIN: i16 = 54;
|
||||
const ICON_TOP_MARGIN: i16 = 48;
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
const MODEL_NAME_FONT: Font = Font::DEMIBOLD;
|
||||
|
||||
use crate::trezorhal::model;
|
||||
|
||||
pub struct WelcomeScreen {
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
impl WelcomeScreen {
|
||||
pub fn new() -> Self {
|
||||
Self { area: Rect::zero() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for WelcomeScreen {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
self.area
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
shape::ToifImage::new(
|
||||
self.area.top_center() + Offset::y(ICON_TOP_MARGIN),
|
||||
theme::ICON_LOGO.toif,
|
||||
)
|
||||
.with_align(Alignment2D::TOP_CENTER)
|
||||
.with_fg(theme::FG)
|
||||
.with_bg(theme::BG)
|
||||
.render(target);
|
||||
|
||||
shape::Text::new(
|
||||
self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN),
|
||||
model::FULL_NAME,
|
||||
)
|
||||
.with_align(Alignment::Center)
|
||||
.with_font(Font::NORMAL)
|
||||
.with_fg(theme::FG)
|
||||
.render(target);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for WelcomeScreen {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("WelcomeScreen");
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
t.string("model", model::FULL_NAME.into());
|
||||
}
|
||||
}
|
34
core/embed/rust/src/ui/model_lincoln/component_msg_obj.rs
Normal file
34
core/embed/rust/src/ui/model_lincoln/component_msg_obj.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::obj::Obj,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{ParagraphSource, Paragraphs},
|
||||
Component, Timeout,
|
||||
},
|
||||
layout::{obj::ComponentMsgObj, result::CANCELLED},
|
||||
},
|
||||
};
|
||||
|
||||
// Clippy/compiler complains about conflicting implementations
|
||||
// TODO move the common impls to a common module
|
||||
#[cfg(not(feature = "clippy"))]
|
||||
impl<'a, T> ComponentMsgObj for Paragraphs<T>
|
||||
where
|
||||
T: ParagraphSource<'a>,
|
||||
{
|
||||
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
// Clippy/compiler complains about conflicting implementations
|
||||
#[cfg(not(feature = "clippy"))]
|
||||
impl<T> ComponentMsgObj for (Timeout, T)
|
||||
where
|
||||
T: Component<Msg = ()>,
|
||||
{
|
||||
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||
Ok(CANCELLED.as_obj())
|
||||
}
|
||||
}
|
24
core/embed/rust/src/ui/model_lincoln/constant.rs
Normal file
24
core/embed/rust/src/ui/model_lincoln/constant.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use crate::ui::geometry::{Offset, Point, Rect};
|
||||
|
||||
use crate::trezorhal::display::{DISPLAY_RESX, DISPLAY_RESY};
|
||||
|
||||
pub const WIDTH: i16 = DISPLAY_RESX as _;
|
||||
pub const HEIGHT: i16 = DISPLAY_RESY as _;
|
||||
|
||||
// TODO: below constants copied from mercury
|
||||
pub const LINE_SPACE: i16 = 4;
|
||||
pub const FONT_BPP: i16 = 4;
|
||||
|
||||
pub const LOADER_OUTER: i16 = 60;
|
||||
pub const LOADER_INNER: i16 = 52;
|
||||
pub const LOADER_ICON_MAX_SIZE: i16 = 64;
|
||||
|
||||
pub const fn size() -> Offset {
|
||||
Offset::new(WIDTH, HEIGHT)
|
||||
}
|
||||
pub const SIZE: Offset = size();
|
||||
|
||||
pub const fn screen() -> Rect {
|
||||
Rect::from_top_left_and_size(Point::zero(), SIZE)
|
||||
}
|
||||
pub const SCREEN: Rect = screen();
|
38
core/embed/rust/src/ui/model_lincoln/cshape/loader.rs
Normal file
38
core/embed/rust/src/ui/model_lincoln/cshape/loader.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use crate::ui::{display::Color, geometry::Point, model_lincoln::constant, shape, shape::Renderer};
|
||||
|
||||
pub enum LoaderRange {
|
||||
Full,
|
||||
FromTo(f32, f32),
|
||||
}
|
||||
|
||||
pub fn render_loader<'s>(
|
||||
center: Point,
|
||||
inactive_color: Color,
|
||||
active_color: Color,
|
||||
background_color: Color,
|
||||
range: LoaderRange,
|
||||
target: &mut impl Renderer<'s>,
|
||||
) {
|
||||
shape::Circle::new(center, constant::LOADER_OUTER)
|
||||
.with_bg(inactive_color)
|
||||
.render(target);
|
||||
|
||||
match range {
|
||||
LoaderRange::Full => {
|
||||
shape::Circle::new(center, constant::LOADER_OUTER)
|
||||
.with_bg(active_color)
|
||||
.render(target);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
shape::Circle::new(center, constant::LOADER_INNER + 2)
|
||||
.with_bg(background_color)
|
||||
.render(target);
|
||||
}
|
3
core/embed/rust/src/ui/model_lincoln/cshape/mod.rs
Normal file
3
core/embed/rust/src/ui/model_lincoln/cshape/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod loader;
|
||||
|
||||
pub use loader::{render_loader, LoaderRange};
|
0
core/embed/rust/src/ui/model_lincoln/flow/mod.rs
Normal file
0
core/embed/rust/src/ui/model_lincoln/flow/mod.rs
Normal file
71
core/embed/rust/src/ui/model_lincoln/mod.rs
Normal file
71
core/embed/rust/src/ui/model_lincoln/mod.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use super::{geometry::Rect, CommonUI};
|
||||
use crate::ui::model_lincoln::theme::backlight;
|
||||
|
||||
#[cfg(feature = "bootloader")]
|
||||
pub mod bootloader;
|
||||
pub mod component;
|
||||
pub mod constant;
|
||||
pub mod theme;
|
||||
|
||||
#[cfg(feature = "micropython")]
|
||||
pub mod component_msg_obj;
|
||||
pub mod cshape;
|
||||
#[cfg(feature = "micropython")]
|
||||
pub mod flow;
|
||||
pub mod screens;
|
||||
#[cfg(feature = "micropython")]
|
||||
pub mod ui_firmware;
|
||||
|
||||
pub struct UILincoln;
|
||||
|
||||
impl CommonUI for UILincoln {
|
||||
#[cfg(feature = "backlight")]
|
||||
fn fadein() {
|
||||
crate::ui::display::fade_backlight_duration(backlight::get_backlight_normal(), 150);
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn fadeout() {
|
||||
crate::ui::display::fade_backlight_duration(backlight::get_backlight_dim(), 150);
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn backlight_on() {
|
||||
crate::ui::display::set_backlight(backlight::get_backlight_normal());
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn get_backlight_none() -> u8 {
|
||||
backlight::get_backlight_none()
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn get_backlight_normal() -> u8 {
|
||||
backlight::get_backlight_normal()
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn get_backlight_low() -> u8 {
|
||||
backlight::get_backlight_low()
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn get_backlight_dim() -> u8 {
|
||||
backlight::get_backlight_dim()
|
||||
}
|
||||
|
||||
#[cfg(feature = "backlight")]
|
||||
fn get_backlight_max() -> u8 {
|
||||
backlight::get_backlight_max()
|
||||
}
|
||||
|
||||
const SCREEN: Rect = constant::SCREEN;
|
||||
|
||||
fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
|
||||
screens::screen_fatal_error(title, msg, footer);
|
||||
}
|
||||
|
||||
fn screen_boot_stage_2() {
|
||||
screens::screen_boot_stage_2();
|
||||
}
|
||||
}
|
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/check24.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/check24.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/check40.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/check40.toif
Normal file
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 233 B |
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/fire24.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/fire24.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/fire32.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/fire32.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/fire40.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/fire40.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/info32.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/info32.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/menu32.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/menu32.toif
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/x24.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/x24.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/x32.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/bootloader/x32.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/lock_full.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/lock_full.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_lincoln/res/warning40.toif
Normal file
BIN
core/embed/rust/src/ui/model_lincoln/res/warning40.toif
Normal file
Binary file not shown.
32
core/embed/rust/src/ui/model_lincoln/screens.rs
Normal file
32
core/embed/rust/src/ui/model_lincoln/screens.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use crate::ui::{component::Component, constant::screen, display};
|
||||
|
||||
use super::{
|
||||
component::{ErrorScreen, WelcomeScreen},
|
||||
constant,
|
||||
};
|
||||
|
||||
use crate::ui::{display::Color, shape::render_on_display};
|
||||
|
||||
pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
|
||||
let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into());
|
||||
frame.place(constant::screen());
|
||||
|
||||
render_on_display(None, Some(Color::black()), |target| {
|
||||
frame.render(target);
|
||||
});
|
||||
|
||||
display::refresh();
|
||||
}
|
||||
|
||||
pub fn screen_boot_stage_2() {
|
||||
let mut frame = WelcomeScreen::new();
|
||||
frame.place(screen());
|
||||
|
||||
display::sync();
|
||||
|
||||
render_on_display(None, Some(Color::black()), |target| {
|
||||
frame.render(target);
|
||||
});
|
||||
|
||||
display::refresh();
|
||||
}
|
50
core/embed/rust/src/ui/model_lincoln/theme/backlight.rs
Normal file
50
core/embed/rust/src/ui/model_lincoln/theme/backlight.rs
Normal file
@ -0,0 +1,50 @@
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
use crate::storage;
|
||||
|
||||
// Typical backlight values.
|
||||
const BACKLIGHT_NORMAL: u8 = 150;
|
||||
const BACKLIGHT_LOW: u8 = 45;
|
||||
const BACKLIGHT_DIM: u8 = 5;
|
||||
const BACKLIGHT_NONE: u8 = 0;
|
||||
const BACKLIGHT_MIN: u8 = 10;
|
||||
const BACKLIGHT_MAX: u8 = 255;
|
||||
|
||||
#[cfg(feature = "bootloader")]
|
||||
pub fn get_backlight_normal() -> u8 {
|
||||
BACKLIGHT_NORMAL
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
pub fn get_backlight_normal() -> u8 {
|
||||
storage::get_brightness()
|
||||
.unwrap_or(BACKLIGHT_NORMAL)
|
||||
.clamp(BACKLIGHT_MIN, BACKLIGHT_MAX)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bootloader")]
|
||||
pub fn get_backlight_low() -> u8 {
|
||||
BACKLIGHT_LOW
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "bootloader"))]
|
||||
pub fn get_backlight_low() -> u8 {
|
||||
storage::get_brightness()
|
||||
.unwrap_or(BACKLIGHT_LOW)
|
||||
.clamp(BACKLIGHT_MIN, BACKLIGHT_LOW)
|
||||
}
|
||||
|
||||
pub fn get_backlight_dim() -> u8 {
|
||||
BACKLIGHT_DIM
|
||||
}
|
||||
|
||||
pub fn get_backlight_none() -> u8 {
|
||||
BACKLIGHT_NONE
|
||||
}
|
||||
|
||||
pub fn get_backlight_max() -> u8 {
|
||||
BACKLIGHT_MAX
|
||||
}
|
||||
|
||||
pub fn get_backlight_min() -> u8 {
|
||||
BACKLIGHT_MIN
|
||||
}
|
249
core/embed/rust/src/ui/model_lincoln/theme/bootloader.rs
Normal file
249
core/embed/rust/src/ui/model_lincoln/theme/bootloader.rs
Normal file
@ -0,0 +1,249 @@
|
||||
use crate::ui::{
|
||||
component::{text::TextStyle, LineBreaking::BreakWordsNoHyphen},
|
||||
constant::{HEIGHT, WIDTH},
|
||||
display::{Color, Font},
|
||||
geometry::{Offset, Point, Rect},
|
||||
util::include_res,
|
||||
};
|
||||
|
||||
use super::super::{
|
||||
component::{ButtonStyle, ButtonStyleSheet, ResultStyle},
|
||||
theme::{BLACK, FG, GREY_DARK, GREY_LIGHT, WHITE},
|
||||
};
|
||||
|
||||
pub const BLD_BG: Color = Color::rgb(0x00, 0x1E, 0xAD);
|
||||
pub const BLD_FG: Color = WHITE;
|
||||
pub const BLD_WIPE_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
|
||||
pub const BLD_WIPE_TEXT_COLOR: Color = WHITE;
|
||||
|
||||
pub const BLD_WARN_COLOR: Color = Color::rgb(0xFF, 0x00, 0x00);
|
||||
|
||||
pub const BLD_WIPE_BTN_COLOR: Color = WHITE;
|
||||
pub const BLD_WIPE_BTN_COLOR_ACTIVE: Color = Color::rgb(0xFA, 0xCF, 0xCF);
|
||||
|
||||
pub const BLD_WIPE_CANCEL_BTN_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41);
|
||||
pub const BLD_WIPE_CANCEL_BTN_COLOR_ACTIVE: Color = Color::rgb(0xAE, 0x09, 0x09);
|
||||
|
||||
pub const BLD_INSTALL_BTN_COLOR_ACTIVE: Color = Color::rgb(0xCD, 0xD2, 0xEF);
|
||||
|
||||
pub const BLD_BTN_COLOR: Color = Color::rgb(0x2D, 0x42, 0xBF);
|
||||
pub const BLD_BTN_COLOR_ACTIVE: Color = Color::rgb(0x04, 0x10, 0x58);
|
||||
|
||||
pub const BLD_TITLE_COLOR: Color = WHITE;
|
||||
|
||||
pub const WELCOME_COLOR: Color = BLACK;
|
||||
pub const WELCOME_HIGHLIGHT_COLOR: Color = Color::rgb(0x28, 0x28, 0x28);
|
||||
|
||||
// Commonly used corner radius (i.e. for buttons).
|
||||
pub const RADIUS: u8 = 2;
|
||||
|
||||
// Commonly used constants for UI elements.
|
||||
pub const CONTENT_PADDING: i16 = 6;
|
||||
pub const TITLE_AREA: Rect = Rect::new(
|
||||
Point::new(CONTENT_PADDING, CONTENT_PADDING),
|
||||
Point::new(WIDTH, CORNER_BUTTON_SIZE + CONTENT_PADDING),
|
||||
);
|
||||
|
||||
pub const CORNER_BUTTON_TOUCH_EXPANSION: i16 = 13;
|
||||
pub const CORNER_BUTTON_SIZE: i16 = 44;
|
||||
pub const CORNER_BUTTON_AREA: Rect = Rect::from_top_left_and_size(
|
||||
Point::new(
|
||||
WIDTH - CORNER_BUTTON_SIZE - CONTENT_PADDING,
|
||||
CONTENT_PADDING,
|
||||
),
|
||||
Offset::uniform(CORNER_BUTTON_SIZE),
|
||||
);
|
||||
pub const BUTTON_AREA_START: i16 = HEIGHT - 56;
|
||||
pub const BUTTON_HEIGHT: i16 = 50;
|
||||
|
||||
// BLD icons
|
||||
pub const X24: &[u8] = include_res!("model_mercury/res/x24.toif");
|
||||
pub const X32: &[u8] = include_res!("model_mercury/res/x32.toif");
|
||||
pub const FIRE24: &[u8] = include_res!("model_mercury/res/fire24.toif");
|
||||
pub const FIRE32: &[u8] = include_res!("model_mercury/res/fire32.toif");
|
||||
pub const FIRE40: &[u8] = include_res!("model_mercury/res/fire40.toif");
|
||||
pub const REFRESH24: &[u8] = include_res!("model_mercury/res/refresh24.toif");
|
||||
pub const MENU32: &[u8] = include_res!("model_mercury/res/menu32.toif");
|
||||
pub const INFO32: &[u8] = include_res!("model_mercury/res/info32.toif");
|
||||
pub const DOWNLOAD24: &[u8] = include_res!("model_mercury/res/download24.toif");
|
||||
pub const WARNING40: &[u8] = include_res!("model_mercury/res/warning40.toif");
|
||||
pub const CHECK24: &[u8] = include_res!("model_mercury/res/check24.toif");
|
||||
pub const CHECK40: &[u8] = include_res!("model_mercury/res/check40.toif");
|
||||
|
||||
pub fn button_confirm() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_BG,
|
||||
button_color: WHITE,
|
||||
icon_color: BLD_BG,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_BG,
|
||||
button_color: BLD_INSTALL_BTN_COLOR_ACTIVE,
|
||||
icon_color: BLD_BG,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: FG,
|
||||
button_color: GREY_DARK,
|
||||
icon_color: BLD_BG,
|
||||
background_color: FG,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_wipe_cancel() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: WHITE,
|
||||
button_color: BLD_WIPE_CANCEL_BTN_COLOR,
|
||||
icon_color: WHITE,
|
||||
background_color: BLD_WIPE_COLOR,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: WHITE,
|
||||
button_color: BLD_WIPE_CANCEL_BTN_COLOR_ACTIVE,
|
||||
icon_color: WHITE,
|
||||
background_color: BLD_WIPE_COLOR,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: GREY_LIGHT,
|
||||
button_color: GREY_DARK,
|
||||
icon_color: GREY_LIGHT,
|
||||
background_color: WHITE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_wipe_confirm() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_WIPE_COLOR,
|
||||
button_color: BLD_WIPE_BTN_COLOR,
|
||||
icon_color: BLD_WIPE_COLOR,
|
||||
background_color: BLD_WIPE_COLOR,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_WIPE_COLOR,
|
||||
button_color: BLD_WIPE_BTN_COLOR_ACTIVE,
|
||||
icon_color: BLD_WIPE_COLOR,
|
||||
background_color: BLD_WIPE_COLOR,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: FG,
|
||||
button_color: GREY_DARK,
|
||||
icon_color: FG,
|
||||
background_color: FG,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_bld_menu() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_FG,
|
||||
button_color: BLD_BG,
|
||||
icon_color: BLD_FG,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_FG,
|
||||
button_color: BLD_BG,
|
||||
icon_color: BLD_FG,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: GREY_LIGHT,
|
||||
button_color: BLD_BG,
|
||||
icon_color: GREY_LIGHT,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn button_bld() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_FG,
|
||||
button_color: BLD_BTN_COLOR,
|
||||
icon_color: BLD_FG,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: BLD_FG,
|
||||
button_color: BLD_BTN_COLOR_ACTIVE,
|
||||
icon_color: BLD_FG,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: Font::BOLD,
|
||||
text_color: GREY_LIGHT,
|
||||
button_color: BLD_BTN_COLOR,
|
||||
icon_color: GREY_LIGHT,
|
||||
background_color: BLD_BG,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn text_title(bg: Color) -> TextStyle {
|
||||
TextStyle::new(
|
||||
Font::BOLD,
|
||||
BLD_TITLE_COLOR,
|
||||
bg,
|
||||
BLD_TITLE_COLOR,
|
||||
BLD_TITLE_COLOR,
|
||||
)
|
||||
}
|
||||
|
||||
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG);
|
||||
pub const TEXT_WARNING: TextStyle = TextStyle::new(
|
||||
Font::BOLD,
|
||||
BLD_WARN_COLOR,
|
||||
BLD_BG,
|
||||
BLD_WARN_COLOR,
|
||||
BLD_WARN_COLOR,
|
||||
);
|
||||
pub const fn text_fingerprint(bg: Color) -> TextStyle {
|
||||
TextStyle::new(Font::NORMAL, BLD_FG, bg, BLD_FG, BLD_FG).with_line_breaking(BreakWordsNoHyphen)
|
||||
}
|
||||
pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::BOLD, BLD_FG, BLD_BG, BLD_FG, BLD_FG);
|
||||
pub const TEXT_WIPE_BOLD: TextStyle = TextStyle::new(
|
||||
Font::BOLD,
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
BLD_WIPE_COLOR,
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
);
|
||||
pub const TEXT_WIPE_NORMAL: TextStyle = TextStyle::new(
|
||||
Font::NORMAL,
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
BLD_WIPE_COLOR,
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
);
|
||||
|
||||
pub const RESULT_WIPE: ResultStyle = ResultStyle::new(
|
||||
BLD_WIPE_TEXT_COLOR,
|
||||
BLD_WIPE_COLOR,
|
||||
BLD_WIPE_CANCEL_BTN_COLOR,
|
||||
);
|
||||
|
||||
pub const RESULT_FW_INSTALL: ResultStyle = ResultStyle::new(BLD_FG, BLD_BG, BLD_BTN_COLOR);
|
||||
|
||||
pub const RESULT_INITIAL: ResultStyle =
|
||||
ResultStyle::new(FG, WELCOME_COLOR, WELCOME_HIGHLIGHT_COLOR);
|
66
core/embed/rust/src/ui/model_lincoln/theme/mod.rs
Normal file
66
core/embed/rust/src/ui/model_lincoln/theme/mod.rs
Normal file
@ -0,0 +1,66 @@
|
||||
pub mod bootloader;
|
||||
|
||||
pub mod backlight;
|
||||
|
||||
use crate::{
|
||||
time::Duration,
|
||||
ui::{display::{Color, Font}, util::include_icon},
|
||||
};
|
||||
|
||||
use super::component::{ButtonStyle, ButtonStyleSheet, ResultStyle};
|
||||
|
||||
pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500);
|
||||
|
||||
// Color palette.
|
||||
// TODO: colors
|
||||
pub const WHITE: Color = Color::rgb(0xFF, 0xFF, 0xFF);
|
||||
pub const BLACK: Color = Color::rgb(0, 0, 0);
|
||||
pub const FG: Color = WHITE; // Default foreground (text & icon) color.
|
||||
pub const BG: Color = BLACK; // Default background color.
|
||||
pub const GREY_DARK: Color = Color::rgb(0x46, 0x48, 0x4A);
|
||||
pub const GREY_LIGHT: Color = Color::rgb(0xC7, 0xCD, 0xD3);
|
||||
|
||||
pub const FATAL_ERROR_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
|
||||
pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41);
|
||||
|
||||
// UI icons (white color).
|
||||
// TODO: icons
|
||||
|
||||
// Welcome screen.
|
||||
include_icon!(ICON_LOGO, "model_lincoln/res/lock_full.toif");
|
||||
|
||||
// Homescreen notifications.
|
||||
include_icon!(ICON_WARNING40, "model_lincoln/res/warning40.toif");
|
||||
|
||||
// TODO: button styles
|
||||
pub const fn button_default() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: Font::DEMIBOLD,
|
||||
text_color: FG,
|
||||
button_color: BG,
|
||||
icon_color: FG,
|
||||
background_color: BG,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: Font::DEMIBOLD,
|
||||
text_color: FG,
|
||||
button_color: BG,
|
||||
icon_color: FG,
|
||||
background_color: BG,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: Font::DEMIBOLD,
|
||||
text_color: FG,
|
||||
button_color: BG,
|
||||
icon_color: FG,
|
||||
background_color: BG,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const RESULT_PADDING: i16 = 6;
|
||||
pub const RESULT_FOOTER_START: i16 = 171;
|
||||
pub const RESULT_FOOTER_HEIGHT: i16 = 62;
|
||||
pub const RESULT_ERROR: ResultStyle =
|
||||
ResultStyle::new(FG, FATAL_ERROR_COLOR, FATAL_ERROR_HIGHLIGHT_COLOR);
|
495
core/embed/rust/src/ui/model_lincoln/ui_firmware.rs
Normal file
495
core/embed/rust/src/ui/model_lincoln/ui_firmware.rs
Normal file
@ -0,0 +1,495 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
io::BinaryData,
|
||||
micropython::{gc::Gc, list::List, obj::Obj},
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::Empty,
|
||||
layout::{
|
||||
obj::{LayoutMaybeTrace, LayoutObj, RootComponent},
|
||||
util::RecoveryType,
|
||||
},
|
||||
ui_firmware::{
|
||||
FirmwareUI, MAX_CHECKLIST_ITEMS, MAX_GROUP_SHARE_LINES, MAX_WORD_QUIZ_ITEMS,
|
||||
},
|
||||
ModelUI,
|
||||
},
|
||||
};
|
||||
|
||||
use super::UILincoln;
|
||||
|
||||
impl FirmwareUI for UILincoln {
|
||||
fn confirm_action(
|
||||
_title: TString<'static>,
|
||||
_action: Option<TString<'static>>,
|
||||
_description: Option<TString<'static>>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_verb: Option<TString<'static>>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
_hold: bool,
|
||||
_hold_danger: bool,
|
||||
_reverse: bool,
|
||||
_prompt_screen: bool,
|
||||
_prompt_title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_address(
|
||||
_title: TString<'static>,
|
||||
_address: Obj,
|
||||
_address_label: Option<TString<'static>>,
|
||||
_verb: Option<TString<'static>>,
|
||||
_info_button: bool,
|
||||
_chunkify: bool,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_blob(
|
||||
_title: TString<'static>,
|
||||
_data: Obj,
|
||||
_description: Option<TString<'static>>,
|
||||
_text_mono: bool,
|
||||
_extra: Option<TString<'static>>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_verb: Option<TString<'static>>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
_verb_info: Option<TString<'static>>,
|
||||
_info: bool,
|
||||
_hold: bool,
|
||||
_chunkify: bool,
|
||||
_page_counter: bool,
|
||||
_prompt_screen: bool,
|
||||
_cancel: bool,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_blob_intro(
|
||||
_title: TString<'static>,
|
||||
_data: Obj,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_verb: Option<TString<'static>>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
_chunkify: bool,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_homescreen(
|
||||
_title: TString<'static>,
|
||||
_image: BinaryData<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_coinjoin(
|
||||
_max_rounds: TString<'static>,
|
||||
_max_feerate: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_emphasized(
|
||||
_title: TString<'static>,
|
||||
_items: Obj,
|
||||
_verb: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_fido(
|
||||
_title: TString<'static>,
|
||||
_app_name: TString<'static>,
|
||||
_icon: Option<TString<'static>>,
|
||||
_accounts: Gc<List>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
#[cfg(feature = "universal_fw")]
|
||||
return Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"));
|
||||
#[cfg(not(feature = "universal_fw"))]
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(
|
||||
c"confirm_fido not used in bitcoin-only firmware",
|
||||
))
|
||||
}
|
||||
|
||||
fn confirm_firmware_update(
|
||||
_description: TString<'static>,
|
||||
_fingerprint: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_modify_fee(
|
||||
_title: TString<'static>,
|
||||
_sign: i32,
|
||||
_user_fee_change: TString<'static>,
|
||||
_total_fee_new: TString<'static>,
|
||||
_fee_rate_amount: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_modify_output(
|
||||
_sign: i32,
|
||||
_amount_change: TString<'static>,
|
||||
_amount_new: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_more(
|
||||
_title: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_button_style_confirm: bool,
|
||||
_items: Obj,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_reset_device(_recovery: bool) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_summary(
|
||||
_amount: TString<'static>,
|
||||
_amount_label: TString<'static>,
|
||||
_fee: TString<'static>,
|
||||
_fee_label: TString<'static>,
|
||||
_title: Option<TString<'static>>,
|
||||
_account_items: Option<Obj>,
|
||||
_extra_items: Option<Obj>,
|
||||
_extra_title: Option<TString<'static>>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_properties(
|
||||
_title: TString<'static>,
|
||||
_items: Obj,
|
||||
_hold: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_value(
|
||||
_title: TString<'static>,
|
||||
_value: Obj,
|
||||
_description: Option<TString<'static>>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_verb: Option<TString<'static>>,
|
||||
_verb_info: Option<TString<'static>>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
_info_button: bool,
|
||||
_hold: bool,
|
||||
_chunkify: bool,
|
||||
_text_mono: bool,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn confirm_with_info(
|
||||
_title: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_info_button: TString<'static>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
_items: Obj,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn check_homescreen_format(_image: BinaryData, _accept_toif: bool) -> bool {
|
||||
false // not implemented
|
||||
}
|
||||
|
||||
fn continue_recovery_homepage(
|
||||
_text: TString<'static>,
|
||||
_subtext: Option<TString<'static>>,
|
||||
_button: Option<TString<'static>>,
|
||||
_recovery_type: RecoveryType,
|
||||
_show_instructions: bool,
|
||||
_remaining_shares: Option<Obj>,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn flow_confirm_output(
|
||||
_title: Option<TString<'static>>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_message: Obj,
|
||||
_amount: Option<Obj>,
|
||||
_chunkify: bool,
|
||||
_text_mono: bool,
|
||||
_account: Option<TString<'static>>,
|
||||
_account_path: Option<TString<'static>>,
|
||||
_br_code: u16,
|
||||
_br_name: TString<'static>,
|
||||
_address: Option<Obj>,
|
||||
_address_title: Option<TString<'static>>,
|
||||
_summary_items: Option<Obj>,
|
||||
_fee_items: Option<Obj>,
|
||||
_summary_title: Option<TString<'static>>,
|
||||
_summary_br_code: Option<u16>,
|
||||
_summary_br_name: Option<TString<'static>>,
|
||||
_cancel_text: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn flow_confirm_set_new_pin(
|
||||
_title: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn flow_get_address(
|
||||
_address: Obj,
|
||||
_title: TString<'static>,
|
||||
_description: Option<TString<'static>>,
|
||||
_extra: Option<TString<'static>>,
|
||||
_chunkify: bool,
|
||||
_address_qr: TString<'static>,
|
||||
_case_sensitive: bool,
|
||||
_account: Option<TString<'static>>,
|
||||
_path: Option<TString<'static>>,
|
||||
_xpubs: Obj,
|
||||
_title_success: TString<'static>,
|
||||
_br_code: u16,
|
||||
_br_name: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn multiple_pages_texts(
|
||||
_title: TString<'static>,
|
||||
_verb: TString<'static>,
|
||||
_items: Gc<List>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn prompt_backup() -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn request_bip39(
|
||||
_prompt: TString<'static>,
|
||||
_prefill_word: TString<'static>,
|
||||
_can_go_back: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn request_slip39(
|
||||
_prompt: TString<'static>,
|
||||
_prefill_word: TString<'static>,
|
||||
_can_go_back: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn request_number(
|
||||
_title: TString<'static>,
|
||||
_count: u32,
|
||||
_min_count: u32,
|
||||
_max_count: u32,
|
||||
_description: Option<TString<'static>>,
|
||||
_more_info_callback: Option<impl Fn(u32) -> TString<'static> + 'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn request_pin(
|
||||
_prompt: TString<'static>,
|
||||
_subprompt: TString<'static>,
|
||||
_allow_cancel: bool,
|
||||
_warning: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn request_passphrase(
|
||||
_prompt: TString<'static>,
|
||||
_max_len: u32,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn select_word(
|
||||
_title: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
_words: [TString<'static>; MAX_WORD_QUIZ_ITEMS],
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn select_word_count(_recovery_type: RecoveryType) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn set_brightness(_current_brightness: Option<u8>) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_address_details(
|
||||
_qr_title: TString<'static>,
|
||||
_address: TString<'static>,
|
||||
_case_sensitive: bool,
|
||||
_details_title: TString<'static>,
|
||||
_account: Option<TString<'static>>,
|
||||
_path: Option<TString<'static>>,
|
||||
_xpubs: Obj,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_checklist(
|
||||
_title: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_active: usize,
|
||||
_items: [TString<'static>; MAX_CHECKLIST_ITEMS],
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_danger(
|
||||
_title: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
_value: TString<'static>,
|
||||
_verb_cancel: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_error(
|
||||
_title: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
_allow_cancel: bool,
|
||||
_time_ms: u32,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_group_share_success(
|
||||
_lines: [TString<'static>; MAX_GROUP_SHARE_LINES],
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_homescreen(
|
||||
_label: TString<'static>,
|
||||
_hold: bool,
|
||||
_notification: Option<TString<'static>>,
|
||||
_notification_level: u8,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_info(
|
||||
_title: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_time_ms: u32,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_info_with_cancel(
|
||||
_title: TString<'static>,
|
||||
_items: Obj,
|
||||
_horizontal: bool,
|
||||
_chunkify: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_lockscreen(
|
||||
_label: TString<'static>,
|
||||
_bootscreen: bool,
|
||||
_coinjoin_authorized: bool,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_mismatch(_title: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_progress(
|
||||
_description: TString<'static>,
|
||||
_indeterminate: bool,
|
||||
_title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_progress_coinjoin(
|
||||
_title: TString<'static>,
|
||||
_indeterminate: bool,
|
||||
_time_ms: u32,
|
||||
_skip_first_paint: bool,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_share_words(
|
||||
_words: heapless::Vec<TString<'static>, 33>,
|
||||
_title: Option<TString<'static>>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_share_words_mercury(
|
||||
_words: heapless::Vec<TString<'static>, 33>,
|
||||
_subtitle: Option<TString<'static>>,
|
||||
_instructions: Obj,
|
||||
_text_footer: Option<TString<'static>>,
|
||||
_text_confirm: TString<'static>,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_remaining_shares(_pages_iterable: Obj) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_simple(
|
||||
text: TString<'static>,
|
||||
_title: Option<TString<'static>>,
|
||||
_button: Option<TString<'static>>,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_success(
|
||||
_title: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
_allow_cancel: bool,
|
||||
_time_ms: u32,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_wait_text(_text: TString<'static>) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn show_warning(
|
||||
_title: TString<'static>,
|
||||
_button: TString<'static>,
|
||||
_value: TString<'static>,
|
||||
_description: TString<'static>,
|
||||
_allow_cancel: bool,
|
||||
_danger: bool,
|
||||
) -> Result<Gc<LayoutObj>, Error> {
|
||||
Err::<Gc<LayoutObj>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
|
||||
fn tutorial() -> Result<impl LayoutMaybeTrace, Error> {
|
||||
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
|
||||
}
|
||||
}
|
@ -57,18 +57,18 @@ pub const BUTTON_AREA_START: i16 = HEIGHT - 56;
|
||||
pub const BUTTON_HEIGHT: i16 = 50;
|
||||
|
||||
// BLD icons
|
||||
pub const X24: &[u8] = include_res!("model_mercury/res/x24.toif");
|
||||
pub const X32: &[u8] = include_res!("model_mercury/res/x32.toif");
|
||||
pub const FIRE24: &[u8] = include_res!("model_mercury/res/fire24.toif");
|
||||
pub const FIRE32: &[u8] = include_res!("model_mercury/res/fire32.toif");
|
||||
pub const FIRE40: &[u8] = include_res!("model_mercury/res/fire40.toif");
|
||||
pub const REFRESH24: &[u8] = include_res!("model_mercury/res/refresh24.toif");
|
||||
pub const MENU32: &[u8] = include_res!("model_mercury/res/menu32.toif");
|
||||
pub const INFO32: &[u8] = include_res!("model_mercury/res/info32.toif");
|
||||
pub const DOWNLOAD24: &[u8] = include_res!("model_mercury/res/download24.toif");
|
||||
pub const WARNING40: &[u8] = include_res!("model_mercury/res/warning40.toif");
|
||||
pub const CHECK24: &[u8] = include_res!("model_mercury/res/check24.toif");
|
||||
pub const CHECK40: &[u8] = include_res!("model_mercury/res/check40.toif");
|
||||
pub const X24: &[u8] = include_res!("model_lincoln/res/bootloader/x24.toif");
|
||||
pub const X32: &[u8] = include_res!("model_lincoln/res/bootloader/x32.toif");
|
||||
pub const FIRE24: &[u8] = include_res!("model_lincoln/res/bootloader/fire24.toif");
|
||||
pub const FIRE32: &[u8] = include_res!("model_lincoln/res/bootloader/fire32.toif");
|
||||
pub const FIRE40: &[u8] = include_res!("model_lincoln/res/bootloader/fire40.toif");
|
||||
pub const REFRESH24: &[u8] = include_res!("model_lincoln/res/bootloader/refresh24.toif");
|
||||
pub const MENU32: &[u8] = include_res!("model_lincoln/res/bootloader/menu32.toif");
|
||||
pub const INFO32: &[u8] = include_res!("model_lincoln/res/bootloader/info32.toif");
|
||||
pub const DOWNLOAD24: &[u8] = include_res!("model_lincoln/res/bootloader/download24.toif");
|
||||
pub const WARNING40: &[u8] = include_res!("model_lincoln/res/bootloader/warning40.toif");
|
||||
pub const CHECK24: &[u8] = include_res!("model_lincoln/res/bootloader/check24.toif");
|
||||
pub const CHECK40: &[u8] = include_res!("model_lincoln/res/bootloader/check40.toif");
|
||||
|
||||
pub fn button_confirm() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
|
@ -502,6 +502,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_TR)},
|
||||
#elif UI_LAYOUT_MERCURY
|
||||
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_MERCURY)},
|
||||
#elif UI_LAYOUT_LINCOLN
|
||||
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_LINCOLN)},
|
||||
#else
|
||||
#error Unknown layout
|
||||
#endif
|
||||
|
@ -31,6 +31,9 @@ def configure_board(
|
||||
|
||||
|
||||
def get_model_ui() -> str:
|
||||
from SCons.Script import ARGUMENTS
|
||||
if ARGUMENTS.get('UI_LINCOLN_DEV', '0') == '1':
|
||||
return "lincoln"
|
||||
return "tt"
|
||||
|
||||
|
||||
|
@ -44,11 +44,12 @@ def generate(env):
|
||||
btc_only = env["bitcoin_only"] == "1"
|
||||
backlight = env["backlight"]
|
||||
optiga = env["optiga"]
|
||||
layout_tt = env["ui_layout"] == "UI_LAYOUT_TT"
|
||||
layout_tr = env["ui_layout"] == "UI_LAYOUT_TR"
|
||||
touch = env["use_touch"]
|
||||
button = env["use_button"]
|
||||
layout_tt = env["ui_layout"] == "UI_LAYOUT_TT"
|
||||
layout_tr = env["ui_layout"] == "UI_LAYOUT_TR"
|
||||
layout_mercury = env["ui_layout"] == "UI_LAYOUT_MERCURY"
|
||||
layout_lincoln = env["ui_layout"] == "UI_LAYOUT_LINCOLN"
|
||||
thp = env["thp"]
|
||||
interim = f"{target[:-4]}.i" # replace .mpy with .i
|
||||
sed_scripts = [
|
||||
@ -58,6 +59,7 @@ def generate(env):
|
||||
rf"-e 's/utils\.UI_LAYOUT == \"TT\"/{layout_tt}/g'",
|
||||
rf"-e 's/utils\.UI_LAYOUT == \"TR\"/{layout_tr}/g'",
|
||||
rf"-e 's/utils\.UI_LAYOUT == \"MERCURY\"/{layout_mercury}/g'",
|
||||
rf"-e 's/utils\.UI_LAYOUT == \"LINCOLN\"/{layout_lincoln}/g'",
|
||||
rf"-e 's/utils\.USE_BUTTON/{button}/g'",
|
||||
rf"-e 's/utils\.USE_TOUCH/{touch}/g'",
|
||||
rf"-e 's/utils\.USE_THP/{thp}/g'",
|
||||
@ -71,6 +73,7 @@ def generate(env):
|
||||
"T": "T2T1",
|
||||
"R": "T2B1",
|
||||
"T3T1": "T3T1",
|
||||
"T3W1": "T3W1",
|
||||
}
|
||||
|
||||
for model_sym, internal_model in MODEL_SYMS.items():
|
||||
|
@ -2,11 +2,12 @@ from __future__ import annotations
|
||||
|
||||
from site_scons import models
|
||||
|
||||
from . import mercury, tr, tt
|
||||
from . import lincoln, mercury, tr, tt
|
||||
|
||||
|
||||
def get_ui_module(model: str):
|
||||
ui_modules = {
|
||||
"lincoln": lincoln,
|
||||
"mercury": mercury,
|
||||
"tr": tr,
|
||||
"tt": tt,
|
||||
|
63
core/site_scons/ui/lincoln.py
Normal file
63
core/site_scons/ui/lincoln.py
Normal file
@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .common import add_font
|
||||
|
||||
|
||||
def init_ui(
|
||||
stage: str,
|
||||
config: list[str],
|
||||
defines: list[str | tuple[str, str]],
|
||||
sources: list[str],
|
||||
rust_features: list[str],
|
||||
):
|
||||
|
||||
rust_features.append("model_lincoln")
|
||||
|
||||
font_normal = None
|
||||
font_demibold = None
|
||||
font_bold = None
|
||||
font_mono = None
|
||||
font_big = None
|
||||
font_normal_upper = None
|
||||
font_bold_upper = None
|
||||
font_sub = None
|
||||
|
||||
if stage == "bootloader":
|
||||
font_normal = "Font_TTSatoshi_DemiBold_21"
|
||||
font_demibold = "Font_TTSatoshi_DemiBold_21"
|
||||
font_bold = "Font_TTHoves_Bold_17_upper"
|
||||
font_mono = "Font_TTSatoshi_DemiBold_21"
|
||||
font_bold_upper = "Font_TTHoves_Bold_17_upper"
|
||||
if stage == "prodtest":
|
||||
font_normal = "Font_TTSatoshi_DemiBold_21"
|
||||
font_bold = "Font_TTSatoshi_DemiBold_21"
|
||||
font_mono = "Font_RobotoMono_Medium_21"
|
||||
if stage == "firmware":
|
||||
font_normal = "Font_TTSatoshi_DemiBold_21"
|
||||
font_demibold = "Font_TTSatoshi_DemiBold_21"
|
||||
font_bold = "Font_TTSatoshi_DemiBold_21"
|
||||
font_mono = "Font_RobotoMono_Medium_21"
|
||||
font_big = "Font_TTSatoshi_DemiBold_42"
|
||||
font_sub = "Font_TTSatoshi_DemiBold_18"
|
||||
rust_features.append("ui_blurring")
|
||||
rust_features.append("ui_jpeg_decoder")
|
||||
rust_features.append("ui_image_buffer")
|
||||
rust_features.append("ui_overlay")
|
||||
|
||||
# fonts
|
||||
add_font("NORMAL", font_normal, defines, sources)
|
||||
add_font("BOLD", font_bold, defines, sources)
|
||||
add_font("DEMIBOLD", font_demibold, defines, sources)
|
||||
add_font("MONO", font_mono, defines, sources)
|
||||
add_font("BIG", font_big, defines, sources)
|
||||
add_font("NORMAL_UPPER", font_normal_upper, defines, sources)
|
||||
add_font("BOLD_UPPER", font_bold_upper, defines, sources)
|
||||
add_font("SUB", font_sub, defines, sources)
|
||||
|
||||
|
||||
def get_ui_layout() -> str:
|
||||
return "UI_LAYOUT_LINCOLN"
|
||||
|
||||
|
||||
def get_ui_layout_path() -> str:
|
||||
return "trezor/ui/layouts/lincoln/"
|
8
core/src/all_modules.py
generated
8
core/src/all_modules.py
generated
@ -173,6 +173,14 @@ trezor.ui.layouts.fido
|
||||
import trezor.ui.layouts.fido
|
||||
trezor.ui.layouts.homescreen
|
||||
import trezor.ui.layouts.homescreen
|
||||
trezor.ui.layouts.lincoln
|
||||
import trezor.ui.layouts.lincoln
|
||||
trezor.ui.layouts.lincoln.fido
|
||||
import trezor.ui.layouts.lincoln.fido
|
||||
trezor.ui.layouts.lincoln.recovery
|
||||
import trezor.ui.layouts.lincoln.recovery
|
||||
trezor.ui.layouts.lincoln.reset
|
||||
import trezor.ui.layouts.lincoln.reset
|
||||
trezor.ui.layouts.mercury
|
||||
import trezor.ui.layouts.mercury
|
||||
trezor.ui.layouts.mercury.fido
|
||||
|
@ -10,5 +10,7 @@ elif utils.UI_LAYOUT == "TT":
|
||||
from .tt import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "MERCURY":
|
||||
from .mercury import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "LINCOLN":
|
||||
from .lincoln import * # noqa: F401,F403
|
||||
else:
|
||||
raise ValueError("Unknown layout")
|
||||
|
@ -6,3 +6,5 @@ elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.fido import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "MERCURY":
|
||||
from .mercury.fido import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "LINCOLN":
|
||||
from .lincoln.fido import * # noqa: F401,F403
|
||||
|
1073
core/src/trezor/ui/layouts/lincoln/__init__.py
Normal file
1073
core/src/trezor/ui/layouts/lincoln/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
54
core/src/trezor/ui/layouts/lincoln/fido.py
Normal file
54
core/src/trezor/ui/layouts/lincoln/fido.py
Normal file
@ -0,0 +1,54 @@
|
||||
import trezorui_api
|
||||
from trezor import ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
from ..common import interact
|
||||
|
||||
|
||||
async def confirm_fido(
|
||||
header: str,
|
||||
app_name: str,
|
||||
icon_name: str | None,
|
||||
accounts: list[str | None],
|
||||
) -> int:
|
||||
"""Webauthn confirmation for one or more credentials."""
|
||||
confirm = trezorui_api.confirm_fido(
|
||||
title=header,
|
||||
app_name=app_name,
|
||||
icon_name=icon_name,
|
||||
accounts=accounts,
|
||||
)
|
||||
result = await interact(confirm, "confirm_fido", ButtonRequestType.Other)
|
||||
|
||||
if __debug__ and result is trezorui_api.CONFIRMED:
|
||||
# debuglink will directly inject a CONFIRMED message which we need to handle
|
||||
# by playing back a click to the Rust layout and getting out the selected number
|
||||
# that way
|
||||
# Or we can just return 0 because this only happens in U2F tests
|
||||
# which don't use multiple credentials.
|
||||
return 0
|
||||
|
||||
# The Rust side returns either an int or `CANCELLED`. We detect the int situation
|
||||
# and assume cancellation otherwise.
|
||||
if isinstance(result, int):
|
||||
return result
|
||||
|
||||
# Late import won't get executed on the happy path.
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
async def confirm_fido_reset() -> bool:
|
||||
from trezor import TR
|
||||
|
||||
confirm = ui.Layout(
|
||||
trezorui_api.confirm_action(
|
||||
title=TR.fido__title_reset,
|
||||
action=TR.fido__erase_credentials,
|
||||
description=TR.words__really_wanna,
|
||||
reverse=True,
|
||||
prompt_screen=True,
|
||||
)
|
||||
)
|
||||
return (await confirm.get_result()) is trezorui_api.CONFIRMED
|
147
core/src/trezor/ui/layouts/lincoln/recovery.py
Normal file
147
core/src/trezor/ui/layouts/lincoln/recovery.py
Normal file
@ -0,0 +1,147 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import trezorui_api
|
||||
from trezor import TR
|
||||
from trezor.enums import ButtonRequestType, RecoveryType
|
||||
|
||||
from ..common import interact
|
||||
from . import raise_if_not_confirmed
|
||||
|
||||
CONFIRMED = trezorui_api.CONFIRMED # global_import_cache
|
||||
CANCELLED = trezorui_api.CANCELLED # global_import_cache
|
||||
INFO = trezorui_api.INFO # global_import_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from apps.management.recovery_device.layout import RemainingSharesInfo
|
||||
|
||||
|
||||
async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||
count = await interact(
|
||||
trezorui_api.select_word_count(recovery_type=recovery_type),
|
||||
"recovery_word_count",
|
||||
ButtonRequestType.MnemonicWordCount,
|
||||
)
|
||||
return int(count)
|
||||
|
||||
|
||||
async def request_word(
|
||||
word_index: int,
|
||||
word_count: int,
|
||||
is_slip39: bool,
|
||||
send_button_request: bool,
|
||||
prefill_word: str = "",
|
||||
) -> str:
|
||||
prompt = TR.recovery__word_x_of_y_template.format(word_index + 1, word_count)
|
||||
can_go_back = word_index > 0
|
||||
if is_slip39:
|
||||
keyboard = trezorui_api.request_slip39(
|
||||
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
|
||||
)
|
||||
else:
|
||||
keyboard = trezorui_api.request_bip39(
|
||||
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
|
||||
)
|
||||
|
||||
word: str = await interact(
|
||||
keyboard,
|
||||
"mnemonic" if send_button_request else None,
|
||||
ButtonRequestType.MnemonicInput,
|
||||
)
|
||||
return word
|
||||
|
||||
|
||||
def format_remaining_shares_info(
|
||||
remaining_shares_info: "RemainingSharesInfo",
|
||||
) -> list[tuple[str, str]]:
|
||||
from trezor import strings
|
||||
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
||||
|
||||
groups, shares_remaining, group_threshold = remaining_shares_info
|
||||
|
||||
pages: list[tuple[str, str]] = []
|
||||
completed_groups = shares_remaining.count(0)
|
||||
|
||||
for group, remaining in zip(groups, shares_remaining):
|
||||
if 0 < remaining < MAX_SHARE_COUNT:
|
||||
title = strings.format_plural(
|
||||
TR.recovery__x_more_items_starting_template_plural,
|
||||
remaining,
|
||||
TR.plurals__x_shares_needed,
|
||||
)
|
||||
words = "\n".join(group)
|
||||
pages.append((title, words))
|
||||
elif remaining == MAX_SHARE_COUNT and completed_groups < group_threshold:
|
||||
groups_remaining = group_threshold - completed_groups
|
||||
title = strings.format_plural(
|
||||
TR.recovery__x_more_items_starting_template_plural,
|
||||
groups_remaining,
|
||||
TR.plurals__x_groups_needed,
|
||||
)
|
||||
words = "\n".join(group)
|
||||
pages.append((title, words))
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
async def show_group_share_success(share_index: int, group_index: int) -> None:
|
||||
await raise_if_not_confirmed(
|
||||
trezorui_api.show_group_share_success(
|
||||
lines=[
|
||||
TR.recovery__you_have_entered,
|
||||
TR.recovery__share_num_template.format(share_index + 1),
|
||||
TR.words__from,
|
||||
TR.recovery__group_num_template.format(group_index + 1),
|
||||
],
|
||||
),
|
||||
"share_success",
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
|
||||
|
||||
async def continue_recovery(
|
||||
_button_label: str, # unused on mercury
|
||||
text: str,
|
||||
subtext: str | None,
|
||||
recovery_type: RecoveryType,
|
||||
show_instructions: bool = False,
|
||||
remaining_shares_info: "RemainingSharesInfo | None" = None,
|
||||
) -> bool:
|
||||
result = await interact(
|
||||
trezorui_api.continue_recovery_homepage(
|
||||
text=text,
|
||||
subtext=subtext,
|
||||
button=None,
|
||||
recovery_type=recovery_type,
|
||||
show_instructions=show_instructions,
|
||||
remaining_shares=(
|
||||
format_remaining_shares_info(remaining_shares_info)
|
||||
if remaining_shares_info
|
||||
else None
|
||||
),
|
||||
),
|
||||
None,
|
||||
ButtonRequestType.Other,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
return result is CONFIRMED
|
||||
|
||||
|
||||
async def show_recovery_warning(
|
||||
br_name: str,
|
||||
content: str,
|
||||
subheader: str | None = None,
|
||||
button: str | None = None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||||
) -> None:
|
||||
button = button or TR.buttons__try_again # def_arg
|
||||
await raise_if_not_confirmed(
|
||||
trezorui_api.show_warning(
|
||||
title=content or TR.words__warning,
|
||||
value=subheader or "",
|
||||
button=button,
|
||||
description="",
|
||||
danger=True,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
356
core/src/trezor/ui/layouts/lincoln/reset.py
Normal file
356
core/src/trezor/ui/layouts/lincoln/reset.py
Normal file
@ -0,0 +1,356 @@
|
||||
from typing import Awaitable, Callable, Sequence
|
||||
|
||||
import trezorui_api
|
||||
from trezor import TR, ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.wire import ActionCancelled
|
||||
|
||||
from ..common import interact
|
||||
from . import raise_if_not_confirmed, show_success
|
||||
|
||||
CONFIRMED = trezorui_api.CONFIRMED # global_import_cache
|
||||
|
||||
|
||||
def show_share_words(
|
||||
share_words: Sequence[str],
|
||||
share_index: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> Awaitable[None]:
|
||||
# FIXME: not implemented
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
async def select_word(
|
||||
words: Sequence[str],
|
||||
share_index: int | None,
|
||||
checked_index: int,
|
||||
count: int,
|
||||
group_index: int | None = None,
|
||||
) -> str:
|
||||
if share_index is None:
|
||||
title: str = TR.reset__check_wallet_backup_title
|
||||
elif group_index is None:
|
||||
title: str = TR.reset__check_share_title_template.format(share_index + 1)
|
||||
else:
|
||||
title: str = TR.reset__check_group_share_title_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
|
||||
# It may happen (with a very low probability)
|
||||
# that there will be less than three unique words to choose from.
|
||||
# In that case, duplicating the last word to make it three.
|
||||
words = list(words)
|
||||
while len(words) < 3:
|
||||
words.append(words[-1])
|
||||
|
||||
result = await interact(
|
||||
trezorui_api.select_word(
|
||||
title=title,
|
||||
description=TR.reset__select_word_x_of_y_template.format(
|
||||
checked_index + 1, count
|
||||
),
|
||||
words=(words[0], words[1], words[2]),
|
||||
),
|
||||
None,
|
||||
)
|
||||
if __debug__ and isinstance(result, str):
|
||||
return result
|
||||
assert isinstance(result, int) and 0 <= result <= 2
|
||||
return words[result]
|
||||
|
||||
|
||||
async def slip39_show_checklist(
|
||||
step: int,
|
||||
advanced: bool,
|
||||
count: int | None = None,
|
||||
threshold: int | None = None,
|
||||
) -> None:
|
||||
items = _slip_39_checklist_items(step, advanced, count, threshold)
|
||||
result = await interact(
|
||||
trezorui_api.show_checklist(
|
||||
title=TR.reset__title_shamir_backup,
|
||||
button=TR.buttons__continue,
|
||||
active=step,
|
||||
items=items,
|
||||
),
|
||||
"slip39_checklist",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
if result != CONFIRMED:
|
||||
raise ActionCancelled
|
||||
|
||||
|
||||
def _slip_39_checklist_items(
|
||||
step: int,
|
||||
advanced: bool,
|
||||
count: int | None = None,
|
||||
threshold: int | None = None,
|
||||
) -> tuple[str, str, str]:
|
||||
if not advanced:
|
||||
entry_1 = (
|
||||
TR.reset__slip39_checklist_num_shares_x_template.format(count)
|
||||
if count
|
||||
else TR.reset__slip39_checklist_set_num_shares
|
||||
)
|
||||
entry_2 = (
|
||||
TR.reset__slip39_checklist_threshold_x_template.format(threshold)
|
||||
if threshold
|
||||
else TR.reset__slip39_checklist_set_threshold
|
||||
)
|
||||
entry_3 = TR.reset__slip39_checklist_write_down_recovery
|
||||
return (entry_1, entry_2, entry_3)
|
||||
else:
|
||||
entry_1 = (
|
||||
TR.reset__slip39_checklist_num_groups_x_template.format(count)
|
||||
if count
|
||||
else TR.reset__slip39_checklist_set_num_groups
|
||||
)
|
||||
entry_2 = (
|
||||
TR.reset__slip39_checklist_threshold_x_template.format(threshold)
|
||||
if threshold
|
||||
else TR.reset__slip39_checklist_set_threshold
|
||||
)
|
||||
entry_3 = TR.reset__slip39_checklist_set_sizes_longer
|
||||
return (entry_1, entry_2, entry_3)
|
||||
|
||||
|
||||
async def _prompt_number(
|
||||
title: str,
|
||||
description: str,
|
||||
info: Callable[[int], str],
|
||||
count: int,
|
||||
min_count: int,
|
||||
max_count: int,
|
||||
br_name: str,
|
||||
) -> int:
|
||||
result = await interact(
|
||||
trezorui_api.request_number(
|
||||
title=title,
|
||||
count=count,
|
||||
min_count=min_count,
|
||||
max_count=max_count,
|
||||
description=description,
|
||||
more_info_callback=info,
|
||||
),
|
||||
br_name,
|
||||
ButtonRequestType.ResetDevice,
|
||||
raise_on_cancel=None,
|
||||
)
|
||||
|
||||
if __debug__ and result is CONFIRMED:
|
||||
# sent by debuglink. debuglink does not change the number of shares anyway
|
||||
# so use the initial one
|
||||
return count
|
||||
|
||||
if result is not trezorui_api.CANCELLED:
|
||||
assert isinstance(result, int)
|
||||
return result
|
||||
else:
|
||||
raise ActionCancelled # user cancelled request number prompt
|
||||
|
||||
|
||||
def slip39_prompt_threshold(
|
||||
num_of_shares: int, group_id: int | None = None
|
||||
) -> Awaitable[int]:
|
||||
count = num_of_shares // 2 + 1
|
||||
# min value of share threshold is 2 unless the number of shares is 1
|
||||
# number of shares 1 is possible in advanced slip39
|
||||
min_count = min(2, num_of_shares)
|
||||
max_count = num_of_shares
|
||||
|
||||
description = (
|
||||
TR.reset__select_threshold
|
||||
if group_id is None
|
||||
else TR.reset__num_shares_for_group_template.format(group_id + 1)
|
||||
)
|
||||
|
||||
def info(count: int) -> str:
|
||||
return (
|
||||
TR.reset__slip39_checklist_more_info_threshold
|
||||
+ "\n"
|
||||
+ TR.reset__slip39_checklist_more_info_threshold_example_template.format(
|
||||
count, num_of_shares, count
|
||||
)
|
||||
)
|
||||
|
||||
return _prompt_number(
|
||||
TR.reset__title_set_threshold,
|
||||
description,
|
||||
info,
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_threshold",
|
||||
)
|
||||
|
||||
|
||||
async def slip39_prompt_number_of_shares(
|
||||
num_words: int, group_id: int | None = None
|
||||
) -> int:
|
||||
count = 5
|
||||
min_count = 1
|
||||
max_count = 16
|
||||
|
||||
description = (
|
||||
TR.reset__num_of_shares_how_many
|
||||
if group_id is None
|
||||
else TR.reset__total_number_of_shares_in_group_template.format(group_id + 1)
|
||||
)
|
||||
|
||||
if group_id is None:
|
||||
info = TR.reset__num_of_shares_long_info_template.format(num_words)
|
||||
else:
|
||||
info = TR.reset__num_of_shares_advanced_info_template.format(
|
||||
num_words, group_id + 1
|
||||
)
|
||||
|
||||
return await _prompt_number(
|
||||
TR.reset__title_set_number_of_shares,
|
||||
description,
|
||||
lambda i: info,
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_shares",
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_number_of_groups() -> int:
|
||||
count = 5
|
||||
min_count = 2
|
||||
max_count = 16
|
||||
description = TR.reset__group_description
|
||||
info = TR.reset__group_info
|
||||
|
||||
return await _prompt_number(
|
||||
TR.reset__title_set_number_of_groups,
|
||||
description,
|
||||
lambda i: info,
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_groups",
|
||||
)
|
||||
|
||||
|
||||
async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
|
||||
count = num_of_groups // 2 + 1
|
||||
min_count = 1
|
||||
max_count = num_of_groups
|
||||
description = TR.reset__required_number_of_groups
|
||||
info = TR.reset__advanced_group_threshold_info
|
||||
|
||||
return await _prompt_number(
|
||||
TR.reset__title_set_group_threshold,
|
||||
description,
|
||||
lambda i: info,
|
||||
count,
|
||||
min_count,
|
||||
max_count,
|
||||
"slip39_group_threshold",
|
||||
)
|
||||
|
||||
|
||||
async def show_intro_backup(single_share: bool, num_of_words: int | None) -> None:
|
||||
if single_share:
|
||||
assert num_of_words is not None
|
||||
description = TR.backup__info_single_share_backup.format(num_of_words)
|
||||
else:
|
||||
description = TR.backup__info_multi_share_backup
|
||||
|
||||
await interact(
|
||||
trezorui_api.show_info(
|
||||
title=TR.backup__title_create_wallet_backup,
|
||||
description=description,
|
||||
),
|
||||
"backup_intro",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
|
||||
def show_warning_backup() -> Awaitable[ui.UiResult]:
|
||||
return interact(
|
||||
trezorui_api.show_warning(
|
||||
title=TR.words__important,
|
||||
value=TR.reset__never_make_digital_copy,
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
danger=False, # Use a less severe icon color
|
||||
),
|
||||
"backup_warning",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
||||
|
||||
|
||||
def show_success_backup() -> Awaitable[None]:
|
||||
return show_success(
|
||||
"success_backup",
|
||||
TR.backup__title_backup_completed,
|
||||
)
|
||||
|
||||
|
||||
def show_reset_warning(
|
||||
br_name: str,
|
||||
content: str,
|
||||
subheader: str | None = None,
|
||||
button: str | None = None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Warning,
|
||||
) -> Awaitable[None]:
|
||||
return raise_if_not_confirmed(
|
||||
trezorui_api.show_warning(
|
||||
title=subheader or "",
|
||||
description=content,
|
||||
value="",
|
||||
button="",
|
||||
allow_cancel=False,
|
||||
danger=True,
|
||||
),
|
||||
br_name,
|
||||
br_code,
|
||||
)
|
||||
|
||||
|
||||
async def show_share_confirmation_success(
|
||||
share_index: int | None = None,
|
||||
num_of_shares: int | None = None,
|
||||
group_index: int | None = None,
|
||||
) -> None:
|
||||
if share_index is None or num_of_shares is None:
|
||||
# it is a BIP39 or a 1-of-1 SLIP39 backup
|
||||
# mercury UI shows only final wallet backup confirmation screen later
|
||||
return
|
||||
|
||||
# TODO: super-shamir copy not done
|
||||
if share_index == num_of_shares - 1:
|
||||
title = TR.reset__share_completed_template.format(share_index + 1)
|
||||
if group_index is None:
|
||||
footer_description = ""
|
||||
else:
|
||||
footer_description = TR.reset__finished_verifying_group_template.format(
|
||||
group_index + 1
|
||||
)
|
||||
else:
|
||||
if group_index is None:
|
||||
title = TR.reset__share_completed_template.format(share_index + 1)
|
||||
footer_description = (
|
||||
TR.instructions__shares_continue_with_x_template.format(share_index + 2)
|
||||
)
|
||||
else:
|
||||
title = TR.reset__continue_with_next_share
|
||||
footer_description = (
|
||||
TR.reset__group_share_checked_successfully_template.format(
|
||||
group_index + 1, share_index + 1
|
||||
)
|
||||
)
|
||||
|
||||
await show_success("success_recovery", title, subheader=footer_description)
|
||||
|
||||
|
||||
def show_share_confirmation_failure() -> Awaitable[None]:
|
||||
return show_reset_warning(
|
||||
"warning_backup_check",
|
||||
TR.words__try_again,
|
||||
TR.reset__incorrect_word_selected,
|
||||
"",
|
||||
ButtonRequestType.ResetDevice,
|
||||
)
|
@ -6,3 +6,5 @@ elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.recovery import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "MERCURY":
|
||||
from .mercury.recovery import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "LINCOLN":
|
||||
from .lincoln.recovery import * # noqa: F401,F403
|
||||
|
@ -6,3 +6,5 @@ elif utils.UI_LAYOUT == "TR":
|
||||
from .tr.reset import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "MERCURY":
|
||||
from .mercury.reset import * # noqa: F401,F403
|
||||
elif utils.UI_LAYOUT == "LINCOLN":
|
||||
from .lincoln.reset import * # noqa: F401,F403
|
||||
|
@ -49,6 +49,7 @@ MODEL_CHOICE = ChoiceType(
|
||||
"T2T1": models.T2T1,
|
||||
"T2B1": models.T2B1,
|
||||
"T3T1": models.T3T1,
|
||||
"T3W1": models.T3W1,
|
||||
# aliases
|
||||
"1": models.T1B1,
|
||||
"one": models.T1B1,
|
||||
|
@ -79,6 +79,7 @@ class LayoutType(Enum):
|
||||
TT = auto()
|
||||
TR = auto()
|
||||
Mercury = auto()
|
||||
Lincoln = auto()
|
||||
|
||||
@classmethod
|
||||
def from_model(cls, model: models.TrezorModel) -> "LayoutType":
|
||||
@ -90,6 +91,8 @@ class LayoutType(Enum):
|
||||
return cls.Mercury
|
||||
if model in (models.T1B1,):
|
||||
return cls.T1
|
||||
if model in (models.T3W1,):
|
||||
return cls.Lincoln
|
||||
raise ValueError(f"Unknown model: {model}")
|
||||
|
||||
|
||||
|
@ -41,13 +41,14 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
|
||||
models.T3B1: 500,
|
||||
models.T2T1: 1000,
|
||||
models.T3T1: 1000,
|
||||
models.T3W1: 1000,
|
||||
}[debug.model]
|
||||
lock_duration = {
|
||||
models.T1B1: 1200,
|
||||
models.T2B1: 1200,
|
||||
models.T3B1: 1200,
|
||||
models.T2T1: 3500,
|
||||
models.T3T1: 3500,
|
||||
models.T3W1: 3500,
|
||||
}[debug.model]
|
||||
|
||||
def hold(duration: int) -> None:
|
||||
|
@ -185,6 +185,7 @@ class ModelsFilter:
|
||||
"safe3": {models.T2B1, models.T3B1},
|
||||
"safe5": {models.T3T1},
|
||||
"mercury": {models.T3T1},
|
||||
"lincoln": {models.T3W1},
|
||||
}
|
||||
|
||||
def __init__(self, node: Node) -> None:
|
||||
|
@ -12,6 +12,7 @@ FIRMWARE_LENGTHS = {
|
||||
models.T2B1: 13 * 128 * 1024,
|
||||
models.T3T1: 208 * 8 * 1024,
|
||||
models.T3B1: 208 * 8 * 1024,
|
||||
models.T3W1: 208 * 8 * 1024, # FIXME: fill in the correct value
|
||||
}
|
||||
|
||||
|
||||
|
@ -43,6 +43,7 @@ MAX_DATA_LENGTH = {
|
||||
models.T2B1: 32 * 1024,
|
||||
models.T3T1: 256 * 1024,
|
||||
models.T3B1: 256 * 1024,
|
||||
models.T3W1: 256 * 1024, # FIXME: fill in correct value
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user