feat(core/rust): JSON output from component tracing

pull/3011/head
matejcik 1 year ago committed by Martin Milata
parent b63b72ed90
commit eee4c624f9

@ -113,6 +113,12 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -241,12 +247,35 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
[[package]]
name = "serde_json"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.1.0"
@ -292,6 +321,7 @@ dependencies = [
"num-derive",
"num-traits",
"qrcodegen",
"serde_json",
]
[[package]]

@ -88,3 +88,6 @@ version = "1.0.69"
[build-dependencies.glob]
optional = true
version = "0.3.0"
[dev-dependencies]
serde_json = "1.0.96"

@ -13,6 +13,7 @@ mod error;
// use trezorhal for its macros early
#[macro_use]
mod trezorhal;
mod maybe_trace;
#[cfg(feature = "micropython")]
#[macro_use]
mod micropython;

@ -0,0 +1,15 @@
#[cfg(feature = "ui_debug")]
mod maybe_trace {
use crate::trace::Trace;
pub trait MaybeTrace: Trace {}
impl<T> MaybeTrace for T where T: Trace {}
}
#[cfg(not(feature = "ui_debug"))]
mod maybe_trace {
pub trait MaybeTrace {}
impl<T> MaybeTrace for T {}
}
pub use maybe_trace::MaybeTrace;

@ -253,10 +253,3 @@ pub fn hexlify_bytes(obj: Obj, offset: usize, max_len: usize) -> Result<StrBuffe
let result = StrBuffer::alloc_with(hex_len, move |buffer| hexlify(bin_slice, buffer))?;
Ok(result.offset(hex_off))
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for StrBuffer {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
self.as_ref().trace(t)
}
}

