1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-03 00:36:06 +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_radius")
.allowlist_function("display_bar_radius_buffer")
.allowlist_function("display_icon")
.allowlist_function("display_image")
.allowlist_function("display_toif_info")
.allowlist_function("display_loader")
@ -321,8 +320,10 @@ fn generate_trezorhal_bindings() {
.allowlist_function("hal_delay")
.allowlist_function("hal_ticks_ms")
// dma2d
.allowlist_function("dma2d_setup_4bpp")
.allowlist_function("dma2d_setup_4bpp_over_4bpp")
.allowlist_function("dma2d_setup_4bpp_over_16bpp")
.allowlist_function("dma2d_start")
.allowlist_function("dma2d_start_blend")
.allowlist_function("dma2d_wait_for_transfer")
//buffers

View File

@ -5,7 +5,7 @@ use num_traits::FromPrimitive;
use crate::trezorhal::buffers::BufferText;
#[derive(PartialEq, Debug, Eq, FromPrimitive)]
#[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)]
pub enum ToifFormat {
FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE 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]) {
unsafe {
ffi::display_image(

View File

@ -1,5 +1,9 @@
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) {
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) }
}
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) {
unsafe {
ffi::dma2d_start_blend(

View File

@ -1,22 +1,38 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display,
display::{toif_info, Color},
geometry::{Alignment, Offset, Point, Rect},
use crate::{
trezorhal::display::{image, ToifFormat},
ui::{
component::{Component, Event, EventCtx, Never},
display,
display::{
toif::{NamedToif, Toif},
Color, Icon,
},
geometry::{Alignment2D, Offset, Point, Rect, CENTER},
},
};
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Image {
image: &'static [u8],
pub toif: Toif,
area: Rect,
}
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 {
image,
toif,
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 {
@ -32,13 +48,14 @@ impl Component for Image {
}
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)) {
if let Some((size, _)) = display::toif_info(self.image) {
sink(Rect::from_center_and_size(self.area.center(), size));
}
sink(Rect::from_center_and_size(
self.area.center(),
self.toif.size,
));
}
}
@ -51,8 +68,8 @@ impl crate::trace::Trace for Image {
}
pub struct BlendedImage {
bg: &'static [u8],
fg: &'static [u8],
bg: Icon,
fg: Icon,
bg_color: Color,
fg_color: Color,
area_color: Color,
@ -61,13 +78,7 @@ pub struct BlendedImage {
}
impl BlendedImage {
pub fn new(
bg: &'static [u8],
fg: &'static [u8],
bg_color: Color,
fg_color: Color,
area_color: Color,
) -> Self {
pub fn new(bg: Icon, fg: Icon, bg_color: Color, fg_color: Color, area_color: Color) -> Self {
Self {
bg,
fg,
@ -93,15 +104,12 @@ impl Component for BlendedImage {
type Msg = Never;
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) {
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)
Rect::from_top_left_and_size(self.bg_top_left, self.bg.toif.size)
}
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)) {
if let Some((size, _)) = display::toif_info(self.bg) {
sink(Rect::from_top_left_and_size(self.bg_top_left, size));
}
sink(Rect::from_top_left_and_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 border::Border;
pub use empty::Empty;
pub use image::Image;
pub use label::Label;
pub use map::Map;
pub use maybe::Maybe;

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
pub mod loader;
#[cfg(feature = "jpeg")]
pub mod tjpgd;
pub mod toif;
use super::{
constant,
@ -14,20 +15,21 @@ use crate::trezorhal::{
dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend,
dma2d_wait_for_transfer,
},
uzlib::UZLIB_WINDOW_SIZE,
};
#[cfg(not(feature = "dma2d"))]
use crate::ui::geometry::TOP_LEFT;
use crate::{
error::Error,
time::Duration,
trezorhal::{
display,
display::ToifFormat,
qr, time,
uzlib::{UzlibContext, UZLIB_WINDOW_SIZE},
},
trezorhal::{display, qr, time, uzlib::UzlibContext},
ui::lerp::Lerp,
};
use core::slice;
use crate::ui::component::image::Image;
pub use crate::ui::display::toif::Icon;
#[cfg(any(feature = "model_tt", feature = "model_tr"))]
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.
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());
@ -306,7 +208,7 @@ pub fn rect_rounded2_partial(
fg_color: Color,
bg_color: Color,
show_percent: i16,
icon: Option<(&[u8], Color)>,
icon: Option<(Icon, Color)>,
) {
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_width = 0;
if let Some((icon_bytes, icon_color)) = icon {
let (toif_size, toif_data) = toif_info_ensure(icon_bytes, ToifFormat::GrayScaleEH);
if toif_size.x <= MAX_ICON_SIZE && toif_size.y <= MAX_ICON_SIZE {
icon_area = Rect::from_center_and_size(center, toif_size);
if let Some((icon, icon_color)) = icon {
if icon.toif.width() <= MAX_ICON_SIZE && icon.toif.height() <= MAX_ICON_SIZE {
icon_area = Rect::from_center_and_size(center, icon.toif.size);
icon_area_clamped = icon_area.clamp(constant::screen());
let mut ctx = UzlibContext::new(toif_data, None);
unwrap!(ctx.uncompress(&mut icon_data), "Decompression failed");
icon.toif.uncompress(&mut icon_data);
icon_colortable = get_color_table(icon_color, bg_color);
icon_width = toif_size.x;
icon_width = icon.toif.width();
use_icon = true;
}
}
@ -533,7 +431,7 @@ fn process_buffer(
#[cfg(feature = "dma2d")]
pub fn text_over_image(
bg_area: Option<(Rect, Color)>,
image_data: &[u8],
image: Image,
text: &str,
font: Font,
offset_img: Offset,
@ -548,8 +446,6 @@ pub fn text_over_image(
let t2 = unsafe { get_buffer_4bpp(1, 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 area;
let offset_img_final;
@ -565,10 +461,10 @@ pub fn text_over_image(
empty_img.buffer.copy_from_slice(&img1.buffer);
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;
} 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;
offset_img_final = Offset::zero();
}
@ -594,7 +490,7 @@ pub fn text_over_image(
set_window(clamped);
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());
@ -662,8 +558,8 @@ pub fn text_over_image(
#[cfg(feature = "dma2d")]
pub fn icon_over_icon(
bg_area: Option<Rect>,
bg: (&[u8], Offset, Color),
fg: (&[u8], Offset, Color),
bg: (Icon, Offset, Color),
fg: (Icon, Offset, Color),
bg_color: Color,
) {
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 empty2 = unsafe { get_buffer_4bpp(2, true) };
let (data_bg, offset_bg, color_icon_bg) = bg;
let (data_fg, offset_fg, color_icon_fg) = fg;
let (icon_bg, offset_bg, color_icon_bg) = bg;
let (icon_fg, offset_fg, color_icon_fg) = fg;
let (toif_bg_size, toif_bg_data) = toif_info_ensure(data_bg, ToifFormat::GrayScaleEH);
assert!(toif_bg_size.x <= constant::WIDTH);
assert_eq!(toif_bg_size.x % 2, 0);
assert!(icon_bg.toif.width() <= constant::WIDTH);
assert_eq!(icon_bg.toif.width() % 2, 0);
let (toif_fg_size, toif_fg_data) = toif_info_ensure(data_fg, ToifFormat::GrayScaleEH);
assert!(toif_bg_size.x <= constant::WIDTH);
assert_eq!(toif_bg_size.x % 2, 0);
assert!(icon_fg.toif.width() <= constant::WIDTH);
assert_eq!(icon_fg.toif.width() % 2, 0);
let area;
let r_bg;
let final_offset_bg;
if let Some(a) = bg_area {
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;
} 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;
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();
set_window(clamped);
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 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());
@ -766,12 +661,12 @@ pub fn icon_over_icon(
#[cfg(not(feature = "dma2d"))]
pub fn icon_over_icon(
bg_area: Option<Rect>,
bg: (&[u8], Offset, Color),
fg: (&[u8], Offset, Color),
bg: (Icon, Offset, Color),
fg: (Icon, Offset, Color),
bg_color: Color,
) {
let (data_bg, offset_bg, color_icon_bg) = bg;
let (data_fg, offset_fg, color_icon_fg) = fg;
let (icon_bg, offset_bg, color_icon_bg) = bg;
let (icon_fg, offset_fg, color_icon_fg) = fg;
let pos_bg = if let Some(area) = bg_area {
rect_fill(area, bg_color);
@ -780,8 +675,8 @@ pub fn icon_over_icon(
Point::from(offset_bg)
};
icon_top_left(pos_bg, data_bg, color_icon_bg, bg_color);
icon_top_left(pos_bg + offset_fg, data_fg, color_icon_fg, color_icon_bg);
icon_bg.draw(pos_bg, TOP_LEFT, color_icon_bg, bg_color);
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

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
/// the rectangle such that it is aligned relative to the `point`.
pub const fn snap(self, point: Point, x: Alignment, y: Alignment) -> Point {
let x_off = match x {
pub const fn snap(self, point: Point, alignment: Alignment2D) -> Point {
let x_off = match alignment.0 {
Alignment::Start => 0,
Alignment::Center => self.x / 2,
Alignment::End => self.x,
};
let y_off = match y {
let y_off = match alignment.1 {
Alignment::Start => 0,
Alignment::Center => self.y / 2,
Alignment::End => self.y,
@ -224,6 +224,12 @@ impl Rect {
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 {
Self {
x0: p0.x,
@ -451,6 +457,14 @@ pub enum Alignment {
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)]
pub enum Axis {
Horizontal,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
//! generated from webauthn_icons.rs.mako
//! (by running `make templates` in `core`)
//! do not edit manually!
use crate::ui::display::toif::NamedToif;
<%
icons: list[tuple[str, str]] = []
for app in fido:
@ -12,15 +15,15 @@ for app in fido:
%>\
% 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
/// 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.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// 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 {
match icon_name.as_ref() {
% for icon_name, var_name in icons:

View File

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

View File

@ -2,6 +2,7 @@ use crate::{
time::Instant,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, FixedHeightBar, Pad},
display::toif::Icon,
geometry::{Grid, Insets, Rect},
util::animation_disabled,
},
@ -126,7 +127,7 @@ pub enum CancelHoldMsg {
impl CancelHold {
pub fn new(button_style: ButtonStyleSheet) -> FixedHeightBar<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")
.styled(button_style)
.into_child(),

View File

@ -7,7 +7,7 @@ use crate::{
trezorhal::usb::usb_configured,
ui::{
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},
geometry::{Offset, Point, Rect},
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 {
2 => (theme::VIOLET, theme::ICON_MAGIC),
1 => (theme::YELLOW, theme::ICON_WARN),
_ => (theme::RED, theme::ICON_WARN),
2 => (theme::VIOLET, Icon::new(theme::ICON_MAGIC)),
1 => (theme::YELLOW, Icon::new(theme::ICON_WARN)),
_ => (theme::RED, Icon::new(theme::ICON_WARN)),
}
}
@ -273,7 +273,7 @@ where
text: locked,
style: theme::TEXT_BOLD,
offset: Offset::new(10, LOCKED_Y),
icon: Some(theme::ICON_LOCK),
icon: Some(Icon::new(theme::ICON_LOCK)),
},
HomescreenText {
text: tap,

View File

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

View File

@ -3,7 +3,8 @@ use crate::{
ui::{
component::{Component, Event, EventCtx},
display,
geometry::{Offset, Rect},
display::toif::Icon,
geometry::{Offset, Rect, CENTER},
model_tt::{
component::{
keyboard::{
@ -139,7 +140,7 @@ impl Component for Bip39Input {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge.
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.set_stylesheet(ctx, theme::button_confirm());
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CONFIRM));
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM)));
} else {
// Auto-complete button.
self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_default());
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CLICK));
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CLICK)));
}
} else {
// Disabled button.

View File

@ -1,6 +1,7 @@
use crate::ui::{
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::{
component::{Button, ButtonMsg},
theme,
@ -38,8 +39,8 @@ where
back: Child::new(Maybe::hidden(
theme::BG,
Button::with_icon_blend(
theme::IMAGE_BG_BACK_BTN_TALL,
theme::ICON_BACK,
Icon::new(theme::IMAGE_BG_BACK_BTN_TALL),
Icon::new(theme::ICON_BACK),
Offset::new(30, 17),
)
.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_size = self.prompt.inner().inner().max_size();
let prompt_top_left = prompt_size.snap(prompt_center, Alignment::Center, Alignment::Center);
let prompt_area = Rect::from_top_left_and_size(prompt_top_left, prompt_size);
let prompt_area = Rect::snap(prompt_center, prompt_size, CENTER);
self.prompt.place(prompt_area);
self.back.place(back_area);

View File

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

View File

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

View File

@ -7,7 +7,8 @@ use crate::{
ui::{
component::{Component, Event, EventCtx},
display,
geometry::{Offset, Rect},
display::toif::Icon,
geometry::{Offset, Rect, CENTER},
model_tt::{
component::{
keyboard::{
@ -173,7 +174,7 @@ impl Component for Slip39Input {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge.
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.set_stylesheet(ctx, theme::button_confirm());
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CONFIRM));
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM)));
} else {
// Disabled button.
self.button.disable(ctx);

View File

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

View File

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

View File

@ -3,7 +3,7 @@ use crate::ui::{
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, FixedHeightBar, Label,
Pad, Paginate,
},
display::{self, Color},
display::{self, toif::Icon, Color},
geometry::{Insets, Rect},
model_tt::component::{Button, ButtonMsg},
};
@ -44,7 +44,7 @@ where
}
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
}

View File

@ -1,7 +1,7 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display,
geometry::{LinearPlacement, Offset, Rect},
display::toif::Icon,
geometry::{LinearPlacement, Offset, Rect, CENTER},
};
use super::theme;
@ -76,11 +76,11 @@ impl Component for ScrollBar {
}
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) {
0 => theme::DOT_INACTIVE,
1 => theme::DOT_INACTIVE_HALF,
_ => theme::DOT_INACTIVE_QUARTER,
0 => Icon::new(theme::DOT_INACTIVE),
1 => Icon::new(theme::DOT_INACTIVE_HALF),
_ => Icon::new(theme::DOT_INACTIVE_QUARTER),
}
}
@ -100,7 +100,7 @@ impl Component for ScrollBar {
);
for i in first_shown..(last_shown + 1) {
let icon = if i == self.active_page {
theme::DOT_ACTIVE
Icon::new(theme::DOT_ACTIVE)
} else if i <= first_shown + 1 {
let before_first_shown = 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;
dotsize(last_shown - i, after_last_shown)
} 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);
}
}

View File

@ -29,7 +29,7 @@ use crate::{
},
Border, Component, Empty, Timeout, TimeoutMsg,
},
display::tjpgd::jpeg_info,
display::{tjpgd::jpeg_info, toif::Icon},
geometry,
layout::{
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(
Button::with_icon(theme::ICON_CANCEL),
Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text("NEXT").styled(theme::button_confirm()),
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(
Button::with_icon(theme::ICON_CANCEL),
Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text("NEXT").styled(theme::button_confirm()),
2,
);
@ -701,7 +701,7 @@ fn new_show_modal(
icon,
title,
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),
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_ERROR,
Icon::new(theme::IMAGE_BG_CIRCLE),
Icon::new(theme::IMAGE_FG_ERROR),
theme::ERROR_COLOR,
theme::FG,
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(
Button::with_icon(theme::ICON_CANCEL),
Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text("CONFIRM").styled(theme::button_confirm()),
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new(
theme::IMAGE_BG_TRIANGLE,
theme::IMAGE_FG_WARN,
Icon::new(theme::IMAGE_BG_TRIANGLE),
Icon::new(theme::IMAGE_FG_WARN),
theme::WARN_COLOR,
theme::FG,
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_SUCCESS,
Icon::new(theme::IMAGE_BG_CIRCLE),
Icon::new(theme::IMAGE_FG_SUCCESS),
theme::SUCCESS_COLOR,
theme::FG,
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 {
let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_INFO,
Icon::new(theme::IMAGE_BG_CIRCLE),
Icon::new(theme::IMAGE_FG_INFO),
theme::INFO_COLOR,
theme::FG,
theme::BG,
@ -1088,8 +1088,8 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
title,
Dialog::new(
Checklist::from_paragraphs(
theme::ICON_LIST_CURRENT,
theme::ICON_LIST_CHECK,
Icon::new(theme::ICON_LIST_CURRENT),
Icon::new(theme::ICON_LIST_CHECK),
active,
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 {
LayoutObj::new(NotificationFrame::new(
theme::ICON_WARN,
Icon::new(theme::ICON_WARN),
notification,
Dialog::new(paragraphs, Button::<&'static str>::abort_info_enter()),
))?
} else {
LayoutObj::new(NotificationFrame::new(
theme::ICON_WARN,
Icon::new(theme::ICON_WARN),
notification,
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 crate::ui::display::toif::NamedToif;
use num_traits::FromPrimitive;
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;
// UI icons (greyscale).
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif");
pub const ICON_CONFIRM: &[u8] = include_res!("model_tt/res/confirm.toif");
pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
pub const ICON_BACK: &[u8] = include_res!("model_tt/res/back.toif");
pub const ICON_CLICK: &[u8] = include_res!("model_tt/res/click.toif");
pub const ICON_NEXT: &[u8] = include_res!("model_tt/res/next.toif");
pub const ICON_WARN: &[u8] = include_res!("model_tt/res/warn-icon.toif");
pub const ICON_MAGIC: &[u8] = include_res!("model_tt/res/magic.toif");
pub const ICON_LIST_CURRENT: &[u8] = include_res!("model_tt/res/current.toif");
pub const ICON_LIST_CHECK: &[u8] = include_res!("model_tt/res/check.toif");
pub const ICON_LOCK: &[u8] = include_res!("model_tt/res/lock.toif");
pub const ICON_CANCEL: NamedToif = NamedToif(include_res!("model_tt/res/cancel.toif"), "cancel");
pub const ICON_CONFIRM: NamedToif = NamedToif(include_res!("model_tt/res/confirm.toif"), "confirm");
pub const ICON_SPACE: NamedToif = NamedToif(include_res!("model_tt/res/space.toif"), "space");
pub const ICON_BACK: NamedToif = NamedToif(include_res!("model_tt/res/back.toif"), "back");
pub const ICON_CLICK: NamedToif = NamedToif(include_res!("model_tt/res/click.toif"), "click");
pub const ICON_NEXT: NamedToif = NamedToif(include_res!("model_tt/res/next.toif"), "next");
pub const ICON_WARN: NamedToif = NamedToif(include_res!("model_tt/res/warn-icon.toif"), "warn");
pub const ICON_MAGIC: NamedToif = NamedToif(include_res!("model_tt/res/magic.toif"), "magic");
pub const ICON_LIST_CURRENT: NamedToif =
NamedToif(include_res!("model_tt/res/current.toif"), "current");
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.
pub const WARN_COLOR: Color = YELLOW;
pub const INFO_COLOR: Color = BLUE;
pub const SUCCESS_COLOR: Color = GREEN;
pub const ERROR_COLOR: Color = RED;
pub const IMAGE_FG_WARN: &[u8] = include_res!("model_tt/res/warn_fg.toif");
pub const IMAGE_FG_SUCCESS: &[u8] = include_res!("model_tt/res/success_fg.toif");
pub const IMAGE_FG_ERROR: &[u8] = include_res!("model_tt/res/error_fg.toif");
pub const IMAGE_FG_INFO: &[u8] = include_res!("model_tt/res/info_fg.toif");
pub const IMAGE_BG_CIRCLE: &[u8] = include_res!("model_tt/res/circle.toif");
pub const IMAGE_BG_TRIANGLE: &[u8] = include_res!("model_tt/res/triangle.toif");
pub const IMAGE_BG_BACK_BTN: &[u8] = include_res!("model_tt/res/back_btn.toif");
pub const IMAGE_BG_BACK_BTN_TALL: &[u8] = include_res!("model_tt/res/back_btn_tall.toif");
pub const IMAGE_FG_WARN: NamedToif =
NamedToif(include_res!("model_tt/res/warn_fg.toif"), "warn_fg");
pub const IMAGE_FG_SUCCESS: NamedToif =
NamedToif(include_res!("model_tt/res/success_fg.toif"), "success_fg");
pub const IMAGE_FG_ERROR: NamedToif =
NamedToif(include_res!("model_tt/res/error_fg.toif"), "error_fg");
pub const IMAGE_FG_INFO: NamedToif =
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
pub const IMAGE_HOMESCREEN: &[u8] = include_res!("model_tt/res/bg.jpg");
// Scrollbar/PIN dots.
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif");
pub const DOT_INACTIVE: &[u8] = include_res!("model_tt/res/scroll-inactive.toif");
pub const DOT_INACTIVE_HALF: &[u8] = include_res!("model_tt/res/scroll-inactive-half.toif");
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_ACTIVE: NamedToif = NamedToif(
include_res!("model_tt/res/scroll-active.toif"),
"scroll-active",
);
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 {
TEXT_NORMAL

View File

@ -1,7 +1,8 @@
use crate::ui::{
component::text::TextStyle,
display,
geometry::{Offset, Point},
display::toif::Icon,
geometry::{Offset, Point, CENTER},
};
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`.
pub fn icon_text_center(
baseline: Point,
icon: &'static [u8],
icon: Icon,
space: i16,
text: &str,
style: TextStyle,
text_offset: Offset,
) {
let toif_info = unwrap!(display::toif_info(icon), "Invalid TOIF data");
let icon_width = toif_info.0.y;
let icon_width = icon.toif.width();
let text_width = style.text_font.text_width(text);
let text_height = style.text_font.text_height();
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.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)]

File diff suppressed because it is too large Load Diff