1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-23 13:51:00 +00:00

refactor(core/rust/ui): text centering in Paragraphs

Only works for paragraphs, not formatted text.

[no changelog]
This commit is contained in:
Martin Milata 2022-07-03 16:24:06 +02:00
parent 247d9d267a
commit f0d5f9069a
4 changed files with 62 additions and 32 deletions

View File

@ -2,7 +2,7 @@ use super::iter::GlyphMetrics;
use crate::ui::{ use crate::ui::{
display, display,
display::{Color, Font}, display::{Color, Font},
geometry::{Offset, Point, Rect}, geometry::{Alignment, Offset, Point, Rect},
}; };
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -39,6 +39,8 @@ pub struct TextLayout {
/// Fonts, colors, line/page breaking behavior. /// Fonts, colors, line/page breaking behavior.
pub style: TextStyle, pub style: TextStyle,
/// Horizontal alignment.
pub align: Alignment,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -90,6 +92,7 @@ impl TextLayout {
padding_top: 0, padding_top: 0,
padding_bottom: 0, padding_bottom: 0,
style, style,
align: Alignment::Start,
} }
} }
@ -98,6 +101,11 @@ impl TextLayout {
self self
} }
pub fn with_align(mut self, align: Alignment) -> Self {
self.align = align;
self
}
pub fn initial_cursor(&self) -> Point { pub fn initial_cursor(&self) -> Point {
self.bounds.top_left() + Offset::y(self.style.text_font.text_height() + self.padding_top) self.bounds.top_left() + Offset::y(self.style.text_font.text_height() + self.padding_top)
} }
@ -173,13 +181,20 @@ impl TextLayout {
} }
while !remaining_text.is_empty() { while !remaining_text.is_empty() {
let remaining_width = self.bounds.x1 - cursor.x;
let span = Span::fit_horizontally( let span = Span::fit_horizontally(
remaining_text, remaining_text,
self.bounds.x1 - cursor.x, remaining_width,
self.style.text_font, self.style.text_font,
self.style.line_breaking, 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,
};
// Report the span at the cursor position. // Report the span at the cursor position.
sink.text(*cursor, self, &remaining_text[..span.length]); sink.text(*cursor, self, &remaining_text[..span.length]);

View File

@ -2,7 +2,8 @@ use heapless::Vec;
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never, Paginate}, component::{Component, Event, EventCtx, Never, Paginate},
geometry::{Dimensions, Insets, LinearPlacement, Rect}, display::Color,
geometry::{Alignment, Dimensions, Insets, LinearPlacement, Rect},
}; };
use super::layout::{LayoutFit, TextLayout, TextStyle}; use super::layout::{LayoutFit, TextLayout, TextStyle};
@ -52,6 +53,14 @@ where
self self
} }
pub fn add_color(self, style: TextStyle, color: Color, content: T) -> Self {
let color_style = TextStyle {
text_color: color,
..style
};
self.add(color_style, content)
}
pub fn add(mut self, style: TextStyle, content: T) -> Self { pub fn add(mut self, style: TextStyle, content: T) -> Self {
if content.as_ref().is_empty() { if content.as_ref().is_empty() {
return self; return self;
@ -71,6 +80,13 @@ where
self self
} }
pub fn centered(mut self) -> Self {
if let Some(ref mut para) = self.list.last_mut() {
para.layout.align = Alignment::Center;
};
self
}
pub fn add_break(mut self) -> Self { pub fn add_break(mut self) -> Self {
if let Some(ref mut para) = self.list.last_mut() { if let Some(ref mut para) = self.list.last_mut() {
para.break_after = true; para.break_after = true;

View File

@ -1,8 +1,6 @@
use core::ops::Deref;
use crate::ui::{ use crate::ui::{
component::{Child, Component, Event, EventCtx, Image, Label, Never}, component::{text::paragraphs::Paragraphs, Child, Component, Event, EventCtx, Image, Never},
geometry::{Grid, Insets, Rect}, geometry::{Grid, Insets, LinearPlacement, Rect},
}; };
use super::{theme, Button}; use super::{theme, Button};
@ -100,38 +98,48 @@ where
pub struct IconDialog<T, U> { pub struct IconDialog<T, U> {
image: Child<Image>, image: Child<Image>,
title: Label<T>, paragraphs: Paragraphs<T>,
description: Option<Label<T>>,
controls: Child<U>, controls: Child<U>,
} }
impl<T, U> IconDialog<T, U> impl<T, U> IconDialog<T, U>
where where
T: Deref<Target = str>, T: AsRef<str>,
U: Component, U: Component,
{ {
pub fn new(icon: &'static [u8], title: T, controls: U) -> Self { pub fn new(icon: &'static [u8], title: T, controls: U) -> Self {
Self { Self {
image: Child::new(Image::new(icon)), image: Child::new(Image::new(icon)),
title: Label::centered(title, theme::label_warning()), paragraphs: Paragraphs::new()
description: None, .with_placement(
LinearPlacement::vertical()
.align_at_start()
.with_spacing(Self::VALUE_SPACE),
)
.add(theme::TEXT_MEDIUM, title)
.centered(),
controls: Child::new(controls), controls: Child::new(controls),
} }
} }
pub fn with_description(mut self, description: T) -> Self { pub fn with_description(mut self, description: T) -> Self {
self.description = Some(Label::centered(description, theme::label_warning_value())); if !description.as_ref().is_empty() {
self.paragraphs = self
.paragraphs
.add_color(theme::TEXT_NORMAL, theme::OFF_WHITE, description)
.centered();
}
self self
} }
pub const ICON_AREA_HEIGHT: i32 = 64; pub const ICON_AREA_HEIGHT: i32 = 64;
pub const DESCRIPTION_SPACE: i32 = 13; pub const DESCRIPTION_SPACE: i32 = 14;
pub const VALUE_SPACE: i32 = 9; pub const VALUE_SPACE: i32 = 5;
} }
impl<T, U> Component for IconDialog<T, U> impl<T, U> Component for IconDialog<T, U>
where where
T: Deref<Target = str>, T: AsRef<str>,
U: Component, U: Component,
{ {
type Msg = DialogMsg<Never, U::Msg>; type Msg = DialogMsg<Never, U::Msg>;
@ -141,33 +149,27 @@ where
let (content, buttons) = bounds.split_bottom(Button::<&str>::HEIGHT); let (content, buttons) = bounds.split_bottom(Button::<&str>::HEIGHT);
let (image, content) = content.split_top(Self::ICON_AREA_HEIGHT); let (image, content) = content.split_top(Self::ICON_AREA_HEIGHT);
let content = content.inset(Insets::top(Self::DESCRIPTION_SPACE)); let content = content.inset(Insets::top(Self::DESCRIPTION_SPACE));
let (title, content) = content.split_top(self.title.size().y);
let content = content.inset(Insets::top(Self::VALUE_SPACE));
self.image.place(image); self.image.place(image);
self.title.place(title); self.paragraphs.place(content);
self.description.place(content);
self.controls.place(buttons); self.controls.place(buttons);
bounds bounds
} }
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.title.event(ctx, event); self.paragraphs.event(ctx, event);
self.description.event(ctx, event);
self.controls.event(ctx, event).map(Self::Msg::Controls) self.controls.event(ctx, event).map(Self::Msg::Controls)
} }
fn paint(&mut self) { fn paint(&mut self) {
self.image.paint(); self.image.paint();
self.title.paint(); self.paragraphs.paint();
self.description.paint();
self.controls.paint(); self.controls.paint();
} }
fn bounds(&self, sink: &mut dyn FnMut(Rect)) { fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.image.bounds(sink); self.image.bounds(sink);
self.title.bounds(sink); self.paragraphs.bounds(sink);
self.description.bounds(sink);
self.controls.bounds(sink); self.controls.bounds(sink);
} }
} }
@ -175,15 +177,12 @@ where
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for IconDialog<T, U> impl<T, U> crate::trace::Trace for IconDialog<T, U>
where where
T: Deref<Target = str>, T: AsRef<str>,
U: crate::trace::Trace, U: crate::trace::Trace,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("IconDialog"); t.open("IconDialog");
t.field("title", &self.title); t.field("content", &self.paragraphs);
if let Some(ref description) = self.description {
t.field("description", description);
}
t.field("controls", &self.controls); t.field("controls", &self.controls);
t.close(); t.close();
} }

View File

@ -77,7 +77,7 @@ where
impl<T, U> ComponentMsgObj for IconDialog<T, U> impl<T, U> ComponentMsgObj for IconDialog<T, U>
where where
T: Deref<Target = str>, T: AsRef<str>,
U: Component, U: Component,
<U as Component>::Msg: TryInto<Obj, Error = Error>, <U as Component>::Msg: TryInto<Obj, Error = Error>,
{ {