@ -188,9 +188,15 @@ mod tests {
let list = unsafe { Gc::as_mut(&mut gc_list) };
for (i, value) in vec.iter().copied().enumerate() {
assert_eq!(value, list.get(i).unwrap().try_into().unwrap());
assert_eq!(
value,
TryInto::<u16>::try_into(list.get(i).unwrap()).unwrap()
);
list.set(i, Obj::from(value + 1)).unwrap();
assert_eq!(value + 1, list.get(i).unwrap().try_into().unwrap());
assert_eq!(
value + 1,
TryInto::<u16>::try_into(list.get(i).unwrap()).unwrap()
);
}
let mut buf = IterBuf::new();
@ -215,7 +221,7 @@ mod tests {
let slice = unsafe { list.as_slice() };
assert_eq!(slice.len(), vec.len());
for i in 0..slice.len() {
assert_eq!(vec[i], slice[i].try_into().unwrap());
assert_eq!(vec[i], TryInto::<u16>::try_into(slice[i]).unwrap());
}
}
@ -228,7 +234,7 @@ mod tests {
let slice = unsafe { Gc::as_mut(&mut list).as_mut_slice() };
assert_eq!(slice.len(), vec.len());
assert_eq!(vec[0], slice[0].try_into().unwrap());
assert_eq!(vec[0], TryInto::<u16>::try_into(slice[0]).unwrap());
for i in 0..slice.len() {
slice[i] = ((i + 10) as u16).into();

@ -9,3 +9,34 @@ pub fn hexlify(data: &[u8], buffer: &mut [u8]) {
i += 2;
}
}
pub fn format_i64(num: i64, buffer: &mut [u8]) -> Option<&str> {
let mut i = 0;
let mut num = num;
let negative = if num < 0 {
num = -num;
true
} else {
false
};
while num > 0 && i < buffer.len() {
buffer[i] = b'0' + ((num % 10) as u8);
num /= 10;
i += 1;
}
match i {
0 => Some("0"),
_ if num > 0 => None,
_ if negative && i == buffer.len() => None,
_ => {
if negative {
buffer[i] = b'-';
i += 1;
}
let result = &mut buffer[..i];
result.reverse();
Some(unsafe { core::str::from_utf8_unchecked(result) })
}
}
}

@ -1,91 +1,212 @@
/// Visitor passed into `Trace` types.
use crate::strutil::format_i64;
pub trait Tracer {
fn child(&mut self, key: &str, value: &dyn Trace);
fn int(&mut self, key: &str, i: i64);
fn string(&mut self, key: &str, s: &str);
fn bool(&mut self, key: &str, b: bool);
fn in_child(&mut self, key: &str, block: &dyn Fn(&mut dyn Tracer));
fn in_list(&mut self, key: &str, block: &dyn Fn(&mut dyn ListTracer));
fn component(&mut self, name: &str) {
self.string("component", name);
}
}
pub trait ListTracer {
fn child(&mut self, value: &dyn Trace);
fn int(&mut self, i: i64);
fn bytes(&mut self, b: &[u8]);
fn string(&mut self, s: &str);
fn symbol(&mut self, name: &str);
fn open(&mut self, name: &str);
fn field(&mut self, name: &str, value: &dyn Trace);
fn close(&mut self);
fn bool(&mut self, b: bool);
fn in_child(&mut self, block: &dyn Fn(&mut dyn Tracer));
fn in_list(&mut self, block: &dyn Fn(&mut dyn ListTracer));
}
/// Value that can describe own structure and data using the `Tracer` interface.
pub trait Trace {
fn trace(&self, d: &mut dyn Tracer);
/// Generic tracer based on a TraceWriter.
pub struct JsonTracer<F: FnMut(&str)> {
write_fn: F,
write_buf: [u8; 32],
buf_pos: usize,
first: bool,
}
impl Trace for &[u8] {
fn trace(&self, t: &mut dyn Tracer) {
t.bytes(self);
impl<F: FnMut(&str)> JsonTracer<F> {
pub fn new(write_fn: F) -> Self {
Self {
write_fn,
write_buf: [0; 32],
buf_pos: 0,
first: true,
}
}
}
impl<const N: usize> Trace for &[u8; N] {
fn trace(&self, t: &mut dyn Tracer) {
t.bytes(&self[..])
fn maybe_comma(&mut self) {
if !self.first {
(self.write_fn)(", ");
}
self.first = false;
}
}
impl Trace for &str {
fn trace(&self, t: &mut dyn Tracer) {
t.string(self);
fn write_int(&mut self, i: i64) {
let s = format_i64(i, &mut self.write_buf).unwrap_or("\"###NUMERR###\"");
(self.write_fn)(s);
}
}
impl Trace for usize {
fn trace(&self, t: &mut dyn Tracer) {
t.int(*self as i64);
// SAFETY: there must be a valid utf8 string in write_buf[..buf_pos]
unsafe fn flush_buf(&mut self) {
if self.buf_pos == 0 {
return;
}
let substr = &self.write_buf[..self.buf_pos];
let s = unsafe { core::str::from_utf8_unchecked(substr) };
(self.write_fn)(s);
self.buf_pos = 0;
}
}
impl<T> Trace for Option<T>
where
T: Trace,
{
fn trace(&self, d: &mut dyn Tracer) {
match self {
Some(v) => v.trace(d),
None => d.symbol("None"),
// SAFETY: there must be a valid utf8 string in write_buf[..buf_pos].
// Given that there is valid utf8 at start, there will be valid utf8 when done.
unsafe fn push_or_flush(&mut self, ch: char) {
let size = ch.len_utf8();
if self.buf_pos + size > self.write_buf.len() {
unsafe { self.flush_buf() };
}
ch.encode_utf8(&mut self.write_buf[self.buf_pos..]);
self.buf_pos += size;
}
fn write_str_quoted(&mut self, s: &str) {
self.buf_pos = 0;
// SAFETY: we clear the writebuf by resetting buf_pos to 0, so its contents
// are only affected by push_or_flush, which keeps contents of write_buf
// correct.
unsafe {
self.push_or_flush('"');
for mut ch in s.chars() {
if ch == '\\' || ch == '"' {
self.push_or_flush('\\');
} else if ch == '\n' {
self.push_or_flush('\\');
ch = 'n';
}
self.push_or_flush(ch);
}
self.push_or_flush('"');
self.flush_buf();
}
}
fn key(&mut self, key: &str) {
self.maybe_comma();
self.write_str_quoted(key);
(self.write_fn)(": ");
}
pub fn root(&mut self, block: &dyn Fn(&mut dyn Tracer)) {
(self.write_fn)("{");
block(self);
(self.write_fn)("}");
}
}
#[cfg(test)]
mod tests {
use super::*;
impl<F: FnMut(&str)> ListTracer for JsonTracer<F> {
fn child(&mut self, value: &dyn Trace) {
ListTracer::in_child(self, &mut |t| value.trace(t));
}
impl Tracer for Vec<u8> {
fn int(&mut self, i: i64) {
self.string(&i.to_string());
}
fn int(&mut self, i: i64) {
self.maybe_comma();
self.write_int(i);
}
fn bytes(&mut self, b: &[u8]) {
self.extend(b)
}
fn string(&mut self, s: &str) {
self.maybe_comma();
self.write_str_quoted(s);
}
fn string(&mut self, s: &str) {
self.extend(s.as_bytes())
}
fn bool(&mut self, b: bool) {
self.maybe_comma();
(self.write_fn)(if b { "true" } else { "false" });
}
fn symbol(&mut self, name: &str) {
self.extend(name.as_bytes())
}
fn in_child(&mut self, block: &dyn Fn(&mut dyn Tracer)) {
self.maybe_comma();
self.first = true;
(self.write_fn)("{");
block(self);
(self.write_fn)("}");
self.first = false;
}
fn open(&mut self, name: &str) {
self.extend(b"<");
self.extend(name.as_bytes());
self.extend(b" ");
}
fn in_list(&mut self, block: &dyn Fn(&mut dyn ListTracer)) {
self.maybe_comma();
self.first = true;
(self.write_fn)("[");
block(self);
(self.write_fn)("]");
self.first = false;
}
}
fn field(&mut self, name: &str, value: &dyn Trace) {
self.extend(name.as_bytes());
self.extend(b":");
value.trace(self);
self.extend(b" ");
}
impl<F: FnMut(&str)> Tracer for JsonTracer<F> {
fn child(&mut self, key: &str, value: &dyn Trace) {
Tracer::in_child(self, key, &|t| value.trace(t));
}
fn close(&mut self) {
self.extend(b">")
}
fn int(&mut self, key: &str, i: i64) {
self.key(key);
self.write_int(i);
}
fn string(&mut self, key: &str, s: &str) {
self.key(key);
self.write_str_quoted(s);
}
fn bool(&mut self, key: &str, b: bool) {
self.key(key);
(self.write_fn)(if b { "true" } else { "false" });
}
fn in_child(&mut self, key: &str, block: &dyn Fn(&mut dyn Tracer)) {
self.key(key);
(self.write_fn)("{");
self.first = true;
block(self);
(self.write_fn)("}");
self.first = false;
}
fn in_list(&mut self, key: &str, block: &dyn Fn(&mut dyn ListTracer)) {
self.key(key);
(self.write_fn)("[");
self.first = true;
block(self);
(self.write_fn)("]");
self.first = false;
}
}
/// Value that can describe own structure and data using the `Tracer`
/// interface.
pub trait Trace {
fn trace(&self, t: &mut dyn Tracer);
}
#[cfg(test)]
pub mod tests {
extern crate serde_json;
use serde_json::Value;
use super::*;
pub fn trace(val: &impl Trace) -> Value {
let mut buf = Vec::new();
let mut tracer = JsonTracer::new(|text| buf.extend_from_slice(text.as_bytes()));
tracer.root(&|t| val.trace(t));
let s = String::from_utf8(buf).unwrap();
//crate::micropython::print::print(s.as_str());
s.parse().unwrap()
}
}

@ -211,16 +211,14 @@ where
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for (T, U)
where
T: Component,
T: crate::trace::Trace,
U: Component,
U: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Tuple");
d.field("0", &self.0);
d.field("1", &self.1);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.in_list("children", &mut |l| {
l.child(&self.0);
l.child(&self.1);
});
}
}
@ -260,25 +258,6 @@ where
}
}
#[cfg(feature = "ui_debug")]
impl<T, U, V> crate::trace::Trace for (T, U, V)
where
T: Component,
T: crate::trace::Trace,
U: Component,
U: crate::trace::Trace,
V: Component,
V: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Tuple");
d.field("0", &self.0);
d.field("1", &self.1);
d.field("2", &self.2);
d.close();
}
}
impl<T> Component for Option<T>
where
T: Component,

@ -20,7 +20,6 @@ impl Component for Empty {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Empty {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Empty");
t.close();
t.component("Empty");
}
}

@ -63,8 +63,7 @@ impl Component for Image {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Image {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Image");
t.close();
t.component("Image");
}
}
@ -139,7 +138,6 @@ impl Component for BlendedImage {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for BlendedImage {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("BlendedImage");
t.close();
t.component("BlendedImage");
}
}

@ -117,6 +117,7 @@ where
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.string(self.text.as_ref())
t.component("Label");
t.string("text", self.text.as_ref());
}
}

@ -232,9 +232,8 @@ impl<T> crate::trace::Trace for Marquee<T>
where
T: AsRef<str>,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Marquee");
d.field("text", &self.text.as_ref());
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Marquee");
t.string("text", self.text.as_ref());
}
}

@ -48,7 +48,7 @@ where
#[cfg(feature = "ui_debug")]
impl<F> crate::trace::Trace for Painter<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.string("Painter")
t.component("Painter");
}
}

