mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-06 21:40:56 +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
|
BOOTLOADER_DEVEL ?= 0
|
||||||
DISABLE_OPTIGA ?= 0
|
DISABLE_OPTIGA ?= 0
|
||||||
TREZOR_MODEL ?= T
|
TREZOR_MODEL ?= T
|
||||||
|
UI_LINCOLN_DEV ?= 0
|
||||||
TREZOR_MEMPERF ?= 0
|
TREZOR_MEMPERF ?= 0
|
||||||
ADDRESS_SANITIZER ?= 0
|
ADDRESS_SANITIZER ?= 0
|
||||||
CMAKELISTS ?= 0
|
CMAKELISTS ?= 0
|
||||||
@ -69,7 +70,12 @@ MODEL_FEATURE = model_tr
|
|||||||
else ifeq ($(TREZOR_MODEL),$(filter $(TREZOR_MODEL),T3W1))
|
else ifeq ($(TREZOR_MODEL),$(filter $(TREZOR_MODEL),T3W1))
|
||||||
MCU = STM32U5
|
MCU = STM32U5
|
||||||
OPENOCD_TARGET = target/stm32u5x.cfg
|
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
|
MODEL_FEATURE = model_tt
|
||||||
|
endif
|
||||||
else ifeq ($(TREZOR_MODEL),$(filter $(TREZOR_MODEL),DISC1))
|
else ifeq ($(TREZOR_MODEL),$(filter $(TREZOR_MODEL),DISC1))
|
||||||
MCU = STM32F4
|
MCU = STM32F4
|
||||||
OPENOCD_TARGET = target/stm32f4x.cfg
|
OPENOCD_TARGET = target/stm32f4x.cfg
|
||||||
@ -151,7 +157,8 @@ SCONS_VARS = \
|
|||||||
TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
|
TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
|
||||||
TREZOR_EMULATOR_DEBUGGABLE=$(TREZOR_EMULATOR_DEBUGGABLE) \
|
TREZOR_EMULATOR_DEBUGGABLE=$(TREZOR_EMULATOR_DEBUGGABLE) \
|
||||||
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" \
|
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" \
|
||||||
TREZOR_MODEL="$(TREZOR_MODEL)"
|
TREZOR_MODEL="$(TREZOR_MODEL)" \
|
||||||
|
UI_LINCOLN_DEV="$(UI_LINCOLN_DEV)"
|
||||||
|
|
||||||
SCONS_OPTS = -Q -j $(JOBS)
|
SCONS_OPTS = -Q -j $(JOBS)
|
||||||
ifeq ($(QUIET_MODE),1)
|
ifeq ($(QUIET_MODE),1)
|
||||||
|
@ -11,6 +11,7 @@ crypto = ["zeroize"]
|
|||||||
model_tt = ["jpeg"]
|
model_tt = ["jpeg"]
|
||||||
model_tr = []
|
model_tr = []
|
||||||
model_mercury = ["jpeg", "dma2d"]
|
model_mercury = ["jpeg", "dma2d"]
|
||||||
|
model_lincoln = ["jpeg", "dma2d"]
|
||||||
micropython = []
|
micropython = []
|
||||||
protobuf = ["micropython"]
|
protobuf = ["micropython"]
|
||||||
ui = []
|
ui = []
|
||||||
|
@ -86,12 +86,24 @@ const DEFAULT_BINDGEN_MACROS_T3T1: &[&str] = &[
|
|||||||
#[cfg(not(feature = "model_mercury"))]
|
#[cfg(not(feature = "model_mercury"))]
|
||||||
const DEFAULT_BINDGEN_MACROS_T3T1: &[&str] = &[];
|
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>) {
|
fn add_bindgen_macros<'a>(clang_args: &mut Vec<&'a str>, envvar: Option<&'a str>) {
|
||||||
let default_macros = DEFAULT_BINDGEN_MACROS_COMMON
|
let default_macros = DEFAULT_BINDGEN_MACROS_COMMON
|
||||||
.iter()
|
.iter()
|
||||||
.chain(DEFAULT_BINDGEN_MACROS_T2T1)
|
.chain(DEFAULT_BINDGEN_MACROS_T2T1)
|
||||||
.chain(DEFAULT_BINDGEN_MACROS_T2B1)
|
.chain(DEFAULT_BINDGEN_MACROS_T2B1)
|
||||||
.chain(DEFAULT_BINDGEN_MACROS_T3T1);
|
.chain(DEFAULT_BINDGEN_MACROS_T3T1)
|
||||||
|
.chain(DEFAULT_BINDGEN_MACROS_T3W1);
|
||||||
|
|
||||||
match envvar {
|
match envvar {
|
||||||
Some(envvar) => clang_args.extend(envvar.split(',')),
|
Some(envvar) => clang_args.extend(envvar.split(',')),
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
//! Reexporting the `constant` module according to the
|
//! Reexporting the `constant` module according to the
|
||||||
//! current feature (Trezor model)
|
//! 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(
|
#[cfg(all(
|
||||||
feature = "model_mercury",
|
feature = "model_mercury",
|
||||||
not(feature = "model_tr"),
|
not(any(feature = "model_tr", feature = "model_tt"),)
|
||||||
not(feature = "model_tt")
|
|
||||||
))]
|
))]
|
||||||
pub use super::model_mercury::constant::*;
|
pub use super::model_mercury::constant::*;
|
||||||
#[cfg(all(feature = "model_tr", not(feature = "model_tt")))]
|
#[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
|
/// Relative offset in 2D space, used for representing translation and
|
||||||
/// dimensions of objects. Absolute positions on the screen are represented by
|
/// dimensions of objects. Absolute positions on the screen are represented by
|
||||||
/// the `Point` type.
|
/// 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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Offset {
|
pub struct Offset {
|
||||||
pub x: i16,
|
pub x: i16,
|
||||||
|
@ -17,6 +17,8 @@ pub mod layout;
|
|||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
|
|
||||||
|
#[cfg(feature = "model_lincoln")]
|
||||||
|
pub mod model_lincoln;
|
||||||
#[cfg(feature = "model_mercury")]
|
#[cfg(feature = "model_mercury")]
|
||||||
pub mod model_mercury;
|
pub mod model_mercury;
|
||||||
#[cfg(feature = "model_tr")]
|
#[cfg(feature = "model_tr")]
|
||||||
@ -32,10 +34,15 @@ pub mod ui_firmware;
|
|||||||
|
|
||||||
pub use ui_common::CommonUI;
|
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(
|
#[cfg(all(
|
||||||
feature = "model_mercury",
|
feature = "model_mercury",
|
||||||
not(feature = "model_tr"),
|
not(any(feature = "model_tr", feature = "model_tt"))
|
||||||
not(feature = "model_tt")
|
|
||||||
))]
|
))]
|
||||||
pub type ModelUI = crate::ui::model_mercury::UIMercury;
|
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;
|
pub const BUTTON_HEIGHT: i16 = 50;
|
||||||
|
|
||||||
// BLD icons
|
// BLD icons
|
||||||
pub const X24: &[u8] = include_res!("model_mercury/res/x24.toif");
|
pub const X24: &[u8] = include_res!("model_lincoln/res/bootloader/x24.toif");
|
||||||
pub const X32: &[u8] = include_res!("model_mercury/res/x32.toif");
|
pub const X32: &[u8] = include_res!("model_lincoln/res/bootloader/x32.toif");
|
||||||
pub const FIRE24: &[u8] = include_res!("model_mercury/res/fire24.toif");
|
pub const FIRE24: &[u8] = include_res!("model_lincoln/res/bootloader/fire24.toif");
|
||||||
pub const FIRE32: &[u8] = include_res!("model_mercury/res/fire32.toif");
|
pub const FIRE32: &[u8] = include_res!("model_lincoln/res/bootloader/fire32.toif");
|
||||||
pub const FIRE40: &[u8] = include_res!("model_mercury/res/fire40.toif");
|
pub const FIRE40: &[u8] = include_res!("model_lincoln/res/bootloader/fire40.toif");
|
||||||
pub const REFRESH24: &[u8] = include_res!("model_mercury/res/refresh24.toif");
|
pub const REFRESH24: &[u8] = include_res!("model_lincoln/res/bootloader/refresh24.toif");
|
||||||
pub const MENU32: &[u8] = include_res!("model_mercury/res/menu32.toif");
|
pub const MENU32: &[u8] = include_res!("model_lincoln/res/bootloader/menu32.toif");
|
||||||
pub const INFO32: &[u8] = include_res!("model_mercury/res/info32.toif");
|
pub const INFO32: &[u8] = include_res!("model_lincoln/res/bootloader/info32.toif");
|
||||||
pub const DOWNLOAD24: &[u8] = include_res!("model_mercury/res/download24.toif");
|
pub const DOWNLOAD24: &[u8] = include_res!("model_lincoln/res/bootloader/download24.toif");
|
||||||
pub const WARNING40: &[u8] = include_res!("model_mercury/res/warning40.toif");
|
pub const WARNING40: &[u8] = include_res!("model_lincoln/res/bootloader/warning40.toif");
|
||||||
pub const CHECK24: &[u8] = include_res!("model_mercury/res/check24.toif");
|
pub const CHECK24: &[u8] = include_res!("model_lincoln/res/bootloader/check24.toif");
|
||||||
pub const CHECK40: &[u8] = include_res!("model_mercury/res/check40.toif");
|
pub const CHECK40: &[u8] = include_res!("model_lincoln/res/bootloader/check40.toif");
|
||||||
|
|
||||||
pub fn button_confirm() -> ButtonStyleSheet {
|
pub fn button_confirm() -> ButtonStyleSheet {
|
||||||
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)},
|
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_TR)},
|
||||||
#elif UI_LAYOUT_MERCURY
|
#elif UI_LAYOUT_MERCURY
|
||||||
{MP_ROM_QSTR(MP_QSTR_UI_LAYOUT), MP_ROM_QSTR(MP_QSTR_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
|
#else
|
||||||
#error Unknown layout
|
#error Unknown layout
|
||||||
#endif
|
#endif
|
||||||
|
@ -31,6 +31,9 @@ def configure_board(
|
|||||||
|
|
||||||
|
|
||||||
def get_model_ui() -> str:
|
def get_model_ui() -> str:
|
||||||
|
from SCons.Script import ARGUMENTS
|
||||||
|
if ARGUMENTS.get('UI_LINCOLN_DEV', '0') == '1':
|
||||||
|
return "lincoln"
|
||||||
return "tt"
|
return "tt"
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,11 +44,12 @@ def generate(env):
|
|||||||
btc_only = env["bitcoin_only"] == "1"
|
btc_only = env["bitcoin_only"] == "1"
|
||||||
backlight = env["backlight"]
|
backlight = env["backlight"]
|
||||||
optiga = env["optiga"]
|
optiga = env["optiga"]
|
||||||
layout_tt = env["ui_layout"] == "UI_LAYOUT_TT"
|
|
||||||
layout_tr = env["ui_layout"] == "UI_LAYOUT_TR"
|
|
||||||
touch = env["use_touch"]
|
touch = env["use_touch"]
|
||||||
button = env["use_button"]
|
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_mercury = env["ui_layout"] == "UI_LAYOUT_MERCURY"
|
||||||
|
layout_lincoln = env["ui_layout"] == "UI_LAYOUT_LINCOLN"
|
||||||
thp = env["thp"]
|
thp = env["thp"]
|
||||||
interim = f"{target[:-4]}.i" # replace .mpy with .i
|
interim = f"{target[:-4]}.i" # replace .mpy with .i
|
||||||
sed_scripts = [
|
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 == \"TT\"/{layout_tt}/g'",
|
||||||
rf"-e 's/utils\.UI_LAYOUT == \"TR\"/{layout_tr}/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 == \"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_BUTTON/{button}/g'",
|
||||||
rf"-e 's/utils\.USE_TOUCH/{touch}/g'",
|
rf"-e 's/utils\.USE_TOUCH/{touch}/g'",
|
||||||
rf"-e 's/utils\.USE_THP/{thp}/g'",
|
rf"-e 's/utils\.USE_THP/{thp}/g'",
|
||||||
@ -71,6 +73,7 @@ def generate(env):
|
|||||||
"T": "T2T1",
|
"T": "T2T1",
|
||||||
"R": "T2B1",
|
"R": "T2B1",
|
||||||
"T3T1": "T3T1",
|
"T3T1": "T3T1",
|
||||||
|
"T3W1": "T3W1",
|
||||||
}
|
}
|
||||||
|
|
||||||
for model_sym, internal_model in MODEL_SYMS.items():
|
for model_sym, internal_model in MODEL_SYMS.items():
|
||||||
|
@ -2,11 +2,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from site_scons import models
|
from site_scons import models
|
||||||
|
|
||||||
from . import mercury, tr, tt
|
from . import lincoln, mercury, tr, tt
|
||||||
|
|
||||||
|
|
||||||
def get_ui_module(model: str):
|
def get_ui_module(model: str):
|
||||||
ui_modules = {
|
ui_modules = {
|
||||||
|
"lincoln": lincoln,
|
||||||
"mercury": mercury,
|
"mercury": mercury,
|
||||||
"tr": tr,
|
"tr": tr,
|
||||||
"tt": tt,
|
"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
|
import trezor.ui.layouts.fido
|
||||||
trezor.ui.layouts.homescreen
|
trezor.ui.layouts.homescreen
|
||||||
import 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
|
trezor.ui.layouts.mercury
|
||||||
import trezor.ui.layouts.mercury
|
import trezor.ui.layouts.mercury
|
||||||
trezor.ui.layouts.mercury.fido
|
trezor.ui.layouts.mercury.fido
|
||||||
|
@ -10,5 +10,7 @@ elif utils.UI_LAYOUT == "TT":
|
|||||||
from .tt import * # noqa: F401,F403
|
from .tt import * # noqa: F401,F403
|
||||||
elif utils.UI_LAYOUT == "MERCURY":
|
elif utils.UI_LAYOUT == "MERCURY":
|
||||||
from .mercury import * # noqa: F401,F403
|
from .mercury import * # noqa: F401,F403
|
||||||
|
elif utils.UI_LAYOUT == "LINCOLN":
|
||||||
|
from .lincoln import * # noqa: F401,F403
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown layout")
|
raise ValueError("Unknown layout")
|
||||||
|
@ -6,3 +6,5 @@ elif utils.UI_LAYOUT == "TR":
|
|||||||
from .tr.fido import * # noqa: F401,F403
|
from .tr.fido import * # noqa: F401,F403
|
||||||
elif utils.UI_LAYOUT == "MERCURY":
|
elif utils.UI_LAYOUT == "MERCURY":
|
||||||
from .mercury.fido import * # noqa: F401,F403
|
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
|
from .tr.recovery import * # noqa: F401,F403
|
||||||
elif utils.UI_LAYOUT == "MERCURY":
|
elif utils.UI_LAYOUT == "MERCURY":
|
||||||
from .mercury.recovery import * # noqa: F401,F403
|
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
|
from .tr.reset import * # noqa: F401,F403
|
||||||
elif utils.UI_LAYOUT == "MERCURY":
|
elif utils.UI_LAYOUT == "MERCURY":
|
||||||
from .mercury.reset import * # noqa: F401,F403
|
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,
|
"T2T1": models.T2T1,
|
||||||
"T2B1": models.T2B1,
|
"T2B1": models.T2B1,
|
||||||
"T3T1": models.T3T1,
|
"T3T1": models.T3T1,
|
||||||
|
"T3W1": models.T3W1,
|
||||||
# aliases
|
# aliases
|
||||||
"1": models.T1B1,
|
"1": models.T1B1,
|
||||||
"one": models.T1B1,
|
"one": models.T1B1,
|
||||||
|
@ -79,6 +79,7 @@ class LayoutType(Enum):
|
|||||||
TT = auto()
|
TT = auto()
|
||||||
TR = auto()
|
TR = auto()
|
||||||
Mercury = auto()
|
Mercury = auto()
|
||||||
|
Lincoln = auto()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_model(cls, model: models.TrezorModel) -> "LayoutType":
|
def from_model(cls, model: models.TrezorModel) -> "LayoutType":
|
||||||
@ -90,6 +91,8 @@ class LayoutType(Enum):
|
|||||||
return cls.Mercury
|
return cls.Mercury
|
||||||
if model in (models.T1B1,):
|
if model in (models.T1B1,):
|
||||||
return cls.T1
|
return cls.T1
|
||||||
|
if model in (models.T3W1,):
|
||||||
|
return cls.Lincoln
|
||||||
raise ValueError(f"Unknown model: {model}")
|
raise ValueError(f"Unknown model: {model}")
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,13 +41,14 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
|
|||||||
models.T3B1: 500,
|
models.T3B1: 500,
|
||||||
models.T2T1: 1000,
|
models.T2T1: 1000,
|
||||||
models.T3T1: 1000,
|
models.T3T1: 1000,
|
||||||
|
models.T3W1: 1000,
|
||||||
}[debug.model]
|
}[debug.model]
|
||||||
lock_duration = {
|
lock_duration = {
|
||||||
models.T1B1: 1200,
|
models.T1B1: 1200,
|
||||||
models.T2B1: 1200,
|
models.T2B1: 1200,
|
||||||
models.T3B1: 1200,
|
models.T3B1: 1200,
|
||||||
models.T2T1: 3500,
|
models.T2T1: 3500,
|
||||||
models.T3T1: 3500,
|
models.T3W1: 3500,
|
||||||
}[debug.model]
|
}[debug.model]
|
||||||
|
|
||||||
def hold(duration: int) -> None:
|
def hold(duration: int) -> None:
|
||||||
|
@ -185,6 +185,7 @@ class ModelsFilter:
|
|||||||
"safe3": {models.T2B1, models.T3B1},
|
"safe3": {models.T2B1, models.T3B1},
|
||||||
"safe5": {models.T3T1},
|
"safe5": {models.T3T1},
|
||||||
"mercury": {models.T3T1},
|
"mercury": {models.T3T1},
|
||||||
|
"lincoln": {models.T3W1},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, node: Node) -> None:
|
def __init__(self, node: Node) -> None:
|
||||||
|
@ -12,6 +12,7 @@ FIRMWARE_LENGTHS = {
|
|||||||
models.T2B1: 13 * 128 * 1024,
|
models.T2B1: 13 * 128 * 1024,
|
||||||
models.T3T1: 208 * 8 * 1024,
|
models.T3T1: 208 * 8 * 1024,
|
||||||
models.T3B1: 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.T2B1: 32 * 1024,
|
||||||
models.T3T1: 256 * 1024,
|
models.T3T1: 256 * 1024,
|
||||||
models.T3B1: 256 * 1024,
|
models.T3B1: 256 * 1024,
|
||||||
|
models.T3W1: 256 * 1024, # FIXME: fill in correct value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user