1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-10 15:30:55 +00:00

perf(core/rust/ui): use 16-bit coordinates

[no changelog]
This commit is contained in:
Martin Milata 2022-09-06 15:29:12 +02:00
parent dd168d2893
commit ef504d40fc
30 changed files with 285 additions and 266 deletions

View File

@ -12,11 +12,11 @@ pub fn backlight(val: i32) -> i32 {
unsafe { ffi::display_backlight(val) }
}
pub fn text(baseline_x: i32, baseline_y: i32, text: &str, font: i32, fgcolor: u16, bgcolor: u16) {
pub fn text(baseline_x: i16, baseline_y: i16, text: &str, font: i32, fgcolor: u16, bgcolor: u16) {
unsafe {
ffi::display_text(
baseline_x,
baseline_y,
baseline_x.into(),
baseline_y.into(),
text.as_ptr() as _,
text.len() as _,
font,
@ -26,11 +26,15 @@ pub fn text(baseline_x: i32, baseline_y: i32, text: &str, font: i32, fgcolor: u1
}
}
pub fn text_width(text: &str, font: i32) -> i32 {
unsafe { ffi::display_text_width(text.as_ptr() as _, text.len() as _, font) }
pub fn text_width(text: &str, font: i32) -> i16 {
unsafe {
ffi::display_text_width(text.as_ptr() as _, text.len() as _, font)
.try_into()
.unwrap_or(i16::MAX)
}
}
pub fn char_width(ch: char, font: i32) -> i32 {
pub fn char_width(ch: char, font: i32) -> i16 {
let mut buf = [0u8; 4];
let encoding = ch.encode_utf8(&mut buf);
text_width(encoding, font)
@ -40,25 +44,39 @@ pub fn get_char_glyph(ch: u8, font: i32) -> *const u8 {
unsafe { ffi::display_get_glyph(font, ch) }
}
pub fn text_height(font: i32) -> i32 {
unsafe { ffi::display_text_height(font) }
pub fn text_height(font: i32) -> i16 {
unsafe {
ffi::display_text_height(font)
.try_into()
.unwrap_or(i16::MAX)
}
}
pub fn bar(x: i32, y: i32, w: i32, h: i32, fgcolor: u16) {
unsafe { ffi::display_bar(x, y, w, h, fgcolor) }
pub fn bar(x: i16, y: i16, w: i16, h: i16, fgcolor: u16) {
unsafe { ffi::display_bar(x.into(), y.into(), w.into(), h.into(), fgcolor) }
}
pub fn bar_radius(x: i32, y: i32, w: i32, h: i32, fgcolor: u16, bgcolor: u16, radius: u8) {
unsafe { ffi::display_bar_radius(x, y, w, h, fgcolor, bgcolor, radius) }
pub fn bar_radius(x: i16, y: i16, w: i16, h: i16, fgcolor: u16, bgcolor: u16, radius: u8) {
unsafe {
ffi::display_bar_radius(
x.into(),
y.into(),
w.into(),
h.into(),
fgcolor,
bgcolor,
radius,
)
}
}
pub fn icon(x: i32, y: i32, w: i32, h: i32, data: &[u8], fgcolor: u16, bgcolor: u16) {
pub fn icon(x: i16, y: i16, w: i16, h: i16, data: &[u8], fgcolor: u16, bgcolor: u16) {
unsafe {
ffi::display_icon(
x,
y,
w,
h,
x.into(),
y.into(),
w.into(),
h.into(),
data.as_ptr() as _,
data.len() as _,
fgcolor,
@ -67,8 +85,17 @@ pub fn icon(x: i32, y: i32, w: i32, h: i32, data: &[u8], fgcolor: u16, bgcolor:
}
}
pub fn image(x: i32, y: i32, w: i32, h: i32, data: &[u8]) {
unsafe { ffi::display_image(x, y, w, h, data.as_ptr() as _, data.len() as _) }
pub fn image(x: i16, y: i16, w: i16, h: i16, data: &[u8]) {
unsafe {
ffi::display_image(
x.into(),
y.into(),
w.into(),
h.into(),
data.as_ptr() as _,
data.len() as _,
)
}
}
pub fn toif_info(data: &[u8]) -> Result<ToifInfo, ()> {
@ -97,7 +124,7 @@ pub fn toif_info(data: &[u8]) -> Result<ToifInfo, ()> {
pub fn loader(
progress: u16,
indeterminate: bool,
yoffset: i32,
yoffset: i16,
fgcolor: u16,
bgcolor: u16,
icon: Option<&[u8]>,
@ -107,7 +134,7 @@ pub fn loader(
ffi::display_loader(
progress,
indeterminate,
yoffset,
yoffset.into(),
fgcolor,
bgcolor,
icon.map(|i| i.as_ptr()).unwrap_or(ptr::null()),
@ -146,11 +173,11 @@ pub fn set_window(x0: u16, y0: u16, x1: u16, y1: u16) {
}
}
pub fn get_offset() -> (i32, i32) {
pub fn get_offset() -> (i16, i16) {
unsafe {
let mut x: c_int = 0;
let mut y: c_int = 0;
ffi::display_offset(ptr::null_mut(), &mut x, &mut y);
(x as i32, y as i32)
(x as i16, y as i16)
}
}

View File

@ -33,8 +33,8 @@ fn qr_version_index(data: &str, thresholds: &[usize]) -> Option<usize> {
}
pub fn render_qrcode(
x: i32,
y: i32,
x: i16,
y: i16,
data: &str,
max_size: u32,
case_sensitive: bool,
@ -69,7 +69,7 @@ pub fn render_qrcode(
buffer[data_len] = 0u8;
let cstr = CStr::from_bytes_with_nul_unchecked(&buffer[..data_len + 1]);
display_qrcode(x, y, cstr.as_ptr() as _, scale as u8);
display_qrcode(x.into(), y.into(), cstr.as_ptr() as _, scale as u8);
Ok(())
}
}

View File

@ -27,7 +27,7 @@ impl<T> GridPlaced<T> {
self
}
pub fn with_spacing(mut self, spacing: i32) -> Self {
pub fn with_spacing(mut self, spacing: i16) -> Self {
self.grid.spacing = spacing;
self
}
@ -80,11 +80,11 @@ where
pub struct FixedHeightBar<T> {
inner: T,
height: i32,
height: i16,
}
impl<T> FixedHeightBar<T> {
pub const fn bottom(inner: T, height: i32) -> Self {
pub const fn bottom(inner: T, height: i16) -> Self {
Self { inner, height }
}
}

View File

@ -6,7 +6,7 @@ struct LineBreak {
/// Index of character **after** the line-break.
offset: usize,
/// Distance from the last line-break of the sequence, in pixels.
width: i32,
width: i16,
style: BreakStyle,
}
@ -19,8 +19,8 @@ enum BreakStyle {
fn limit_line_breaks(
breaks: impl Iterator<Item = LineBreak>,
line_height: i32,
available_height: i32,
line_height: i16,
available_height: i16,
) -> impl Iterator<Item = LineBreak> {
breaks.take(available_height as usize / line_height as usize)
}
@ -42,7 +42,7 @@ fn break_text_to_spans(
text_font: impl GlyphMetrics,
hyphen_font: impl GlyphMetrics,
breaking: LineBreaking,
available_width: i32,
available_width: i16,
) -> impl Iterator<Item = Span> {
let mut finished = false;
let mut last_break = LineBreak {
@ -89,7 +89,7 @@ fn select_line_breaks(
text_font: impl GlyphMetrics,
hyphen_font: impl GlyphMetrics,
breaking: LineBreaking,
available_width: i32,
available_width: i16,
) -> impl Iterator<Item = LineBreak> {
let hyphen_width = hyphen_font.char_width('-');
@ -159,16 +159,16 @@ fn select_line_breaks(
}
pub trait GlyphMetrics {
fn char_width(&self, ch: char) -> i32;
fn line_height(&self) -> i32;
fn char_width(&self, ch: char) -> i16;
fn line_height(&self) -> i16;
}
impl GlyphMetrics for Font {
fn char_width(&self, ch: char) -> i32 {
fn char_width(&self, ch: char) -> i16 {
Font::char_width(*self, ch)
}
fn line_height(&self) -> i32 {
fn line_height(&self) -> i16 {
Font::line_height(*self)
}
}
@ -205,21 +205,21 @@ mod tests {
#[derive(Copy, Clone)]
struct Fixed {
width: i32,
height: i32,
width: i16,
height: i16,
}
impl GlyphMetrics for Fixed {
fn char_width(&self, _ch: char) -> i32 {
fn char_width(&self, _ch: char) -> i16 {
self.width
}
fn line_height(&self) -> i32 {
fn line_height(&self) -> i16 {
self.height
}
}
fn break_text(s: &str, w: i32) -> Vec<Span> {
fn break_text(s: &str, w: i16) -> Vec<Span> {
break_text_to_spans(
s,
Fixed {
@ -236,7 +236,7 @@ mod tests {
.collect::<Vec<_>>()
}
fn line_breaks(s: &str, w: i32) -> Vec<LineBreak> {
fn line_breaks(s: &str, w: i16) -> Vec<LineBreak> {
select_line_breaks(
s.char_indices(),
Fixed {
@ -253,7 +253,7 @@ mod tests {
.collect::<Vec<_>>()
}
fn hard(offset: usize, width: i32) -> LineBreak {
fn hard(offset: usize, width: i16) -> LineBreak {
LineBreak {
offset,
width,
@ -261,7 +261,7 @@ mod tests {
}
}
fn whitespace(offset: usize, width: i32) -> LineBreak {
fn whitespace(offset: usize, width: i16) -> LineBreak {
LineBreak {
offset,
width,
@ -269,7 +269,7 @@ mod tests {
}
}
fn inside_word(offset: usize, width: i32) -> LineBreak {
fn inside_word(offset: usize, width: i16) -> LineBreak {
LineBreak {
offset,
width,

View File

@ -32,10 +32,10 @@ pub struct TextLayout {
/// Additional space before beginning of text, can be negative to shift text
/// upwards.
pub padding_top: i32,
pub padding_top: i16,
/// Additional space between end of text and bottom of bounding box, can be
/// negative.
pub padding_bottom: i32,
pub padding_bottom: i16,
/// Fonts, colors, line/page breaking behavior.
pub style: TextStyle,
@ -252,7 +252,7 @@ impl TextLayout {
}
}
fn layout_height(&self, init_cursor: Point, end_cursor: Point) -> i32 {
fn layout_height(&self, init_cursor: Point, end_cursor: Point) -> i16 {
self.padding_top
+ self.style.text_font.text_height()
+ (end_cursor.y - init_cursor.y)
@ -262,13 +262,13 @@ impl TextLayout {
pub enum LayoutFit {
/// Entire content fits. Vertical size is returned in `height`.
Fitting { processed_chars: usize, height: i32 },
Fitting { processed_chars: usize, height: i16 },
/// Content fits partially or not at all.
OutOfBounds { processed_chars: usize, height: i32 },
OutOfBounds { processed_chars: usize, height: i16 },
}
impl LayoutFit {
pub fn height(&self) -> i32 {
pub fn height(&self) -> i16 {
match self {
LayoutFit::Fitting { height, .. } => *height,
LayoutFit::OutOfBounds { height, .. } => *height,
@ -400,7 +400,7 @@ struct Span {
impl Span {
fn fit_horizontally(
text: &str,
max_width: i32,
max_width: i16,
text_font: impl GlyphMetrics,
breaking: LineBreaking,
) -> Self {
@ -489,16 +489,16 @@ mod tests {
use super::*;
pub struct Fixed {
pub width: i32,
pub height: i32,
pub width: i16,
pub height: i16,
}
impl GlyphMetrics for Fixed {
fn char_width(&self, _ch: char) -> i32 {
fn char_width(&self, _ch: char) -> i16 {
self.width
}
fn line_height(&self) -> i32 {
fn line_height(&self) -> i16 {
self.height
}
}
@ -555,7 +555,7 @@ mod tests {
);
}
fn spans_from(text: &str, max_width: i32) -> Vec<(&str, bool)> {
fn spans_from(text: &str, max_width: i16) -> Vec<(&str, bool)> {
let mut spans = vec![];
let mut remaining_text = text;
loop {

View File

@ -11,13 +11,13 @@ use super::layout::{LayoutFit, TextLayout, TextStyle};
pub const MAX_PARAGRAPHS: usize = 9;
/// Maximum space between paragraphs. Actual result may be smaller (even 0) if
/// it would make paragraphs overflow the bounding box.
pub const DEFAULT_SPACING: i32 = 0;
pub const DEFAULT_SPACING: i16 = 0;
/// Offset of paragraph text from the top of the paragraph bounding box. Tweak
/// these values to get nice alignment of baselines relative to the surrounding
/// components.
pub const PARAGRAPH_TOP_SPACE: i32 = -1;
pub const PARAGRAPH_TOP_SPACE: i16 = -1;
/// Offset of paragraph bounding box bottom relative to bottom of its text.
pub const PARAGRAPH_BOTTOM_SPACE: i32 = 5;
pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5;
pub struct Paragraphs<T> {
area: Rect,
@ -48,7 +48,7 @@ where
self
}
pub fn with_spacing(mut self, spacing: i32) -> Self {
pub fn with_spacing(mut self, spacing: i16) -> Self {
self.placement = self.placement.with_spacing(spacing);
self
}
@ -352,7 +352,7 @@ impl<T> Checklist<T>
where
T: AsRef<str>,
{
const CHECK_WIDTH: i32 = 16;
const CHECK_WIDTH: i16 = 16;
const DONE_OFFSET: Offset = Offset::new(-2, 6);
const CURRENT_OFFSET: Offset = Offset::new(2, 3);

View File

@ -66,46 +66,35 @@ 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_info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert!(toif_info.grayscale);
let (toif_size, toif_data) = toif_info_ensure(data, true);
display::icon(
top_left.x,
top_left.y,
toif_info.width.into(),
toif_info.height.into(),
&data[12..], // Skip TOIF header.
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_info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert!(toif_info.grayscale);
let r = Rect::from_center_and_size(
center,
Offset::new(toif_info.width.into(), toif_info.height.into()),
);
let (toif_size, toif_data) = toif_info_ensure(data, true);
let r = Rect::from_center_and_size(center, toif_size);
display::icon(
r.x0,
r.y0,
r.width(),
r.height(),
&data[12..], // Skip TOIF header.
toif_data,
fg_color.into(),
bg_color.into(),
);
}
pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let toif_info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert!(toif_info.grayscale);
let r = Rect::from_center_and_size(
center,
Offset::new(toif_info.width.into(), toif_info.height.into()),
);
let (toif_size, toif_data) = toif_info_ensure(data, true);
let r = Rect::from_center_and_size(center, toif_size);
let area = r.translate(get_offset());
let clamped = area.clamp(constant::screen());
@ -116,7 +105,7 @@ pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let mut dest = [0_u8; 1];
let mut window = [0; UZLIB_WINDOW_SIZE];
let mut ctx = UzlibContext::new(&data[12..], Some(&mut window));
let mut ctx = UzlibContext::new(toif_data, Some(&mut window));
for py in area.y0..area.y1 {
for px in area.x0..area.x1 {
@ -141,26 +130,19 @@ pub fn icon_rust(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
}
pub fn image(center: Point, data: &[u8]) {
let toif_info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert!(!toif_info.grayscale);
let (toif_size, toif_data) = toif_info_ensure(data, false);
let r = Rect::from_center_and_size(
center,
Offset::new(toif_info.width.into(), toif_info.height.into()),
);
display::image(
r.x0,
r.y0,
r.width(),
r.height(),
&data[12..], // Skip TOIF header.
);
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, bool)> {
if let Ok(info) = display::toif_info(data) {
Some((
Offset::new(info.width.into(), info.height.into()),
Offset::new(
unwrap!(info.width.try_into()),
unwrap!(info.height.try_into()),
),
info.grayscale,
))
} else {
@ -168,6 +150,19 @@ pub fn toif_info(data: &[u8]) -> Option<(Offset, bool)> {
}
}
/// Aborts if the TOIF file does not have the correct grayscale flag, do not use
/// with user-supplied inputs.
fn toif_info_ensure(data: &[u8], grayscale: bool) -> (Offset, &[u8]) {
let info = unwrap!(display::toif_info(data), "Invalid TOIF data");
assert_eq!(grayscale, info.grayscale);
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());
@ -243,7 +238,7 @@ impl<'a> TextOverlay<'a> {
/// The implementation could be replaced by (cos(`angle`), sin(`angle`)),
/// if we allow trigonometric functions.
/// In the meantime, approximate this with predefined octagon
fn get_vector(angle: i32) -> Point {
fn get_vector(angle: i16) -> Point {
//octagon vertices
let v = [
Point::new(0, 1000),
@ -257,7 +252,7 @@ fn get_vector(angle: i32) -> Point {
];
let angle = angle % 360;
let vertices = v.len() as i32;
let vertices = v.len() as i16;
let sector_length = 360 / vertices; // only works if 360 is divisible by vertices
let sector = angle / sector_length;
let sector_angle = (angle % sector_length) as f32;
@ -285,17 +280,17 @@ fn is_clockwise_or_equal_inc(n_v1: Point, v2: Point) -> bool {
}
/// Draw a rounded rectangle with corner radius 2
/// Draws only a part (sector of a corresponding circe)
/// Draws only a part (sector of a corresponding circle)
/// of the rectangle according to `show_percent` argument,
/// and optionally draw an `icon` inside
pub fn rect_rounded2_partial(
area: Rect,
fg_color: Color,
bg_color: Color,
show_percent: i32,
show_percent: i16,
icon: Option<(&[u8], Color)>,
) {
const MAX_ICON_SIZE: u16 = 64;
const MAX_ICON_SIZE: i16 = 64;
let r = area.translate(get_offset());
let clamped = r.clamp(constant::screen());
@ -313,20 +308,16 @@ pub fn rect_rounded2_partial(
let mut icon_width = 0;
if let Some((icon_bytes, icon_color)) = icon {
let toif_info = unwrap!(display::toif_info(icon_bytes), "Invalid TOIF data");
assert!(toif_info.grayscale);
let (toif_size, toif_data) = toif_info_ensure(icon_bytes, true);
if toif_info.width <= MAX_ICON_SIZE && toif_info.height <= MAX_ICON_SIZE {
icon_area = Rect::from_center_and_size(
center,
Offset::new(toif_info.width.into(), toif_info.height.into()),
);
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());
let mut ctx = UzlibContext::new(&icon_bytes[12..], None);
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_width = toif_info.width.into();
icon_width = toif_size.x;
use_icon = true;
}
}
@ -409,7 +400,7 @@ fn rect_rounded2_get_pixel(
size: Offset,
colortable: [Color; 16],
fill: bool,
line_width: i32,
line_width: i16,
) -> Color {
let border = (p.x >= 0 && p.x < line_width)
|| ((p.x >= size.x - line_width) && p.x <= (size.x - 1))
@ -447,8 +438,8 @@ pub fn bar_with_text_and_fill(
overlay: Option<TextOverlay>,
fg_color: Color,
bg_color: Color,
fill_from: i32,
fill_to: i32,
fill_from: i16,
fill_to: i16,
) {
let r = area.translate(get_offset());
let clamped = r.clamp(constant::screen());
@ -481,7 +472,7 @@ pub fn bar_with_text_and_fill(
}
// Used on T1 only.
pub fn dotted_line(start: Point, width: i32, color: Color) {
pub fn dotted_line(start: Point, width: i16, color: Color) {
for x in (start.x..width).step_by(2) {
display::bar(x, start.y, 1, 1, color.into());
}
@ -492,7 +483,7 @@ pub const LOADER_MAX: u16 = 1000;
pub fn loader(
progress: u16,
y_offset: i32,
y_offset: i16,
fg_color: Color,
bg_color: Color,
icon: Option<(&[u8], Color)>,
@ -510,7 +501,7 @@ pub fn loader(
pub fn loader_indeterminate(
progress: u16,
y_offset: i32,
y_offset: i16,
fg_color: Color,
bg_color: Color,
icon: Option<(&[u8], Color)>,
@ -599,11 +590,11 @@ pub fn get_color_table(fg_color: Color, bg_color: Color) -> [Color; 16] {
}
pub struct Glyph {
pub width: i32,
pub height: i32,
pub adv: i32,
pub bearing_x: i32,
pub bearing_y: i32,
pub width: i16,
pub height: i16,
pub adv: i16,
pub bearing_x: i16,
pub bearing_y: i16,
data: &'static [u8],
}
@ -619,8 +610,8 @@ impl Glyph {
/// - data must have static lifetime
pub unsafe fn load(data: *const u8) -> Self {
unsafe {
let width = *data.offset(0) as i32;
let height = *data.offset(1) as i32;
let width = *data.offset(0) as i16;
let height = *data.offset(1) as i16;
let data_bits = constant::FONT_BPP * width * height;
@ -633,15 +624,15 @@ impl Glyph {
Glyph {
width,
height,
adv: *data.offset(2) as i32,
bearing_x: *data.offset(3) as i32,
bearing_y: *data.offset(4) as i32,
adv: *data.offset(2) as i16,
bearing_x: *data.offset(3) as i16,
bearing_y: *data.offset(4) as i16,
data: slice::from_raw_parts(data.offset(5), data_bytes as usize),
}
}
}
pub fn print(&self, pos: Point, colortable: [Color; 16]) -> i32 {
pub fn print(&self, pos: Point, colortable: [Color; 16]) -> i16 {
let bearing = Offset::new(self.bearing_x, -self.bearing_y);
let size = Offset::new(self.width, self.height);
let pos_adj = pos + bearing;
@ -663,22 +654,22 @@ impl Glyph {
self.adv
}
pub fn unpack_bpp1(&self, a: i32) -> u8 {
pub fn unpack_bpp1(&self, a: i16) -> u8 {
let c_data = self.data[(a / 8) as usize];
((c_data >> (7 - (a % 8))) & 0x01) * 15
}
pub fn unpack_bpp2(&self, a: i32) -> u8 {
pub fn unpack_bpp2(&self, a: i16) -> u8 {
let c_data = self.data[(a / 4) as usize];
((c_data >> (6 - (a % 4) * 2)) & 0x03) * 5
}
pub fn unpack_bpp4(&self, a: i32) -> u8 {
pub fn unpack_bpp4(&self, a: i16) -> u8 {
let c_data = self.data[(a / 2) as usize];
(c_data >> (4 - (a % 2) * 4)) & 0x0F
}
pub fn unpack_bpp8(&self, a: i32) -> u8 {
pub fn unpack_bpp8(&self, a: i16) -> u8 {
let c_data = self.data[a as usize];
c_data >> 4
}
@ -704,19 +695,19 @@ impl Font {
Self(id)
}
pub fn text_width(self, text: &str) -> i32 {
display::text_width(text, self.0)
pub fn text_width(self, text: &str) -> i16 {
display::text_width(text, self.0) as i16
}
pub fn char_width(self, ch: char) -> i32 {
display::char_width(ch, self.0)
pub fn char_width(self, ch: char) -> i16 {
display::char_width(ch, self.0) as i16
}
pub fn text_height(self) -> i32 {
display::text_height(self.0)
pub fn text_height(self) -> i16 {
display::text_height(self.0) as i16
}
pub fn line_height(self) -> i32 {
pub fn line_height(self) -> i16 {
constant::LINE_SPACE + self.text_height()
}

View File

@ -1,7 +1,7 @@
use crate::ui::lerp::Lerp;
use core::ops::{Add, Neg, Sub};
const fn min(a: i32, b: i32) -> i32 {
const fn min(a: i16, b: i16) -> i16 {
if a < b {
a
} else {
@ -9,7 +9,7 @@ const fn min(a: i32, b: i32) -> i32 {
}
}
const fn max(a: i32, b: i32) -> i32 {
const fn max(a: i16, b: i16) -> i16 {
if a > b {
a
} else {
@ -17,7 +17,7 @@ const fn max(a: i32, b: i32) -> i32 {
}
}
const fn clamp(x: i32, min: i32, max: i32) -> i32 {
const fn clamp(x: i16, min: i16, max: i16) -> i16 {
if x < min {
min
} else if x > max {
@ -32,16 +32,16 @@ const fn clamp(x: i32, min: i32, max: i32) -> i32 {
/// the `Point` type.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Offset {
pub x: i32,
pub y: i32,
pub x: i16,
pub y: i16,
}
impl Offset {
pub const fn new(x: i32, y: i32) -> Self {
pub const fn new(x: i16, y: i16) -> Self {
Self { x, y }
}
pub const fn uniform(a: i32) -> Self {
pub const fn uniform(a: i16) -> Self {
Self::new(a, a)
}
@ -49,22 +49,22 @@ impl Offset {
Self::new(0, 0)
}
pub const fn x(x: i32) -> Self {
pub const fn x(x: i16) -> Self {
Self::new(x, 0)
}
pub const fn y(y: i32) -> Self {
pub const fn y(y: i16) -> Self {
Self::new(0, y)
}
pub const fn on_axis(axis: Axis, a: i32) -> Self {
pub const fn on_axis(axis: Axis, a: i16) -> Self {
match axis {
Axis::Horizontal => Self::new(a, 0),
Axis::Vertical => Self::new(0, a),
}
}
pub const fn axis(&self, axis: Axis) -> i32 {
pub const fn axis(&self, axis: Axis) -> i16 {
match axis {
Axis::Horizontal => self.x,
Axis::Vertical => self.y,
@ -132,12 +132,12 @@ impl Sub<Offset> for Offset {
/// coordinates, vectors, and offsets are represented by the `Offset` type.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Point {
pub x: i32,
pub y: i32,
pub x: i16,
pub y: i16,
}
impl Point {
pub const fn new(x: i32, y: i32) -> Self {
pub const fn new(x: i16, y: i16) -> Self {
Self { x, y }
}
@ -184,7 +184,7 @@ impl Sub<Point> for Point {
impl Lerp for Point {
fn lerp(a: Self, b: Self, t: f32) -> Self {
Point::new(i32::lerp(a.x, b.x, t), i32::lerp(a.y, b.y, t))
Point::new(i16::lerp(a.x, b.x, t), i16::lerp(a.y, b.y, t))
}
}
@ -192,10 +192,10 @@ impl Lerp for Point {
/// bottom-right point `x1`,`y1`.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Rect {
pub x0: i32,
pub y0: i32,
pub x1: i32,
pub y1: i32,
pub x0: i16,
pub y0: i16,
pub x1: i16,
pub y1: i16,
}
impl Rect {
@ -238,19 +238,19 @@ impl Rect {
Self::from_top_left_and_size(self.top_left(), size)
}
pub const fn with_width(self, width: i32) -> Self {
pub const fn with_width(self, width: i16) -> Self {
self.with_size(Offset::new(width, self.height()))
}
pub const fn with_height(self, height: i32) -> Self {
pub const fn with_height(self, height: i16) -> Self {
self.with_size(Offset::new(self.width(), height))
}
pub const fn width(&self) -> i32 {
pub const fn width(&self) -> i16 {
self.x1 - self.x0
}
pub const fn height(&self) -> i32 {
pub const fn height(&self) -> i16 {
self.y1 - self.y0
}
@ -304,7 +304,7 @@ impl Rect {
}
}
pub const fn cut_from_left(&self, width: i32) -> Self {
pub const fn cut_from_left(&self, width: i16) -> Self {
Self {
x0: self.x0,
y0: self.y0,
@ -313,7 +313,7 @@ impl Rect {
}
}
pub const fn cut_from_right(&self, width: i32) -> Self {
pub const fn cut_from_right(&self, width: i16) -> Self {
Self {
x0: self.x1 - width,
y0: self.y0,
@ -322,7 +322,7 @@ impl Rect {
}
}
pub const fn split_top(self, height: i32) -> (Self, Self) {
pub const fn split_top(self, height: i16) -> (Self, Self) {
let height = clamp(height, 0, self.height());
let top = Self {
@ -336,11 +336,11 @@ impl Rect {
(top, bottom)
}
pub const fn split_bottom(self, height: i32) -> (Self, Self) {
pub const fn split_bottom(self, height: i16) -> (Self, Self) {
self.split_top(self.height() - height)
}
pub const fn split_left(self, width: i32) -> (Self, Self) {
pub const fn split_left(self, width: i16) -> (Self, Self) {
let width = clamp(width, 0, self.width());
let left = Self {
@ -354,7 +354,7 @@ impl Rect {
(left, right)
}
pub const fn split_right(self, width: i32) -> (Self, Self) {
pub const fn split_right(self, width: i16) -> (Self, Self) {
self.split_left(self.width() - width)
}
@ -379,14 +379,14 @@ impl Rect {
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Insets {
pub top: i32,
pub right: i32,
pub bottom: i32,
pub left: i32,
pub top: i16,
pub right: i16,
pub bottom: i16,
pub left: i16,
}
impl Insets {
pub const fn new(top: i32, right: i32, bottom: i32, left: i32) -> Self {
pub const fn new(top: i16, right: i16, bottom: i16, left: i16) -> Self {
Self {
top,
right,
@ -395,27 +395,27 @@ impl Insets {
}
}
pub const fn uniform(d: i32) -> Self {
pub const fn uniform(d: i16) -> Self {
Self::new(d, d, d, d)
}
pub const fn top(d: i32) -> Self {
pub const fn top(d: i16) -> Self {
Self::new(d, 0, 0, 0)
}
pub const fn right(d: i32) -> Self {
pub const fn right(d: i16) -> Self {
Self::new(0, d, 0, 0)
}
pub const fn bottom(d: i32) -> Self {
pub const fn bottom(d: i16) -> Self {
Self::new(0, 0, d, 0)
}
pub const fn left(d: i32) -> Self {
pub const fn left(d: i16) -> Self {
Self::new(0, 0, 0, d)
}
pub const fn sides(d: i32) -> Self {
pub const fn sides(d: i16) -> Self {
Self::new(0, d, 0, d)
}
}
@ -455,7 +455,7 @@ pub struct Grid {
/// Number of columns (cells on the x-axis) in the grid.
pub cols: usize,
/// Padding between cells.
pub spacing: i32,
pub spacing: i16,
/// Total area covered by this grid.
pub area: Rect,
}
@ -470,15 +470,15 @@ impl Grid {
}
}
pub const fn with_spacing(self, spacing: i32) -> Self {
pub const fn with_spacing(self, spacing: i16) -> Self {
Self { spacing, ..self }
}
pub const fn row_col(&self, row: usize, col: usize) -> Rect {
let ncols = self.cols as i32;
let nrows = self.rows as i32;
let col = min(col as i32, ncols - 1);
let row = min(row as i32, nrows - 1);
let ncols = self.cols as i16;
let nrows = self.rows as i16;
let col = min(col as i16, ncols - 1);
let row = min(row as i16, nrows - 1);
// Total number of horizontal pixels used for spacing.
let spacing_width = self.spacing * (ncols - 1);
@ -535,7 +535,7 @@ pub struct GridCellSpan {
pub struct LinearPlacement {
pub axis: Axis,
pub align: Alignment,
pub spacing: i32,
pub spacing: i16,
}
impl LinearPlacement {
@ -576,14 +576,14 @@ impl LinearPlacement {
}
}
pub const fn with_spacing(self, spacing: i32) -> Self {
pub const fn with_spacing(self, spacing: i16) -> Self {
Self { spacing, ..self }
}
/// Arranges all `items` by parameters configured in `self` into `area`.
/// Does not change the size of the items (only the position).
pub fn arrange(&self, area: Rect, items: &mut [impl Dimensions]) {
let size_sum: i32 = items
let size_sum: i16 = items
.iter_mut()
.map(|i| i.area().size().axis(self.axis))
.sum();
@ -609,7 +609,7 @@ impl LinearPlacement {
sink: &mut dyn FnMut(Point),
) {
let item_size = size.axis(self.axis);
let (mut cursor, spacing) = self.compute_spacing(area, count, (count as i32) * item_size);
let (mut cursor, spacing) = self.compute_spacing(area, count, (count as i16) * item_size);
let cross_coord =
area.size().axis(self.axis.cross()) / 2 - size.axis(self.axis.cross()) / 2;
@ -623,15 +623,15 @@ impl LinearPlacement {
}
}
const fn compute_spacing(&self, area: Rect, count: usize, size_sum: i32) -> (i32, i32) {
const fn compute_spacing(&self, area: Rect, count: usize, size_sum: i16) -> (i16, i16) {
let spacing_count = count.saturating_sub(1);
let spacing_sum = spacing_count as i32 * self.spacing;
let spacing_sum = spacing_count as i16 * self.spacing;
let naive_size = size_sum + spacing_sum;
let available_space = area.size().axis(self.axis);
// scale down spacing to fit everything into area
let (total_size, spacing) = if naive_size > available_space {
let scaled_space = (available_space - size_sum) / max(spacing_count as i32, 1);
let scaled_space = (available_space - size_sum) / max(spacing_count as i16, 1);
// forbid negative spacing
(available_space, max(scaled_space, 0))
} else {

View File

@ -65,6 +65,7 @@ macro_rules! impl_lerp_for_uint {
};
}
impl_lerp_for_int!(i16);
impl_lerp_for_int!(i32);
impl_lerp_for_uint!(u8);
impl_lerp_for_uint!(u16);

View File

@ -37,7 +37,7 @@ where
type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect {
const TITLE_SPACE: i32 = 4;
const TITLE_SPACE: i16 = 4;
let (title_area, content_area) = bounds.split_top(theme::FONT_BOLD.line_height());
let content_area = content_area.inset(Insets::top(TITLE_SPACE));

View File

@ -134,9 +134,9 @@ pub struct ScrollBar {
}
impl ScrollBar {
pub const WIDTH: i32 = 8;
pub const WIDTH: i16 = 8;
pub const DOT_SIZE: Offset = Offset::new(4, 4);
pub const DOT_INTERVAL: i32 = 6;
pub const DOT_INTERVAL: i16 = 6;
pub fn vertical() -> Self {
Self {
@ -205,7 +205,7 @@ impl Component for ScrollBar {
}
fn paint(&mut self) {
let count = self.page_count as i32;
let count = self.page_count as i16;
let interval = {
let available_height = self.area.height();
let naive_height = count * Self::DOT_INTERVAL;

View File

@ -1,9 +1,9 @@
use crate::ui::geometry::{Offset, Point, Rect};
pub const WIDTH: i32 = 128;
pub const HEIGHT: i32 = 64;
pub const LINE_SPACE: i32 = 1;
pub const FONT_BPP: i32 = 1;
pub const WIDTH: i16 = 128;
pub const HEIGHT: i16 = 64;
pub const LINE_SPACE: i16 = 1;
pub const FONT_BPP: i16 = 1;
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)

View File

@ -18,7 +18,7 @@ pub struct HoldToConfirm {
pos: ButtonPos,
loader: Loader,
baseline: Point,
text_width: i32,
text_width: i16,
}
impl HoldToConfirm {

View File

@ -37,7 +37,7 @@ where
type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect {
const TITLE_SPACE: i32 = 4;
const TITLE_SPACE: i16 = 4;
let (title_area, content_area) = bounds.split_top(theme::FONT_BOLD.line_height());
let content_area = content_area.inset(Insets::top(TITLE_SPACE));

View File

@ -111,8 +111,8 @@ impl Loader {
matches!(self.progress(now), Some(display::LOADER_MIN))
}
pub fn paint_loader(&mut self, style: &LoaderStyle, done: i32) {
let invert_from = ((self.area.width() + 1) * done) / (display::LOADER_MAX as i32);
pub fn paint_loader(&mut self, style: &LoaderStyle, done: i16) {
let invert_from = ((self.area.width() + 1) * done) / (display::LOADER_MAX as i16);
display::bar_with_text_and_fill(
self.area,
@ -169,11 +169,11 @@ impl Component for Loader {
if let State::Initial = self.state {
self.paint_loader(self.styles.normal, 0);
} else if let State::Grown = self.state {
self.paint_loader(self.styles.normal, display::LOADER_MAX as i32);
self.paint_loader(self.styles.normal, display::LOADER_MAX as i16);
} else {
let progress = self.progress(now);
if let Some(done) = progress {
self.paint_loader(self.styles.normal, done as i32);
self.paint_loader(self.styles.normal, done as i16);
} else {
self.paint_loader(self.styles.normal, 0);
}

View File

@ -134,9 +134,9 @@ pub struct ScrollBar {
}
impl ScrollBar {
pub const WIDTH: i32 = 8;
pub const WIDTH: i16 = 8;
pub const DOT_SIZE: Offset = Offset::new(4, 4);
pub const DOT_INTERVAL: i32 = 6;
pub const DOT_INTERVAL: i16 = 6;
pub fn vertical() -> Self {
Self {
@ -205,7 +205,7 @@ impl Component for ScrollBar {
}
fn paint(&mut self) {
let count = self.page_count as i32;
let count = self.page_count as i16;
let interval = {
let available_height = self.area.height();
let naive_height = count * Self::DOT_INTERVAL;

View File

@ -78,7 +78,7 @@ impl ResultAnim {
matches!(self.progress(now), Some(display::LOADER_MAX))
}
pub fn paint_anim(&mut self, done: i32) {
pub fn paint_anim(&mut self, done: i16) {
display::rect_rounded2_partial(
self.area,
theme::FG,
@ -128,11 +128,11 @@ impl Component for ResultAnim {
if let State::Initial = self.state {
self.paint_anim(0);
} else if let State::Grown = self.state {
self.paint_anim(display::LOADER_MAX as i32);
self.paint_anim(display::LOADER_MAX as i16);
} else {
let progress = self.progress(now);
if let Some(done) = progress {
self.paint_anim(done as i32);
self.paint_anim(done as i16);
} else {
self.paint_anim(0);
}

View File

@ -29,12 +29,12 @@ pub struct ResultPopup {
autoclose: bool,
}
const ANIM_SIZE: i32 = 18;
const BUTTON_HEIGHT: i32 = 13;
const ANIM_SPACE: i32 = 11;
const ANIM_POS: i32 = 32;
const ANIM_POS_ADJ_HEADLINE: i32 = 10;
const ANIM_POS_ADJ_BUTTON: i32 = 6;
const ANIM_SIZE: i16 = 18;
const BUTTON_HEIGHT: i16 = 13;
const ANIM_SPACE: i16 = 11;
const ANIM_POS: i16 = 32;
const ANIM_POS_ADJ_HEADLINE: i16 = 10;
const ANIM_POS_ADJ_BUTTON: i16 = 6;
impl ResultPopup {
pub fn new(

View File

@ -1,9 +1,9 @@
use crate::ui::geometry::{Offset, Point, Rect};
pub const WIDTH: i32 = 128;
pub const HEIGHT: i32 = 128;
pub const LINE_SPACE: i32 = 1;
pub const FONT_BPP: i32 = 1;
pub const WIDTH: i16 = 128;
pub const HEIGHT: i16 = 128;
pub const LINE_SPACE: i16 = 1;
pub const FONT_BPP: i16 = 1;
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)

View File

@ -31,7 +31,7 @@ pub struct Button<T> {
impl<T> Button<T> {
/// Offsets the baseline of the button text either up (negative) or down
/// (positive).
pub const BASELINE_OFFSET: i32 = -3;
pub const BASELINE_OFFSET: i16 = -3;
pub fn new(content: ButtonContent<T>) -> Self {
Self {
@ -335,7 +335,7 @@ pub struct ButtonStyle {
pub background_color: Color,
pub border_color: Color,
pub border_radius: u8,
pub border_width: i32,
pub border_width: i16,
}
impl<T> Button<T> {

View File

@ -135,9 +135,9 @@ where
}
}
pub const ICON_AREA_PADDING: i32 = 2;
pub const ICON_AREA_HEIGHT: i32 = 60;
pub const VALUE_SPACE: i32 = 5;
pub const ICON_AREA_PADDING: i16 = 2;
pub const ICON_AREA_HEIGHT: i16 = 60;
pub const VALUE_SPACE: i16 = 5;
}
impl<T, U> Component for IconDialog<T, U>

View File

@ -44,7 +44,7 @@ where
type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect {
const TITLE_SPACE: i32 = theme::BUTTON_SPACING;
const TITLE_SPACE: i16 = theme::BUTTON_SPACING;
let (title_area, content_area) = bounds
.inset(self.border)
@ -105,11 +105,11 @@ where
T: Component,
U: AsRef<str>,
{
const HEIGHT: i32 = 42;
const HEIGHT: i16 = 42;
const COLOR: Color = theme::YELLOW;
const FONT: Font = theme::FONT_BOLD;
const TEXT_OFFSET: Offset = Offset::new(1, -2);
const ICON_SPACE: i32 = 8;
const ICON_SPACE: i16 = 8;
pub fn new(icon: &'static [u8], title: U, content: T) -> Self {
Self {

View File

@ -10,9 +10,9 @@ use crate::{
},
};
pub const HEADER_HEIGHT: i32 = 25;
pub const HEADER_PADDING_SIDE: i32 = 5;
pub const HEADER_PADDING_BOTTOM: i32 = 12;
pub const HEADER_HEIGHT: i16 = 25;
pub const HEADER_PADDING_SIDE: i16 = 5;
pub const HEADER_PADDING_BOTTOM: i16 = 12;
/// Contains state commonly used in implementations multi-tap keyboards.
pub struct MultiTapKeyboard {

View File

@ -30,9 +30,9 @@ const MAX_VISIBLE_DIGITS: usize = 16;
const DIGIT_COUNT: usize = 10; // 0..10
const ERASE_HOLD_DURATION: Duration = Duration::from_secs(2);
const HEADER_HEIGHT: i32 = 25;
const HEADER_PADDING_SIDE: i32 = 5;
const HEADER_PADDING_BOTTOM: i32 = 12;
const HEADER_HEIGHT: i16 = 25;
const HEADER_PADDING_SIDE: i16 = 5;
const HEADER_PADDING_BOTTOM: i16 = 12;
const HEADER_PADDING: Insets = Insets::new(
theme::borders().top,
@ -269,9 +269,9 @@ struct PinDots {
}
impl PinDots {
const DOT: i32 = 6;
const PADDING: i32 = 6;
const TWITCH: i32 = 4;
const DOT: i16 = 6;
const PADDING: i16 = 6;
const TWITCH: i16 = 4;
fn new(style: LabelStyle) -> Self {
Self {
@ -289,8 +289,8 @@ impl PinDots {
fn size(&self) -> Offset {
let ndots = self.digits.len().min(MAX_VISIBLE_DOTS);
let mut width = Self::DOT * (ndots as i32);
width += Self::PADDING * (ndots.saturating_sub(1) as i32);
let mut width = Self::DOT * (ndots as i16);
width += Self::PADDING * (ndots.saturating_sub(1) as i16);
Offset::new(width, Self::DOT)
}
@ -328,7 +328,7 @@ impl PinDots {
fn paint_digits(&self, area: Rect) {
let center = area.center() + Offset::y(theme::FONT_MONO.text_height() / 2);
let right =
center + Offset::x(theme::FONT_MONO.text_width("0") * (MAX_VISIBLE_DOTS as i32) / 2);
center + Offset::x(theme::FONT_MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2);
let digits = self.digits.len();
if digits <= MAX_VISIBLE_DOTS {

View File

@ -23,7 +23,7 @@ enum State {
}
pub struct Loader {
offset_y: i32,
offset_y: i16,
state: State,
growing_duration: Duration,
shrinking_duration: Duration,

View File

@ -194,9 +194,9 @@ pub struct PageLayout {
}
impl PageLayout {
const SCROLLBAR_WIDTH: i32 = 10;
const SCROLLBAR_SPACE: i32 = 10;
const HINT_OFF: i32 = 19;
const SCROLLBAR_WIDTH: i16 = 10;
const SCROLLBAR_SPACE: i16 = 10;
const HINT_OFF: i16 = 19;
pub fn new(area: Rect) -> Self {
let (_, hint) = area.split_bottom(Self::HINT_OFF);
@ -339,7 +339,7 @@ mod tests {
String::from_utf8(t).unwrap()
}
fn swipe(component: &mut impl Component, points: &[(i32, i32)]) {
fn swipe(component: &mut impl Component, points: &[(i16, i16)]) {
let last = points.len().saturating_sub(1);
let mut first = true;
let mut ctx = EventCtx::new();

View File

@ -15,11 +15,11 @@ pub struct ScrollBar {
}
impl ScrollBar {
pub const DOT_SIZE: i32 = 6;
pub const DOT_SIZE: i16 = 6;
/// Edge to edge.
const DOT_INTERVAL: i32 = 6;
const DOT_INTERVAL: i16 = 6;
/// Edge of last dot to center of arrow icon.
const ARROW_SPACE: i32 = 26;
const ARROW_SPACE: i16 = 26;
const ICON_UP: &'static [u8] = include_res!("model_tt/res/scroll-up.toif");
const ICON_DOWN: &'static [u8] = include_res!("model_tt/res/scroll-down.toif");

View File

@ -74,7 +74,7 @@ impl Swipe {
self.allow_up || self.allow_down || self.allow_left || self.allow_right
}
fn ratio(&self, dist: i32) -> f32 {
fn ratio(&self, dist: i16) -> f32 {
(dist as f32 / Self::DISTANCE as f32).min(1.0)
}

View File

@ -1,9 +1,9 @@
use crate::ui::geometry::{Offset, Point, Rect};
pub const WIDTH: i32 = 240;
pub const HEIGHT: i32 = 240;
pub const LINE_SPACE: i32 = 4;
pub const FONT_BPP: i32 = 4;
pub const WIDTH: i16 = 240;
pub const HEIGHT: i16 = 240;
pub const LINE_SPACE: i16 = 4;
pub const FONT_BPP: i16 = 4;
pub const fn size() -> Offset {
Offset::new(WIDTH, HEIGHT)

View File

@ -48,7 +48,7 @@ pub const RADIUS: u8 = 2;
pub const QR_SIDE_MAX: u32 = 140;
// Size of icons in the UI (i.e. inside buttons).
pub const ICON_SIZE: i32 = 16;
pub const ICON_SIZE: i16 = 16;
// UI icons (greyscale).
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif");
@ -423,16 +423,16 @@ pub const FORMATTED: FormattedFonts = FormattedFonts {
mono: FONT_MONO,
};
pub const CONTENT_BORDER: i32 = 5;
pub const KEYBOARD_SPACING: i32 = 8;
pub const BUTTON_HEIGHT: i32 = 38;
pub const BUTTON_SPACING: i32 = 6;
pub const CHECKLIST_SPACING: i32 = 10;
pub const RECOVERY_SPACING: i32 = 18;
pub const CONTENT_BORDER: i16 = 5;
pub const KEYBOARD_SPACING: i16 = 8;
pub const BUTTON_HEIGHT: i16 = 38;
pub const BUTTON_SPACING: i16 = 6;
pub const CHECKLIST_SPACING: i16 = 10;
pub const RECOVERY_SPACING: i16 = 18;
/// Standard button height in pixels.
pub const fn button_rows(count: usize) -> i32 {
let count = count as i32;
pub const fn button_rows(count: usize) -> i16 {
let count = count as i16;
BUTTON_HEIGHT * count + BUTTON_SPACING * count.saturating_sub(1)
}