1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-03 16:56:07 +00:00

refactor(core/rust): introduce icon/image type

[no changelog]
This commit is contained in:
tychovrahe 2023-01-02 16:30:16 +01:00 committed by matejcik
parent 461f566777
commit 236396338c
35 changed files with 1568 additions and 1478 deletions

View File

@ -288,7 +288,6 @@ fn generate_trezorhal_bindings() {
.allowlist_function("display_bar") .allowlist_function("display_bar")
.allowlist_function("display_bar_radius") .allowlist_function("display_bar_radius")
.allowlist_function("display_bar_radius_buffer") .allowlist_function("display_bar_radius_buffer")
.allowlist_function("display_icon")
.allowlist_function("display_image") .allowlist_function("display_image")
.allowlist_function("display_toif_info") .allowlist_function("display_toif_info")
.allowlist_function("display_loader") .allowlist_function("display_loader")
@ -321,8 +320,10 @@ fn generate_trezorhal_bindings() {
.allowlist_function("hal_delay") .allowlist_function("hal_delay")
.allowlist_function("hal_ticks_ms") .allowlist_function("hal_ticks_ms")
// dma2d // dma2d
.allowlist_function("dma2d_setup_4bpp")
.allowlist_function("dma2d_setup_4bpp_over_4bpp") .allowlist_function("dma2d_setup_4bpp_over_4bpp")
.allowlist_function("dma2d_setup_4bpp_over_16bpp") .allowlist_function("dma2d_setup_4bpp_over_16bpp")
.allowlist_function("dma2d_start")
.allowlist_function("dma2d_start_blend") .allowlist_function("dma2d_start_blend")
.allowlist_function("dma2d_wait_for_transfer") .allowlist_function("dma2d_wait_for_transfer")
//buffers //buffers

View File

@ -5,7 +5,7 @@ use num_traits::FromPrimitive;
use crate::trezorhal::buffers::BufferText; use crate::trezorhal::buffers::BufferText;
#[derive(PartialEq, Debug, Eq, FromPrimitive)] #[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)]
pub enum ToifFormat { pub enum ToifFormat {
FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE as _, FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE as _,
GrayScaleOH = ffi::toif_format_t_TOIF_GRAYSCALE_OH as _, GrayScaleOH = ffi::toif_format_t_TOIF_GRAYSCALE_OH as _,
@ -103,21 +103,6 @@ pub fn bar_radius_buffer(x: i16, y: i16, w: i16, h: i16, radius: u8, buffer: &mu
} }
} }
pub fn icon(x: i16, y: i16, w: i16, h: i16, data: &[u8], fgcolor: u16, bgcolor: u16) {
unsafe {
ffi::display_icon(
x.into(),
y.into(),
w.into(),
h.into(),
data.as_ptr() as _,
data.len() as _,
fgcolor,
bgcolor,
)
}
}
pub fn image(x: i16, y: i16, w: i16, h: i16, data: &[u8]) { pub fn image(x: i16, y: i16, w: i16, h: i16, data: &[u8]) {
unsafe { unsafe {
ffi::display_image( ffi::display_image(

View File

@ -1,5 +1,9 @@
use super::ffi; use super::ffi;
pub fn dma2d_setup_4bpp(fg_color: u16, bg_color: u16) {
unsafe { ffi::dma2d_setup_4bpp(fg_color, bg_color) }
}
pub fn dma2d_setup_4bpp_over_4bpp(fg_color: u16, bg_color: u16, overlay_color: u16) { pub fn dma2d_setup_4bpp_over_4bpp(fg_color: u16, bg_color: u16, overlay_color: u16) {
unsafe { ffi::dma2d_setup_4bpp_over_4bpp(fg_color, bg_color, overlay_color) } unsafe { ffi::dma2d_setup_4bpp_over_4bpp(fg_color, bg_color, overlay_color) }
} }
@ -8,6 +12,16 @@ pub fn dma2d_setup_4bpp_over_16bpp(overlay_color: u16) {
unsafe { ffi::dma2d_setup_4bpp_over_16bpp(overlay_color) } unsafe { ffi::dma2d_setup_4bpp_over_16bpp(overlay_color) }
} }
pub fn dma2d_start(buffer: &[u8], pixels: i16) {
unsafe {
ffi::dma2d_start(
buffer.as_ptr() as _,
ffi::DISPLAY_DATA_ADDRESS as _,
pixels as _,
);
}
}
pub fn dma2d_start_blend(overlay_buffer: &[u8], bg_buffer: &[u8], pixels: i16) { pub fn dma2d_start_blend(overlay_buffer: &[u8], bg_buffer: &[u8], pixels: i16) {
unsafe { unsafe {
ffi::dma2d_start_blend( ffi::dma2d_start_blend(

View File

@ -1,22 +1,38 @@
use crate::ui::{ use crate::{
component::{Component, Event, EventCtx, Never}, trezorhal::display::{image, ToifFormat},
display, ui::{
display::{toif_info, Color}, component::{Component, Event, EventCtx, Never},
geometry::{Alignment, Offset, Point, Rect}, display,
display::{
toif::{NamedToif, Toif},
Color, Icon,
},
geometry::{Alignment2D, Offset, Point, Rect, CENTER},
},
}; };
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Image { pub struct Image {
image: &'static [u8], pub toif: Toif,
area: Rect, area: Rect,
} }
impl Image { impl Image {
pub fn new(image: &'static [u8]) -> Self { pub fn new(named_toif: NamedToif) -> Self {
let toif = Toif::new(named_toif);
ensure!(toif.format == ToifFormat::FullColorLE, toif.name);
Self { Self {
image, toif,
area: Rect::zero(), area: Rect::zero(),
} }
} }
/// Display the icon with baseline Point, aligned according to the
/// `alignment` argument.
pub fn draw(&self, baseline: Point, alignment: Alignment2D) {
let r = Rect::snap(baseline, self.toif.size, alignment);
image(r.x0, r.y0, r.width(), r.height(), self.toif.data);
}
} }
impl Component for Image { impl Component for Image {
@ -32,13 +48,14 @@ impl Component for Image {
} }
fn paint(&mut self) { fn paint(&mut self) {
display::image(self.area.center(), self.image) self.draw(self.area.center(), CENTER);
} }
fn bounds(&self, sink: &mut dyn FnMut(Rect)) { fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
if let Some((size, _)) = display::toif_info(self.image) { sink(Rect::from_center_and_size(
sink(Rect::from_center_and_size(self.area.center(), size)); self.area.center(),
} self.toif.size,
));
} }
} }
@ -51,8 +68,8 @@ impl crate::trace::Trace for Image {
} }
pub struct BlendedImage { pub struct BlendedImage {
bg: &'static [u8], bg: Icon,
fg: &'static [u8], fg: Icon,
bg_color: Color, bg_color: Color,
fg_color: Color, fg_color: Color,
area_color: Color, area_color: Color,
@ -61,13 +78,7 @@ pub struct BlendedImage {
} }
impl BlendedImage { impl BlendedImage {
pub fn new( pub fn new(bg: Icon, fg: Icon, bg_color: Color, fg_color: Color, area_color: Color) -> Self {
bg: &'static [u8],
fg: &'static [u8],
bg_color: Color,
fg_color: Color,
area_color: Color,
) -> Self {
Self { Self {
bg, bg,
fg, fg,
@ -93,15 +104,12 @@ impl Component for BlendedImage {
type Msg = Never; type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let (bg_size, _) = unwrap!(toif_info(self.bg)); self.bg_top_left = self.bg.toif.size.snap(bounds.center(), CENTER);
self.bg_top_left = bg_size.snap(bounds.center(), Alignment::Center, Alignment::Center); let ft_top_left = self.fg.toif.size.snap(bounds.center(), CENTER);
self.fg_offset = ft_top_left - self.bg_top_left;
if let Some((fg_size, _)) = toif_info(self.fg) { Rect::from_top_left_and_size(self.bg_top_left, self.bg.toif.size)
let ft_top_left = fg_size.snap(bounds.center(), Alignment::Center, Alignment::Center);
self.fg_offset = ft_top_left - self.bg_top_left;
}
Rect::from_top_left_and_size(self.bg_top_left, bg_size)
} }
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> { fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
@ -113,9 +121,10 @@ impl Component for BlendedImage {
} }
fn bounds(&self, sink: &mut dyn FnMut(Rect)) { fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
if let Some((size, _)) = display::toif_info(self.bg) { sink(Rect::from_top_left_and_size(
sink(Rect::from_top_left_and_size(self.bg_top_left, size)); self.bg_top_left,
} self.bg.toif.size,
));
} }
} }

View File

@ -17,7 +17,6 @@ pub mod timeout;
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken}; pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken};
pub use border::Border; pub use border::Border;
pub use empty::Empty; pub use empty::Empty;
pub use image::Image;
pub use label::Label; pub use label::Label;
pub use map::Map; pub use map::Map;
pub use maybe::Maybe; pub use maybe::Maybe;

View File

@ -1,9 +1,9 @@
#[cfg(feature = "jpeg")] #[cfg(feature = "jpeg")]
use crate::ui::geometry::Offset; use crate::ui::geometry::Offset;
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never}, component::{image::Image, Component, Event, EventCtx, Never},
display, display,
geometry::Rect, geometry::{Rect, CENTER},
}; };
pub struct Painter<F> { pub struct Painter<F> {
@ -62,8 +62,8 @@ where
Painter::new(f) Painter::new(f)
} }
pub fn image_painter(image: &'static [u8]) -> Painter<impl FnMut(Rect)> { pub fn image_painter(image: Image) -> Painter<impl FnMut(Rect)> {
let f = move |area: Rect| display::image(area.center(), image); let f = move |area: Rect| image.draw(area.center(), CENTER);
Painter::new(f) Painter::new(f)
} }

View File

@ -2,8 +2,8 @@ use heapless::Vec;
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never, Paginate}, component::{Component, Event, EventCtx, Never, Paginate},
display, display::toif::Icon,
geometry::{Alignment, Insets, LinearPlacement, Offset, Point, Rect}, geometry::{Alignment, Insets, LinearPlacement, Offset, Point, Rect, TOP_LEFT},
}; };
use super::layout::{LayoutFit, TextLayout, TextStyle}; use super::layout::{LayoutFit, TextLayout, TextStyle};
@ -505,8 +505,8 @@ pub struct Checklist<T> {
area: Rect, area: Rect,
paragraphs: Paragraphs<T>, paragraphs: Paragraphs<T>,
current: usize, current: usize,
icon_current: &'static [u8], icon_current: Icon,
icon_done: &'static [u8], icon_done: Icon,
} }
impl<T> Checklist<T> { impl<T> Checklist<T> {
@ -515,8 +515,8 @@ impl<T> Checklist<T> {
const CURRENT_OFFSET: Offset = Offset::new(2, 3); const CURRENT_OFFSET: Offset = Offset::new(2, 3);
pub fn from_paragraphs( pub fn from_paragraphs(
icon_current: &'static [u8], icon_current: Icon,
icon_done: &'static [u8], icon_done: Icon,
current: usize, current: usize,
paragraphs: Paragraphs<T>, paragraphs: Paragraphs<T>,
) -> Self { ) -> Self {
@ -529,11 +529,11 @@ impl<T> Checklist<T> {
} }
} }
fn paint_icon(&self, layout: &TextLayout, icon: &'static [u8], offset: Offset) { fn paint_icon(&self, layout: &TextLayout, icon: Icon, offset: Offset) {
let top_left = Point::new(self.area.x0, layout.bounds.y0); let top_left = Point::new(self.area.x0, layout.bounds.y0);
display::icon_top_left( icon.draw(
top_left + offset, top_left + offset,
icon, TOP_LEFT,
layout.style.text_color, layout.style.text_color,
layout.style.background_color, layout.style.background_color,
); );

View File

@ -1,10 +1,7 @@
use crate::{ use crate::ui::{
trezorhal::uzlib::UzlibContext, constant, display,
ui::{ display::Color,
constant, display, geometry::{Offset, Point, Rect},
display::{Color, ToifFormat},
geometry::{Offset, Point, Rect},
},
}; };
use core::slice::from_raw_parts; use core::slice::from_raw_parts;
@ -16,7 +13,7 @@ use crate::trezorhal::{
use crate::ui::{ use crate::ui::{
constant::{screen, LOADER_OUTER}, constant::{screen, LOADER_OUTER},
display::toif_info_ensure, display::toif::{Icon, NamedToif},
}; };
pub const LOADER_MIN: u16 = 0; pub const LOADER_MIN: u16 = 0;
@ -41,17 +38,15 @@ pub fn loader_uncompress(
bg_color: Color, bg_color: Color,
progress: u16, progress: u16,
indeterminate: bool, indeterminate: bool,
icon: Option<(&[u8], Color)>, icon: Option<(Icon, Color)>,
) { ) {
const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE; const ICON_MAX_SIZE: i16 = constant::LOADER_ICON_MAX_SIZE;
if let Some((data, color)) = icon { if let Some((icon, color)) = icon {
let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH); if icon.toif.width() <= ICON_MAX_SIZE && icon.toif.height() <= ICON_MAX_SIZE {
if toif_size.x <= ICON_MAX_SIZE && toif_size.y <= ICON_MAX_SIZE {
let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize]; let mut icon_data = [0_u8; ((ICON_MAX_SIZE * ICON_MAX_SIZE) / 2) as usize];
let mut ctx = UzlibContext::new(toif_data, None); icon.toif.uncompress(&mut icon_data);
unwrap!(ctx.uncompress(&mut icon_data), "Decompression failed"); let i = Some((icon, color, icon.toif.size));
let i = Some((icon_data.as_ref(), color, toif_size));
loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, i); loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, i);
} else { } else {
loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None); loader_rust(y_offset, fg_color, bg_color, progress, indeterminate, None);
@ -78,7 +73,7 @@ pub extern "C" fn loader_uncompress_r(
let i = if icon_data != 0 { let i = if icon_data != 0 {
let data_slice = unsafe { from_raw_parts(icon_data as _, icon_data_size as _) }; let data_slice = unsafe { from_raw_parts(icon_data as _, icon_data_size as _) };
Some((data_slice, ic_color)) Some((Icon::new(NamedToif(data_slice, "loader icon")), ic_color))
} else { } else {
None None
}; };
@ -193,7 +188,7 @@ pub fn loader_rust(
bg_color: Color, bg_color: Color,
progress: u16, progress: u16,
indeterminate: bool, indeterminate: bool,
icon: Option<(&[u8], Color, Offset)>, icon: Option<(Icon, Color, Offset)>,
) { ) {
let center = screen().center() + Offset::new(0, y_offset); let center = screen().center() + Offset::new(0, y_offset);
let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2)); let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2));
@ -209,14 +204,14 @@ pub fn loader_rust(
let mut icon_area = Rect::zero(); let mut icon_area = Rect::zero();
let mut icon_area_clamped = Rect::zero(); let mut icon_area_clamped = Rect::zero();
let mut icon_width = 0; let mut icon_width = 0;
let mut icon_data = [].as_ref(); let mut icon_data = None;
if let Some((data, color, size)) = icon { if let Some((data, color, size)) = icon {
if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE { if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE {
icon_width = size.x; icon_width = size.x;
icon_area = Rect::from_center_and_size(center, size); icon_area = Rect::from_center_and_size(center, size);
icon_area_clamped = icon_area.clamp(constant::screen()); icon_area_clamped = icon_area.clamp(constant::screen());
icon_data = data; icon_data = Some(data);
use_icon = true; use_icon = true;
icon_colortable = display::get_color_table(color, bg_color); icon_colortable = display::get_color_table(color, bg_color);
} }
@ -242,7 +237,8 @@ pub fn loader_rust(
let x_i = x_c - icon_area.x0; let x_i = x_c - icon_area.x0;
let y_i = y_c - icon_area.y0; let y_i = y_c - icon_area.y0;
let data = icon_data[(((x_i & 0xFE) + (y_i * icon_width)) / 2) as usize]; let data = unwrap!(icon_data).data()
[(((x_i & 0xFE) + (y_i * icon_width)) / 2) as usize];
if (x_i & 0x01) == 0 { if (x_i & 0x01) == 0 {
underlying_color = icon_colortable[(data & 0xF) as usize]; underlying_color = icon_colortable[(data & 0xF) as usize];
} else { } else {
@ -273,7 +269,7 @@ pub fn loader_rust(
bg_color: Color, bg_color: Color,
progress: u16, progress: u16,
indeterminate: bool, indeterminate: bool,
icon: Option<(&[u8], Color, Offset)>, icon: Option<(Icon, Color, Offset)>,
) { ) {
let center = screen().center() + Offset::new(0, y_offset); let center = screen().center() + Offset::new(0, y_offset);
let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2)); let r = Rect::from_center_and_size(center, Offset::uniform(LOADER_OUTER as i16 * 2));
@ -288,16 +284,16 @@ pub fn loader_rust(
let mut icon_width = 0; let mut icon_width = 0;
let mut icon_offset = 0; let mut icon_offset = 0;
let mut icon_color = Color::from_u16(0); let mut icon_color = Color::from_u16(0);
let mut icon_data = [].as_ref(); let mut icon_data = None;
if let Some((data, color, size)) = icon { if let Some((icon, color, size)) = icon {
if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE { if size.x <= ICON_MAX_SIZE && size.y <= ICON_MAX_SIZE {
icon_width = size.x; icon_width = size.x;
icon_area = Rect::from_center_and_size(center, size); icon_area = Rect::from_center_and_size(center, size);
icon_area_clamped = icon_area.clamp(constant::screen()); icon_area_clamped = icon_area.clamp(constant::screen());
icon_offset = (icon_area_clamped.x0 - r.x0) / 2; icon_offset = (icon_area_clamped.x0 - r.x0) / 2;
icon_color = color; icon_color = color;
icon_data = data; icon_data = Some(icon);
use_icon = true; use_icon = true;
} }
} }
@ -341,7 +337,7 @@ pub fn loader_rust(
icon_buffer_used.buffer[icon_offset as usize..(icon_offset + icon_width / 2) as usize] icon_buffer_used.buffer[icon_offset as usize..(icon_offset + icon_width / 2) as usize]
.copy_from_slice( .copy_from_slice(
&icon_data[(y_i * (icon_width / 2)) as usize &unwrap!(icon_data).toif.data[(y_i * (icon_width / 2)) as usize
..((y_i + 1) * (icon_width / 2)) as usize], ..((y_i + 1) * (icon_width / 2)) as usize],
); );
icon_buffer = icon_buffer_used; icon_buffer = icon_buffer_used;
@ -380,7 +376,7 @@ pub fn loader(
y_offset: i16, y_offset: i16,
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
icon: Option<(&[u8], Color)>, icon: Option<(Icon, Color)>,
) { ) {
loader_uncompress(y_offset, fg_color, bg_color, progress, false, icon); loader_uncompress(y_offset, fg_color, bg_color, progress, false, icon);
} }
@ -390,7 +386,7 @@ pub fn loader_indeterminate(
y_offset: i16, y_offset: i16,
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
icon: Option<(&[u8], Color)>, icon: Option<(Icon, Color)>,
) { ) {
loader_uncompress(y_offset, fg_color, bg_color, progress, true, icon); loader_uncompress(y_offset, fg_color, bg_color, progress, true, icon);
} }

View File

@ -2,6 +2,7 @@
pub mod loader; pub mod loader;
#[cfg(feature = "jpeg")] #[cfg(feature = "jpeg")]
pub mod tjpgd; pub mod tjpgd;
pub mod toif;
use super::{ use super::{
constant, constant,
@ -14,20 +15,21 @@ use crate::trezorhal::{
dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend, dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend,
dma2d_wait_for_transfer, dma2d_wait_for_transfer,
}, },
uzlib::UZLIB_WINDOW_SIZE,
}; };
#[cfg(not(feature = "dma2d"))]
use crate::ui::geometry::TOP_LEFT;
use crate::{ use crate::{
error::Error, error::Error,
time::Duration, time::Duration,
trezorhal::{ trezorhal::{display, qr, time, uzlib::UzlibContext},
display,
display::ToifFormat,
qr, time,
uzlib::{UzlibContext, UZLIB_WINDOW_SIZE},
},
ui::lerp::Lerp, ui::lerp::Lerp,
}; };
use core::slice; use core::slice;
use crate::ui::component::image::Image;
pub use crate::ui::display::toif::Icon;
#[cfg(any(feature = "model_tt", feature = "model_tr"))] #[cfg(any(feature = "model_tt", feature = "model_tr"))]
pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN}; pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN};
@ -81,106 +83,6 @@ pub fn rect_fill_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u8)
); );
} }
/// NOTE: Cannot start at odd x-coordinate. In this case icon is shifted 1px
/// left.
pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
display::icon(
top_left.x,
top_left.y,
toif_size.x,
toif_size.y,
toif_data,
fg_color.into(),
bg_color.into(),
);
}
pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
let r = Rect::from_center_and_size(center, toif_size);
display::icon(
r.x0,
r.y0,
r.width(),
r.height(),
toif_data,
fg_color.into(),
bg_color.into(),
);
}
pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::GrayScaleEH);
let r = Rect::from_center_and_size(center, toif_size);
let area = r.translate(get_offset());
let clamped = area.clamp(constant::screen());
let colortable = get_color_table(fg_color, bg_color);
set_window(clamped);
let mut dest = [0_u8; 1];
let mut window = [0; UZLIB_WINDOW_SIZE];
let mut ctx = UzlibContext::new(toif_data, Some(&mut window));
for py in area.y0..area.y1 {
for px in area.x0..area.x1 {
let p = Point::new(px, py);
let x = p.x - area.x0;
if clamped.contains(p) {
if x % 2 == 0 {
unwrap!(ctx.uncompress(&mut dest), "Decompression failed");
pixeldata(colortable[(dest[0] & 0xF) as usize]);
} else {
pixeldata(colortable[(dest[0] >> 4) as usize]);
}
} else if x % 2 == 0 {
//continue unzipping but dont write to display
unwrap!(ctx.uncompress(&mut dest), "Decompression failed");
}
}
}
pixeldata_dirty();
}
pub fn image(center: Point, data: &[u8]) {
let (toif_size, toif_data) = toif_info_ensure(data, ToifFormat::FullColorLE);
let r = Rect::from_center_and_size(center, toif_size);
display::image(r.x0, r.y0, r.width(), r.height(), toif_data);
}
pub fn toif_info(data: &[u8]) -> Option<(Offset, ToifFormat)> {
if let Ok(info) = display::toif_info(data) {
Some((
Offset::new(
unwrap!(info.width.try_into()),
unwrap!(info.height.try_into()),
),
info.format,
))
} else {
None
}
}
/// Aborts if the TOIF file does not have the correct grayscale flag, do not use
/// with user-supplied inputs.
pub(crate) fn toif_info_ensure(data: &[u8], format: ToifFormat) -> (Offset, &[u8]) {
let info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert_eq!(info.format, format);
let size = Offset::new(
unwrap!(info.width.try_into()),
unwrap!(info.height.try_into()),
);
let payload = &data[12..]; // Skip TOIF header.
(size, payload)
}
// Used on T1 only. // Used on T1 only.
pub fn rect_fill_rounded1(r: Rect, fg_color: Color, bg_color: Color) { pub fn rect_fill_rounded1(r: Rect, fg_color: Color, bg_color: Color) {
display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into()); display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into());
@ -306,7 +208,7 @@ pub fn rect_rounded2_partial(
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
show_percent: i16, show_percent: i16,
icon: Option<(&[u8], Color)>, icon: Option<(Icon, Color)>,
) { ) {
const MAX_ICON_SIZE: i16 = 64; const MAX_ICON_SIZE: i16 = 64;
@ -325,17 +227,13 @@ pub fn rect_rounded2_partial(
let mut icon_data = [0_u8; ((MAX_ICON_SIZE * MAX_ICON_SIZE) / 2) as usize]; let mut icon_data = [0_u8; ((MAX_ICON_SIZE * MAX_ICON_SIZE) / 2) as usize];
let mut icon_width = 0; let mut icon_width = 0;
if let Some((icon_bytes, icon_color)) = icon { if let Some((icon, icon_color)) = icon {
let (toif_size, toif_data) = toif_info_ensure(icon_bytes, ToifFormat::GrayScaleEH); if icon.toif.width() <= MAX_ICON_SIZE && icon.toif.height() <= MAX_ICON_SIZE {
icon_area = Rect::from_center_and_size(center, icon.toif.size);
if toif_size.x <= MAX_ICON_SIZE && toif_size.y <= MAX_ICON_SIZE {
icon_area = Rect::from_center_and_size(center, toif_size);
icon_area_clamped = icon_area.clamp(constant::screen()); icon_area_clamped = icon_area.clamp(constant::screen());
icon.toif.uncompress(&mut icon_data);
let mut ctx = UzlibContext::new(toif_data, None);
unwrap!(ctx.uncompress(&mut icon_data), "Decompression failed");
icon_colortable = get_color_table(icon_color, bg_color); icon_colortable = get_color_table(icon_color, bg_color);
icon_width = toif_size.x; icon_width = icon.toif.width();
use_icon = true; use_icon = true;
} }
} }
@ -533,7 +431,7 @@ fn process_buffer(
#[cfg(feature = "dma2d")] #[cfg(feature = "dma2d")]
pub fn text_over_image( pub fn text_over_image(
bg_area: Option<(Rect, Color)>, bg_area: Option<(Rect, Color)>,
image_data: &[u8], image: Image,
text: &str, text: &str,
font: Font, font: Font,
offset_img: Offset, offset_img: Offset,
@ -548,8 +446,6 @@ pub fn text_over_image(
let t2 = unsafe { get_buffer_4bpp(1, true) }; let t2 = unsafe { get_buffer_4bpp(1, true) };
let empty_t = unsafe { get_buffer_4bpp(2, true) }; let empty_t = unsafe { get_buffer_4bpp(2, true) };
let (toif_size, toif_data) = toif_info_ensure(image_data, ToifFormat::FullColorLE);
let r_img; let r_img;
let area; let area;
let offset_img_final; let offset_img_final;
@ -565,10 +461,10 @@ pub fn text_over_image(
empty_img.buffer.copy_from_slice(&img1.buffer); empty_img.buffer.copy_from_slice(&img1.buffer);
area = a; area = a;
r_img = Rect::from_top_left_and_size(a.top_left() + offset_img, toif_size); r_img = Rect::from_top_left_and_size(a.top_left() + offset_img, image.toif.size);
offset_img_final = offset_img; offset_img_final = offset_img;
} else { } else {
area = Rect::from_top_left_and_size(offset_img.into(), toif_size); area = Rect::from_top_left_and_size(offset_img.into(), image.toif.size);
r_img = area; r_img = area;
offset_img_final = Offset::zero(); offset_img_final = Offset::zero();
} }
@ -594,7 +490,7 @@ pub fn text_over_image(
set_window(clamped); set_window(clamped);
let mut window = [0; UZLIB_WINDOW_SIZE]; let mut window = [0; UZLIB_WINDOW_SIZE];
let mut ctx = UzlibContext::new(toif_data, Some(&mut window)); let mut ctx = image.toif.decompression_context(Some(&mut window));
dma2d_setup_4bpp_over_16bpp(text_color.into()); dma2d_setup_4bpp_over_16bpp(text_color.into());
@ -662,8 +558,8 @@ pub fn text_over_image(
#[cfg(feature = "dma2d")] #[cfg(feature = "dma2d")]
pub fn icon_over_icon( pub fn icon_over_icon(
bg_area: Option<Rect>, bg_area: Option<Rect>,
bg: (&[u8], Offset, Color), bg: (Icon, Offset, Color),
fg: (&[u8], Offset, Color), fg: (Icon, Offset, Color),
bg_color: Color, bg_color: Color,
) { ) {
let bg1 = unsafe { get_buffer_16bpp(0, true) }; let bg1 = unsafe { get_buffer_16bpp(0, true) };
@ -673,41 +569,40 @@ pub fn icon_over_icon(
let fg2 = unsafe { get_buffer_4bpp(1, true) }; let fg2 = unsafe { get_buffer_4bpp(1, true) };
let empty2 = unsafe { get_buffer_4bpp(2, true) }; let empty2 = unsafe { get_buffer_4bpp(2, true) };
let (data_bg, offset_bg, color_icon_bg) = bg; let (icon_bg, offset_bg, color_icon_bg) = bg;
let (data_fg, offset_fg, color_icon_fg) = fg; let (icon_fg, offset_fg, color_icon_fg) = fg;
let (toif_bg_size, toif_bg_data) = toif_info_ensure(data_bg, ToifFormat::GrayScaleEH); assert!(icon_bg.toif.width() <= constant::WIDTH);
assert!(toif_bg_size.x <= constant::WIDTH); assert_eq!(icon_bg.toif.width() % 2, 0);
assert_eq!(toif_bg_size.x % 2, 0);
let (toif_fg_size, toif_fg_data) = toif_info_ensure(data_fg, ToifFormat::GrayScaleEH); assert!(icon_fg.toif.width() <= constant::WIDTH);
assert!(toif_bg_size.x <= constant::WIDTH); assert_eq!(icon_fg.toif.width() % 2, 0);
assert_eq!(toif_bg_size.x % 2, 0);
let area; let area;
let r_bg; let r_bg;
let final_offset_bg; let final_offset_bg;
if let Some(a) = bg_area { if let Some(a) = bg_area {
area = a; area = a;
r_bg = Rect::from_top_left_and_size(a.top_left() + offset_bg, toif_bg_size); r_bg = Rect::from_top_left_and_size(a.top_left() + offset_bg, icon_bg.toif.size);
final_offset_bg = offset_bg; final_offset_bg = offset_bg;
} else { } else {
r_bg = Rect::from_top_left_and_size(Point::new(offset_bg.x, offset_bg.y), toif_bg_size); r_bg =
Rect::from_top_left_and_size(Point::new(offset_bg.x, offset_bg.y), icon_bg.toif.size);
area = r_bg; area = r_bg;
final_offset_bg = Offset::zero(); final_offset_bg = Offset::zero();
} }
let r_fg = Rect::from_top_left_and_size(area.top_left() + offset_fg, toif_fg_size); let r_fg = Rect::from_top_left_and_size(area.top_left() + offset_fg, icon_fg.toif.size);
let clamped = area.clamp(constant::screen()).ensure_even_width(); let clamped = area.clamp(constant::screen()).ensure_even_width();
set_window(clamped); set_window(clamped);
let mut window_bg = [0; UZLIB_WINDOW_SIZE]; let mut window_bg = [0; UZLIB_WINDOW_SIZE];
let mut ctx_bg = UzlibContext::new(toif_bg_data, Some(&mut window_bg)); let mut ctx_bg = UzlibContext::new(icon_bg.toif.data, Some(&mut window_bg));
let mut window_fg = [0; UZLIB_WINDOW_SIZE]; let mut window_fg = [0; UZLIB_WINDOW_SIZE];
let mut ctx_fg = UzlibContext::new(toif_fg_data, Some(&mut window_fg)); let mut ctx_fg = UzlibContext::new(icon_fg.toif.data, Some(&mut window_fg));
dma2d_setup_4bpp_over_4bpp(color_icon_bg.into(), bg_color.into(), color_icon_fg.into()); dma2d_setup_4bpp_over_4bpp(color_icon_bg.into(), bg_color.into(), color_icon_fg.into());
@ -766,12 +661,12 @@ pub fn icon_over_icon(
#[cfg(not(feature = "dma2d"))] #[cfg(not(feature = "dma2d"))]
pub fn icon_over_icon( pub fn icon_over_icon(
bg_area: Option<Rect>, bg_area: Option<Rect>,
bg: (&[u8], Offset, Color), bg: (Icon, Offset, Color),
fg: (&[u8], Offset, Color), fg: (Icon, Offset, Color),
bg_color: Color, bg_color: Color,
) { ) {
let (data_bg, offset_bg, color_icon_bg) = bg; let (icon_bg, offset_bg, color_icon_bg) = bg;
let (data_fg, offset_fg, color_icon_fg) = fg; let (icon_fg, offset_fg, color_icon_fg) = fg;
let pos_bg = if let Some(area) = bg_area { let pos_bg = if let Some(area) = bg_area {
rect_fill(area, bg_color); rect_fill(area, bg_color);
@ -780,8 +675,8 @@ pub fn icon_over_icon(
Point::from(offset_bg) Point::from(offset_bg)
}; };
icon_top_left(pos_bg, data_bg, color_icon_bg, bg_color); icon_bg.draw(pos_bg, TOP_LEFT, color_icon_bg, bg_color);
icon_top_left(pos_bg + offset_fg, data_fg, color_icon_fg, color_icon_bg); icon_fg.draw(pos_bg + offset_fg, TOP_LEFT, color_icon_fg, color_icon_bg);
} }
/// Gets a color of a pixel on `p` coordinates of rounded rectangle with corner /// Gets a color of a pixel on `p` coordinates of rounded rectangle with corner

View File

@ -0,0 +1,131 @@
use crate::{
trezorhal,
trezorhal::{
display::ToifFormat,
uzlib::{UzlibContext, UZLIB_WINDOW_SIZE},
},
ui::{
constant,
display::{get_color_table, get_offset, pixeldata, pixeldata_dirty, set_window},
geometry::{Alignment2D, Offset, Point, Rect},
},
};
use super::Color;
const TOIF_HEADER_LENGTH: usize = 12;
/// Storing the icon together with its name
/// Needs to be a tuple-struct, so it can be made `const`
#[derive(Debug, Clone, Copy)]
pub struct NamedToif(pub &'static [u8], pub &'static str);
pub fn toif_info(data: &[u8]) -> Option<(Offset, ToifFormat)> {
if let Ok(info) = trezorhal::display::toif_info(data) {
Some((
Offset::new(
unwrap!(info.width.try_into()),
unwrap!(info.height.try_into()),
),
info.format,
))
} else {
None
}
}
pub fn icon(icon: &Icon, center: Point, fg_color: Color, bg_color: Color) {
let r = Rect::from_center_and_size(center, icon.toif.size);
let area = r.translate(get_offset());
let clamped = area.clamp(constant::screen());
let colortable = get_color_table(fg_color, bg_color);
set_window(clamped);
let mut dest = [0_u8; 1];
let mut window = [0; UZLIB_WINDOW_SIZE];
let mut ctx = icon.toif.decompression_context(Some(&mut window));
for py in area.y0..area.y1 {
for px in area.x0..area.x1 {
let p = Point::new(px, py);
let x = p.x - area.x0;
if clamped.contains(p) {
if x % 2 == 0 {
unwrap!(ctx.uncompress(&mut dest), "Decompression failed");
pixeldata(colortable[(dest[0] & 0xF) as usize]);
} else {
pixeldata(colortable[(dest[0] >> 4) as usize]);
}
} else if x % 2 == 0 {
//continue unzipping but dont write to display
unwrap!(ctx.uncompress(&mut dest), "Decompression failed");
}
}
}
pixeldata_dirty();
}
/// Holding toif data and allowing it to draw itself.
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Toif {
pub data: &'static [u8],
pub name: &'static str, // useful for debugging purposes.
pub size: Offset,
pub format: ToifFormat,
}
impl Toif {
pub fn new(named_toif: NamedToif) -> Self {
let info = unwrap!(toif_info(named_toif.0));
Self {
data: named_toif.0[TOIF_HEADER_LENGTH..].as_ref(),
name: named_toif.1,
size: info.0,
format: info.1,
}
}
pub fn width(&self) -> i16 {
self.size.x
}
pub fn height(&self) -> i16 {
self.size.y
}
pub fn uncompress(&self, dest: &mut [u8]) {
let mut ctx = self.decompression_context(None);
unwrap!(ctx.uncompress(dest), self.name);
}
pub fn decompression_context<'a>(
&'a self,
window: Option<&'a mut [u8; UZLIB_WINDOW_SIZE]>,
) -> UzlibContext {
UzlibContext::new(self.data, window)
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Icon {
pub toif: Toif,
}
impl Icon {
pub fn new(named_toif: NamedToif) -> Self {
let toif = Toif::new(named_toif);
ensure!(toif.format == ToifFormat::GrayScaleEH, toif.name);
Self { toif }
}
/// Display the icon with baseline Point, aligned according to the
/// `alignment` argument.
pub fn draw(&self, baseline: Point, alignment: Alignment2D, fg_color: Color, bg_color: Color) {
let r = Rect::snap(baseline, self.toif.size, alignment);
icon(self, r.center(), fg_color, bg_color);
}
}

View File

@ -77,13 +77,13 @@ impl Offset {
/// With `self` representing a rectangle size, returns top-left corner of /// With `self` representing a rectangle size, returns top-left corner of
/// the rectangle such that it is aligned relative to the `point`. /// the rectangle such that it is aligned relative to the `point`.
pub const fn snap(self, point: Point, x: Alignment, y: Alignment) -> Point { pub const fn snap(self, point: Point, alignment: Alignment2D) -> Point {
let x_off = match x { let x_off = match alignment.0 {
Alignment::Start => 0, Alignment::Start => 0,
Alignment::Center => self.x / 2, Alignment::Center => self.x / 2,
Alignment::End => self.x, Alignment::End => self.x,
}; };
let y_off = match y { let y_off = match alignment.1 {
Alignment::Start => 0, Alignment::Start => 0,
Alignment::Center => self.y / 2, Alignment::Center => self.y / 2,
Alignment::End => self.y, Alignment::End => self.y,
@ -224,6 +224,12 @@ impl Rect {
Self::new(Point::zero(), Point::zero()) Self::new(Point::zero(), Point::zero())
} }
/// Returns a rectangle of `size` such that `point` is on position specified
/// by `alignment`.
pub const fn snap(point: Point, size: Offset, alignment: Alignment2D) -> Rect {
Self::from_top_left_and_size(size.snap(point, alignment), size)
}
pub const fn from_top_left_and_size(p0: Point, size: Offset) -> Self { pub const fn from_top_left_and_size(p0: Point, size: Offset) -> Self {
Self { Self {
x0: p0.x, x0: p0.x,
@ -451,6 +457,14 @@ pub enum Alignment {
End, End,
} }
pub type Alignment2D = (Alignment, Alignment);
pub const TOP_LEFT: Alignment2D = (Alignment::Start, Alignment::Start);
pub const TOP_RIGHT: Alignment2D = (Alignment::Start, Alignment::End);
pub const CENTER: Alignment2D = (Alignment::Center, Alignment::Center);
pub const BOTTOM_LEFT: Alignment2D = (Alignment::Start, Alignment::End);
pub const BOTTOM_RIGHT: Alignment2D = (Alignment::End, Alignment::End);
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum Axis { pub enum Axis {
Horizontal, Horizontal,

View File

@ -4,6 +4,7 @@ use crate::{
animation::Animation, animation::Animation,
component::{Component, Event, EventCtx}, component::{Component, Event, EventCtx},
display, display,
display::toif::Icon,
geometry::Rect, geometry::Rect,
model_tr::theme, model_tr::theme,
}, },
@ -23,11 +24,11 @@ pub struct ResultAnim {
area: Rect, area: Rect,
state: State, state: State,
growing_duration: Duration, growing_duration: Duration,
icon: &'static [u8], icon: Icon,
} }
impl ResultAnim { impl ResultAnim {
pub fn new(icon: &'static [u8]) -> Self { pub fn new(icon: Icon) -> Self {
Self { Self {
area: Rect::zero(), area: Rect::zero(),
state: State::Initial, state: State::Initial,

View File

@ -6,6 +6,7 @@ use crate::{
Child, Component, ComponentExt, Event, EventCtx, Label, Pad, Child, Component, ComponentExt, Event, EventCtx, Label, Pad,
}, },
constant::screen, constant::screen,
display::toif::Icon,
geometry::{Alignment, Insets, LinearPlacement, Point, Rect}, geometry::{Alignment, Insets, LinearPlacement, Point, Rect},
model_tr::{ model_tr::{
component::{Button, ButtonMsg, ButtonPos, ResultAnim, ResultAnimMsg}, component::{Button, ButtonMsg, ButtonPos, ResultAnim, ResultAnimMsg},
@ -38,7 +39,7 @@ const ANIM_POS_ADJ_BUTTON: i16 = 6;
impl<S: ParagraphStrType> ResultPopup<S> { impl<S: ParagraphStrType> ResultPopup<S> {
pub fn new( pub fn new(
icon: &'static [u8], icon: Icon,
text: S, text: S,
headline: Option<&'static str>, headline: Option<&'static str>,
button_text: Option<&'static str>, button_text: Option<&'static str>,

View File

@ -4,9 +4,9 @@ use crate::{
component::{ component::{
Component, ComponentExt, Event, EventCtx, FixedHeightBar, GridPlaced, Map, TimerToken, Component, ComponentExt, Event, EventCtx, FixedHeightBar, GridPlaced, Map, TimerToken,
}, },
display::{self, Color, Font}, display::{self, toif::Icon, Color, Font},
event::TouchEvent, event::TouchEvent,
geometry::{Insets, Offset, Rect}, geometry::{Insets, Offset, Rect, CENTER},
}, },
}; };
@ -48,11 +48,11 @@ impl<T> Button<T> {
Self::new(ButtonContent::Text(text)) Self::new(ButtonContent::Text(text))
} }
pub fn with_icon(image: &'static [u8]) -> Self { pub fn with_icon(icon: Icon) -> Self {
Self::new(ButtonContent::Icon(image)) Self::new(ButtonContent::Icon(icon))
} }
pub fn with_icon_blend(bg: &'static [u8], fg: &'static [u8], fg_offset: Offset) -> Self { pub fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
Self::new(ButtonContent::IconBlend(bg, fg, fg_offset)) Self::new(ButtonContent::IconBlend(bg, fg, fg_offset))
} }
@ -198,17 +198,17 @@ impl<T> Button<T> {
); );
} }
ButtonContent::Icon(icon) => { ButtonContent::Icon(icon) => {
display::icon( icon.draw(
self.area.center(), self.area.center(),
icon, CENTER,
style.text_color, style.text_color,
style.button_color, style.button_color,
); );
} }
ButtonContent::IconBlend(bg, fg, offset) => display::icon_over_icon( ButtonContent::IconBlend(bg, fg, offset) => display::icon_over_icon(
Some(self.area), Some(self.area),
(bg, Offset::zero(), style.button_color), (*bg, Offset::zero(), style.button_color),
(fg, *offset, style.text_color), (*fg, *offset, style.text_color),
style.background_color, style.background_color,
), ),
} }
@ -328,8 +328,8 @@ enum State {
pub enum ButtonContent<T> { pub enum ButtonContent<T> {
Empty, Empty,
Text(T), Text(T),
Icon(&'static [u8]), Icon(Icon),
IconBlend(&'static [u8], &'static [u8], Offset), IconBlend(Icon, Icon, Offset),
} }
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
@ -396,7 +396,7 @@ impl<T> Button<T> {
let (left, right_size_factor) = if let Some(verb) = left { let (left, right_size_factor) = if let Some(verb) = left {
(Button::with_text(verb), 1) (Button::with_text(verb), 1)
} else { } else {
(Button::with_icon(theme::ICON_CANCEL), 2) (Button::with_icon(Icon::new(theme::ICON_CANCEL)), 2)
}; };
let right = Button::with_text(right).styled(theme::button_confirm()); let right = Button::with_text(right).styled(theme::button_confirm());
@ -417,7 +417,7 @@ impl<T> Button<T> {
{ {
let right = Button::with_text(confirm).styled(theme::button_confirm()); let right = Button::with_text(confirm).styled(theme::button_confirm());
let top = Button::with_text(info); let top = Button::with_text(info);
let left = Button::with_icon(theme::ICON_CANCEL); let left = Button::with_icon(Icon::new(theme::ICON_CANCEL));
theme::button_bar_rows( theme::button_bar_rows(
2, 2,
( (

View File

@ -6,6 +6,7 @@ use crate::ui::{
}, },
Child, Component, Event, EventCtx, Never, Child, Component, Event, EventCtx, Never,
}, },
display::toif::Icon,
geometry::{Insets, LinearPlacement, Rect}, geometry::{Insets, LinearPlacement, Rect},
}; };
@ -128,8 +129,8 @@ where
let [l0, l1, l2, l3] = lines; let [l0, l1, l2, l3] = lines;
Self { Self {
image: Child::new(BlendedImage::new( image: Child::new(BlendedImage::new(
theme::IMAGE_BG_CIRCLE, Icon::new(theme::IMAGE_BG_CIRCLE),
theme::IMAGE_FG_SUCCESS, Icon::new(theme::IMAGE_FG_SUCCESS),
theme::SUCCESS_COLOR, theme::SUCCESS_COLOR,
theme::FG, theme::FG,
theme::BG, theme::BG,

View File

@ -1,5 +1,5 @@
use crate::ui::{ use crate::ui::{
component::{Child, Component, Event, EventCtx, Image, Label}, component::{image::Image, Child, Component, Event, EventCtx, Label},
display, display,
geometry::{Alignment, Insets, Rect}, geometry::{Alignment, Insets, Rect},
model_tt::component::{ model_tt::component::{

View File

@ -2,42 +2,45 @@
//! (by running `make templates` in `core`) //! (by running `make templates` in `core`)
//! do not edit manually! //! do not edit manually!
const ICON_AWS: &[u8] = include_res!("model_tt/res/fido/icon_aws.toif"); use crate::ui::display::toif::NamedToif;
const ICON_BINANCE: &[u8] = include_res!("model_tt/res/fido/icon_binance.toif");
const ICON_BITBUCKET: &[u8] = include_res!("model_tt/res/fido/icon_bitbucket.toif");
const ICON_BITFINEX: &[u8] = include_res!("model_tt/res/fido/icon_bitfinex.toif"); const ICON_AWS: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_aws.toif"), "AWS");
const ICON_BITWARDEN: &[u8] = include_res!("model_tt/res/fido/icon_bitwarden.toif"); const ICON_BINANCE: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_binance.toif"), "BINANCE");
const ICON_CLOUDFLARE: &[u8] = include_res!("model_tt/res/fido/icon_cloudflare.toif"); const ICON_BITBUCKET: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_bitbucket.toif"), "BITBUCKET");
const ICON_COINBASE: &[u8] = include_res!("model_tt/res/fido/icon_coinbase.toif"); const ICON_BITFINEX: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_bitfinex.toif"), "BITFINEX");
const ICON_DASHLANE: &[u8] = include_res!("model_tt/res/fido/icon_dashlane.toif"); const ICON_BITWARDEN: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_bitwarden.toif"), "BITWARDEN");
const ICON_DROPBOX: &[u8] = include_res!("model_tt/res/fido/icon_dropbox.toif"); const ICON_CLOUDFLARE: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_cloudflare.toif"), "CLOUDFLARE");
const ICON_DUO: &[u8] = include_res!("model_tt/res/fido/icon_duo.toif"); const ICON_COINBASE: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_coinbase.toif"), "COINBASE");
const ICON_FACEBOOK: &[u8] = include_res!("model_tt/res/fido/icon_facebook.toif"); const ICON_DASHLANE: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_dashlane.toif"), "DASHLANE");
const ICON_FASTMAIL: &[u8] = include_res!("model_tt/res/fido/icon_fastmail.toif"); const ICON_DROPBOX: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_dropbox.toif"), "DROPBOX");
const ICON_FEDORA: &[u8] = include_res!("model_tt/res/fido/icon_fedora.toif"); const ICON_DUO: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_duo.toif"), "DUO");
const ICON_GANDI: &[u8] = include_res!("model_tt/res/fido/icon_gandi.toif"); const ICON_FACEBOOK: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_facebook.toif"), "FACEBOOK");
const ICON_GEMINI: &[u8] = include_res!("model_tt/res/fido/icon_gemini.toif"); const ICON_FASTMAIL: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_fastmail.toif"), "FASTMAIL");
const ICON_GITHUB: &[u8] = include_res!("model_tt/res/fido/icon_github.toif"); const ICON_FEDORA: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_fedora.toif"), "FEDORA");
const ICON_GITLAB: &[u8] = include_res!("model_tt/res/fido/icon_gitlab.toif"); const ICON_GANDI: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_gandi.toif"), "GANDI");
const ICON_GOOGLE: &[u8] = include_res!("model_tt/res/fido/icon_google.toif"); const ICON_GEMINI: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_gemini.toif"), "GEMINI");
const ICON_INVITY: &[u8] = include_res!("model_tt/res/fido/icon_invity.toif"); const ICON_GITHUB: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_github.toif"), "GITHUB");
const ICON_KEEPER: &[u8] = include_res!("model_tt/res/fido/icon_keeper.toif"); const ICON_GITLAB: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_gitlab.toif"), "GITLAB");
const ICON_KRAKEN: &[u8] = include_res!("model_tt/res/fido/icon_kraken.toif"); const ICON_GOOGLE: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_google.toif"), "GOOGLE");
const ICON_LOGIN_GOV: &[u8] = include_res!("model_tt/res/fido/icon_login.gov.toif"); const ICON_INVITY: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_invity.toif"), "INVITY");
const ICON_MICROSOFT: &[u8] = include_res!("model_tt/res/fido/icon_microsoft.toif"); const ICON_KEEPER: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_keeper.toif"), "KEEPER");
const ICON_MOJEID: &[u8] = include_res!("model_tt/res/fido/icon_mojeid.toif"); const ICON_KRAKEN: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_kraken.toif"), "KRAKEN");
const ICON_NAMECHEAP: &[u8] = include_res!("model_tt/res/fido/icon_namecheap.toif"); const ICON_LOGIN_GOV: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_login.gov.toif"), "LOGIN_GOV");
const ICON_PROTON: &[u8] = include_res!("model_tt/res/fido/icon_proton.toif"); const ICON_MICROSOFT: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_microsoft.toif"), "MICROSOFT");
const ICON_SLUSHPOOL: &[u8] = include_res!("model_tt/res/fido/icon_slushpool.toif"); const ICON_MOJEID: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_mojeid.toif"), "MOJEID");
const ICON_STRIPE: &[u8] = include_res!("model_tt/res/fido/icon_stripe.toif"); const ICON_NAMECHEAP: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_namecheap.toif"), "NAMECHEAP");
const ICON_TUTANOTA: &[u8] = include_res!("model_tt/res/fido/icon_tutanota.toif"); const ICON_PROTON: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_proton.toif"), "PROTON");
const ICON_SLUSHPOOL: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_slushpool.toif"), "SLUSHPOOL");
const ICON_STRIPE: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_stripe.toif"), "STRIPE");
const ICON_TUTANOTA: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_tutanota.toif"), "TUTANOTA");
/// Default icon when app does not have its own /// Default icon when app does not have its own
const ICON_WEBAUTHN: &[u8] = include_res!("model_tt/res/fido/icon_webauthn.toif"); const ICON_WEBAUTHN: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_webauthn.toif"), "WEBAUTHN");
/// Translates icon name into its data. /// Translates icon name into its data.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not /// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// supplied. /// supplied.
pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8] { pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> NamedToif {
if let Some(icon_name) = icon_name { if let Some(icon_name) = icon_name {
match icon_name.as_ref() { match icon_name.as_ref() {
"aws" => ICON_AWS, "aws" => ICON_AWS,

View File

@ -1,6 +1,9 @@
//! generated from webauthn_icons.rs.mako //! generated from webauthn_icons.rs.mako
//! (by running `make templates` in `core`) //! (by running `make templates` in `core`)
//! do not edit manually! //! do not edit manually!
use crate::ui::display::toif::NamedToif;
<% <%
icons: list[tuple[str, str]] = [] icons: list[tuple[str, str]] = []
for app in fido: for app in fido:
@ -12,15 +15,15 @@ for app in fido:
%>\ %>\
% for icon_name, var_name in icons: % for icon_name, var_name in icons:
const ICON_${var_name}: &[u8] = include_res!("model_tt/res/fido/icon_${icon_name}.toif"); const ICON_${var_name}: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_${icon_name}.toif"), "${var_name}");
% endfor % endfor
/// Default icon when app does not have its own /// Default icon when app does not have its own
const ICON_WEBAUTHN: &[u8] = include_res!("model_tt/res/fido/icon_webauthn.toif"); const ICON_WEBAUTHN: NamedToif = NamedToif(include_res!("model_tt/res/fido/icon_webauthn.toif"), "WEBAUTHN");
/// Translates icon name into its data. /// Translates icon name into its data.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not /// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// supplied. /// supplied.
pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8] { pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> NamedToif {
if let Some(icon_name) = icon_name { if let Some(icon_name) = icon_name {
match icon_name.as_ref() { match icon_name.as_ref() {
% for icon_name, var_name in icons: % for icon_name, var_name in icons:

View File

@ -1,7 +1,7 @@
use super::theme; use super::theme;
use crate::ui::{ use crate::ui::{
component::{label::Label, text::TextStyle, Child, Component, Event, EventCtx}, component::{label::Label, text::TextStyle, Child, Component, Event, EventCtx},
display::{self, Color, Font}, display::{self, toif::Icon, Color, Font},
geometry::{Alignment, Insets, Offset, Rect}, geometry::{Alignment, Insets, Offset, Rect},
util::icon_text_center, util::icon_text_center,
}; };
@ -100,7 +100,7 @@ where
pub struct NotificationFrame<T, U> { pub struct NotificationFrame<T, U> {
area: Rect, area: Rect,
icon: &'static [u8], icon: Icon,
title: U, title: U,
content: Child<T>, content: Child<T>,
} }
@ -116,7 +116,7 @@ where
const ICON_SPACE: i16 = 8; const ICON_SPACE: i16 = 8;
const BORDER: i16 = 8; const BORDER: i16 = 8;
pub fn new(icon: &'static [u8], title: U, content: T) -> Self { pub fn new(icon: Icon, title: U, content: T) -> Self {
Self { Self {
icon, icon,
title, title,
@ -129,7 +129,7 @@ where
self.content.inner() self.content.inner()
} }
pub fn paint_notification(area: Rect, icon: &'static [u8], title: &str, color: Color) { pub fn paint_notification(area: Rect, icon: Icon, title: &str, color: Color) {
let (area, _) = area let (area, _) = area
.inset(Insets::uniform(Self::BORDER)) .inset(Insets::uniform(Self::BORDER))
.split_top(Self::HEIGHT); .split_top(Self::HEIGHT);

View File

@ -2,6 +2,7 @@ use crate::{
time::Instant, time::Instant,
ui::{ ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, FixedHeightBar, Pad}, component::{Child, Component, ComponentExt, Event, EventCtx, FixedHeightBar, Pad},
display::toif::Icon,
geometry::{Grid, Insets, Rect}, geometry::{Grid, Insets, Rect},
util::animation_disabled, util::animation_disabled,
}, },
@ -126,7 +127,7 @@ pub enum CancelHoldMsg {
impl CancelHold { impl CancelHold {
pub fn new(button_style: ButtonStyleSheet) -> FixedHeightBar<Self> { pub fn new(button_style: ButtonStyleSheet) -> FixedHeightBar<Self> {
theme::button_bar(Self { theme::button_bar(Self {
cancel: Some(Button::with_icon(theme::ICON_CANCEL).into_child()), cancel: Some(Button::with_icon(Icon::new(theme::ICON_CANCEL)).into_child()),
hold: Button::with_text("HOLD TO CONFIRM") hold: Button::with_text("HOLD TO CONFIRM")
.styled(button_style) .styled(button_style)
.into_child(), .into_child(),

View File

@ -7,7 +7,7 @@ use crate::{
trezorhal::usb::usb_configured, trezorhal::usb::usb_configured,
ui::{ ui::{
component::{Component, Event, EventCtx, Pad, TimerToken}, component::{Component, Event, EventCtx, Pad, TimerToken},
display::{self, tjpgd::jpeg_info, Color, Font}, display::{self, tjpgd::jpeg_info, toif::Icon, Color, Font},
event::{TouchEvent, USBEvent}, event::{TouchEvent, USBEvent},
geometry::{Offset, Point, Rect}, geometry::{Offset, Point, Rect},
model_tt::{constant, theme::IMAGE_HOMESCREEN}, model_tt::{constant, theme::IMAGE_HOMESCREEN},
@ -60,11 +60,11 @@ where
} }
} }
fn level_to_style(level: u8) -> (Color, &'static [u8]) { fn level_to_style(level: u8) -> (Color, Icon) {
match level { match level {
2 => (theme::VIOLET, theme::ICON_MAGIC), 2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)),
1 => (theme::YELLOW, theme::ICON_WARN), 1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)),
_ => (theme::RED, theme::ICON_WARN), _ => (theme::RED, Icon::new(theme::ICON_WARN)),
} }
} }
@ -273,7 +273,7 @@ where
text: locked, text: locked,
style: theme::TEXT_BOLD, style: theme::TEXT_BOLD,
offset: Offset::new(10, LOCKED_Y), offset: Offset::new(10, LOCKED_Y),
icon: Some(theme::ICON_LOCK), icon: Some(Icon::new(theme::ICON_LOCK)),
}, },
HomescreenText { HomescreenText {
text: tap, text: tap,

View File

@ -7,12 +7,11 @@ use crate::{
trezorhal::{ trezorhal::{
buffers::{get_blurring_buffer, get_jpeg_buffer, get_jpeg_work_buffer, BufferJpeg}, buffers::{get_blurring_buffer, get_jpeg_buffer, get_jpeg_work_buffer, BufferJpeg},
display, display,
display::{bar_radius_buffer, ToifFormat}, display::bar_radius_buffer,
uzlib::UzlibContext,
}, },
ui::{ ui::{
constant::screen, constant::screen,
display::{position_buffer, set_window, toif_info_ensure, Color}, display::{position_buffer, set_window, Color},
geometry::{Offset, Point, Rect}, geometry::{Offset, Point, Rect},
}, },
}; };
@ -20,7 +19,10 @@ use crate::{
use crate::ui::{ use crate::ui::{
component::text::TextStyle, component::text::TextStyle,
constant::{HEIGHT, WIDTH}, constant::{HEIGHT, WIDTH},
display::tjpgd::{BufferInput, BufferOutput, JDEC}, display::{
tjpgd::{BufferInput, BufferOutput, JDEC},
Icon,
},
model_tt::theme, model_tt::theme,
util::icon_text_center, util::icon_text_center,
}; };
@ -30,13 +32,13 @@ pub struct HomescreenText<'a> {
pub text: &'a str, pub text: &'a str,
pub style: TextStyle, pub style: TextStyle,
pub offset: Offset, pub offset: Offset,
pub icon: Option<&'static [u8]>, pub icon: Option<Icon>,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct HomescreenNotification<'a> { pub struct HomescreenNotification<'a> {
pub text: &'a str, pub text: &'a str,
pub icon: &'static [u8], pub icon: Icon,
pub color: Color, pub color: Color,
} }
@ -129,12 +131,10 @@ fn homescreen_position_text(
let text_width_clamped = text_width.clamp(0, screen().width()); let text_width_clamped = text_width.clamp(0, screen().width());
let icon_size = if let Some(icon) = text.icon { let icon_size = if let Some(icon) = text.icon {
let (icon_size, icon_data) = toif_info_ensure(icon, ToifFormat::GrayScaleEH); assert!(icon.toif.width() <= HOMESCREEN_MAX_ICON_SIZE);
assert!(icon_size.x <= HOMESCREEN_MAX_ICON_SIZE); assert!(icon.toif.height() <= HOMESCREEN_MAX_ICON_SIZE);
assert!(icon_size.y <= HOMESCREEN_MAX_ICON_SIZE); icon.toif.uncompress(icon_buffer);
let mut ctx = UzlibContext::new(icon_data, None); icon.toif.size
unwrap!(ctx.uncompress(icon_buffer), "Decompression failed");
icon_size
} else { } else {
Offset::zero() Offset::zero()
}; };

View File

@ -3,7 +3,8 @@ use crate::{
ui::{ ui::{
component::{Component, Event, EventCtx}, component::{Component, Event, EventCtx},
display, display,
geometry::{Offset, Rect}, display::toif::Icon,
geometry::{Offset, Rect, CENTER},
model_tt::{ model_tt::{
component::{ component::{
keyboard::{ keyboard::{
@ -139,7 +140,7 @@ impl Component for Bip39Input {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and // Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge. // 16px from the right edge.
let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0); let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0);
display::icon(icon_center, icon, style.text_color, style.button_color); icon.draw(icon_center, CENTER, style.text_color, style.button_color);
} }
} }
@ -216,13 +217,13 @@ impl Bip39Input {
self.button.enable(ctx); self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_confirm()); self.button.set_stylesheet(ctx, theme::button_confirm());
self.button self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CONFIRM)); .set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM)));
} else { } else {
// Auto-complete button. // Auto-complete button.
self.button.enable(ctx); self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_default()); self.button.set_stylesheet(ctx, theme::button_default());
self.button self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CLICK)); .set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CLICK)));
} }
} else { } else {
// Disabled button. // Disabled button.

View File

@ -1,6 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe}, component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe},
geometry::{Alignment, Grid, Offset, Rect}, display::toif::Icon,
geometry::{Grid, Offset, Rect, CENTER},
model_tt::{ model_tt::{
component::{Button, ButtonMsg}, component::{Button, ButtonMsg},
theme, theme,
@ -38,8 +39,8 @@ where
back: Child::new(Maybe::hidden( back: Child::new(Maybe::hidden(
theme::BG, theme::BG,
Button::with_icon_blend( Button::with_icon_blend(
theme::IMAGE_BG_BACK_BTN_TALL, Icon::new(theme::IMAGE_BG_BACK_BTN_TALL),
theme::ICON_BACK, Icon::new(theme::ICON_BACK),
Offset::new(30, 17), Offset::new(30, 17),
) )
.styled(theme::button_clear()) .styled(theme::button_clear())
@ -100,8 +101,7 @@ where
let prompt_center = grid.row_col(0, 0).union(grid.row_col(0, 3)).center(); let prompt_center = grid.row_col(0, 0).union(grid.row_col(0, 3)).center();
let prompt_size = self.prompt.inner().inner().max_size(); let prompt_size = self.prompt.inner().inner().max_size();
let prompt_top_left = prompt_size.snap(prompt_center, Alignment::Center, Alignment::Center); let prompt_area = Rect::snap(prompt_center, prompt_size, CENTER);
let prompt_area = Rect::from_top_left_and_size(prompt_top_left, prompt_size);
self.prompt.place(prompt_area); self.prompt.place(prompt_area);
self.back.place(back_area); self.back.place(back_area);

View File

@ -1,6 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{base::ComponentExt, Child, Component, Event, EventCtx, Never}, component::{base::ComponentExt, Child, Component, Event, EventCtx, Never},
display, display,
display::toif::Icon,
geometry::{Grid, Insets, Offset, Rect}, geometry::{Grid, Insets, Offset, Rect},
model_tt::component::{ model_tt::component::{
button::{Button, ButtonContent, ButtonMsg}, button::{Button, ButtonContent, ButtonMsg},
@ -46,12 +47,12 @@ impl PassphraseKeyboard {
Self { Self {
page_swipe: Swipe::horizontal(), page_swipe: Swipe::horizontal(),
input: Input::new().into_child(), input: Input::new().into_child(),
confirm: Button::with_icon(theme::ICON_CONFIRM) confirm: Button::with_icon(Icon::new(theme::ICON_CONFIRM))
.styled(theme::button_confirm()) .styled(theme::button_confirm())
.into_child(), .into_child(),
back: Button::with_icon_blend( back: Button::with_icon_blend(
theme::IMAGE_BG_BACK_BTN, Icon::new(theme::IMAGE_BG_BACK_BTN),
theme::ICON_BACK, Icon::new(theme::ICON_BACK),
Offset::new(30, 12), Offset::new(30, 12),
) )
.styled(theme::button_reset()) .styled(theme::button_reset())
@ -61,7 +62,7 @@ impl PassphraseKeyboard {
keys: KEYBOARD.map(|page| { keys: KEYBOARD.map(|page| {
page.map(|text| { page.map(|text| {
if text == " " { if text == " " {
let icon = theme::ICON_SPACE; let icon = Icon::new(theme::ICON_SPACE);
Child::new(Button::with_icon(icon)) Child::new(Button::with_icon(icon))
} else { } else {
Child::new(Button::with_text(text)) Child::new(Button::with_text(text))

View File

@ -9,9 +9,9 @@ use crate::{
base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe, base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe,
Never, Pad, TimerToken, Never, Pad, TimerToken,
}, },
display::{self, Font}, display::{self, toif::Icon, Font},
event::TouchEvent, event::TouchEvent,
geometry::{Alignment, Grid, Insets, Offset, Rect}, geometry::{Grid, Insets, Offset, Rect, CENTER, TOP_LEFT},
model_tt::component::{ model_tt::component::{
button::{Button, ButtonContent, ButtonMsg, ButtonMsg::Clicked}, button::{Button, ButtonContent, ButtonMsg, ButtonMsg::Clicked},
theme, theme,
@ -70,8 +70,8 @@ where
) -> Self { ) -> Self {
// Control buttons. // Control buttons.
let erase_btn = Button::with_icon_blend( let erase_btn = Button::with_icon_blend(
theme::IMAGE_BG_BACK_BTN, Icon::new(theme::IMAGE_BG_BACK_BTN),
theme::ICON_BACK, Icon::new(theme::ICON_BACK),
Offset::new(30, 12), Offset::new(30, 12),
) )
.styled(theme::button_reset()) .styled(theme::button_reset())
@ -79,7 +79,8 @@ where
.initially_enabled(false); .initially_enabled(false);
let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child(); let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child();
let cancel_btn = Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()); let cancel_btn =
Button::with_icon(Icon::new(theme::ICON_CANCEL)).styled(theme::button_cancel());
let cancel_btn = let cancel_btn =
Maybe::new(Pad::with_background(theme::BG), cancel_btn, allow_cancel).into_child(); Maybe::new(Pad::with_background(theme::BG), cancel_btn, allow_cancel).into_child();
@ -95,7 +96,7 @@ where
textbox_pad: Pad::with_background(theme::label_default().background_color), textbox_pad: Pad::with_background(theme::label_default().background_color),
erase_btn, erase_btn,
cancel_btn, cancel_btn,
confirm_btn: Button::with_icon(theme::ICON_CONFIRM) confirm_btn: Button::with_icon(Icon::new(theme::ICON_CONFIRM))
.styled(theme::button_confirm()) .styled(theme::button_confirm())
.initially_enabled(false) .initially_enabled(false)
.into_child(), .into_child(),
@ -369,9 +370,7 @@ impl PinDots {
} }
fn paint_dots(&self, area: Rect) { fn paint_dots(&self, area: Rect) {
let mut cursor = self let mut cursor = self.size().snap(area.center(), CENTER);
.size()
.snap(area.center(), Alignment::Center, Alignment::Center);
let digits = self.digits.len(); let digits = self.digits.len();
let dots_visible = digits.min(MAX_VISIBLE_DOTS); let dots_visible = digits.min(MAX_VISIBLE_DOTS);
@ -384,9 +383,9 @@ impl PinDots {
// Small leftmost dot. // Small leftmost dot.
if digits > dots_visible + 1 { if digits > dots_visible + 1 {
display::icon_top_left( Icon::new(theme::DOT_SMALL).draw(
cursor - Offset::x(2 * step), cursor - Offset::x(2 * step),
theme::DOT_SMALL, TOP_LEFT,
self.style.text_color, self.style.text_color,
self.style.background_color, self.style.background_color,
); );
@ -394,9 +393,9 @@ impl PinDots {
// Greyed out dot. // Greyed out dot.
if digits > dots_visible { if digits > dots_visible {
display::icon_top_left( Icon::new(theme::DOT_ACTIVE).draw(
cursor - Offset::x(step), cursor - Offset::x(step),
theme::DOT_ACTIVE, TOP_LEFT,
theme::GREY_LIGHT, theme::GREY_LIGHT,
self.style.background_color, self.style.background_color,
); );
@ -404,9 +403,9 @@ impl PinDots {
// Draw a dot for each PIN digit. // Draw a dot for each PIN digit.
for _ in 0..dots_visible { for _ in 0..dots_visible {
display::icon_top_left( Icon::new(theme::DOT_ACTIVE).draw(
cursor, cursor,
theme::DOT_ACTIVE, TOP_LEFT,
self.style.text_color, self.style.text_color,
self.style.background_color, self.style.background_color,
); );

View File

@ -7,7 +7,8 @@ use crate::{
ui::{ ui::{
component::{Component, Event, EventCtx}, component::{Component, Event, EventCtx},
display, display,
geometry::{Offset, Rect}, display::toif::Icon,
geometry::{Offset, Rect, CENTER},
model_tt::{ model_tt::{
component::{ component::{
keyboard::{ keyboard::{
@ -173,7 +174,7 @@ impl Component for Slip39Input {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and // Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge. // 16px from the right edge.
let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0); let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0);
display::icon(icon_center, icon, style.text_color, style.button_color); icon.draw(icon_center, CENTER, style.text_color, style.button_color);
} }
} }
@ -225,7 +226,7 @@ impl Slip39Input {
self.button.enable(ctx); self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_confirm()); self.button.set_stylesheet(ctx, theme::button_confirm());
self.button self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CONFIRM)); .set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM)));
} else { } else {
// Disabled button. // Disabled button.
self.button.disable(ctx); self.button.disable(ctx);

View File

@ -3,7 +3,7 @@ use crate::{
ui::{ ui::{
animation::Animation, animation::Animation,
component::{Component, Event, EventCtx}, component::{Component, Event, EventCtx},
display::{self, Color}, display::{self, toif::Icon, Color},
geometry::{Offset, Rect}, geometry::{Offset, Rect},
model_tt::constant, model_tt::constant,
util::animation_disabled, util::animation_disabled,
@ -173,6 +173,7 @@ impl Component for Loader {
} else { } else {
self.styles.active self.styles.active
}; };
display::loader( display::loader(
progress, progress,
self.offset_y, self.offset_y,
@ -190,7 +191,7 @@ pub struct LoaderStyleSheet {
} }
pub struct LoaderStyle { pub struct LoaderStyle {
pub icon: Option<(&'static [u8], Color)>, pub icon: Option<(Icon, Color)>,
pub loader_color: Color, pub loader_color: Color,
pub background_color: Color, pub background_color: Color,
} }

View File

@ -1,6 +1,7 @@
mod button; mod button;
mod dialog; mod dialog;
mod fido; mod fido;
#[rustfmt::skip]
mod fido_icons; mod fido_icons;
mod frame; mod frame;
mod hold_to_confirm; mod hold_to_confirm;

View File

@ -3,7 +3,7 @@ use crate::ui::{
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, FixedHeightBar, Label, base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, FixedHeightBar, Label,
Pad, Paginate, Pad, Paginate,
}, },
display::{self, Color}, display::{self, toif::Icon, Color},
geometry::{Insets, Rect}, geometry::{Insets, Rect},
model_tt::component::{Button, ButtonMsg}, model_tt::component::{Button, ButtonMsg},
}; };
@ -44,7 +44,7 @@ where
} }
pub fn with_back_button(mut self) -> Self { pub fn with_back_button(mut self) -> Self {
self.button_back = Some(Button::with_icon(theme::ICON_BACK)); self.button_back = Some(Button::with_icon(Icon::new(theme::ICON_BACK)));
self self
} }

View File

@ -1,7 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never}, component::{Component, Event, EventCtx, Never},
display, display::toif::Icon,
geometry::{LinearPlacement, Offset, Rect}, geometry::{LinearPlacement, Offset, Rect, CENTER},
}; };
use super::theme; use super::theme;
@ -76,11 +76,11 @@ impl Component for ScrollBar {
} }
fn paint(&mut self) { fn paint(&mut self) {
fn dotsize(distance: usize, nhidden: usize) -> &'static [u8] { fn dotsize(distance: usize, nhidden: usize) -> Icon {
match (nhidden.saturating_sub(distance)).min(2 - distance) { match (nhidden.saturating_sub(distance)).min(2 - distance) {
0 => theme::DOT_INACTIVE, 0 => Icon::new(theme::DOT_INACTIVE),
1 => theme::DOT_INACTIVE_HALF, 1 => Icon::new(theme::DOT_INACTIVE_HALF),
_ => theme::DOT_INACTIVE_QUARTER, _ => Icon::new(theme::DOT_INACTIVE_QUARTER),
} }
} }
@ -100,7 +100,7 @@ impl Component for ScrollBar {
); );
for i in first_shown..(last_shown + 1) { for i in first_shown..(last_shown + 1) {
let icon = if i == self.active_page { let icon = if i == self.active_page {
theme::DOT_ACTIVE Icon::new(theme::DOT_ACTIVE)
} else if i <= first_shown + 1 { } else if i <= first_shown + 1 {
let before_first_shown = first_shown; let before_first_shown = first_shown;
dotsize(i - first_shown, before_first_shown) dotsize(i - first_shown, before_first_shown)
@ -108,9 +108,9 @@ impl Component for ScrollBar {
let after_last_shown = self.page_count - 1 - last_shown; let after_last_shown = self.page_count - 1 - last_shown;
dotsize(last_shown - i, after_last_shown) dotsize(last_shown - i, after_last_shown)
} else { } else {
theme::DOT_INACTIVE Icon::new(theme::DOT_INACTIVE)
}; };
display::icon(cursor, icon, theme::FG, theme::BG); icon.draw(cursor, CENTER, theme::FG, theme::BG);
cursor = cursor + Offset::on_axis(self.layout.axis, Self::DOT_INTERVAL); cursor = cursor + Offset::on_axis(self.layout.axis, Self::DOT_INTERVAL);
} }
} }

View File

@ -29,7 +29,7 @@ use crate::{
}, },
Border, Component, Empty, Timeout, TimeoutMsg, Border, Component, Empty, Timeout, TimeoutMsg,
}, },
display::tjpgd::jpeg_info, display::{tjpgd::jpeg_info, toif::Icon},
geometry, geometry,
layout::{ layout::{
obj::{ComponentMsgObj, LayoutObj}, obj::{ComponentMsgObj, LayoutObj},
@ -615,7 +615,7 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
]); ]);
let buttons = Button::cancel_confirm( let buttons = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL), Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text("NEXT").styled(theme::button_confirm()), Button::with_text("NEXT").styled(theme::button_confirm()),
2, 2,
); );
@ -650,7 +650,7 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
]); ]);
let buttons = Button::cancel_confirm( let buttons = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL), Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text("NEXT").styled(theme::button_confirm()), Button::with_text("NEXT").styled(theme::button_confirm()),
2, 2,
); );
@ -701,7 +701,7 @@ fn new_show_modal(
icon, icon,
title, title,
Button::cancel_confirm( Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()), Button::with_icon(Icon::new(theme::ICON_CANCEL)).styled(theme::button_cancel()),
Button::with_text(button).styled(button_style), Button::with_text(button).styled(button_style),
2, 2,
), ),
@ -730,8 +730,8 @@ fn new_show_modal(
extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new( let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE, Icon::new(theme::IMAGE_BG_CIRCLE),
theme::IMAGE_FG_ERROR, Icon::new(theme::IMAGE_FG_ERROR),
theme::ERROR_COLOR, theme::ERROR_COLOR,
theme::FG, theme::FG,
theme::BG, theme::BG,
@ -759,7 +759,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
}; };
let controls = Button::cancel_confirm( let controls = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL), Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text("CONFIRM").styled(theme::button_confirm()), Button::with_text("CONFIRM").styled(theme::button_confirm()),
2, 2,
); );
@ -777,8 +777,8 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new( let icon = BlendedImage::new(
theme::IMAGE_BG_TRIANGLE, Icon::new(theme::IMAGE_BG_TRIANGLE),
theme::IMAGE_FG_WARN, Icon::new(theme::IMAGE_FG_WARN),
theme::WARN_COLOR, theme::WARN_COLOR,
theme::FG, theme::FG,
theme::BG, theme::BG,
@ -791,8 +791,8 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map
extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new( let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE, Icon::new(theme::IMAGE_BG_CIRCLE),
theme::IMAGE_FG_SUCCESS, Icon::new(theme::IMAGE_FG_SUCCESS),
theme::SUCCESS_COLOR, theme::SUCCESS_COLOR,
theme::FG, theme::FG,
theme::BG, theme::BG,
@ -805,8 +805,8 @@ extern "C" fn new_show_success(n_args: usize, args: *const Obj, kwargs: *mut Map
extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| { let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new( let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE, Icon::new(theme::IMAGE_BG_CIRCLE),
theme::IMAGE_FG_INFO, Icon::new(theme::IMAGE_FG_INFO),
theme::INFO_COLOR, theme::INFO_COLOR,
theme::FG, theme::FG,
theme::BG, theme::BG,
@ -1088,8 +1088,8 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
title, title,
Dialog::new( Dialog::new(
Checklist::from_paragraphs( Checklist::from_paragraphs(
theme::ICON_LIST_CURRENT, Icon::new(theme::ICON_LIST_CURRENT),
theme::ICON_LIST_CHECK, Icon::new(theme::ICON_LIST_CHECK),
active, active,
paragraphs paragraphs
.into_paragraphs() .into_paragraphs()
@ -1141,13 +1141,13 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
let obj = if info_button { let obj = if info_button {
LayoutObj::new(NotificationFrame::new( LayoutObj::new(NotificationFrame::new(
theme::ICON_WARN, Icon::new(theme::ICON_WARN),
notification, notification,
Dialog::new(paragraphs, Button::<&'static str>::abort_info_enter()), Dialog::new(paragraphs, Button::<&'static str>::abort_info_enter()),
))? ))?
} else { } else {
LayoutObj::new(NotificationFrame::new( LayoutObj::new(NotificationFrame::new(
theme::ICON_WARN, Icon::new(theme::ICON_WARN),
notification, notification,
Dialog::new(paragraphs, Button::cancel_confirm_text(None, button)), Dialog::new(paragraphs, Button::cancel_confirm_text(None, button)),
))? ))?

View File

@ -12,6 +12,7 @@ use crate::{
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet}; use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet};
use crate::ui::display::toif::NamedToif;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500); pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500);
@ -52,41 +53,67 @@ pub const QR_SIDE_MAX: u32 = 140;
pub const ICON_SIZE: i16 = 16; pub const ICON_SIZE: i16 = 16;
// UI icons (greyscale). // UI icons (greyscale).
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif"); pub const ICON_CANCEL: NamedToif = NamedToif(include_res!("model_tt/res/cancel.toif"), "cancel");
pub const ICON_CONFIRM: &[u8] = include_res!("model_tt/res/confirm.toif"); pub const ICON_CONFIRM: NamedToif = NamedToif(include_res!("model_tt/res/confirm.toif"), "confirm");
pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif"); pub const ICON_SPACE: NamedToif = NamedToif(include_res!("model_tt/res/space.toif"), "space");
pub const ICON_BACK: &[u8] = include_res!("model_tt/res/back.toif"); pub const ICON_BACK: NamedToif = NamedToif(include_res!("model_tt/res/back.toif"), "back");
pub const ICON_CLICK: &[u8] = include_res!("model_tt/res/click.toif"); pub const ICON_CLICK: NamedToif = NamedToif(include_res!("model_tt/res/click.toif"), "click");
pub const ICON_NEXT: &[u8] = include_res!("model_tt/res/next.toif"); pub const ICON_NEXT: NamedToif = NamedToif(include_res!("model_tt/res/next.toif"), "next");
pub const ICON_WARN: &[u8] = include_res!("model_tt/res/warn-icon.toif"); pub const ICON_WARN: NamedToif = NamedToif(include_res!("model_tt/res/warn-icon.toif"), "warn");
pub const ICON_MAGIC: &[u8] = include_res!("model_tt/res/magic.toif"); pub const ICON_MAGIC: NamedToif = NamedToif(include_res!("model_tt/res/magic.toif"), "magic");
pub const ICON_LIST_CURRENT: &[u8] = include_res!("model_tt/res/current.toif"); pub const ICON_LIST_CURRENT: NamedToif =
pub const ICON_LIST_CHECK: &[u8] = include_res!("model_tt/res/check.toif"); NamedToif(include_res!("model_tt/res/current.toif"), "current");
pub const ICON_LOCK: &[u8] = include_res!("model_tt/res/lock.toif"); pub const ICON_LIST_CHECK: NamedToif = NamedToif(include_res!("model_tt/res/check.toif"), "check");
pub const ICON_LOCK: NamedToif = NamedToif(include_res!("model_tt/res/lock.toif"), "lock");
// Large, three-color icons. // Large, three-color icons.
pub const WARN_COLOR: Color = YELLOW; pub const WARN_COLOR: Color = YELLOW;
pub const INFO_COLOR: Color = BLUE; pub const INFO_COLOR: Color = BLUE;
pub const SUCCESS_COLOR: Color = GREEN; pub const SUCCESS_COLOR: Color = GREEN;
pub const ERROR_COLOR: Color = RED; pub const ERROR_COLOR: Color = RED;
pub const IMAGE_FG_WARN: &[u8] = include_res!("model_tt/res/warn_fg.toif"); pub const IMAGE_FG_WARN: NamedToif =
pub const IMAGE_FG_SUCCESS: &[u8] = include_res!("model_tt/res/success_fg.toif"); NamedToif(include_res!("model_tt/res/warn_fg.toif"), "warn_fg");
pub const IMAGE_FG_ERROR: &[u8] = include_res!("model_tt/res/error_fg.toif"); pub const IMAGE_FG_SUCCESS: NamedToif =
pub const IMAGE_FG_INFO: &[u8] = include_res!("model_tt/res/info_fg.toif"); NamedToif(include_res!("model_tt/res/success_fg.toif"), "success_fg");
pub const IMAGE_BG_CIRCLE: &[u8] = include_res!("model_tt/res/circle.toif"); pub const IMAGE_FG_ERROR: NamedToif =
pub const IMAGE_BG_TRIANGLE: &[u8] = include_res!("model_tt/res/triangle.toif"); NamedToif(include_res!("model_tt/res/error_fg.toif"), "error_fg");
pub const IMAGE_BG_BACK_BTN: &[u8] = include_res!("model_tt/res/back_btn.toif"); pub const IMAGE_FG_INFO: NamedToif =
pub const IMAGE_BG_BACK_BTN_TALL: &[u8] = include_res!("model_tt/res/back_btn_tall.toif"); NamedToif(include_res!("model_tt/res/info_fg.toif"), "info_fg");
pub const IMAGE_BG_CIRCLE: NamedToif =
NamedToif(include_res!("model_tt/res/circle.toif"), "circle");
pub const IMAGE_BG_TRIANGLE: NamedToif =
NamedToif(include_res!("model_tt/res/triangle.toif"), "triangle");
pub const IMAGE_BG_BACK_BTN: NamedToif =
NamedToif(include_res!("model_tt/res/back_btn.toif"), "back_btn");
pub const IMAGE_BG_BACK_BTN_TALL: NamedToif = NamedToif(
include_res!("model_tt/res/back_btn_tall.toif"),
"back_btn_tall",
);
// Default homescreen // Default homescreen
pub const IMAGE_HOMESCREEN: &[u8] = include_res!("model_tt/res/bg.jpg"); pub const IMAGE_HOMESCREEN: &[u8] = include_res!("model_tt/res/bg.jpg");
// Scrollbar/PIN dots. // Scrollbar/PIN dots.
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif"); pub const DOT_ACTIVE: NamedToif = NamedToif(
pub const DOT_INACTIVE: &[u8] = include_res!("model_tt/res/scroll-inactive.toif"); include_res!("model_tt/res/scroll-active.toif"),
pub const DOT_INACTIVE_HALF: &[u8] = include_res!("model_tt/res/scroll-inactive-half.toif"); "scroll-active",
pub const DOT_INACTIVE_QUARTER: &[u8] = include_res!("model_tt/res/scroll-inactive-quarter.toif"); );
pub const DOT_SMALL: &[u8] = include_res!("model_tt/res/scroll-small.toif"); pub const DOT_INACTIVE: NamedToif = NamedToif(
include_res!("model_tt/res/scroll-inactive.toif"),
"scroll-inactive",
);
pub const DOT_INACTIVE_HALF: NamedToif = NamedToif(
include_res!("model_tt/res/scroll-inactive-half.toif"),
"scroll-inactive-half",
);
pub const DOT_INACTIVE_QUARTER: NamedToif = NamedToif(
include_res!("model_tt/res/scroll-inactive-quarter.toif"),
"scroll-inactive-quarter",
);
pub const DOT_SMALL: NamedToif = NamedToif(
include_res!("model_tt/res/scroll-small.toif"),
"scroll-small",
);
pub const fn label_default() -> TextStyle { pub const fn label_default() -> TextStyle {
TEXT_NORMAL TEXT_NORMAL

View File

@ -1,7 +1,8 @@
use crate::ui::{ use crate::ui::{
component::text::TextStyle, component::text::TextStyle,
display, display,
geometry::{Offset, Point}, display::toif::Icon,
geometry::{Offset, Point, CENTER},
}; };
pub trait ResultExt { pub trait ResultExt {
@ -64,14 +65,13 @@ pub fn set_animation_disabled(_disabled: bool) {}
/// Display an icon and a text centered relative to given `Point`. /// Display an icon and a text centered relative to given `Point`.
pub fn icon_text_center( pub fn icon_text_center(
baseline: Point, baseline: Point,
icon: &'static [u8], icon: Icon,
space: i16, space: i16,
text: &str, text: &str,
style: TextStyle, style: TextStyle,
text_offset: Offset, text_offset: Offset,
) { ) {
let toif_info = unwrap!(display::toif_info(icon), "Invalid TOIF data"); let icon_width = icon.toif.width();
let icon_width = toif_info.0.y;
let text_width = style.text_font.text_width(text); let text_width = style.text_font.text_width(text);
let text_height = style.text_font.text_height(); let text_height = style.text_font.text_height();
let text_center = baseline + Offset::new((icon_width + space) / 2, text_height / 2); let text_center = baseline + Offset::new((icon_width + space) / 2, text_height / 2);
@ -84,7 +84,12 @@ pub fn icon_text_center(
style.text_color, style.text_color,
style.background_color, style.background_color,
); );
display::icon(icon_center, icon, style.text_color, style.background_color); icon.draw(
icon_center,
CENTER,
style.text_color,
style.background_color,
);
} }
#[cfg(test)] #[cfg(test)]

File diff suppressed because it is too large Load Diff