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_mercury/component/dialog.rs

234 lines
6.3 KiB

use crate::{
strutil::StringType,
ui::{
component::{
image::BlendedImage,
text::{
paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
TextStyle,
},
Child, Component, Event, EventCtx, Never,
},
geometry::{Insets, LinearPlacement, Rect},
shape::Renderer,
},
};
use super::theme;
pub enum DialogMsg<T, U> {
Content(T),
Controls(U),
}
pub struct Dialog<T, U> {
content: Child<T>,
controls: Child<U>,
}
impl<T, U> Dialog<T, U>
where
T: Component,
U: Component,
{
pub fn new(content: T, controls: U) -> Self {
Self {
content: Child::new(content),
controls: Child::new(controls),
}
}
pub fn inner(&self) -> &T {
self.content.inner()
}
}
impl<T, U> Component for Dialog<T, U>
where
T: Component,
U: Component,
{
type Msg = DialogMsg<T::Msg, U::Msg>;
fn place(&mut self, bounds: Rect) -> Rect {
let controls_area = self.controls.place(bounds);
let content_area = bounds
.inset(Insets::bottom(controls_area.height()))
.inset(Insets::bottom(theme::BUTTON_SPACING))
.inset(Insets::left(theme::CONTENT_BORDER));
self.content.place(content_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.content
.event(ctx, event)
.map(Self::Msg::Content)
.or_else(|| self.controls.event(ctx, event).map(Self::Msg::Controls))
}
fn paint(&mut self) {
self.content.paint();
self.controls.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.content.render(target);
self.controls.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.content.bounds(sink);
self.controls.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Dialog<T, U>
where
T: crate::trace::Trace,
U: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Dialog");
t.child("content", &self.content);
t.child("controls", &self.controls);
}
}
pub struct IconDialog<T, U> {
image: Child<BlendedImage>,
paragraphs: Paragraphs<ParagraphVecShort<T>>,
controls: Child<U>,
}
impl<T, U> IconDialog<T, U>
where
T: StringType,
U: Component,
{
pub fn new(icon: BlendedImage, title: T, controls: U) -> Self {
Self {
image: Child::new(icon),
paragraphs: Paragraphs::new(ParagraphVecShort::from_iter([Paragraph::new(
&theme::TEXT_DEMIBOLD,
title,
)
.centered()]))
.with_placement(
LinearPlacement::vertical()
.align_at_center()
.with_spacing(Self::VALUE_SPACE),
),
controls: Child::new(controls),
}
}
pub fn with_paragraph(mut self, para: Paragraph<T>) -> Self {
if !para.content().as_ref().is_empty() {
self.paragraphs.inner_mut().add(para);
}
self
}
pub fn with_text(self, style: &'static TextStyle, text: T) -> Self {
self.with_paragraph(Paragraph::new(style, text).centered())
}
pub fn with_description(self, description: T) -> Self {
self.with_text(&theme::TEXT_NORMAL_OFF_WHITE, description)
}
pub fn with_value(self, value: T) -> Self {
self.with_text(&theme::TEXT_MONO, value)
}
pub fn new_shares(lines: [T; 4], controls: U) -> Self {
let [l0, l1, l2, l3] = lines;
Self {
image: Child::new(BlendedImage::new(
theme::IMAGE_BG_CIRCLE,
theme::IMAGE_FG_SUCCESS,
theme::SUCCESS_COLOR,
theme::FG,
theme::BG,
)),
paragraphs: ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, l0).centered(),
Paragraph::new(&theme::TEXT_DEMIBOLD, l1).centered(),
Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, l2).centered(),
Paragraph::new(&theme::TEXT_DEMIBOLD, l3).centered(),
])
.into_paragraphs()
.with_placement(LinearPlacement::vertical().align_at_center()),
controls: Child::new(controls),
}
}
pub const ICON_AREA_PADDING: i16 = 2;
pub const ICON_AREA_HEIGHT: i16 = 60;
pub const VALUE_SPACE: i16 = 5;
}
impl<T, U> Component for IconDialog<T, U>
where
T: StringType,
U: Component,
{
type Msg = DialogMsg<Never, U::Msg>;
fn place(&mut self, bounds: Rect) -> Rect {
let bounds = bounds
.inset(theme::borders())
.inset(Insets::top(Self::ICON_AREA_PADDING));
let controls_area = self.controls.place(bounds);
let content_area = bounds.inset(Insets::bottom(controls_area.height()));
let (image_area, content_area) = content_area.split_top(Self::ICON_AREA_HEIGHT);
self.image.place(image_area);
self.paragraphs.place(content_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.paragraphs.event(ctx, event);
self.controls.event(ctx, event).map(Self::Msg::Controls)
}
fn paint(&mut self) {
self.image.paint();
self.paragraphs.paint();
self.controls.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.image.render(target);
self.paragraphs.render(target);
self.controls.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.image.bounds(sink);
self.paragraphs.bounds(sink);
self.controls.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for IconDialog<T, U>
where
T: StringType,
U: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("IconDialog");
t.child("image", &self.image);
t.child("content", &self.paragraphs);
t.child("controls", &self.controls);
}
}