@ -71,10 +71,9 @@ where
T: Component,
T: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("GridPlaced");
d.field("inner", &self.inner);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("GridPlaced");
t.child("inner", &self.inner);
}
}
@ -115,10 +114,9 @@ where
T: Component,
T: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("FixedHeightBar");
d.field("inner", &self.inner);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("FixedHeightBar");
t.child("inner", &self.inner);
}
}
@ -188,10 +186,9 @@ where
T: Component,
T: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Floating");
d.field("inner", &self.inner);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Floating");
t.child("inner", &self.inner);
}
}
@ -277,13 +274,12 @@ where
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Split<T, U>
where
T: Component + crate::trace::Trace,
U: Component + crate::trace::Trace,
T: crate::trace::Trace,
U: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Split");
d.field("first", &self.first);
d.field("second", &self.second);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Split");
t.child("first", &self.first);
t.child("second", &self.second);
}
}

@ -150,8 +150,7 @@ impl Component for Qr {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Qr {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Qr");
t.field("text", &self.text.as_str());
t.close();
t.component("Qr");
t.string("text", self.text.as_ref());
}
}

@ -144,25 +144,6 @@ where
}
}
#[cfg(feature = "ui_debug")]
pub mod trace {
use crate::ui::component::text::layout::trace::TraceSink;
use super::*;
pub struct TraceText<'a, F, T>(pub &'a FormattedText<F, T>);
impl<'a, F, T> crate::trace::Trace for TraceText<'a, F, T>
where
F: AsRef<str>,
T: AsRef<str>,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
self.0.layout_content(&mut TraceSink(d));
}
}
}
#[cfg(feature = "ui_debug")]
impl<F, T> crate::trace::Trace for FormattedText<F, T>
where
@ -170,9 +151,15 @@ where
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Text");
t.field("content", &trace::TraceText(self));
t.close();
use crate::ui::component::text::layout::trace::TraceSink;
use core::cell::Cell;
let fit: Cell<Option<LayoutFit>> = Cell::new(None);
t.component("FormattedText");
t.in_list("text", &mut |l| {
let result = self.layout_content(&mut TraceSink(l));
fit.set(Some(result));
});
t.bool("fits", matches!(fit.get(), Some(LayoutFit::Fitting { .. })));
}
}

