You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs

273 lines
8.2 KiB

use crate::{
strutil::TString,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
display::{self, Color, Font},
geometry::{Point, Rect},
shape,
shape::Renderer,
},
};
use super::{
theme::{BUTTON_HEIGHT, TITLE_AREA_HEIGHT, WHITE},
ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos,
};
const ALERT_AREA_START: i16 = 39;
#[derive(Copy, Clone)]
pub enum ConfirmMsg {
Cancel = 1,
Confirm = 2,
}
pub struct Confirm<U> {
bg: Pad,
bg_color: Color,
title: TString<'static>,
message: Child<Label<U>>,
alert: Option<Label<U>>,
info_title: Option<TString<'static>>,
info_text: Option<Label<U>>,
button_text: TString<'static>,
buttons: ButtonController,
/// Whether we are on the info screen (optional extra screen)
showing_info_screen: bool,
two_btn_confirm: bool,
}
impl<U> Confirm<U>
where
U: AsRef<str>,
{
pub fn new<T: Into<TString<'static>>>(
bg_color: Color,
title: T,
message: Label<U>,
alert: Option<Label<U>>,
button_text: T,
two_btn_confirm: bool,
) -> Self {
let button_text = button_text.into();
let btn_layout =
Self::get_button_layout_general(false, button_text, false, two_btn_confirm);
Self {
bg: Pad::with_background(bg_color).with_clear(),
bg_color,
title: title.into(),
message: Child::new(message),
alert,
info_title: None,
info_text: None,
button_text,
buttons: ButtonController::new(btn_layout),
showing_info_screen: false,
two_btn_confirm,
}
}
/// Adding optional info screen
pub fn with_info_screen<T: Into<TString<'static>>>(
mut self,
info_title: T,
info_text: Label<U>,
) -> Self {
self.info_title = Some(info_title.into());
self.info_text = Some(info_text);
self.buttons = ButtonController::new(self.get_button_layout());
self
}
fn has_info_screen(&self) -> bool {
self.info_title.is_some()
}
fn get_button_layout(&self) -> ButtonLayout {
Self::get_button_layout_general(
self.showing_info_screen,
self.button_text,
self.has_info_screen(),
self.two_btn_confirm,
)
}
/// Not relying on self here, to call it in constructor.
fn get_button_layout_general(
showing_info_screen: bool,
button_text: TString<'static>,
has_info_screen: bool,
two_btn_confirm: bool,
) -> ButtonLayout {
if showing_info_screen {
ButtonLayout::arrow_none_none()
} else if has_info_screen {
ButtonLayout::cancel_armed_info(button_text)
} else if two_btn_confirm {
ButtonLayout::cancel_armed_none(button_text)
} else {
ButtonLayout::cancel_none_text(button_text)
}
}
/// Reflecting the current page in the buttons.
fn update_buttons(&mut self) {
let btn_layout = self.get_button_layout();
self.buttons.set(btn_layout);
}
fn update_everything(&mut self, ctx: &mut EventCtx) {
self.bg.clear();
self.update_buttons();
self.info_text.request_complete_repaint(ctx);
self.message.request_complete_repaint(ctx);
self.alert.request_complete_repaint(ctx);
self.buttons.request_complete_repaint(ctx);
self.request_complete_repaint(ctx);
}
}
impl<U> Component for Confirm<U>
where
U: AsRef<str>,
{
type Msg = ConfirmMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.bg.place(bounds);
// Divide the screen into areas
let (_title_area, minus_title) = bounds.split_top(TITLE_AREA_HEIGHT);
let (between_title_and_buttons, button_area) = minus_title.split_bottom(BUTTON_HEIGHT);
// Texts for the main screen
let (message_area, alert_area) = if self.alert.is_some() {
between_title_and_buttons.split_top(ALERT_AREA_START - TITLE_AREA_HEIGHT)
} else {
(between_title_and_buttons, Rect::zero())
};
self.message.place(message_area);
self.alert.place(alert_area);
// Text for the info screen
self.info_text.place(between_title_and_buttons);
self.buttons.place(button_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let msg = self.buttons.event(ctx, event);
if self.showing_info_screen {
// Showing the info screen currently - going back with the left button
if let Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) = msg {
self.showing_info_screen = false;
self.update_everything(ctx);
};
None
} else if self.has_info_screen() {
// Being on the "main" screen but with an info screen available on the right
match msg {
Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) => {
Some(ConfirmMsg::Cancel)
}
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, _)) => {
Some(ConfirmMsg::Confirm)
}
Some(ButtonControllerMsg::Triggered(ButtonPos::Right, _)) => {
self.showing_info_screen = true;
self.update_everything(ctx);
None
}
_ => None,
}
} else if self.two_btn_confirm {
match msg {
Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) => {
Some(ConfirmMsg::Cancel)
}
Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, _)) => {
Some(ConfirmMsg::Confirm)
}
_ => None,
}
} else {
// There is just one main screen without info screen
match msg {
Some(ButtonControllerMsg::Triggered(ButtonPos::Left, _)) => {
Some(ConfirmMsg::Cancel)
}
Some(ButtonControllerMsg::Triggered(ButtonPos::Right, _)) => {
Some(ConfirmMsg::Confirm)
}
_ => None,
}
}
}
fn paint(&mut self) {
self.bg.paint();
let display_top_left = |text: TString<'static>| {
text.map(|t| {
display::text_top_left(Point::zero(), t, Font::BOLD, WHITE, self.bg_color)
});
};
// We are either on the info screen or on the "main" screen
if self.showing_info_screen {
if let Some(title) = self.info_title {
display_top_left(title);
}
self.info_text.paint();
} else {
display_top_left(self.title);
self.message.paint();
self.alert.paint();
}
self.buttons.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
let mut display_top_left = |text: TString<'static>| {
text.map(|t| {
shape::Text::new(Point::zero(), t)
.with_font(Font::BOLD)
.with_fg(WHITE)
.render(target);
});
};
// We are either on the info screen or on the "main" screen
if self.showing_info_screen {
if let Some(title) = self.info_title {
display_top_left(title);
}
self.info_text.render(target);
} else {
display_top_left(self.title);
self.message.render(target);
self.alert.render(target);
}
self.buttons.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.buttons.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<U> crate::trace::Trace for Confirm<U>
where
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("BlConfirm");
}
}