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:
parent
247d9d267a
commit
f0d5f9069a
@ -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]);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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>,
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user