@ -445,31 +445,32 @@ impl LayoutSink for TextRenderer {
#[cfg(feature = "ui_debug")]
pub mod trace {
use crate::ui::geometry::Point;
use crate::{trace::ListTracer, ui::geometry::Point};
use super::*;
pub struct TraceSink<'a>(pub &'a mut dyn crate::trace::Tracer);
/// `LayoutSink` for debugging purposes.
pub struct TraceSink<'a>(pub &'a mut dyn ListTracer);
impl<'a> LayoutSink for TraceSink<'a> {
impl LayoutSink for TraceSink<'_> {
fn text(&mut self, _cursor: Point, _layout: &TextLayout, text: &str) {
self.0.string(text);
self.0.string(&text);
}
fn hyphen(&mut self, _cursor: Point, _layout: &TextLayout) {
self.0.string("-");
self.0.string(&"-");
}
fn ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
self.0.string("...");
self.0.string(&ELLIPSIS);
}
fn prev_page_ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {
self.0.string("...");
self.0.string(&ELLIPSIS);
}
fn line_break(&mut self, _cursor: Point) {
self.0.string("\n");
self.0.string(&"\n");
}
}
}

@ -238,17 +238,23 @@ pub mod trace {
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Paragraphs");
Self::foreach_visible(
&self.source,
&self.visible,
self.offset,
&mut |layout, content| {
layout.layout_text(content, &mut layout.initial_cursor(), &mut TraceSink(t));
t.string("\n");
},
);
t.close();
t.string("component", "Paragraphs");
t.in_list("paragraphs", &mut |par_list| {
Self::foreach_visible(
&self.source,
&self.visible,
self.offset,
&mut |layout, content| {
par_list.in_list(&mut |par| {
layout.layout_text(
content,
&mut layout.initial_cursor(),
&mut TraceSink(par),
);
});
},
);
});
}
}
}
@ -606,10 +612,9 @@ where
#[cfg(feature = "ui_debug")]
impl<T: ParagraphSource> crate::trace::Trace for Checklist<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Checklist");
t.field("current", &self.current);
t.field("items", &self.paragraphs);
t.close();
t.component("Checklist");
t.int("current", self.current as i64);
t.child("items", &self.paragraphs);
}
}

@ -53,8 +53,7 @@ impl Component for Timeout {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Timeout {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Timeout");
t.int(self.time_ms as i64);
t.close();
t.component("Timeout");
t.int("time_ms", self.time_ms as i64);
}
}

@ -5,6 +5,7 @@ use core::{
use crate::{
error::Error,
maybe_trace::MaybeTrace,
micropython::{
buffer::StrBuffer,
gc::Gc,
@ -35,24 +36,6 @@ pub trait ComponentMsgObj: Component {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error>;
}
#[cfg(feature = "ui_debug")]
mod maybe_trace {
pub trait MaybeTrace: crate::trace::Trace {}
impl<T> MaybeTrace for T where T: crate::trace::Trace {}
}
#[cfg(not(feature = "ui_debug"))]
mod maybe_trace {
pub trait MaybeTrace {}
impl<T> MaybeTrace for T {}
}
/// Stand-in for the optionally-compiled trait `Trace`.
/// If UI debugging is enabled, `MaybeTrace` implies `Trace` and is implemented
/// for everything that implements Trace. If disabled, `MaybeTrace` is
/// implemented for everything.
use maybe_trace::MaybeTrace;
/// Object-safe interface between trait `Component` and MicroPython world. It
/// converts the result of `Component::event` into `Obj` via the
/// `ComponentMsgObj` trait, in order to easily return the value to Python. It
@ -213,55 +196,23 @@ impl LayoutObj {
/// raises an exception.
#[cfg(feature = "ui_debug")]
fn obj_trace(&self, callback: Obj) {
use crate::trace::{Trace, Tracer};
struct CallbackTracer(Obj);
impl Tracer for CallbackTracer {
fn int(&mut self, i: i64) {
self.0.call_with_n_args(&[i.try_into().unwrap()]).unwrap();
}
fn bytes(&mut self, b: &[u8]) {
self.0.call_with_n_args(&[b.try_into().unwrap()]).unwrap();
}
fn string(&mut self, s: &str) {
self.0.call_with_n_args(&[s.try_into().unwrap()]).unwrap();
}
fn symbol(&mut self, name: &str) {
self.0
.call_with_n_args(&[
"<".try_into().unwrap(),
name.try_into().unwrap(),
">".try_into().unwrap(),
])
.unwrap();
}
fn open(&mut self, name: &str) {
self.0
.call_with_n_args(&["<".try_into().unwrap(), name.try_into().unwrap()])
.unwrap();
}
fn field(&mut self, name: &str, value: &dyn Trace) {
self.0
.call_with_n_args(&[name.try_into().unwrap(), ": ".try_into().unwrap()])
.unwrap();
value.trace(self);
}
fn close(&mut self) {
self.0.call_with_n_args(&[">".try_into().unwrap()]).unwrap();
}
}
self.inner
.borrow()
.root
.trace(&mut CallbackTracer(callback));
use crate::trace::JsonTracer;
let mut tracer = JsonTracer::new(|text: &str| {
unwrap!(callback.call_with_n_args(&[unwrap!(text.try_into())]));
});
// For Reasons(tm), we must pass a closure in which we call `root.trace(t)`,
// instead of passing `root` into the tracer.
// (The Reasons being, root is a `Gc<dyn ObjComponent>`, and `Gc` does not
// implement `Trace`, and `dyn ObjComponent` is unsized so we can't deref it to
// claim that it implements `Trace`, and we also can't upcast it to `&dyn Trace`
// because trait upcasting is unstable.
// Luckily, calling `root.trace()` works perfectly fine in spite of the above.)
tracer.root(&|t| {
self.inner.borrow().root.trace(t);
});
}
fn obj_page_count(&self) -> Obj {

@ -155,15 +155,14 @@ where
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Button<T>
where
T: AsRef<str> + crate::trace::Trace,
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Button");
t.component("Button");
match &self.content {
ButtonContent::Text(text) => t.field("text", text),
ButtonContent::Icon(_) => t.symbol("icon"),
ButtonContent::Text(text) => t.string("text", text.as_ref()),
ButtonContent::Icon(_) => t.bool("icon", true),
}
t.close();
}
}

@ -82,9 +82,8 @@ impl Component for HoldToConfirm {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for HoldToConfirm {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("HoldToConfirm");
self.loader.trace(d);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("HoldToConfirm");
t.child("loader", &self.loader);
}
}

@ -81,14 +81,9 @@ where
U: crate::trace::Trace + AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Dialog");
t.field("content", &self.content);
if let Some(label) = &self.left_btn {
t.field("left", label);
}
if let Some(label) = &self.right_btn {
t.field("right", label);
}
t.close();
t.component("Dialog");
t.child("content", &self.content);
self.left_btn.as_ref().map(|b| t.child("left", b));
self.right_btn.as_ref().map(|b| t.child("right", b));
}
}

@ -68,12 +68,11 @@ where
impl<T, U> crate::trace::Trace for Frame<T, U>
where
T: crate::trace::Trace,
U: crate::trace::Trace + AsRef<str>,
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Frame");
t.field("title", &self.title);
t.field("content", &self.content);
t.close();
t.component("Frame");
t.string("title", self.title.as_ref());
t.child("content", &self.content);
}
}

