1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-19 03:40:59 +00:00

refactor(core/mercury): extract frame header to separate component

[no changelog]
This commit is contained in:
tychovrahe 2024-07-11 09:55:35 +02:00 committed by TychoVrahe
parent 7d90552d81
commit e6a2a3b263
3 changed files with 166 additions and 67 deletions

View File

@ -2,7 +2,6 @@ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{ component::{
label::Label,
swipe_detect::{SwipeConfig, SwipeSettings}, swipe_detect::{SwipeConfig, SwipeSettings},
text::TextStyle, text::TextStyle,
Component, Event, Component, Event,
@ -19,19 +18,15 @@ use crate::{
}, },
}; };
use super::{theme, Button, ButtonMsg, ButtonStyleSheet, CancelInfoConfirmMsg, Footer}; use super::{theme, ButtonMsg, ButtonStyleSheet, CancelInfoConfirmMsg, Footer, Header};
const BUTTON_EXPAND_BORDER: i16 = 32;
#[derive(Clone)] #[derive(Clone)]
pub struct Frame<T> { pub struct Frame<T> {
border: Insets, border: Insets,
bounds: Rect, bounds: Rect,
title: Label<'static>,
subtitle: Option<Label<'static>>,
button: Option<Button>,
button_msg: CancelInfoConfirmMsg, button_msg: CancelInfoConfirmMsg,
content: T, content: T,
header: Header,
footer: Option<Footer<'static>>, footer: Option<Footer<'static>>,
swipe: SwipeConfig, swipe: SwipeConfig,
internal_page_cnt: usize, internal_page_cnt: usize,
@ -50,13 +45,11 @@ where
{ {
pub const fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self { pub const fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self {
Self { Self {
title: Label::new(title, alignment, theme::label_title_main()).vertically_centered(),
bounds: Rect::zero(), bounds: Rect::zero(),
subtitle: None,
border: theme::borders(), border: theme::borders(),
button: None,
button_msg: CancelInfoConfirmMsg::Cancelled, button_msg: CancelInfoConfirmMsg::Cancelled,
content, content,
header: Header::new(alignment, title),
footer: None, footer: None,
swipe: SwipeConfig::new(), swipe: SwipeConfig::new(),
internal_page_cnt: 1, internal_page_cnt: 1,
@ -88,21 +81,13 @@ where
#[inline(never)] #[inline(never)]
pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self { pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self {
let style = theme::TEXT_SUB_GREY; self.header = self.header.with_subtitle(subtitle);
self.title = self.title.top_aligned();
self.subtitle = Some(Label::new(subtitle, self.title.alignment(), style));
self self
} }
#[inline(never)] #[inline(never)]
fn with_button(mut self, icon: Icon, msg: CancelInfoConfirmMsg, enabled: bool) -> Self { fn with_button(mut self, icon: Icon, msg: CancelInfoConfirmMsg, enabled: bool) -> Self {
let touch_area = Insets::uniform(BUTTON_EXPAND_BORDER); self.header = self.header.with_button(icon, enabled);
self.button = Some(
Button::with_icon(icon)
.with_expanded_touch_area(touch_area)
.initially_enabled(enabled)
.styled(theme::button_default()),
);
self.button_msg = msg; self.button_msg = msg;
self self
} }
@ -121,21 +106,17 @@ where
} }
pub fn title_styled(mut self, style: TextStyle) -> Self { pub fn title_styled(mut self, style: TextStyle) -> Self {
self.title = self.title.styled(style); self.header = self.header.styled(style);
self self
} }
pub fn subtitle_styled(mut self, style: TextStyle) -> Self { pub fn subtitle_styled(mut self, style: TextStyle) -> Self {
if let Some(subtitle) = self.subtitle.take() { self.header = self.header.subtitle_styled(style);
self.subtitle = Some(subtitle.styled(style))
}
self self
} }
pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self { pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self {
if self.button.is_some() { self.header = self.header.button_styled(style);
self.button = Some(self.button.unwrap().styled(style));
}
self self
} }
@ -169,7 +150,7 @@ where
} }
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: TString<'static>) { pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: TString<'static>) {
self.title.set_text(new_title); self.header.update_title(new_title);
ctx.request_paint(); ctx.request_paint();
} }
@ -179,16 +160,7 @@ where
new_subtitle: TString<'static>, new_subtitle: TString<'static>,
new_style: Option<TextStyle>, new_style: Option<TextStyle>,
) { ) {
let style = new_style.unwrap_or(theme::TEXT_SUB_GREY); self.header.update_subtitle(new_subtitle, new_style);
match &mut self.subtitle {
Some(subtitle) => {
subtitle.set_style(style);
subtitle.set_text(new_subtitle);
}
None => {
self.subtitle = Some(Label::new(new_subtitle, self.title.alignment(), style));
}
}
ctx.request_paint(); ctx.request_paint();
} }
@ -245,19 +217,7 @@ where
content_area = content_area.inset(Insets::top(theme::SPACING)); content_area = content_area.inset(Insets::top(theme::SPACING));
header_area = header_area.inset(Insets::sides(theme::SPACING)); header_area = header_area.inset(Insets::sides(theme::SPACING));
if let Some(b) = &mut self.button { self.header.place(header_area);
let (rest, button_area) = header_area.split_right(TITLE_HEIGHT);
header_area = rest;
b.place(button_area);
}
if self.subtitle.is_some() {
let title_area = self.title.place(header_area);
let remaining = header_area.inset(Insets::top(title_area.height()));
let _subtitle_area = self.subtitle.place(remaining);
} else {
self.title.place(header_area);
}
if let Some(footer) = &mut self.footer { if let Some(footer) = &mut self.footer {
// FIXME: spacer at the bottom might be applied also for usage without footer // FIXME: spacer at the bottom might be applied also for usage without footer
@ -290,8 +250,6 @@ where
} }
} }
self.title.event(ctx, event);
self.subtitle.event(ctx, event);
self.footer.event(ctx, event); self.footer.event(ctx, event);
let msg = self.content.event(ctx, event).map(FrameMsg::Content); let msg = self.content.event(ctx, event).map(FrameMsg::Content);
if let Some(count) = ctx.page_count() { if let Some(count) = ctx.page_count() {
@ -301,23 +259,19 @@ where
if msg.is_some() { if msg.is_some() {
return msg; return msg;
} }
if let Some(ButtonMsg::Clicked) = self.button.event(ctx, event) { if let Some(ButtonMsg::Clicked) = self.header.event(ctx, event) {
return Some(FrameMsg::Button(self.button_msg)); return Some(FrameMsg::Button(self.button_msg));
} }
None None
} }
fn paint(&mut self) { fn paint(&mut self) {
self.title.paint(); self.header.paint();
self.subtitle.paint();
self.button.paint();
self.footer.paint(); self.footer.paint();
self.content.paint(); self.content.paint();
} }
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.title.render(target); self.header.render(target);
self.subtitle.render(target);
self.button.render(target);
self.footer.render(target); self.footer.render(target);
self.content.render(target); self.content.render(target);
@ -367,14 +321,9 @@ where
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Frame"); t.component("Frame");
t.child("title", &self.title); t.child("header", &self.header);
t.child("content", &self.content); t.child("content", &self.content);
if let Some(subtitle) = &self.subtitle {
t.child("subtitle", subtitle);
}
if let Some(button) = &self.button {
t.child("button", button);
}
if let Some(footer) = &self.footer { if let Some(footer) = &self.footer {
t.child("footer", footer); t.child("footer", footer);
} }

