mmilata/block-justify
Martin Milata 1 year ago
parent 497285f9a1
commit 3c580b8338

@ -75,6 +75,7 @@ stdenvNoCC.mkDerivation ({
curl # for connect tests
editorconfig-checker
gcc-arm-embedded
gdb
git
gitAndTools.git-subrepo
gnumake

@ -107,7 +107,7 @@ test_emu_click: ## run click tests
test_emu_ui: ## run ui integration tests
$(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) \
--ui=test --ui-check-missing --not-generate-report-after-each-test
--ui=test --ui-check-missing
test_emu_ui_multicore: ## run ui integration tests using multiple cores
$(PYTEST) -n auto $(TESTPATH)/device_tests $(TESTOPTS) \

@ -1,10 +1,10 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display::Font,
geometry::{Alignment, Offset, Rect},
geometry::{Offset, Rect},
};
use super::{text::TextStyle, TextLayout};
use super::{text::{TextAlign, TextStyle}, TextLayout};
pub struct Label<T> {
text: T,
@ -15,7 +15,7 @@ impl<T> Label<T>
where
T: AsRef<str>,
{
pub fn new(text: T, align: Alignment, style: TextStyle) -> Self {
pub fn new(text: T, align: TextAlign, style: TextStyle) -> Self {
Self {
text,
layout: TextLayout::new(style).with_align(align),
@ -23,15 +23,15 @@ where
}
pub fn left_aligned(text: T, style: TextStyle) -> Self {
Self::new(text, Alignment::Start, style)
Self::new(text, TextAlign::Left, style)
}
pub fn right_aligned(text: T, style: TextStyle) -> Self {
Self::new(text, Alignment::End, style)
Self::new(text, TextAlign::Right, style)
}
pub fn centered(text: T, style: TextStyle) -> Self {
Self::new(text, Alignment::Center, style)
Self::new(text, TextAlign::Center, style)
}
pub fn text(&self) -> &T {

@ -2,7 +2,7 @@ use super::iter::GlyphMetrics;
use crate::ui::{
display,
display::{Color, Font},
geometry::{Alignment, Dimensions, Offset, Point, Rect},
geometry::{Dimensions, Offset, Point, Rect},
};
#[derive(Copy, Clone)]
@ -26,6 +26,14 @@ pub enum PageBreaking {
CutAndInsertEllipsis,
}
#[derive(Copy, Clone)]
pub enum TextAlign {
Left,
Center,
Right,
Block,
}
/// Visual instructions for laying out a formatted block of text.
#[derive(Copy, Clone)]
pub struct TextLayout {
@ -42,7 +50,7 @@ pub struct TextLayout {
/// Fonts, colors, line/page breaking behavior.
pub style: TextStyle,
/// Horizontal alignment.
pub align: Alignment,
pub align: TextAlign,
}
#[derive(Copy, Clone)]
@ -63,6 +71,8 @@ pub struct TextStyle {
pub line_breaking: LineBreaking,
/// Specifies what to do at the end of the page.
pub page_breaking: PageBreaking,
/// Increase or decrease space size, in pixels.
pub space_adjust: i16,
}
impl TextStyle {
@ -81,6 +91,7 @@ impl TextStyle {
ellipsis_color,
line_breaking: LineBreaking::BreakAtWhitespace,
page_breaking: PageBreaking::CutAndInsertEllipsis,
space_adjust: 0,
}
}
@ -93,6 +104,11 @@ impl TextStyle {
self.page_breaking = page_breaking;
self
}
pub const fn with_space_adjust(mut self, space_adjust: i16) -> Self {
self.space_adjust = space_adjust;
self
}
}
impl TextLayout {
@ -104,7 +120,7 @@ impl TextLayout {
padding_top: 0,
padding_bottom: 0,
style,
align: Alignment::Start,
align: TextAlign::Left,
}
}
@ -113,7 +129,7 @@ impl TextLayout {
self
}
pub fn with_align(mut self, align: Alignment) -> Self {
pub fn with_align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
@ -197,18 +213,29 @@ impl TextLayout {
let span = Span::fit_horizontally(
remaining_text,
remaining_width,
self.style.text_font,
AdjustSpace::new(self.style.text_font, self.style.space_adjust),
self.style.line_breaking,
);
cursor.x += match self.align {
Alignment::Start => 0,
Alignment::Center => (remaining_width - span.advance.x) / 2,
Alignment::End => remaining_width - span.advance.x,
TextAlign::Left | TextAlign::Block => 0,
TextAlign::Center => (remaining_width - span.advance.x) / 2,
TextAlign::Right => remaining_width - span.advance.x,
};
let space_adjust = match self.align {
TextAlign::Block if self.style.space_adjust == 0 => {
let spaces: i16 = unwrap!(remaining_text[..span.length].chars().filter(|c| *c == ' ').count().try_into());
if spaces > 0 {
((remaining_width - span.advance.x) / spaces).min(self.style.text_font.char_width(' ') * 8 / 7)
} else {
0
}
}
_ => self.style.space_adjust,
};
// Report the span at the cursor position.
sink.text(*cursor, self, &remaining_text[..span.length]);
sink.text(*cursor, self, &remaining_text[..span.length], space_adjust);
// Continue with the rest of the remaining_text.
remaining_text = &remaining_text[span.length + span.skip_next_chars..];
@ -300,7 +327,7 @@ impl LayoutFit {
/// Visitor for text segment operations.
pub trait LayoutSink {
fn text(&mut self, _cursor: Point, _layout: &TextLayout, _text: &str) {}
fn text(&mut self, _cursor: Point, _layout: &TextLayout, _text: &str, _space_adjust: i16) {}
fn hyphen(&mut self, _cursor: Point, _layout: &TextLayout) {}
fn ellipsis(&mut self, _cursor: Point, _layout: &TextLayout) {}
fn line_break(&mut self, _cursor: Point) {}
@ -314,10 +341,23 @@ impl LayoutSink for TextNoOp {}
pub struct TextRenderer;
impl LayoutSink for TextRenderer {
fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str) {
fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str, space_adjust: i16) {
let mut c = cursor;
let mut t = text;
while let Some(i) = t.find(' ') {
display::text(
c,
&t[..i+1],
layout.style.text_font,
layout.style.text_color,
layout.style.background_color,
);
c.x += layout.style.text_font.text_width(&t[..i+1]) + space_adjust;
t = &t[i+1..];
}
display::text(
cursor,
text,
c,
t,
layout.style.text_font,
layout.style.text_color,
layout.style.background_color,
@ -345,6 +385,29 @@ impl LayoutSink for TextRenderer {
}
}
struct AdjustSpace<T>(T, i16);
impl<T> AdjustSpace<T> {
fn new(metrics: T, adjust: i16) -> Self {
Self(metrics, adjust)
}
}
impl<T: GlyphMetrics> GlyphMetrics for AdjustSpace<T> {
fn char_width(&self, ch: char) -> i16 {
let width = self.0.char_width(ch);
if ch == ' ' {
width + self.1
} else {
width
}
}
fn line_height(&self) -> i16 {
self.0.line_height()
}
}
#[cfg(feature = "ui_debug")]
pub mod trace {
use crate::ui::geometry::Point;
@ -354,7 +417,7 @@ pub mod trace {
pub struct TraceSink<'a>(pub &'a mut dyn crate::trace::Tracer);
impl<'a> LayoutSink for TraceSink<'a> {
fn text(&mut self, _cursor: Point, _layout: &TextLayout, text: &str) {
fn text(&mut self, _cursor: Point, _layout: &TextLayout, text: &str, _space_adjust: i16) {
self.0.string(text);
}

@ -3,4 +3,4 @@ mod iter;
pub mod layout;
pub mod paragraphs;
pub use layout::{LineBreaking, PageBreaking, TextStyle};
pub use layout::{LineBreaking, PageBreaking, TextAlign, TextStyle};

@ -3,10 +3,10 @@ use heapless::Vec;
use crate::ui::{
component::{Component, Event, EventCtx, Never, Paginate},
display,
geometry::{Alignment, Insets, LinearPlacement, Offset, Point, Rect},
geometry::{Insets, LinearPlacement, Offset, Point, Rect},
};
use super::layout::{LayoutFit, TextLayout, TextStyle};
use super::layout::{LayoutFit, TextLayout, TextStyle, TextAlign};
/// Used as an upper bound of number of different styles we may render on single
/// page.
@ -252,7 +252,7 @@ pub struct Paragraph<T> {
/// Paragraph style.
style: &'static TextStyle,
/// Paragraph alignment.
align: Alignment,
align: TextAlign,
/// Place next paragraph on new page.
break_after: bool,
/// Try to keep this and the next paragraph on the same page. NOTE: doesn't
@ -265,14 +265,19 @@ impl<T> Paragraph<T> {
Self {
content,
style,
align: Alignment::Start,
align: TextAlign::Left,
break_after: false,
no_break: false,
}
}
pub const fn centered(mut self) -> Self {
self.align = Alignment::Center;
self.align = TextAlign::Center;
self
}
pub const fn blockaligned(mut self) -> Self {
self.align = TextAlign::Block;
self
}

@ -1,7 +1,7 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Image, Label},
component::{Child, Component, Event, EventCtx, Image, Label, text::TextAlign},
display,
geometry::{Alignment, Insets, Rect},
geometry::{Insets, Rect},
model_tt::component::{
fido_icons::get_fido_icon_data,
swipe::{Swipe, SwipeDirection},
@ -60,8 +60,8 @@ where
page_swipe.allow_left = scrollbar.has_next_page();
Self {
app_name: Label::new(app_name, Alignment::Center, theme::TEXT_BOLD),
account_name: Label::new("".into(), Alignment::Center, theme::TEXT_BOLD),
app_name: Label::new(app_name, TextAlign::Center, theme::TEXT_BOLD),
account_name: Label::new("".into(), TextAlign::Center, theme::TEXT_BOLD),
page_swipe,
icon: Child::new(Image::new(icon_data)),
get_account,

@ -1,8 +1,8 @@
use super::theme;
use crate::ui::{
component::{label::Label, text::TextStyle, Child, Component, Event, EventCtx},
component::{label::Label, text::{TextAlign, TextStyle}, Child, Component, Event, EventCtx},
display::{self, Color, Font},
geometry::{Alignment, Insets, Offset, Rect},
geometry::{Insets, Offset, Rect},
util::icon_text_center,
};
@ -17,7 +17,7 @@ where
T: Component,
U: AsRef<str>,
{
pub fn new(style: TextStyle, alignment: Alignment, title: U, content: T) -> Self {
pub fn new(style: TextStyle, alignment: TextAlign, title: U, content: T) -> Self {
Self {
title: Child::new(Label::new(title, alignment, style)),
border: theme::borders_scroll(),
@ -26,15 +26,15 @@ where
}
pub fn left_aligned(style: TextStyle, title: U, content: T) -> Self {
Self::new(style, Alignment::Start, title, content)
Self::new(style, TextAlign::Left, title, content)
}
pub fn right_aligned(style: TextStyle, title: U, content: T) -> Self {
Self::new(style, Alignment::End, title, content)
Self::new(style, TextAlign::Right, title, content)
}
pub fn centered(style: TextStyle, title: U, content: T) -> Self {
Self::new(style, Alignment::Center, title, content)
Self::new(style, TextAlign::Center, title, content)
}
pub fn with_border(mut self, border: Insets) -> Self {

@ -339,10 +339,10 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
if !reverse {
paragraphs
.add(Paragraph::new(&theme::TEXT_BOLD, action))
.add(Paragraph::new(&theme::TEXT_NORMAL, description));
.add(Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned());
} else {
paragraphs
.add(Paragraph::new(&theme::TEXT_NORMAL, description))
.add(Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned())
.add(Paragraph::new(&theme::TEXT_BOLD, action));
}
paragraphs.into_paragraphs()
@ -473,9 +473,9 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
let url: StrBuffer = "https://trezor.io/tos".into();
let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_BOLD, prompt),
Paragraph::new(&theme::TEXT_NORMAL, description),
Paragraph::new(&theme::TEXT_BOLD, url),
Paragraph::new(&theme::TEXT_BOLD, prompt).blockaligned(),
Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned(),
Paragraph::new(&theme::TEXT_BOLD, url).blockaligned(),
]);
let buttons = Button::cancel_confirm_text(None, "CONTINUE");
@ -609,9 +609,9 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
};
let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, description.into()),
Paragraph::new(&theme::TEXT_NORMAL, description.into()).blockaligned(),
Paragraph::new(&theme::TEXT_MONO, change),
Paragraph::new(&theme::TEXT_NORMAL, "\nTransaction fee:".into()),
Paragraph::new(&theme::TEXT_NORMAL, "\nTransaction fee:".into()).blockaligned(),
Paragraph::new(&theme::TEXT_MONO, total_fee_new),
]);
@ -794,7 +794,7 @@ extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map)
theme::label_title(),
t,
Dialog::new(
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]),
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned()]),
theme::button_bar(Button::with_text(button).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
@ -805,7 +805,7 @@ extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map)
LayoutObj::new(Border::new(
theme::borders(),
Dialog::new(
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]),
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned()]),
theme::button_bar(Button::with_text(button).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
@ -961,7 +961,7 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map)
let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
let words: [StrBuffer; 3] = iter_into_array(words_iterable)?;
let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]);
let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned()]);
let buttons = Button::select_word(words);
let obj = LayoutObj::new(Frame::left_aligned(
@ -1184,7 +1184,7 @@ extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs:
let [title, description]: [StrBuffer; 2] = iter_into_array(page)?;
paragraphs
.add(Paragraph::new(&theme::TEXT_BOLD, title))
.add(Paragraph::new(&theme::TEXT_NORMAL, description).break_after());
.add(Paragraph::new(&theme::TEXT_NORMAL, description).blockaligned().break_after());
}
let obj = LayoutObj::new(Frame::left_aligned(

@ -382,6 +382,10 @@ pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::BOLD, FG, BG, GREY_LIGHT,
pub const TEXT_MONO: TextStyle = TextStyle::new(Font::MONO, FG, BG, GREY_LIGHT, GREY_LIGHT)
.with_line_breaking(LineBreaking::BreakWordsNoHyphen)
.with_page_breaking(PageBreaking::Cut);
pub const TEXT_MONO_GROUPS: TextStyle = TextStyle::new(Font::MONO, FG, BG, GREY_LIGHT, GREY_LIGHT)
.with_line_breaking(LineBreaking::BreakAtWhitespace)
.with_page_breaking(PageBreaking::Cut)
.with_space_adjust(-5);
/// Convert Python-side numeric id to a `TextStyle`.
pub fn textstyle_number(num: i32) -> &'static TextStyle {

@ -110,6 +110,7 @@ async def get_address(
title=title,
multisig_index=multisig_index,
xpubs=_get_xpubs(coin, multisig_xpub_magic, pubnodes),
group_by=4,
)
else:
title = address_n_to_str(address_n)
@ -119,6 +120,7 @@ async def get_address(
address_qr=address,
case_sensitive=address_case_sensitive,
title=title,
group_by=4,
)
return Address(address=address, mac=mac)

@ -356,6 +356,11 @@ async def show_xpub(ctx: GenericContext, xpub: str, title: str) -> None:
)
def _address_group_by(address, group_by):
if group_by < 2:
return address
return " ".join(address[i*group_by:i*group_by+group_by] for i in range(1 + len(address) // group_by))
async def show_address(
ctx: GenericContext,
address: str,
@ -368,7 +373,9 @@ async def show_address(
xpubs: Sequence[str] = (),
address_extra: str | None = None,
title_qr: str | None = None,
group_by: int = 0,
) -> None:
#address = _address_group_by(address, group_by)
is_multisig = len(xpubs) > 0
while True:
result = await interact(

Loading…
Cancel
Save