@ -193,8 +193,7 @@ pub struct LoaderStyle {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Loader {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Loader");
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Loader");
}
}

@ -119,11 +119,10 @@ where
T: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ButtonPage");
t.field("active_page", &self.scrollbar.active_page);
t.field("page_count", &self.scrollbar.page_count);
t.field("content", &self.content);
t.close();
t.component("ButtonPage");
t.int("active_page", self.scrollbar.active_page as i64);
t.int("page_count", self.scrollbar.page_count as i64);
t.child("content", &self.content);
}
}

@ -143,8 +143,7 @@ impl Component for ResultAnim {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ResultAnim {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("ResultAnim");
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ResultAnim");
}
}

@ -157,12 +157,11 @@ impl<S: ParagraphStrType> Component for ResultPopup<S> {
#[cfg(feature = "ui_debug")]
impl<S: ParagraphStrType> crate::trace::Trace for ResultPopup<S> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("ResultPopup");
self.text.trace(d);
self.button.trace(d);
self.headline.trace(d);
self.result_anim.trace(d);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ResultPopup");
t.child("text", &self.text);
self.button.as_ref().map(|b| t.child("button", b));
self.headline.as_ref().map(|h| t.child("headline", h));
t.child("result_anim", &self.result_anim);
}
}

@ -147,8 +147,10 @@ pub static mp_module_trezorui2: Module = obj_module! {
#[cfg(test)]
mod tests {
extern crate json;
use crate::{
trace::Trace,
trace::tests::trace,
ui::{
component::Component,
model_tr::{
@ -160,12 +162,6 @@ mod tests {
use super::*;
fn trace(val: &impl Trace) -> String {
let mut t = Vec::new();
val.trace(&mut t);
String::from_utf8(t).unwrap()
}
impl<T, U> ComponentMsgObj for Dialog<T, U>
where
T: ComponentMsgObj,

@ -194,15 +194,14 @@ where
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for AddressDetails<T>
where
T: ParagraphStrType + crate::trace::Trace,
T: ParagraphStrType,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("AddressDetails");
t.component("AddressDetails");
match self.current_page {
0 => self.qr_code.trace(t),
1 => self.details.trace(t),
_ => self.xpub_view.trace(t),
0 => t.child("qr_code", &self.qr_code),
1 => t.child("details", &self.details),
_ => t.child("xpub_view", &self.xpub_view),
}
t.close();
}
}

@ -323,18 +323,20 @@ where
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Button<T>
where
T: AsRef<str> + crate::trace::Trace,
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Button");
t.component("Button");
match &self.content {
ButtonContent::Empty => {}
ButtonContent::Text(text) => t.field("text", text),
ButtonContent::Icon(_) => t.symbol("icon"),
ButtonContent::IconAndText(_) => {}
ButtonContent::IconBlend(_, _, _) => t.symbol("icon"),
ButtonContent::Text(text) => t.string("text", text.as_ref()),
ButtonContent::Icon(_) => t.bool("icon", true),
ButtonContent::IconAndText(content) => {
t.string("text", content.text);
t.bool("icon", true);
}
ButtonContent::IconBlend(_, _, _) => t.bool("icon", true),
}
t.close();
}
}

@ -1,12 +1,16 @@
use core::mem;
use crate::ui::{
component::{
base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split,
use crate::{
maybe_trace::MaybeTrace,
ui::{
component::{
base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label,
Split,
},
display::loader::{loader_circular_uncompress, LoaderDimensions},
geometry::{Alignment, Insets, Rect},
util::animation_disabled,
},
display::loader::{loader_circular_uncompress, LoaderDimensions},
geometry::{Alignment, Insets, Rect},
util::animation_disabled,
};
use super::{theme, Frame};
@ -31,7 +35,10 @@ impl<T, U> CoinJoinProgress<T, U>
where
T: AsRef<str>,
{
pub fn new(text: T, indeterminate: bool) -> CoinJoinProgress<T, impl Component<Msg = Never>>
pub fn new(
text: T,
indeterminate: bool,
) -> CoinJoinProgress<T, impl Component<Msg = Never> + MaybeTrace>
where
T: AsRef<str>,
{
@ -121,11 +128,12 @@ where
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for CoinJoinProgress<T, U>
where
T: crate::trace::Trace,
U: Component,
T: AsRef<str>,
U: Component + crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("CoinJoinProgress");
t.close();
t.component("CoinJoinProgress");
t.child("label", &self.label);
t.child("content", &self.content);
}
}

@ -85,10 +85,9 @@ where
U: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Dialog");
t.field("content", &self.content);
t.field("controls", &self.controls);
t.close();
t.component("Dialog");
t.child("content", &self.content);
t.child("controls", &self.controls);
}
}
@ -208,10 +207,9 @@ where
U: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("IconDialog");
t.field("content", &self.paragraphs);
t.field("image", &self.image);
t.field("controls", &self.controls);
t.close();
t.component("IconDialog");
t.child("image", &self.image);
t.child("content", &self.paragraphs);
t.child("controls", &self.controls);
}
}

