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:
parent
7d90552d81
commit
e6a2a3b263
@ -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);
|
||||||
}
|
}
|
||||||
|
148
core/embed/rust/src/ui/model_mercury/component/header.rs
Normal file
148
core/embed/rust/src/ui/model_mercury/component/header.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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")]
|
||||||
|
Loading…
Reference in New Issue
Block a user