View File

@ -0,0 +1,148 @@
use crate::{
strutil::TString,
ui::{
component::{text::TextStyle, Component, Event, EventCtx, Label},
display::Icon,
geometry::{Alignment, Insets, Rect},
model_mercury::{
component::{Button, ButtonMsg, ButtonStyleSheet},
theme,
theme::TITLE_HEIGHT,
},
shape::Renderer,
},
};
const BUTTON_EXPAND_BORDER: i16 = 32;
#[derive(Clone)]
pub struct Header {
area: Rect,
title: Label<'static>,
subtitle: Option<Label<'static>>,
button: Option<Button>,
}
impl Header {
pub const fn new(alignment: Alignment, title: TString<'static>) -> Self {
Self {
area: Rect::zero(),
title: Label::new(title, alignment, theme::label_title_main()).vertically_centered(),
subtitle: None,
button: None,
}
}
pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self {
let style = theme::TEXT_SUB_GREY;
self.title = self.title.top_aligned();
self.subtitle = Some(Label::new(subtitle, self.title.alignment(), style));
self
}
pub fn styled(mut self, style: TextStyle) -> Self {
self.title = self.title.styled(style);
self
}
pub fn subtitle_styled(mut self, style: TextStyle) -> Self {
if let Some(subtitle) = self.subtitle.take() {
self.subtitle = Some(subtitle.styled(style))
}
self
}
pub fn update_title(&mut self, title: TString<'static>) {
self.title.set_text(title);
}
pub fn update_subtitle(
&mut self,
new_subtitle: TString<'static>,
new_style: Option<TextStyle>,
) {
let style = new_style.unwrap_or(theme::TEXT_SUB_GREY);
match &mut self.subtitle {
Some(subtitle) => {
subtitle.set_style(style);
subtitle.set_text(new_subtitle);
}
None => {
self.subtitle = Some(Label::new(new_subtitle, self.title.alignment(), style));
}
}
}
pub fn with_button(mut self, icon: Icon, enabled: bool) -> Self {
let touch_area = Insets::uniform(BUTTON_EXPAND_BORDER);
self.button = Some(
Button::with_icon(icon)
.with_expanded_touch_area(touch_area)
.initially_enabled(enabled)
.styled(theme::button_default()),
);
self
}
pub fn button_styled(mut self, style: ButtonStyleSheet) -> Self {
if self.button.is_some() {
self.button = Some(self.button.unwrap().styled(style));
}
self
}
}
impl Component for Header {
type Msg = ButtonMsg;
fn place(&mut self, bounds: Rect) -> Rect {
let header_area = if let Some(b) = &mut self.button {
let (rest, button_area) = bounds.split_right(TITLE_HEIGHT);
b.place(button_area);
rest
} else {
bounds
};
if self.subtitle.is_some() {
let title_area = self.title.place(header_area);
let remaining = header_area.inset(Insets::top(title_area.height()));
let _subtitle_area = self.subtitle.place(remaining);
} else {
self.title.place(header_area);
}
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.title.event(ctx, event);
self.subtitle.event(ctx, event);
self.button.event(ctx, event)
}
fn paint(&mut self) {
todo!()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.button.render(target);
self.title.render(target);
self.subtitle.render(target);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Header {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Header");
t.child("title", &self.title);
if let Some(subtitle) = &self.subtitle {
t.child("subtitle", subtitle);
}
if let Some(button) = &self.button {
t.child("button", button);
}
}
}

View File

@ -11,6 +11,7 @@ mod vertical_menu;
mod fido_icons; mod fido_icons;
mod error; mod error;
mod frame; mod frame;
mod header;
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
mod hold_to_confirm; mod hold_to_confirm;
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
@ -49,6 +50,7 @@ pub use error::ErrorScreen;
pub use fido::{FidoConfirm, FidoMsg}; pub use fido::{FidoConfirm, FidoMsg};
pub use footer::Footer; pub use footer::Footer;
pub use frame::{Frame, FrameMsg}; pub use frame::{Frame, FrameMsg};
pub use header::Header;
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
pub use hold_to_confirm::HoldToConfirm; pub use hold_to_confirm::HoldToConfirm;
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]