@ -205,7 +205,6 @@ where
F: Fn(usize) -> T,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("FidoPaginatedPage");
t.close();
t.component("FidoConfirm");
}
}

@ -186,19 +186,13 @@ where
impl<T, U> crate::trace::Trace for Frame<T, U>
where
T: crate::trace::Trace,
U: crate::trace::Trace + AsRef<str>,
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Frame");
t.field("title", &self.title);
if let Some(s) = &self.subtitle {
t.field("subtitle", s);
}
if let Some(b) = &self.button {
t.field("button", b);
}
t.field("content", &self.content);
t.close();
t.component("Frame");
t.child("title", &self.title);
self.subtitle.as_ref().map(|s| t.child("subtitle", s));
self.button.as_ref().map(|b| t.child("button", b));
}
}
@ -279,12 +273,11 @@ where
impl<T, U> crate::trace::Trace for NotificationFrame<T, U>
where
T: crate::trace::Trace,
U: crate::trace::Trace + AsRef<str>,
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("NotificationFrame");
t.field("title", &self.title);
t.field("content", &self.content);
t.close();
t.component("NotificationFrame");
t.string("title", self.title.as_ref());
t.child("content", &self.content);
}
}

@ -108,10 +108,9 @@ impl<T> crate::trace::Trace for HoldToConfirm<T>
where
T: crate::trace::Trace,
{
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("HoldToConfirm");
self.content.trace(d);
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("HoldToConfirm");
t.child("content", &self.content);
}
}
@ -181,8 +180,8 @@ impl Component for CancelHold {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for CancelHold {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.string("CancelHold")
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("CancelHold");
}
}

@ -258,10 +258,9 @@ where
#[cfg(feature = "ui_debug")]
impl<T: AsRef<str>> crate::trace::Trace for Homescreen<T> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Homescreen");
d.field("label", &self.label.as_ref());
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Homescreen");
t.string("label", self.label.as_ref());
}
}
@ -392,8 +391,7 @@ fn is_image_toif(buffer: &[u8]) -> bool {
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Lockscreen<T> {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Lockscreen");
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Lockscreen");
}
}

@ -138,10 +138,9 @@ where
T: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("HorizontalPage");
t.field("active_page", &self.scrollbar.active_page);
t.field("page_count", &self.scrollbar.page_count);
t.field("content", &self.content);
t.close();
t.component("HorizontalPage");
t.int("active_page", self.scrollbar.active_page as i64);
t.int("page_count", self.scrollbar.page_count as i64);
t.child("content", &self.content);
}
}

@ -193,7 +193,6 @@ pub enum MnemonicInputMsg {
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for MnemonicKeyboard<T, U> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("MnemonicKeyboard");
t.close();
t.component("MnemonicKeyboard");
}
}

@ -377,7 +377,6 @@ impl Component for Input {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for PassphraseKeyboard {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("PassphraseKeyboard");
t.close();
t.component("PassphraseKeyboard");
}
}

@ -467,7 +467,6 @@ where
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("PinKeyboard");
t.close();
t.component("PinKeyboard");
}
}

@ -68,7 +68,6 @@ impl Component for SelectWordCount {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for SelectWordCount {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("SelectWordCount");
t.close();
t.component("SelectWordCount");
}
}

@ -198,9 +198,8 @@ pub struct LoaderStyle {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Loader {
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
d.open("Loader");
d.close();
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Loader");
}
}

