mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 22:38:08 +00:00
refactor(core/rust): support for StrBuffer slicing
[no changelog]
This commit is contained in:
parent
452857757a
commit
4135b00708
@ -16,9 +16,14 @@ use super::ffi;
|
||||
///
|
||||
/// Given the above assumptions about MicroPython strings, working with
|
||||
/// StrBuffers in Rust is safe.
|
||||
///
|
||||
/// 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)]
|
||||
pub struct StrBuffer {
|
||||
ptr: *const u8,
|
||||
len: usize,
|
||||
len: u16,
|
||||
off: u16,
|
||||
}
|
||||
|
||||
impl StrBuffer {
|
||||
@ -27,22 +32,41 @@ impl StrBuffer {
|
||||
}
|
||||
|
||||
pub fn alloc(val: &str) -> Result<Self, Error> {
|
||||
unsafe {
|
||||
Self::alloc_with(val.len(), |buffer| {
|
||||
// SAFETY: Memory should be freshly allocated and as such cannot overlap.
|
||||
ptr::copy_nonoverlapping(val.as_ptr(), buffer.as_mut_ptr(), buffer.len())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc_with(len: usize, func: impl FnOnce(&mut [u8])) -> Result<Self, Error> {
|
||||
// SAFETY:
|
||||
// We assume that if `gc_alloc` returns successfully, the result is a valid
|
||||
// pointer to GC-controlled memory of at least `val.len() + 1` bytes.
|
||||
unsafe {
|
||||
let raw = ffi::gc_alloc(val.len() + 1, 0) as *mut u8;
|
||||
let raw = ffi::gc_alloc(len + 1, 0) as *mut u8;
|
||||
if raw.is_null() {
|
||||
return Err(Error::AllocationFailed);
|
||||
}
|
||||
// SAFETY: Memory should be freshly allocated and as such cannot overlap.
|
||||
ptr::copy_nonoverlapping(val.as_ptr(), raw, val.len());
|
||||
|
||||
// SAFETY: GC returns valid pointers, slice is discarded after `func`.
|
||||
let bytes = slice::from_raw_parts_mut(raw, len);
|
||||
// GC returns uninitialized memory which we must make sure to overwrite,
|
||||
// otherwise leftover references may keep alive otherwise dead
|
||||
// objects. Zero the entire buffer so we don't have to rely on
|
||||
// `func` doing it.
|
||||
bytes.fill(0);
|
||||
func(bytes);
|
||||
str::from_utf8(bytes).map_err(|_| Error::OutOfRange)?;
|
||||
|
||||
// Null-terminate the string for C ASCIIZ compatibility. This will not be
|
||||
// reflected in Rust-visible slice, the zero byte is after the end.
|
||||
raw.add(val.len()).write(0);
|
||||
raw.add(len).write(0);
|
||||
Ok(Self {
|
||||
ptr: raw,
|
||||
len: val.len(),
|
||||
len: unwrap!(len.try_into()),
|
||||
off: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -51,7 +75,22 @@ impl StrBuffer {
|
||||
if self.ptr.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(self.ptr, self.len) }
|
||||
unsafe { slice::from_raw_parts(self.ptr.add(self.off.into()), self.len.into()) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(&self, skip_bytes: usize) -> Self {
|
||||
let off: u16 = unwrap!(skip_bytes.try_into());
|
||||
assert!(off <= self.len);
|
||||
assert!(self.as_ref().is_char_boundary(skip_bytes));
|
||||
Self {
|
||||
ptr: self.ptr,
|
||||
// Does not overflow because `off <= self.len`.
|
||||
len: self.len - off,
|
||||
// `self.off + off` could only overflow if `self.off + self.len` could overflow, and
|
||||
// given that `off` only advances by as much as `len` decreases, that should not be
|
||||
// possible either.
|
||||
off: self.off + off,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,7 +109,8 @@ impl TryFrom<Obj> for StrBuffer {
|
||||
let bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_READ)?;
|
||||
let new = Self {
|
||||
ptr: bufinfo.buf as _,
|
||||
len: bufinfo.len as _,
|
||||
len: bufinfo.len.try_into()?,
|
||||
off: 0,
|
||||
};
|
||||
|
||||
// MicroPython _should_ ensure that values of type `str` are UTF-8.
|
||||
@ -103,6 +143,8 @@ impl AsRef<str> for StrBuffer {
|
||||
// - If constructed from a MicroPython string, we check validity of UTF-8 at
|
||||
// construction time. Python semantics promise not to mutate the underlying
|
||||
// data from under us.
|
||||
// - If constructed by `offset()`, we expect the input to be UTF-8 and check
|
||||
// that we split the string at character boundary.
|
||||
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
|
||||
}
|
||||
}
|
||||
@ -111,7 +153,8 @@ impl From<&'static str> for StrBuffer {
|
||||
fn from(val: &'static str) -> Self {
|
||||
Self {
|
||||
ptr: val.as_ptr(),
|
||||
len: val.len(),
|
||||
len: unwrap!(val.len().try_into()),
|
||||
off: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,31 +226,43 @@ pub unsafe fn get_buffer_mut<'a>(obj: Obj) -> Result<&'a mut [u8], Error> {
|
||||
}
|
||||
}
|
||||
|
||||
fn hexlify(data: &[u8], buffer: &mut [u8]) {
|
||||
const HEX_LOWER: [u8; 16] = *b"0123456789abcdef";
|
||||
let mut i: usize = 0;
|
||||
for b in data.iter().take(buffer.len() / 2) {
|
||||
let hi: usize = ((b & 0xf0) >> 4).into();
|
||||
let lo: usize = (b & 0x0f).into();
|
||||
buffer[i] = HEX_LOWER[hi];
|
||||
buffer[i + 1] = HEX_LOWER[lo];
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hexlify_bytes(obj: Obj, offset: usize, max_len: usize) -> Result<StrBuffer, Error> {
|
||||
if !obj.is_bytes() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
|
||||
// Convert offset to byte representation, handle case where it points in the
|
||||
// middle of a byte.
|
||||
let bin_off = offset / 2;
|
||||
let hex_off = offset % 2;
|
||||
|
||||
// SAFETY:
|
||||
// (a) only immutable references are taken
|
||||
// (b) reference is discarded before returning to micropython
|
||||
let bin_slice = unsafe { get_buffer(obj)? };
|
||||
let bin_slice = &bin_slice[bin_off..];
|
||||
|
||||
let max_len = max_len & !1;
|
||||
let hex_len = (bin_slice.len() * 2).min(max_len);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Version of `get_buffer` for strings that ties the string lifetime with
|
||||
/// another object that is expected to be placed on stack or in micropython heap
|
||||
/// and be the beginning of a chain of references that lead to the `obj`.
|
||||
///
|
||||
/// SAFETY:
|
||||
/// The caller must ensure that:
|
||||
/// (a) the `_owner` is an object visible to the micropython GC,
|
||||
/// (b) the `_owner` contains a reference that leads to `obj`, possibly through
|
||||
/// other objects,
|
||||
/// (c) that path is not broken as long as the returned reference lives.
|
||||
pub unsafe fn get_str_owner<T: ?Sized>(_owner: &T, obj: Obj) -> Result<&str, Error> {
|
||||
if !obj.is_str() {
|
||||
return Err(Error::TypeError);
|
||||
}
|
||||
// SAFETY:
|
||||
// (a) only immutable references are taken.
|
||||
// (b) micropython guarantees immutability and the buffer is not freed/moved as
|
||||
// long as _owner satisfies precondition.
|
||||
let buffer = unsafe { get_buffer(obj)? };
|
||||
str::from_utf8(buffer).map_err(|_| Error::TypeError)
|
||||
}
|
||||
|
@ -24,21 +24,25 @@ pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5;
|
||||
pub type ParagraphVecLong<T> = Vec<Paragraph<T>, 32>;
|
||||
pub type ParagraphVecShort<T> = Vec<Paragraph<T>, 8>;
|
||||
|
||||
/// Maximum number of characters that can be displayed on screen at once. Used
|
||||
/// for on-the-fly conversion of binary data to hexadecimal representation.
|
||||
/// NOTE: can be fine-tuned for particular model screen to decrease memory
|
||||
/// consumption and conversion time.
|
||||
pub const SCRATCH_BUFFER_LEN: usize = 256;
|
||||
/// Trait for internal representation of strings, which need to support
|
||||
/// converting to short-lived &str reference as well as creating a new string by
|
||||
/// skipping some number of bytes. Exists so that we can support `StrBuffer` as
|
||||
/// well as `&'static str`.
|
||||
///
|
||||
/// NOTE: do not implement this trait for `&'static str` in firmware. We always
|
||||
/// use StrBuffer because using multiple internal representations results in
|
||||
/// multiple copies of the code in flash memory.
|
||||
pub trait ParagraphStrType: AsRef<str> {
|
||||
fn skip_prefix(&self, bytes: usize) -> Self;
|
||||
}
|
||||
|
||||
pub trait ParagraphSource {
|
||||
/// Determines the output type produced.
|
||||
type StrType: ParagraphStrType;
|
||||
|
||||
/// Return text and associated style for given paragraph index and character
|
||||
/// offset within the paragraph.
|
||||
///
|
||||
/// Implementations can use the provided buffer to perform some kind of
|
||||
/// conversion (currently used for displaying binary data in
|
||||
/// hexadecimal) and return a reference to this buffer. Caller needs to
|
||||
/// make sure the buffer is large enough to fit screenfull of characters.
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str>;
|
||||
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType>;
|
||||
|
||||
/// Number of paragraphs.
|
||||
fn size(&self) -> usize;
|
||||
@ -51,46 +55,6 @@ pub trait ParagraphSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> ParagraphSource for Vec<Paragraph<T>, N>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
self[index].offset_as_ref(offset)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> ParagraphSource for [Paragraph<T>; N]
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
self[index].offset_as_ref(offset)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ParagraphSource for Paragraph<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, _buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
assert_eq!(index, 0);
|
||||
self.offset_as_ref(offset)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Paragraphs<T> {
|
||||
area: Rect,
|
||||
placement: LinearPlacement,
|
||||
@ -140,10 +104,10 @@ where
|
||||
|
||||
/// Helper for `change_offset` which should not get monomorphized as it
|
||||
/// doesn't refer to T or Self.
|
||||
fn dyn_change_offset(
|
||||
fn dyn_change_offset<S: ParagraphStrType>(
|
||||
mut area: Rect,
|
||||
mut offset: PageOffset,
|
||||
source: &dyn ParagraphSource,
|
||||
source: &dyn ParagraphSource<StrType = S>,
|
||||
visible: &mut Vec<TextLayout, MAX_LINES>,
|
||||
) {
|
||||
visible.clear();
|
||||
@ -174,24 +138,23 @@ where
|
||||
|
||||
/// Iterate over visible layouts (bounding box, style) together
|
||||
/// with corresponding string content. Should not get monomorphized.
|
||||
fn foreach_visible<'a, 'b>(
|
||||
source: &'a dyn ParagraphSource,
|
||||
fn foreach_visible<'a, 'b, S: ParagraphStrType>(
|
||||
source: &'a dyn ParagraphSource<StrType = S>,
|
||||
visible: &'a [TextLayout],
|
||||
offset: PageOffset,
|
||||
func: &'b mut dyn FnMut(&TextLayout, &str),
|
||||
) {
|
||||
let mut buffer = [0; SCRATCH_BUFFER_LEN];
|
||||
let mut vis_iter = visible.iter();
|
||||
let mut chr = offset.chr;
|
||||
|
||||
for par in offset.par..source.size() {
|
||||
let s = source.at(par, chr, &mut buffer).content;
|
||||
if s.is_empty() {
|
||||
let s = source.at(par, chr).content;
|
||||
if s.as_ref().is_empty() {
|
||||
chr = 0;
|
||||
continue;
|
||||
}
|
||||
if let Some(layout) = vis_iter.next() {
|
||||
func(layout, s);
|
||||
func(layout, s.as_ref());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -328,9 +291,9 @@ impl<T> Paragraph<T> {
|
||||
}
|
||||
|
||||
/// Copy style and replace content.
|
||||
pub fn with_content<U>(&self, content: U) -> Paragraph<U> {
|
||||
pub fn map<U>(&self, func: impl FnOnce(&T) -> U) -> Paragraph<U> {
|
||||
Paragraph {
|
||||
content,
|
||||
content: func(&self.content),
|
||||
style: self.style,
|
||||
align: self.align,
|
||||
break_after: self.break_after,
|
||||
@ -338,13 +301,6 @@ impl<T> Paragraph<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset_as_ref(&self, offset: usize) -> Paragraph<&str>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
self.with_content(&self.content.as_ref()[offset..])
|
||||
}
|
||||
|
||||
fn layout(&self, area: Rect) -> TextLayout {
|
||||
TextLayout {
|
||||
padding_top: PARAGRAPH_TOP_SPACE,
|
||||
@ -361,7 +317,7 @@ struct PageOffset {
|
||||
/// Index of paragraph.
|
||||
par: usize,
|
||||
|
||||
/// Index of character in the paragraph.
|
||||
/// Index of (the first byte of) the character in the paragraph.
|
||||
chr: usize,
|
||||
}
|
||||
|
||||
@ -376,17 +332,16 @@ impl PageOffset {
|
||||
///
|
||||
/// If the returned remaining area is not None then it holds that
|
||||
/// `next_offset.par == self.par + 1`.
|
||||
fn advance(
|
||||
fn advance<S: ParagraphStrType>(
|
||||
mut self,
|
||||
area: Rect,
|
||||
source: &dyn ParagraphSource,
|
||||
source: &dyn ParagraphSource<StrType = S>,
|
||||
full_height: i16,
|
||||
) -> (PageOffset, Option<Rect>, Option<TextLayout>) {
|
||||
let mut buffer = [0; SCRATCH_BUFFER_LEN];
|
||||
let paragraph = source.at(self.par, self.chr, &mut buffer);
|
||||
let paragraph = source.at(self.par, self.chr);
|
||||
|
||||
// Skip empty paragraphs.
|
||||
if paragraph.content.is_empty() {
|
||||
if paragraph.content.as_ref().is_empty() {
|
||||
self.par += 1;
|
||||
self.chr = 0;
|
||||
return (self, Some(area), None);
|
||||
@ -394,9 +349,8 @@ impl PageOffset {
|
||||
|
||||
// Handle the `no_break` flag used to keep key-value pair on the same page.
|
||||
if paragraph.no_break && self.chr == 0 {
|
||||
let mut next_buffer = [0; SCRATCH_BUFFER_LEN];
|
||||
if let Some(next_paragraph) =
|
||||
(self.par + 1 < source.size()).then(|| source.at(self.par + 1, 0, &mut next_buffer))
|
||||
(self.par + 1 < source.size()).then(|| source.at(self.par + 1, 0))
|
||||
{
|
||||
if Self::should_place_pair_on_next_page(
|
||||
¶graph,
|
||||
@ -411,7 +365,7 @@ impl PageOffset {
|
||||
|
||||
// Find out the dimensions of the paragraph at given char offset.
|
||||
let mut layout = paragraph.layout(area);
|
||||
let fit = layout.fit_text(paragraph.content);
|
||||
let fit = layout.fit_text(paragraph.content.as_ref());
|
||||
let (used, remaining_area) = area.split_top(fit.height());
|
||||
layout.bounds = used;
|
||||
|
||||
@ -441,9 +395,9 @@ impl PageOffset {
|
||||
)
|
||||
}
|
||||
|
||||
fn should_place_pair_on_next_page(
|
||||
this_paragraph: &Paragraph<&str>,
|
||||
next_paragraph: &Paragraph<&str>,
|
||||
fn should_place_pair_on_next_page<S: ParagraphStrType>(
|
||||
this_paragraph: &Paragraph<S>,
|
||||
next_paragraph: &Paragraph<S>,
|
||||
area: Rect,
|
||||
full_height: i16,
|
||||
) -> bool {
|
||||
@ -456,11 +410,11 @@ impl PageOffset {
|
||||
let full_area = area.with_height(full_height);
|
||||
let key_height = this_paragraph
|
||||
.layout(full_area)
|
||||
.fit_text(this_paragraph.content)
|
||||
.fit_text(this_paragraph.content.as_ref())
|
||||
.height();
|
||||
let val_height = next_paragraph
|
||||
.layout(full_area)
|
||||
.fit_text(next_paragraph.content)
|
||||
.fit_text(next_paragraph.content.as_ref())
|
||||
.height();
|
||||
let screen_full_threshold = this_paragraph.style.text_font.line_height()
|
||||
+ next_paragraph.style.text_font.line_height();
|
||||
@ -492,9 +446,9 @@ struct PageBreakIterator<'a, T> {
|
||||
}
|
||||
|
||||
impl<T: ParagraphSource> PageBreakIterator<'_, T> {
|
||||
fn dyn_next(
|
||||
fn dyn_next<S: ParagraphStrType>(
|
||||
mut area: Rect,
|
||||
paragraphs: &dyn ParagraphSource,
|
||||
paragraphs: &dyn ParagraphSource<StrType = S>,
|
||||
mut offset: PageOffset,
|
||||
) -> Option<PageOffset> {
|
||||
let full_height = area.height();
|
||||
|
@ -1,18 +1,17 @@
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::{
|
||||
buffer::{get_buffer, get_str_owner, StrBuffer},
|
||||
buffer::{hexlify_bytes, StrBuffer},
|
||||
gc::Gc,
|
||||
iter::{Iter, IterBuf},
|
||||
list::List,
|
||||
obj::Obj,
|
||||
},
|
||||
ui::component::text::{
|
||||
paragraphs::{Paragraph, ParagraphSource},
|
||||
paragraphs::{Paragraph, ParagraphSource, ParagraphStrType},
|
||||
TextStyle,
|
||||
},
|
||||
};
|
||||
use core::str;
|
||||
use cstr_core::cstr;
|
||||
use heapless::Vec;
|
||||
|
||||
@ -41,19 +40,11 @@ where
|
||||
vec.into_array().map_err(|_| err)
|
||||
}
|
||||
|
||||
fn hexlify<'a>(data: &[u8], buffer: &'a mut [u8]) -> &'a str {
|
||||
const HEX_LOWER: [u8; 16] = *b"0123456789abcdef";
|
||||
let mut i: usize = 0;
|
||||
for b in data.iter().take(buffer.len() / 2) {
|
||||
let hi: usize = ((b & 0xf0) >> 4).into();
|
||||
let lo: usize = (b & 0x0f).into();
|
||||
buffer[i] = HEX_LOWER[hi];
|
||||
buffer[i + 1] = HEX_LOWER[lo];
|
||||
i += 2;
|
||||
}
|
||||
// SAFETY: only <0x7f bytes are used to construct the string
|
||||
unsafe { str::from_utf8_unchecked(&buffer[0..i]) }
|
||||
}
|
||||
/// Maximum number of characters that can be displayed on screen at once. Used
|
||||
/// for on-the-fly conversion of binary data to hexadecimal representation.
|
||||
/// NOTE: can be fine-tuned for particular model screen to decrease memory
|
||||
/// consumption and conversion time.
|
||||
pub const MAX_HEX_CHARS_ON_SCREEN: usize = 256;
|
||||
|
||||
pub enum StrOrBytes {
|
||||
Str(StrBuffer),
|
||||
@ -61,35 +52,13 @@ pub enum StrOrBytes {
|
||||
}
|
||||
|
||||
impl StrOrBytes {
|
||||
pub fn as_str_offset<'a>(&'a self, offset: usize, buffer: &'a mut [u8]) -> &'a str {
|
||||
pub fn as_str_offset(&self, offset: usize) -> StrBuffer {
|
||||
match self {
|
||||
StrOrBytes::Str(x) => &x.as_ref()[offset..],
|
||||
StrOrBytes::Bytes(x) => Self::hexlify(*x, offset, buffer),
|
||||
StrOrBytes::Str(x) => x.skip_prefix(offset),
|
||||
StrOrBytes::Bytes(x) => hexlify_bytes(*x, offset, MAX_HEX_CHARS_ON_SCREEN)
|
||||
.unwrap_or_else(|_| StrBuffer::from("ERROR")),
|
||||
}
|
||||
}
|
||||
|
||||
fn hexlify(obj: Obj, offset: usize, buffer: &mut [u8]) -> &str {
|
||||
if !obj.is_bytes() {
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
// Convert offset to byte representation, handle case where it points in the
|
||||
// middle of a byte.
|
||||
let bin_off = offset / 2;
|
||||
let hex_off = offset % 2;
|
||||
|
||||
// SAFETY:
|
||||
// (a) only immutable references are taken
|
||||
// (b) reference is discarded before returning to micropython
|
||||
let bin_slice = if let Ok(buf) = unsafe { get_buffer(obj) } {
|
||||
&buf[bin_off..]
|
||||
} else {
|
||||
return "ERROR";
|
||||
};
|
||||
let hexadecimal = hexlify(bin_slice, buffer);
|
||||
|
||||
&hexadecimal[hex_off..]
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Obj> for StrOrBytes {
|
||||
@ -116,11 +85,13 @@ pub struct ConfirmBlob {
|
||||
}
|
||||
|
||||
impl ParagraphSource for ConfirmBlob {
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
type StrType = StrBuffer;
|
||||
|
||||
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
|
||||
match index {
|
||||
0 => Paragraph::new(self.description_font, &self.description.as_ref()[offset..]),
|
||||
1 => Paragraph::new(self.extra_font, &self.extra.as_ref()[offset..]),
|
||||
2 => Paragraph::new(self.data_font, self.data.as_str_offset(offset, buffer)),
|
||||
0 => Paragraph::new(self.description_font, self.description.skip_prefix(offset)),
|
||||
1 => Paragraph::new(self.extra_font, self.extra.skip_prefix(offset)),
|
||||
2 => Paragraph::new(self.data_font, self.data.as_str_offset(offset)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -154,8 +125,10 @@ impl PropsList {
|
||||
}
|
||||
|
||||
impl ParagraphSource for PropsList {
|
||||
fn at<'a>(&'a self, index: usize, offset: usize, buffer: &'a mut [u8]) -> Paragraph<&'a str> {
|
||||
let block = move |buffer| {
|
||||
type StrType = StrBuffer;
|
||||
|
||||
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
|
||||
let block = move || {
|
||||
let entry = self.items.get(index / 2)?;
|
||||
let [key, value, value_is_mono]: [Obj; 3] = iter_into_objs(entry)?;
|
||||
let value_is_mono: bool = bool::try_from(value_is_mono)?;
|
||||
@ -181,19 +154,15 @@ impl ParagraphSource for PropsList {
|
||||
};
|
||||
|
||||
if obj == Obj::const_none() {
|
||||
return Ok(Paragraph::new(style, ""));
|
||||
return Ok(Paragraph::new(style, StrBuffer::empty()));
|
||||
}
|
||||
|
||||
let para = if obj.is_str() {
|
||||
// SAFETY:
|
||||
// As long as self is visible to GC, the string will be also. The paragraph
|
||||
// rendering does not keep the returned references for long so we can reasonably
|
||||
// expect for the chain of references from self to the buffer not to be broken.
|
||||
let s = unsafe { get_str_owner(self, obj)? };
|
||||
Paragraph::new(style, &s[offset..])
|
||||
let content: StrBuffer = obj.try_into()?;
|
||||
Paragraph::new(style, content.skip_prefix(offset))
|
||||
} else if obj.is_bytes() {
|
||||
let s = StrOrBytes::hexlify(obj, offset, buffer);
|
||||
Paragraph::new(style, s)
|
||||
let content = hexlify_bytes(obj, offset, MAX_HEX_CHARS_ON_SCREEN)?;
|
||||
Paragraph::new(style, content)
|
||||
} else {
|
||||
return Err(Error::TypeError);
|
||||
};
|
||||
@ -204,9 +173,9 @@ impl ParagraphSource for PropsList {
|
||||
Ok(para)
|
||||
}
|
||||
};
|
||||
match block(buffer) {
|
||||
Ok(p) => p,
|
||||
Err(_) => Paragraph::new(self.value_font, "ERROR"),
|
||||
match block() {
|
||||
Ok(para) => para,
|
||||
Err(_) => Paragraph::new(self.value_font, StrBuffer::from("ERROR")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,3 +183,48 @@ impl ParagraphSource for PropsList {
|
||||
2 * self.items.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ParagraphStrType, const N: usize> ParagraphSource for Vec<Paragraph<T>, N> {
|
||||
type StrType = T;
|
||||
|
||||
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
|
||||
let para = &self[index];
|
||||
para.map(|content| content.skip_prefix(offset))
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ParagraphStrType, const N: usize> ParagraphSource for [Paragraph<T>; N] {
|
||||
type StrType = T;
|
||||
|
||||
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
|
||||
let para = &self[index];
|
||||
para.map(|content| content.skip_prefix(offset))
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ParagraphStrType> ParagraphSource for Paragraph<T> {
|
||||
type StrType = T;
|
||||
|
||||
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
|
||||
assert_eq!(index, 0);
|
||||
self.map(|content| content.skip_prefix(offset))
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl ParagraphStrType for StrBuffer {
|
||||
fn skip_prefix(&self, chars: usize) -> Self {
|
||||
self.offset(chars)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
time::Instant,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
text::paragraphs::{Paragraph, ParagraphStrType, Paragraphs},
|
||||
Child, Component, ComponentExt, Event, EventCtx, Label, Pad,
|
||||
},
|
||||
constant::screen,
|
||||
@ -18,13 +18,13 @@ pub enum ResultPopupMsg {
|
||||
Confirmed,
|
||||
}
|
||||
|
||||
pub struct ResultPopup {
|
||||
pub struct ResultPopup<S> {
|
||||
area: Rect,
|
||||
pad: Pad,
|
||||
result_anim: Child<ResultAnim>,
|
||||
headline_baseline: Point,
|
||||
headline: Option<Label<&'static str>>,
|
||||
text: Child<Paragraphs<Paragraph<&'static str>>>,
|
||||
text: Child<Paragraphs<Paragraph<S>>>,
|
||||
button: Option<Child<Button<&'static str>>>,
|
||||
autoclose: bool,
|
||||
}
|
||||
@ -36,10 +36,10 @@ const ANIM_POS: i16 = 32;
|
||||
const ANIM_POS_ADJ_HEADLINE: i16 = 10;
|
||||
const ANIM_POS_ADJ_BUTTON: i16 = 6;
|
||||
|
||||
impl ResultPopup {
|
||||
impl<S: ParagraphStrType> ResultPopup<S> {
|
||||
pub fn new(
|
||||
icon: &'static [u8],
|
||||
text: &'static str,
|
||||
text: S,
|
||||
headline: Option<&'static str>,
|
||||
button_text: Option<&'static str>,
|
||||
) -> Self {
|
||||
@ -86,7 +86,7 @@ impl ResultPopup {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ResultPopup {
|
||||
impl<S: ParagraphStrType> Component for ResultPopup<S> {
|
||||
type Msg = ResultPopupMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
@ -155,7 +155,7 @@ impl Component for ResultPopup {
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for ResultPopup {
|
||||
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);
|
||||
|
@ -1,7 +1,9 @@
|
||||
use crate::ui::{
|
||||
component::{
|
||||
image::BlendedImage,
|
||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
||||
text::paragraphs::{
|
||||
Paragraph, ParagraphSource, ParagraphStrType, ParagraphVecShort, Paragraphs, VecExt,
|
||||
},
|
||||
Child, Component, Event, EventCtx, Never,
|
||||
},
|
||||
geometry::{Insets, LinearPlacement, Rect},
|
||||
@ -93,18 +95,17 @@ pub struct IconDialog<T, U> {
|
||||
|
||||
impl<T, U> IconDialog<T, U>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
U: Component,
|
||||
{
|
||||
pub fn new(icon: BlendedImage, title: T, controls: U) -> Self {
|
||||
Self {
|
||||
image: Child::new(icon),
|
||||
paragraphs: ParagraphVecShort::from_iter([Paragraph::new(
|
||||
paragraphs: Paragraphs::new(ParagraphVecShort::from_iter([Paragraph::new(
|
||||
&theme::TEXT_DEMIBOLD,
|
||||
title,
|
||||
)
|
||||
.centered()])
|
||||
.into_paragraphs()
|
||||
.centered()]))
|
||||
.with_placement(
|
||||
LinearPlacement::vertical()
|
||||
.align_at_center()
|
||||
@ -152,7 +153,7 @@ where
|
||||
|
||||
impl<T, U> Component for IconDialog<T, U>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
U: Component,
|
||||
{
|
||||
type Msg = DialogMsg<Never, U::Msg>;
|
||||
@ -193,7 +194,7 @@ where
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, U> crate::trace::Trace for IconDialog<T, U>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
U: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -2,7 +2,7 @@ use crate::ui::{
|
||||
component::{
|
||||
base::ComponentExt,
|
||||
paginated::Paginate,
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
text::paragraphs::{Paragraph, ParagraphStrType, Paragraphs},
|
||||
Child, Component, Event, EventCtx, Pad,
|
||||
},
|
||||
display::{self, Font},
|
||||
@ -33,7 +33,7 @@ where
|
||||
impl<T, F> NumberInputDialog<T, F>
|
||||
where
|
||||
F: Fn(u32) -> T,
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
{
|
||||
pub fn new(min: u32, max: u32, init_value: u32, description_func: F) -> Self {
|
||||
let text = description_func(init_value);
|
||||
@ -69,7 +69,7 @@ where
|
||||
|
||||
impl<T, F> Component for NumberInputDialog<T, F>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
F: Fn(u32) -> T,
|
||||
{
|
||||
type Msg = NumberInputDialogMsg;
|
||||
@ -131,7 +131,7 @@ where
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T, F> crate::trace::Trace for NumberInputDialog<T, F>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
F: Fn(u32) -> T,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
|
@ -385,7 +385,7 @@ mod tests {
|
||||
trace::Trace,
|
||||
ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
text::paragraphs::{Paragraph, ParagraphStrType, Paragraphs},
|
||||
Empty,
|
||||
},
|
||||
event::TouchEvent,
|
||||
@ -398,6 +398,12 @@ mod tests {
|
||||
|
||||
const SCREEN: Rect = constant::screen().inset(theme::borders());
|
||||
|
||||
impl ParagraphStrType for &'static str {
|
||||
fn skip_prefix(&self, chars: usize) -> Self {
|
||||
&self[chars..]
|
||||
}
|
||||
}
|
||||
|
||||
fn trace(val: &impl Trace) -> String {
|
||||
let mut t = Vec::new();
|
||||
val.trace(&mut t);
|
||||
|
@ -21,8 +21,8 @@ use crate::{
|
||||
painter,
|
||||
text::{
|
||||
paragraphs::{
|
||||
Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort,
|
||||
Paragraphs, VecExt,
|
||||
Checklist, Paragraph, ParagraphSource, ParagraphStrType, ParagraphVecLong,
|
||||
ParagraphVecShort, Paragraphs, VecExt,
|
||||
},
|
||||
TextStyle,
|
||||
},
|
||||
@ -122,7 +122,7 @@ where
|
||||
|
||||
impl<T, U> ComponentMsgObj for IconDialog<T, U>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
U: Component,
|
||||
<U as Component>::Msg: TryInto<Obj, Error = Error>,
|
||||
{
|
||||
@ -263,7 +263,7 @@ where
|
||||
|
||||
impl<T, F> ComponentMsgObj for NumberInputDialog<T, F>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
T: ParagraphStrType,
|
||||
F: Fn(u32) -> T,
|
||||
{
|
||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||
@ -1061,8 +1061,9 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu
|
||||
"RECOVERY MODE"
|
||||
};
|
||||
|
||||
let paragraphs =
|
||||
Paragraphs::new([Paragraph::new(&theme::TEXT_BOLD, "Number of words?").centered()]);
|
||||
let paragraphs = Paragraphs::new(
|
||||
Paragraph::new(&theme::TEXT_BOLD, StrBuffer::from("Number of words?")).centered(),
|
||||
);
|
||||
|
||||
let obj = LayoutObj::new(
|
||||
Frame::new(title, Dialog::new(paragraphs, SelectWordCount::new()))
|
||||
|
Loading…
Reference in New Issue
Block a user