mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-29 18:08:19 +00:00
TR-rust: necessary changes for TR UI
This commit is contained in:
parent
da64b21078
commit
df7af9734d
@ -20,7 +20,7 @@ use super::ffi;
|
||||
/// The `off` field represents offset from the `ptr` and allows us to do
|
||||
/// substring slices while keeping the head pointer as required by GC.
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct StrBuffer {
|
||||
ptr: *const u8,
|
||||
len: u16,
|
||||
|
@ -1,5 +1,8 @@
|
||||
use heapless::String;
|
||||
|
||||
#[cfg(feature = "model_tr")]
|
||||
use crate::ui::model_tr::component::ButtonPos;
|
||||
|
||||
/// Visitor passed into `Trace` types.
|
||||
pub trait Tracer {
|
||||
fn int(&mut self, i: i64);
|
||||
@ -27,6 +30,18 @@ pub const EMPTY_BTN: &str = "---";
|
||||
/// Value that can describe own structure and data using the `Tracer` interface.
|
||||
pub trait Trace {
|
||||
fn trace(&self, t: &mut dyn Tracer);
|
||||
/// Describes what happens when a certain button is triggered.
|
||||
#[cfg(feature = "model_tr")]
|
||||
fn get_btn_action(&self, _pos: ButtonPos) -> String<25> {
|
||||
"Default".into()
|
||||
}
|
||||
/// Report actions for all three buttons in easy-to-parse format.
|
||||
#[cfg(feature = "model_tr")]
|
||||
fn report_btn_actions(&self, t: &mut dyn Tracer) {
|
||||
t.kw_pair("left_action", &self.get_btn_action(ButtonPos::Left));
|
||||
t.kw_pair("middle_action", &self.get_btn_action(ButtonPos::Middle));
|
||||
t.kw_pair("right_action", &self.get_btn_action(ButtonPos::Right));
|
||||
}
|
||||
}
|
||||
|
||||
impl Trace for &[u8] {
|
||||
|
@ -9,3 +9,41 @@ pub fn shuffle<T>(slice: &mut [T]) {
|
||||
slice.swap(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a random number in the range [min, max].
|
||||
pub fn uniform_between(min: u32, max: u32) -> u32 {
|
||||
assert!(max > min);
|
||||
uniform(max - min + 1) + min
|
||||
}
|
||||
|
||||
/// Returns a random number in the range [min, max] except one `except` number.
|
||||
pub fn uniform_between_except(min: u32, max: u32, except: u32) -> u32 {
|
||||
// Generate uniform_between as long as it is not except
|
||||
loop {
|
||||
let rand = uniform_between(min, max);
|
||||
if rand != except {
|
||||
return rand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn uniform_between_test() {
|
||||
for _ in 0..10 {
|
||||
assert!((10..=11).contains(&uniform_between(10, 11)));
|
||||
assert!((10..=12).contains(&uniform_between(10, 12)));
|
||||
assert!((256..=512).contains(&uniform_between(256, 512)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uniform_between_except_test() {
|
||||
for _ in 0..10 {
|
||||
assert!(uniform_between_except(10, 12, 11) != 11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,10 @@ impl<T> Child<T> {
|
||||
self.component
|
||||
}
|
||||
|
||||
pub fn inner_mut(&mut self) -> &mut T {
|
||||
&mut self.component
|
||||
}
|
||||
|
||||
/// Access inner component mutably, track whether a paint call has been
|
||||
/// requested, and propagate the flag upwards the component tree.
|
||||
pub fn mutate<F, U>(&mut self, ctx: &mut EventCtx, component_func: F) -> U
|
||||
|
@ -54,6 +54,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: T) {
|
||||
self.text = text;
|
||||
}
|
||||
|
||||
pub fn start(&mut self, ctx: &mut EventCtx, now: Instant) {
|
||||
// Not starting if animations are disabled.
|
||||
if animation_disabled() {
|
||||
|
@ -1,8 +1,3 @@
|
||||
use crate::ui::component::{
|
||||
text::layout::{LayoutFit, TextNoOp},
|
||||
FormattedText,
|
||||
};
|
||||
|
||||
pub enum AuxPageMsg {
|
||||
/// Page component was instantiated with BACK button on every page and it
|
||||
/// was pressed.
|
||||
@ -34,61 +29,3 @@ pub trait Paginate {
|
||||
/// Navigate to the given page.
|
||||
fn change_page(&mut self, active_page: usize);
|
||||
}
|
||||
|
||||
impl<F, T> Paginate for FormattedText<F, T>
|
||||
where
|
||||
F: AsRef<str>,
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn page_count(&mut self) -> usize {
|
||||
let mut page_count = 1; // There's always at least one page.
|
||||
let mut char_offset = 0;
|
||||
|
||||
loop {
|
||||
let fit = self.layout_content(&mut TextNoOp);
|
||||
match fit {
|
||||
LayoutFit::Fitting { .. } => {
|
||||
break; // TODO: We should consider if there's more content
|
||||
// to render.
|
||||
}
|
||||
LayoutFit::OutOfBounds {
|
||||
processed_chars, ..
|
||||
} => {
|
||||
page_count += 1;
|
||||
char_offset += processed_chars;
|
||||
self.set_char_offset(char_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the char offset back to the beginning.
|
||||
self.set_char_offset(0);
|
||||
|
||||
page_count
|
||||
}
|
||||
|
||||
fn change_page(&mut self, to_page: usize) {
|
||||
let mut active_page = 0;
|
||||
let mut char_offset = 0;
|
||||
|
||||
// Make sure we're starting from the beginning.
|
||||
self.set_char_offset(char_offset);
|
||||
|
||||
while active_page < to_page {
|
||||
let fit = self.layout_content(&mut TextNoOp);
|
||||
match fit {
|
||||
LayoutFit::Fitting { .. } => {
|
||||
break; // TODO: We should consider if there's more content
|
||||
// to render.
|
||||
}
|
||||
LayoutFit::OutOfBounds {
|
||||
processed_chars, ..
|
||||
} => {
|
||||
active_page += 1;
|
||||
char_offset += processed_chars;
|
||||
self.set_char_offset(char_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +354,7 @@ impl TextLayout {
|
||||
}
|
||||
|
||||
/// Overall height of the content, including paddings.
|
||||
fn layout_height(&self, init_cursor: Point, end_cursor: Point) -> i16 {
|
||||
pub 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)
|
||||
@ -562,7 +562,7 @@ pub struct Span {
|
||||
}
|
||||
|
||||
impl Span {
|
||||
fn fit_horizontally(
|
||||
pub fn fit_horizontally(
|
||||
text: &str,
|
||||
max_width: i16,
|
||||
text_font: impl GlyphMetrics,
|
||||
|
@ -625,6 +625,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Paginate for Checklist<T>
|
||||
where
|
||||
T: ParagraphSource,
|
||||
{
|
||||
fn page_count(&mut self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn change_page(&mut self, _to_page: usize) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: ParagraphSource> crate::trace::Trace for Checklist<T> {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -16,6 +16,9 @@ use super::{
|
||||
};
|
||||
use crate::{micropython::buffer::StrBuffer, time::Duration};
|
||||
|
||||
#[cfg(feature = "model_tr")]
|
||||
use super::model_tr::component::ButtonDetails;
|
||||
|
||||
// NOTE: not defining a common trait, like
|
||||
// Debug {fn print(&self);}, so that the trait does
|
||||
// not need to be imported when using the
|
||||
@ -97,6 +100,33 @@ impl Font {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "model_tr")]
|
||||
impl ButtonDetails {
|
||||
pub fn print(&self) {
|
||||
let text: String<20> = if let Some(text) = self.text {
|
||||
text.as_ref().into()
|
||||
} else {
|
||||
"None".into()
|
||||
};
|
||||
let force_width: String<20> = if let Some(force_width) = self.force_width {
|
||||
inttostr!(force_width).into()
|
||||
} else {
|
||||
"None".into()
|
||||
};
|
||||
println!(
|
||||
"ButtonDetails:: ",
|
||||
"text: ",
|
||||
text.as_ref(),
|
||||
", with_outline: ",
|
||||
booltostr!(self.with_outline),
|
||||
", with_arms: ",
|
||||
booltostr!(self.with_arms),
|
||||
", force_width: ",
|
||||
force_width.as_ref()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Offset {
|
||||
pub fn print(&self) {
|
||||
println!(
|
||||
|
@ -5,6 +5,8 @@ pub mod loader;
|
||||
pub mod tjpgd;
|
||||
pub mod toif;
|
||||
|
||||
use heapless::String;
|
||||
|
||||
use super::{
|
||||
constant,
|
||||
geometry::{Offset, Point, Rect},
|
||||
@ -126,16 +128,16 @@ pub fn rect_fill_corners(r: Rect, fg_color: Color) {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TextOverlay<'a> {
|
||||
pub struct TextOverlay<T> {
|
||||
area: Rect,
|
||||
text: &'a str,
|
||||
text: T,
|
||||
font: Font,
|
||||
max_height: i16,
|
||||
baseline: i16,
|
||||
}
|
||||
|
||||
impl<'a> TextOverlay<'a> {
|
||||
pub fn new(text: &'a str, font: Font) -> Self {
|
||||
impl<T: AsRef<str>> TextOverlay<T> {
|
||||
pub fn new(text: T, font: Font) -> Self {
|
||||
let area = Rect::zero();
|
||||
|
||||
Self {
|
||||
@ -147,8 +149,17 @@ impl<'a> TextOverlay<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: T) {
|
||||
self.text = text;
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> &T {
|
||||
&self.text
|
||||
}
|
||||
|
||||
// baseline relative to the underlying render area
|
||||
pub fn place(&mut self, baseline: Point) {
|
||||
let text_width = self.font.text_width(self.text);
|
||||
let text_width = self.font.text_width(self.text.as_ref());
|
||||
let text_height = self.font.text_height();
|
||||
|
||||
let text_area_start = baseline + Offset::new(-(text_width / 2), -text_height);
|
||||
@ -167,7 +178,12 @@ impl<'a> TextOverlay<'a> {
|
||||
|
||||
let p_rel = Point::new(p.x - self.area.x0, p.y - self.area.y0);
|
||||
|
||||
for g in self.text.bytes().filter_map(|c| self.font.get_glyph(c)) {
|
||||
for g in self
|
||||
.text
|
||||
.as_ref()
|
||||
.bytes()
|
||||
.filter_map(|c| self.font.get_glyph(c))
|
||||
{
|
||||
let top = self.max_height - self.baseline - g.bearing_y;
|
||||
let char_area = Rect::new(
|
||||
Point::new(tot_adv + g.bearing_x, top),
|
||||
@ -756,9 +772,9 @@ fn rect_rounded2_get_pixel(
|
||||
/// Optionally draws a text inside the rectangle and adjusts its color to match
|
||||
/// the fill. The coordinates of the text are specified in the TextOverlay
|
||||
/// struct.
|
||||
pub fn bar_with_text_and_fill(
|
||||
pub fn bar_with_text_and_fill<T: AsRef<str>>(
|
||||
area: Rect,
|
||||
overlay: Option<TextOverlay>,
|
||||
overlay: Option<&TextOverlay<T>>,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
fill_from: i16,
|
||||
@ -836,6 +852,59 @@ pub fn paint_point(point: &Point, color: Color) {
|
||||
display::bar(point.x, point.y, 1, 1, color.into());
|
||||
}
|
||||
|
||||
/// Draws longer multiline texts inside an area.
|
||||
/// Does not add any characters on the line boundaries.
|
||||
///
|
||||
/// If it fits, returns the rest of the area.
|
||||
/// If it does not fit, returns `None`.
|
||||
pub fn text_multiline(
|
||||
area: Rect,
|
||||
text: &str,
|
||||
font: Font,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
) -> Option<Rect> {
|
||||
let line_height = font.line_height();
|
||||
let characters_overall = text.chars().count();
|
||||
let mut taken_from_top = 0;
|
||||
let mut characters_drawn = 0;
|
||||
'lines: loop {
|
||||
let baseline = area.top_left() + Offset::y(line_height + taken_from_top);
|
||||
if !area.contains(baseline) {
|
||||
// The whole area was consumed.
|
||||
return None;
|
||||
}
|
||||
let mut line_text: String<50> = String::new();
|
||||
'characters: loop {
|
||||
if let Some(character) = text.chars().nth(characters_drawn) {
|
||||
characters_drawn += 1;
|
||||
if character == '\n' {
|
||||
// The line is forced to end.
|
||||
break 'characters;
|
||||
}
|
||||
unwrap!(line_text.push(character));
|
||||
} else {
|
||||
// No more characters to draw.
|
||||
break 'characters;
|
||||
}
|
||||
if font.text_width(&line_text) > area.width() {
|
||||
// Cannot fit on the line anymore.
|
||||
line_text.pop();
|
||||
characters_drawn -= 1;
|
||||
break 'characters;
|
||||
}
|
||||
}
|
||||
text_left(baseline, &line_text, font, fg_color, bg_color);
|
||||
taken_from_top += line_height;
|
||||
if characters_drawn == characters_overall {
|
||||
// No more lines to draw.
|
||||
break 'lines;
|
||||
}
|
||||
}
|
||||
// Some of the area was unused and is free to draw some further text.
|
||||
Some(area.split_top(taken_from_top).1)
|
||||
}
|
||||
|
||||
/// Display text left-aligned to a certain Point
|
||||
pub fn text_left(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
||||
display::text(
|
||||
|
@ -9,8 +9,14 @@ pub enum PhysicalButton {
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ButtonEvent {
|
||||
/// Button pressed down.
|
||||
/// ▼ * | * ▼
|
||||
ButtonPressed(PhysicalButton),
|
||||
/// Button released up.
|
||||
/// ▲ * | * ▲
|
||||
ButtonReleased(PhysicalButton),
|
||||
HoldStarted,
|
||||
HoldEnded,
|
||||
}
|
||||
|
||||
impl ButtonEvent {
|
||||
|
@ -239,6 +239,21 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_top_right_and_size(p0: Point, size: Offset) -> Self {
|
||||
let top_left = Point::new(p0.x - size.x, p0.y);
|
||||
Self::from_top_left_and_size(top_left, size)
|
||||
}
|
||||
|
||||
pub const fn from_bottom_left_and_size(p0: Point, size: Offset) -> Self {
|
||||
let top_left = Point::new(p0.x, p0.y - size.y);
|
||||
Self::from_top_left_and_size(top_left, size)
|
||||
}
|
||||
|
||||
pub const fn from_bottom_right_and_size(p0: Point, size: Offset) -> Self {
|
||||
let top_left = Point::new(p0.x - size.x, p0.y - size.y);
|
||||
Self::from_top_left_and_size(top_left, size)
|
||||
}
|
||||
|
||||
pub const fn from_center_and_size(p: Point, size: Offset) -> Self {
|
||||
let x0 = p.x - size.x / 2;
|
||||
let y0 = p.y - size.y / 2;
|
||||
@ -304,6 +319,14 @@ impl Rect {
|
||||
self.bottom_left().center(self.bottom_right())
|
||||
}
|
||||
|
||||
pub const fn left_center(&self) -> Point {
|
||||
self.bottom_left().center(self.top_left())
|
||||
}
|
||||
|
||||
pub const fn right_center(&self) -> Point {
|
||||
self.bottom_right().center(self.top_right())
|
||||
}
|
||||
|
||||
/// Whether a `Point` is inside the `Rect`.
|
||||
pub const fn contains(&self, point: Point) -> bool {
|
||||
point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
|
||||
@ -364,6 +387,26 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the `Rect` wider to the left side.
|
||||
pub const fn extend_left(&self, width: i16) -> Self {
|
||||
Self {
|
||||
x0: self.x0 - width,
|
||||
y0: self.y0,
|
||||
x1: self.x1,
|
||||
y1: self.y1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the `Rect` wider to the right side.
|
||||
pub const fn extend_right(&self, width: i16) -> Self {
|
||||
Self {
|
||||
x0: self.x0,
|
||||
y0: self.y0,
|
||||
x1: self.x1 + width,
|
||||
y1: self.y1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Split `Rect` into top and bottom, given the top one's `height`.
|
||||
pub const fn split_top(self, height: i16) -> (Self, Self) {
|
||||
let height = clamp(height, 0, self.height());
|
||||
@ -404,6 +447,16 @@ impl Rect {
|
||||
self.split_left(self.width() - width)
|
||||
}
|
||||
|
||||
/// Split `Rect` into left, center and right, given the center one's
|
||||
/// `width`. Center element is symmetric, left and right have the same
|
||||
/// size.
|
||||
pub const fn split_center(self, width: i16) -> (Self, Self, Self) {
|
||||
let left_right_width = (self.width() - width) / 2;
|
||||
let (left, center_right) = self.split_left(left_right_width);
|
||||
let (center, right) = center_right.split_left(width);
|
||||
(left, center, right)
|
||||
}
|
||||
|
||||
pub const fn clamp(self, limit: Rect) -> Self {
|
||||
Self {
|
||||
x0: max(self.x0, limit.x0),
|
||||
|
@ -40,6 +40,16 @@ pub fn iter_into_objs<const N: usize>(iterable: Obj) -> Result<[Obj; N], Error>
|
||||
}
|
||||
|
||||
pub fn iter_into_array<T, const N: usize>(iterable: Obj) -> Result<[T; N], Error>
|
||||
where
|
||||
T: TryFrom<Obj, Error = Error>,
|
||||
{
|
||||
let err = Error::ValueError(cstr!("Invalid iterable length"));
|
||||
let vec: Vec<T, N> = iter_into_vec(iterable)?;
|
||||
// Returns error if array.len() != N
|
||||
vec.into_array().map_err(|_| err)
|
||||
}
|
||||
|
||||
pub fn iter_into_vec<T, const N: usize>(iterable: Obj) -> Result<Vec<T, N>, Error>
|
||||
where
|
||||
T: TryFrom<Obj, Error = Error>,
|
||||
{
|
||||
@ -49,8 +59,7 @@ where
|
||||
for item in Iter::try_from_obj_with_buf(iterable, &mut iter_buf)? {
|
||||
vec.push(item.try_into()?).map_err(|_| err)?;
|
||||
}
|
||||
// Returns error if array.len() != N
|
||||
vec.into_array().map_err(|_| err)
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
/// Maximum number of characters that can be displayed on screen at once. Used
|
||||
|
@ -24,3 +24,15 @@ macro_rules! inttostr {
|
||||
heapless::String::<10>::from($int).as_str()
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)] // Mostly for debugging purposes.
|
||||
/// Transforms bool into string slice. For example for printing.
|
||||
macro_rules! booltostr {
|
||||
($bool:expr) => {{
|
||||
if $bool {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
@ -142,6 +142,13 @@ pub fn icon_text_center(
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert char to a String of chosen length.
|
||||
pub fn char_to_string<const L: usize>(ch: char) -> String<L> {
|
||||
let mut s = String::new();
|
||||
unwrap!(s.push(ch));
|
||||
s
|
||||
}
|
||||
|
||||
/// Returns text to be fit on one line of a given length.
|
||||
/// When the text is too long to fit, it is truncated with ellipsis
|
||||
/// on the left side.
|
||||
|
Loading…
Reference in New Issue
Block a user