@ -135,12 +135,11 @@ where
F: Fn(u32) -> T,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("NumberInputDialog");
t.field("input", &self.input);
t.field("paragraphs", &self.paragraphs);
t.field("info_button", &self.info_button);
t.field("confirm_button", &self.confirm_button);
t.close();
t.component("NumberInputDialog");
t.child("input", &self.input);
t.child("paragraphs", &self.paragraphs);
t.child("info_button", &self.info_button);
t.child("confirm_button", &self.confirm_button);
}
}
@ -238,8 +237,7 @@ impl Component for NumberInput {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInput {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("NumberInput");
t.field("value", &(self.value as usize));
t.close();
t.component("NumberInput");
t.int("value", self.value as i64);
}
}

@ -311,12 +311,11 @@ where
U: crate::trace::Trace + Component,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("SwipePage");
t.field("active_page", &self.scrollbar.active_page);
t.field("page_count", &self.scrollbar.page_count);
t.field("content", &self.content);
t.field("controls", &self.controls);
t.close();
t.component("SwipePage");
t.int("active_page", self.scrollbar.active_page as i64);
t.int("page_count", self.scrollbar.page_count as i64);
t.child("content", &self.content);
t.child("controls", &self.controls);
}
}
@ -495,8 +494,10 @@ where
#[cfg(test)]
mod tests {
extern crate serde_json;
use crate::{
trace::Trace,
trace::tests::trace,
ui::{
component::{
text::paragraphs::{Paragraph, ParagraphStrType, Paragraphs},
@ -518,12 +519,6 @@ mod tests {
}
}
fn trace(val: &impl Trace) -> String {
let mut t = Vec::new();
val.trace(&mut t);
String::from_utf8(t).unwrap()
}
fn swipe(component: &mut impl Component, points: &[(i16, i16)]) {
let last = points.len().saturating_sub(1);
let mut first = true;
@ -560,8 +555,18 @@ mod tests {
);
page.place(SCREEN);
let expected =
"<SwipePage active_page:0 page_count:1 content:<Paragraphs > controls:<Empty > >";
let expected = serde_json::json!({
"component": "SwipePage",
"active_page": 0,
"page_count": 1,
"content": {
"component": "Paragraphs",
"paragraphs": [],
},
"controls": {
"component": "Empty",
},
});
assert_eq!(trace(&page), expected);
swipe_up(&mut page);
@ -588,7 +593,21 @@ mod tests {
);
page.place(SCREEN);
let expected = "<SwipePage active_page:0 page_count:1 content:<Paragraphs This is the first\nparagraph and it should\nfit on the screen\nentirely.\nSecond, bold, paragraph\nshould also fit on the\nscreen whole I think.\n> controls:<Empty > >";
let expected = serde_json::json!({
"component": "SwipePage",
"active_page": 0,
"page_count": 1,
"content": {
"component": "Paragraphs",
"paragraphs": [
["This is the first", "\n", "paragraph and it should", "\n", "fit on the screen", "\n", "entirely."],
["Second, bold, paragraph", "\n", "should also fit on the", "\n", "screen whole I think."],
],
},
"controls": {
"component": "Empty",
},
});
assert_eq!(trace(&page), expected);
swipe_up(&mut page);
@ -611,18 +630,61 @@ mod tests {
);
page.place(SCREEN);
let expected1 = "<SwipePage active_page:0 page_count:2 content:<Paragraphs This is somewhat long\nparagraph that goes on\nand on and on and on and\non and will definitely not\nfit on just a single\nscreen. You have to\nswipe a bit to see all the\ntext it contains I guess....\n> controls:<FixedHeightBar inner:<Button text:NO > > >";
let expected2 = "<SwipePage active_page:1 page_count:2 content:<Paragraphs There's just so much\nletters in it.\n> controls:<FixedHeightBar inner:<Button text:NO > > >";
let first_page = serde_json::json!({
"component": "SwipePage",
"active_page": 0,
"page_count": 2,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"This is somewhat long", "\n",
"paragraph that goes on", "\n",
"and on and on and on and", "\n",
"on and will definitely not", "\n",
"fit on just a single", "\n",
"screen. You have to", "\n",
"swipe a bit to see all the", "\n",
"text it contains I guess.", "...",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Button",
"text": "NO",
},
},
});
let second_page = serde_json::json!({
"component": "SwipePage",
"active_page": 1,
"page_count": 2,
"content": {
"component": "Paragraphs",
"paragraphs": [
["There's just so much", "\n", "letters in it."],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Button",
"text": "NO",
},
},
});
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
swipe_down(&mut page);
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected2);
assert_eq!(trace(&page), second_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected2);
assert_eq!(trace(&page), second_page);
swipe_down(&mut page);
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
}
#[test]
@ -647,25 +709,101 @@ mod tests {
);
page.place(SCREEN);
let expected1 = "<SwipePage active_page:0 page_count:3 content:<Paragraphs This paragraph is using a\nbold font. It doesn't need\nto be all that long.\nAnd this one is u\nsing MONO. Monosp\nace is nice for n\numbers, they...\n> controls:<FixedHeightBar inner:<Button text:IDK > > >";
let expected2 = "<SwipePage active_page:1 page_count:3 content:<Paragraphs ...have the same\nwidth and can be\nscanned quickly.\nEven if they span\nseveral pages or\nsomething.\nLet's add another one...\n> controls:<FixedHeightBar inner:<Button text:IDK > > >";
let expected3 = "<SwipePage active_page:2 page_count:3 content:<Paragraphs for a good measure. This\none should overflow all\nthe way to the third page\nwith a bit of luck.\n> controls:<FixedHeightBar inner:<Button text:IDK > > >";
let first_page = serde_json::json!({
"component": "SwipePage",
"active_page": 0,
"page_count": 3,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"This paragraph is using a", "\n",
"bold font. It doesn't need", "\n",
"to be all that long.",
],
[
"And this one is u", "\n",
"sing MONO. Monosp", "\n",
"ace is nice for n", "\n",
"umbers, they", "...",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Button",
"text": "IDK",
},
},
});
let second_page = serde_json::json!({
"component": "SwipePage",
"active_page": 1,
"page_count": 3,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"...", "have the same", "\n",
"width and can be", "\n",
"scanned quickly.", "\n",
"Even if they span", "\n",
"several pages or", "\n",
"something.",
],
[
"Let's add another one", "...",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Button",
"text": "IDK",
},
},
});
let third_page = serde_json::json!({
"component": "SwipePage",
"active_page": 2,
"page_count": 3,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"for a good measure. This", "\n",
"one should overflow all", "\n",
"the way to the third page", "\n",
"with a bit of luck.",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Button",
"text": "IDK",
},
},
});
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
swipe_down(&mut page);
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected2);
assert_eq!(trace(&page), second_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected3);
assert_eq!(trace(&page), third_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected3);
assert_eq!(trace(&page), third_page);
swipe_down(&mut page);
assert_eq!(trace(&page), expected2);
assert_eq!(trace(&page), second_page);
swipe_down(&mut page);
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
swipe_down(&mut page);
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
}
#[test]
@ -681,16 +819,70 @@ mod tests {
);
page.place(SCREEN);
let expected1 = "<SwipePage active_page:0 page_count:3 content:<Paragraphs Short one.\n> controls:<FixedHeightBar inner:<Empty > > >";
let expected2 = "<SwipePage active_page:1 page_count:3 content:<Paragraphs Short two.\n> controls:<FixedHeightBar inner:<Empty > > >";
let expected3 = "<SwipePage active_page:2 page_count:3 content:<Paragraphs Short three.\n> controls:<FixedHeightBar inner:<Empty > > >";
let first_page = serde_json::json!({
"component": "SwipePage",
"active_page": 0,
"page_count": 3,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"Short one.",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Empty",
},
},
});
let second_page = serde_json::json!({
"component": "SwipePage",
"active_page": 1,
"page_count": 3,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"Short two.",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Empty",
},
},
});
let third_page = serde_json::json!({
"component": "SwipePage",
"active_page": 2,
"page_count": 3,
"content": {
"component": "Paragraphs",
"paragraphs": [
[
"Short three.",
],
],
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Empty",
},
},
});
assert_eq!(trace(&page), expected1);
assert_eq!(trace(&page), first_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected2);
assert_eq!(trace(&page), second_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected3);
assert_eq!(trace(&page), third_page);
swipe_up(&mut page);
assert_eq!(trace(&page), expected3);
assert_eq!(trace(&page), third_page);
}
}

@ -133,7 +133,6 @@ where
T: ParagraphStrType,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Progress");
t.close();
t.component("Progress");
}
}

@ -65,8 +65,7 @@ impl Component for WelcomeScreen {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for WelcomeScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("WelcomeScreen");
t.string(MODEL_NAME);
t.close();
t.component("WelcomeScreen");
t.string("model", MODEL_NAME);
}
}

@ -1961,25 +1961,17 @@ pub static mp_module_trezorui2: Module = obj_module! {
#[cfg(test)]
mod tests {
extern crate serde_json;
use crate::{
trace::Trace,
ui::{
component::{Component, FormattedText},
geometry::Rect,
model_tt::constant,
},
trace::tests::trace,
ui::{geometry::Rect, model_tt::constant},
};
use super::*;
const SCREEN: Rect = constant::screen().inset(theme::borders());
fn trace(val: &impl Trace) -> String {
let mut t = std::vec::Vec::new();
val.trace(&mut t);
String::from_utf8(t).unwrap()
}
#[test]
fn trace_example_layout() {
let buttons =
@ -1994,9 +1986,31 @@ mod tests {
buttons,
);
layout.place(SCREEN);
assert_eq!(
trace(&layout),
"<Dialog content:<Text content:Testing text layout, with\nsome text, and some\nmore text. And parame-\nters! > controls:<FixedHeightBar inner:<Split first:<Button text:Left > second:<Button text:Right > > > >",
)
let expected = serde_json::json!({
"component": "Dialog",
"content": {
"component": "FormattedText",
"text": ["Testing text layout, with", "\n", "some text, and some", "\n",
"more text. And ", "parame", "-", "\n", "ters!"],
"fits": true,
},
"controls": {
"component": "FixedHeightBar",
"inner": {
"component": "Split",
"first": {
"component": "Button",
"text": "Left",
},
"second": {
"component": "Button",
"text": "Right",
},
},
},
});
assert_eq!(trace(&layout), expected);
}
}

Loading…
Cancel
Save