1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-04 21:05:29 +00:00

refactor(core/mercury): remove unnecessary Child

[no changelog]
This commit is contained in:
Martin Milata 2024-07-18 17:19:22 +02:00
parent 1212a7319a
commit 888e384f79
10 changed files with 140 additions and 198 deletions

View File

@ -6,9 +6,7 @@ use crate::{
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{base::Never, Bar, Component, Empty, Event, EventCtx, Label, Split},
base::Never, Bar, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split,
},
display::loader::{loader_circular_uncompress, LoaderDimensions}, display::loader::{loader_circular_uncompress, LoaderDimensions},
geometry::{Insets, Offset, Rect}, geometry::{Insets, Offset, Rect},
model_mercury::constant, model_mercury::constant,
@ -30,7 +28,7 @@ const LOADER_SPEED: u16 = 5;
pub struct CoinJoinProgress<U> { pub struct CoinJoinProgress<U> {
value: u16, value: u16,
indeterminate: bool, indeterminate: bool,
content: Child<Frame<Split<Empty, U>>>, content: Frame<Split<Empty, U>>,
// Label is not a child since circular loader paints large black rectangle which overlaps it. // Label is not a child since circular loader paints large black rectangle which overlaps it.
// To work around this, draw label every time loader is drawn. // To work around this, draw label every time loader is drawn.
label: Label<'static>, label: Label<'static>,
@ -65,8 +63,7 @@ where
content: Frame::centered( content: Frame::centered(
TR::coinjoin__title_progress.into(), TR::coinjoin__title_progress.into(),
Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner), Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner),
) ),
.into_child(),
label: Label::centered(text, theme::TEXT_NORMAL), label: Label::centered(text, theme::TEXT_NORMAL),
}) })
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad}, component::{Component, Event, EventCtx, Label, Never, Pad},
constant::screen, constant::screen,
geometry::{Alignment2D, Point, Rect}, geometry::{Alignment2D, Point, Rect},
shape, shape,
@ -26,9 +26,9 @@ const STYLE: &ResultStyle = &super::theme::RESULT_ERROR;
pub struct ErrorScreen<'a> { pub struct ErrorScreen<'a> {
bg: Pad, bg: Pad,
title: Child<Label<'a>>, title: Label<'a>,
message: Child<Label<'a>>, message: Label<'a>,
footer: Child<ResultFooter<'a>>, footer: ResultFooter<'a>,
} }
impl<'a> ErrorScreen<'a> { impl<'a> ErrorScreen<'a> {
@ -42,9 +42,9 @@ impl<'a> ErrorScreen<'a> {
Self { Self {
bg: Pad::with_background(FATAL_ERROR_COLOR).with_clear(), bg: Pad::with_background(FATAL_ERROR_COLOR).with_clear(),
title: Child::new(title), title,
message: Child::new(message), message,
footer: Child::new(footer), footer,
} }
} }
} }

View File

@ -1,9 +1,7 @@
use crate::{ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{ component::{image::Image, Component, Event, EventCtx, Label, Swipe, SwipeDirection},
image::Image, Child, Component, Event, EventCtx, Label, Swipe, SwipeDirection,
},
display, display,
geometry::{Insets, Rect}, geometry::{Insets, Rect},
model_mercury::component::{fido_icons::get_fido_icon_data, theme, ScrollBar}, model_mercury::component::{fido_icons::get_fido_icon_data, theme, ScrollBar},
@ -30,7 +28,7 @@ pub struct FidoConfirm<F: Fn(usize) -> TString<'static>, U> {
page_swipe: Swipe, page_swipe: Swipe,
app_name: Label<'static>, app_name: Label<'static>,
account_name: Label<'static>, account_name: Label<'static>,
icon: Child<Image>, icon: Image,
/// Function/closure that will return appropriate page on demand. /// Function/closure that will return appropriate page on demand.
get_account: F, get_account: F,
scrollbar: ScrollBar, scrollbar: ScrollBar,
@ -79,7 +77,7 @@ where
app_name: Label::centered(app_name, theme::TEXT_DEMIBOLD), app_name: Label::centered(app_name, theme::TEXT_DEMIBOLD),
account_name: Label::centered(current_account, theme::TEXT_DEMIBOLD), account_name: Label::centered(current_account, theme::TEXT_DEMIBOLD),
page_swipe, page_swipe,
icon: Child::new(Image::new(icon_data)), icon: Image::new(icon_data),
get_account, get_account,
scrollbar, scrollbar,
fade: Cell::new(false), fade: Cell::new(false),

View File

@ -2,7 +2,7 @@ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{ component::{
maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe, Swipe, maybe::paint_overlapping, Component, Event, EventCtx, Label, Maybe, Swipe,
SwipeDirection, SwipeDirection,
}, },
geometry::{Alignment, Grid, Insets, Rect}, geometry::{Alignment, Grid, Insets, Rect},
@ -24,17 +24,17 @@ pub enum MnemonicKeyboardMsg {
pub struct MnemonicKeyboard<T> { pub struct MnemonicKeyboard<T> {
/// Initial prompt, displayed on empty input. /// Initial prompt, displayed on empty input.
prompt: Child<Maybe<Label<'static>>>, prompt: Maybe<Label<'static>>,
/// Delete a char button. /// Delete a char button.
erase: Child<Maybe<Button>>, erase: Maybe<Button>,
/// Go to previous word button /// Go to previous word button
back: Child<Maybe<Button>>, back: Maybe<Button>,
/// Input area, acting as the auto-complete and confirm button. /// Input area, acting as the auto-complete and confirm button.
input: Child<Maybe<T>>, input: Maybe<T>,
/// Area with keypads - used for rounded overlay /// Area with keypads - used for rounded overlay
keypad_area: Rect, keypad_area: Rect,
/// Key buttons. /// Key buttons.
keys: [Child<Button>; MNEMONIC_KEY_COUNT], keys: [Button; MNEMONIC_KEY_COUNT],
/// Swipe controller - allowing for going to the previous word. /// Swipe controller - allowing for going to the previous word.
swipe: Swipe, swipe: Swipe,
/// Whether going back is allowed (is not on the very first word). /// Whether going back is allowed (is not on the very first word).
@ -56,26 +56,20 @@ where
.styled(theme::button_default()) .styled(theme::button_default())
.with_expanded_touch_area(Insets::right(BACK_BUTTON_RIGHT_EXPAND)); .with_expanded_touch_area(Insets::right(BACK_BUTTON_RIGHT_EXPAND));
Self { Self {
prompt: Child::new(Maybe::new( prompt: Maybe::new(
theme::BG, theme::BG,
Label::centered(prompt, theme::TEXT_MAIN_GREY_LIGHT).vertically_centered(), Label::centered(prompt, theme::TEXT_MAIN_GREY_LIGHT).vertically_centered(),
prompt_visible, prompt_visible,
)), ),
erase: Child::new(Maybe::new(theme::BG, erase_btn, !prompt_visible)), erase: Maybe::new(theme::BG, erase_btn, !prompt_visible),
back: Child::new(Maybe::new( back: Maybe::new(theme::BG, back_btn, prompt_visible && can_go_back),
theme::BG, input: Maybe::new(theme::BG, input, !prompt_visible),
back_btn,
prompt_visible && can_go_back,
)),
input: Child::new(Maybe::new(theme::BG, input, !prompt_visible)),
keypad_area: Rect::zero(), keypad_area: Rect::zero(),
keys: T::keys() keys: T::keys().map(|t| {
.map(|t| { Button::with_text(t.into())
Button::with_text(t.into()) .styled(theme::button_keyboard())
.styled(theme::button_keyboard()) .with_text_align(Alignment::Center)
.with_text_align(Alignment::Center) }),
})
.map(Child::new),
swipe: Swipe::new().right(), swipe: Swipe::new().right(),
can_go_back, can_go_back,
} }
@ -90,32 +84,23 @@ where
/// completion mask and the pending key. /// completion mask and the pending key.
fn toggle_key_buttons(&mut self, ctx: &mut EventCtx) { fn toggle_key_buttons(&mut self, ctx: &mut EventCtx) {
for (key, btn) in self.keys.iter_mut().enumerate() { for (key, btn) in self.keys.iter_mut().enumerate() {
let enabled = self let enabled = self.input.inner().can_key_press_lead_to_a_valid_word(key);
.input btn.enable_if(ctx, enabled);
.inner()
.inner()
.can_key_press_lead_to_a_valid_word(key);
btn.mutate(ctx, |ctx, b| b.enable_if(ctx, enabled));
} }
} }
/// After edit operations, we need to either show or hide the prompt, the /// After edit operations, we need to either show or hide the prompt, the
/// input, the erase button and the back button. /// input, the erase button and the back button.
fn toggle_prompt_or_input(&mut self, ctx: &mut EventCtx) { fn toggle_prompt_or_input(&mut self, ctx: &mut EventCtx) {
let prompt_visible = self.input.inner().inner().is_empty(); let prompt_visible = self.input.inner().is_empty();
self.prompt self.prompt.show_if(ctx, prompt_visible);
.mutate(ctx, |ctx, p| p.show_if(ctx, prompt_visible)); self.input.show_if(ctx, !prompt_visible);
self.input self.erase.show_if(ctx, !prompt_visible);
.mutate(ctx, |ctx, i| i.show_if(ctx, !prompt_visible)); self.back.show_if(ctx, prompt_visible && self.can_go_back);
self.erase
.mutate(ctx, |ctx, b| b.show_if(ctx, !prompt_visible));
self.back.mutate(ctx, |ctx, b| {
b.show_if(ctx, prompt_visible && self.can_go_back)
});
} }
pub fn mnemonic(&self) -> Option<&'static str> { pub fn mnemonic(&self) -> Option<&'static str> {
self.input.inner().inner().mnemonic() self.input.inner().mnemonic()
} }
} }
@ -181,14 +166,12 @@ where
match self.erase.event(ctx, event) { match self.erase.event(ctx, event) {
Some(ButtonMsg::Clicked) => { Some(ButtonMsg::Clicked) => {
self.input self.input.inner_mut().on_backspace_click(ctx);
.mutate(ctx, |ctx, i| i.inner_mut().on_backspace_click(ctx));
self.on_input_change(ctx); self.on_input_change(ctx);
return None; return None;
} }
Some(ButtonMsg::LongPressed) => { Some(ButtonMsg::LongPressed) => {
self.input self.input.inner_mut().on_backspace_long_press(ctx);
.mutate(ctx, |ctx, i| i.inner_mut().on_backspace_long_press(ctx));
self.on_input_change(ctx); self.on_input_change(ctx);
return None; return None;
} }
@ -196,8 +179,7 @@ where
} }
for (key, btn) in self.keys.iter_mut().enumerate() { for (key, btn) in self.keys.iter_mut().enumerate() {
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) { if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
self.input self.input.inner_mut().on_key_click(ctx, key);
.mutate(ctx, |ctx, i| i.inner_mut().on_key_click(ctx, key));
self.on_input_change(ctx); self.on_input_change(ctx);
return None; return None;
} }
@ -213,7 +195,7 @@ where
} }
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
if self.input.inner().inner().is_empty() { if self.input.inner().is_empty() {
self.prompt.render(target); self.prompt.render(target);
if self.can_go_back { if self.can_go_back {
self.back.render(target); self.back.render(target);

View File

@ -3,8 +3,8 @@ use crate::{
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Label, base::ComponentExt, text::common::TextBox, Component, Event, EventCtx, Label, Maybe,
Maybe, Never, Swipe, SwipeDirection, Never, Swipe, SwipeDirection,
}, },
display, display,
geometry::{Alignment, Grid, Insets, Offset, Rect}, geometry::{Alignment, Grid, Insets, Offset, Rect},
@ -75,14 +75,14 @@ impl From<KeyboardLayout> for ButtonContent {
pub struct PassphraseKeyboard { pub struct PassphraseKeyboard {
page_swipe: Swipe, page_swipe: Swipe,
input: Child<Input>, input: Input,
input_prompt: Child<Label<'static>>, input_prompt: Label<'static>,
erase_btn: Child<Maybe<Button>>, erase_btn: Maybe<Button>,
cancel_btn: Child<Maybe<Button>>, cancel_btn: Maybe<Button>,
confirm_btn: Child<Button>, confirm_btn: Button,
next_btn: Child<Button>, next_btn: Button,
keypad_area: Rect, keypad_area: Rect,
keys: [Child<Button>; KEY_COUNT], keys: [Button; KEY_COUNT],
active_layout: KeyboardLayout, active_layout: KeyboardLayout,
fade: Cell<bool>, fade: Cell<bool>,
} }
@ -105,43 +105,38 @@ impl PassphraseKeyboard {
let confirm_btn = Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24) let confirm_btn = Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24)
.styled(theme::button_passphrase_confirm()) .styled(theme::button_passphrase_confirm())
.with_radius(15) .with_radius(15);
.into_child();
let next_btn = Button::new(active_layout.next().into()) let next_btn = Button::new(active_layout.next().into())
.styled(theme::button_passphrase_next()) .styled(theme::button_passphrase_next())
.with_text_align(Alignment::Center) .with_text_align(Alignment::Center);
.into_child();
let erase_btn = Button::with_icon(theme::ICON_DELETE) let erase_btn = Button::with_icon(theme::ICON_DELETE)
.styled(theme::button_keyboard_erase()) .styled(theme::button_keyboard_erase())
.with_long_press(theme::ERASE_HOLD_DURATION) .with_long_press(theme::ERASE_HOLD_DURATION)
.initially_enabled(false); .initially_enabled(false);
let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child(); let erase_btn = Maybe::hidden(theme::BG, erase_btn);
let cancel_btn = let cancel_btn =
Button::with_icon(theme::ICON_CLOSE).styled(theme::button_keyboard_cancel()); Button::with_icon(theme::ICON_CLOSE).styled(theme::button_keyboard_cancel());
let cancel_btn = Maybe::visible(theme::BG, cancel_btn).into_child(); let cancel_btn = Maybe::visible(theme::BG, cancel_btn);
Self { Self {
page_swipe: Swipe::horizontal(), page_swipe: Swipe::horizontal(),
input: Input::new().into_child(), input: Input::new(),
input_prompt: Label::left_aligned( input_prompt: Label::left_aligned(
TString::from_translation(TR::passphrase__title_enter), TString::from_translation(TR::passphrase__title_enter),
theme::label_keyboard(), theme::label_keyboard(),
) ),
.into_child(),
erase_btn, erase_btn,
cancel_btn, cancel_btn,
confirm_btn, confirm_btn,
next_btn, next_btn,
keypad_area: Rect::zero(), keypad_area: Rect::zero(),
keys: KEYBOARD[active_layout.to_usize().unwrap()].map(|text| { keys: KEYBOARD[active_layout.to_usize().unwrap()].map(|text| {
Child::new( Button::new(Self::key_content(text))
Button::new(Self::key_content(text)) .styled(theme::button_keyboard())
.styled(theme::button_keyboard()) .with_text_align(Alignment::Center)
.with_text_align(Alignment::Center),
)
}), }),
active_layout, active_layout,
fade: Cell::new(false), fade: Cell::new(false),
@ -174,8 +169,7 @@ impl PassphraseKeyboard {
_ => self.active_layout, _ => self.active_layout,
}; };
// Clear the pending state. // Clear the pending state.
self.input self.input.multi_tap.clear_pending_state(ctx);
.mutate(ctx, |ctx, i| i.multi_tap.clear_pending_state(ctx));
// Update keys. // Update keys.
self.replace_keys_contents(ctx); self.replace_keys_contents(ctx);
// Reset backlight to normal level on next paint. // Reset backlight to normal level on next paint.
@ -186,13 +180,12 @@ impl PassphraseKeyboard {
} }
fn replace_keys_contents(&mut self, ctx: &mut EventCtx) { fn replace_keys_contents(&mut self, ctx: &mut EventCtx) {
self.next_btn.mutate(ctx, |ctx, b| { self.next_btn
b.set_content(ctx, self.active_layout.next().into()); .set_content(ctx, self.active_layout.next().into());
});
for (i, btn) in self.keys.iter_mut().enumerate() { for (i, btn) in self.keys.iter_mut().enumerate() {
let text = KEYBOARD[self.active_layout.to_usize().unwrap()][i]; let text = KEYBOARD[self.active_layout.to_usize().unwrap()][i];
let content = Self::key_content(text); let content = Self::key_content(text);
btn.mutate(ctx, |ctx, b| b.set_content(ctx, content)); btn.set_content(ctx, content);
btn.request_complete_repaint(ctx); btn.request_complete_repaint(ctx);
} }
} }
@ -201,15 +194,11 @@ impl PassphraseKeyboard {
fn after_edit(&mut self, ctx: &mut EventCtx) { fn after_edit(&mut self, ctx: &mut EventCtx) {
// When the input is empty, enable cancel button. Otherwise show erase and // When the input is empty, enable cancel button. Otherwise show erase and
// confirm button. // confirm button.
let is_empty = self.input.inner().textbox.is_empty(); let is_empty = self.input.textbox.is_empty();
self.erase_btn.mutate(ctx, |ctx, btn| { self.erase_btn.show_if(ctx, !is_empty);
btn.show_if(ctx, !is_empty); self.erase_btn.inner_mut().enable_if(ctx, !is_empty);
btn.inner_mut().enable_if(ctx, !is_empty); self.cancel_btn.show_if(ctx, is_empty);
}); self.cancel_btn.inner_mut().enable_if(ctx, is_empty);
self.cancel_btn.mutate(ctx, |ctx, btn| {
btn.show_if(ctx, is_empty);
btn.inner_mut().enable_if(ctx, is_empty);
});
self.update_input_btns_state(ctx); self.update_input_btns_state(ctx);
} }
@ -218,13 +207,11 @@ impl PassphraseKeyboard {
fn update_input_btns_state(&mut self, ctx: &mut EventCtx) { fn update_input_btns_state(&mut self, ctx: &mut EventCtx) {
let active_states = self.get_buttons_active_states(); let active_states = self.get_buttons_active_states();
for (key, btn) in self.keys.iter_mut().enumerate() { for (key, btn) in self.keys.iter_mut().enumerate() {
btn.mutate(ctx, |ctx, b| { if active_states[key] {
if active_states[key] { btn.enable(ctx);
b.enable(ctx); } else {
} else { btn.disable(ctx);
b.disable(ctx); }
}
});
} }
} }
@ -241,9 +228,9 @@ impl PassphraseKeyboard {
/// We should disable the input when the passphrase has reached maximum /// We should disable the input when the passphrase has reached maximum
/// length and we are not cycling through the characters. /// length and we are not cycling through the characters.
fn is_button_active(&self, key: usize) -> bool { fn is_button_active(&self, key: usize) -> bool {
let textbox_not_full = self.input.inner().textbox.len() < MAX_LENGTH; let textbox_not_full = self.input.textbox.len() < MAX_LENGTH;
let key_is_pending = { let key_is_pending = {
if let Some(pending) = self.input.inner().multi_tap.pending_key() { if let Some(pending) = self.input.multi_tap.pending_key() {
pending == key pending == key
} else { } else {
false false
@ -253,7 +240,7 @@ impl PassphraseKeyboard {
} }
pub fn passphrase(&self) -> &str { pub fn passphrase(&self) -> &str {
self.input.inner().textbox.content() self.input.textbox.content()
} }
} }
@ -307,9 +294,8 @@ impl Component for PassphraseKeyboard {
} }
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if self.input.inner().multi_tap.is_timeout_event(event) { if self.input.multi_tap.is_timeout_event(event) {
self.input self.input.multi_tap.clear_pending_state(ctx);
.mutate(ctx, |ctx, i| i.multi_tap.clear_pending_state(ctx));
return None; return None;
} }
if let Some(swipe) = self.page_swipe.event(ctx, event) { if let Some(swipe) = self.page_swipe.event(ctx, event) {
@ -331,18 +317,14 @@ impl Component for PassphraseKeyboard {
match self.erase_btn.event(ctx, event) { match self.erase_btn.event(ctx, event) {
Some(ButtonMsg::Clicked) => { Some(ButtonMsg::Clicked) => {
self.input.mutate(ctx, |ctx, i| { self.input.multi_tap.clear_pending_state(ctx);
i.multi_tap.clear_pending_state(ctx); self.input.textbox.delete_last(ctx);
i.textbox.delete_last(ctx);
});
self.after_edit(ctx); self.after_edit(ctx);
return None; return None;
} }
Some(ButtonMsg::LongPressed) => { Some(ButtonMsg::LongPressed) => {
self.input.mutate(ctx, |ctx, i| { self.input.multi_tap.clear_pending_state(ctx);
i.multi_tap.clear_pending_state(ctx); self.input.textbox.clear(ctx);
i.textbox.clear(ctx);
});
self.after_edit(ctx); self.after_edit(ctx);
return None; return None;
} }
@ -362,11 +344,9 @@ impl Component for PassphraseKeyboard {
if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) { if let Some(ButtonMsg::Clicked) = btn.event(ctx, event) {
// Key button was clicked. If this button is pending, let's cycle the pending // Key button was clicked. If this button is pending, let's cycle the pending
// character in textbox. If not, let's just append the first character. // character in textbox. If not, let's just append the first character.
let text = Self::key_text(btn.inner().content()); let text = Self::key_text(btn.content());
self.input.mutate(ctx, |ctx, i| { let edit = text.map(|c| self.input.multi_tap.click_key(ctx, key, c));
let edit = text.map(|c| i.multi_tap.click_key(ctx, key, c)); self.input.textbox.apply(ctx, edit);
i.textbox.apply(ctx, edit);
});
self.after_edit(ctx); self.after_edit(ctx);
return None; return None;
} }
@ -383,7 +363,7 @@ impl Component for PassphraseKeyboard {
self.next_btn.render(target); self.next_btn.render(target);
self.erase_btn.render(target); self.erase_btn.render(target);
self.confirm_btn.render(target); self.confirm_btn.render(target);
if self.input.inner().textbox.is_empty() { if self.input.textbox.is_empty() {
self.cancel_btn.render(target); self.cancel_btn.render(target);
// FIXME: when prompt fixed in Figma // FIXME: when prompt fixed in Figma
// self.input_prompt.render(target); // self.input_prompt.render(target);

View File

@ -8,7 +8,7 @@ use crate::{
component::{ component::{
base::{AttachType, ComponentExt}, base::{AttachType, ComponentExt},
text::TextStyle, text::TextStyle,
Child, Component, Event, EventCtx, Label, Never, Pad, SwipeDirection, TimerToken, Component, Event, EventCtx, Label, Never, Pad, SwipeDirection, TimerToken,
}, },
display::Font, display::Font,
event::TouchEvent, event::TouchEvent,
@ -246,12 +246,12 @@ pub struct PinKeyboard<'a> {
allow_cancel: bool, allow_cancel: bool,
show_erase: bool, show_erase: bool,
show_cancel: bool, show_cancel: bool,
major_prompt: Child<Label<'a>>, major_prompt: Label<'a>,
minor_prompt: Child<Label<'a>>, minor_prompt: Label<'a>,
major_warning: Option<Child<Label<'a>>>, major_warning: Option<Label<'a>>,
keypad_area: Rect, keypad_area: Rect,
textbox_area: Rect, textbox_area: Rect,
textbox: Child<PinDots>, textbox: PinDots,
erase_btn: Button, erase_btn: Button,
cancel_btn: Button, cancel_btn: Button,
confirm_btn: Button, confirm_btn: Button,
@ -282,15 +282,13 @@ impl<'a> PinKeyboard<'a> {
allow_cancel, allow_cancel,
show_erase: false, show_erase: false,
show_cancel: allow_cancel, show_cancel: allow_cancel,
major_prompt: Label::left_aligned(major_prompt, theme::label_keyboard()).into_child(), major_prompt: Label::left_aligned(major_prompt, theme::label_keyboard()),
minor_prompt: Label::right_aligned(minor_prompt, theme::label_keyboard_minor()) minor_prompt: Label::right_aligned(minor_prompt, theme::label_keyboard_minor()),
.into_child(), major_warning: major_warning
major_warning: major_warning.map(|text| { .map(|text| Label::left_aligned(text, theme::label_keyboard_warning())),
Label::left_aligned(text, theme::label_keyboard_warning()).into_child()
}),
keypad_area: Rect::zero(), keypad_area: Rect::zero(),
textbox_area: Rect::zero(), textbox_area: Rect::zero(),
textbox: PinDots::new(theme::label_default()).into_child(), textbox: PinDots::new(theme::label_default()),
erase_btn, erase_btn,
cancel_btn, cancel_btn,
confirm_btn: Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24) confirm_btn: Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24)
@ -318,8 +316,8 @@ impl<'a> PinKeyboard<'a> {
} }
fn pin_modified(&mut self, ctx: &mut EventCtx) { fn pin_modified(&mut self, ctx: &mut EventCtx) {
let is_full = self.textbox.inner().is_full(); let is_full = self.textbox.is_full();
let is_empty = self.textbox.inner().is_empty(); let is_empty = self.textbox.is_empty();
self.textbox.request_complete_repaint(ctx); self.textbox.request_complete_repaint(ctx);
@ -343,7 +341,7 @@ impl<'a> PinKeyboard<'a> {
} }
pub fn pin(&self) -> &str { pub fn pin(&self) -> &str {
self.textbox.inner().pin() self.textbox.pin()
} }
fn get_button_alpha(&self, x: usize, y: usize, attach_time: f32, close_time: f32) -> u8 { fn get_button_alpha(&self, x: usize, y: usize, attach_time: f32, close_time: f32) -> u8 {
@ -454,12 +452,12 @@ impl Component for PinKeyboard<'_> {
} }
match self.erase_btn.event(ctx, event) { match self.erase_btn.event(ctx, event) {
Some(ButtonMsg::Clicked) => { Some(ButtonMsg::Clicked) => {
self.textbox.mutate(ctx, |ctx, t| t.pop(ctx)); self.textbox.pop(ctx);
self.pin_modified(ctx); self.pin_modified(ctx);
return None; return None;
} }
Some(ButtonMsg::LongPressed) => { Some(ButtonMsg::LongPressed) => {
self.textbox.mutate(ctx, |ctx, t| t.clear(ctx)); self.textbox.clear(ctx);
self.pin_modified(ctx); self.pin_modified(ctx);
return None; return None;
} }
@ -469,7 +467,7 @@ impl Component for PinKeyboard<'_> {
if let Some(Clicked) = btn.0.event(ctx, event) { if let Some(Clicked) = btn.0.event(ctx, event) {
if let ButtonContent::Text(text) = btn.0.content() { if let ButtonContent::Text(text) = btn.0.content() {
text.map(|text| { text.map(|text| {
self.textbox.mutate(ctx, |ctx, t| t.push(ctx, text)); self.textbox.push(ctx, text);
}); });
self.pin_modified(ctx); self.pin_modified(ctx);
return None; return None;
@ -493,7 +491,7 @@ impl Component for PinKeyboard<'_> {
self.erase_btn.render_with_alpha(target, erase_alpha); self.erase_btn.render_with_alpha(target, erase_alpha);
} }
if self.textbox.inner().is_empty() { if self.textbox.is_empty() {
if let Some(ref w) = self.major_warning { if let Some(ref w) = self.major_warning {
w.render(target); w.render(target);
} else { } else {
@ -710,7 +708,7 @@ impl crate::trace::Trace for PinKeyboard<'_> {
} }
} }
t.string("digits_order", digits_order.as_str().into()); t.string("digits_order", digits_order.as_str().into());
t.string("pin", self.textbox.inner().pin().into()); t.string("pin", self.textbox.pin().into());
t.bool("display_digits", self.textbox.inner().display_digits); t.bool("display_digits", self.textbox.display_digits);
} }
} }

View File

@ -3,9 +3,8 @@ use crate::{
strutil::{self, TString}, strutil::{self, TString},
ui::{ ui::{
component::{ component::{
base::ComponentExt,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
Child, Component, Event, EventCtx, Pad, SwipeDirection, Component, Event, EventCtx, Pad, SwipeDirection,
}, },
display::Font, display::Font,
event::SwipeEvent, event::SwipeEvent,
@ -23,8 +22,8 @@ pub enum NumberInputDialogMsg {
pub struct NumberInputDialog { pub struct NumberInputDialog {
area: Rect, area: Rect,
input: Child<NumberInput>, input: NumberInput,
paragraphs: Child<Paragraphs<Paragraph<'static>>>, paragraphs: Paragraphs<Paragraph<'static>>,
paragraphs_pad: Pad, paragraphs_pad: Pad,
} }
@ -32,15 +31,14 @@ impl NumberInputDialog {
pub fn new(min: u32, max: u32, init_value: u32, text: TString<'static>) -> Result<Self, Error> { pub fn new(min: u32, max: u32, init_value: u32, text: TString<'static>) -> Result<Self, Error> {
Ok(Self { Ok(Self {
area: Rect::zero(), area: Rect::zero(),
input: NumberInput::new(min, max, init_value).into_child(), input: NumberInput::new(min, max, init_value),
paragraphs: Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text)) paragraphs: Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text)),
.into_child(),
paragraphs_pad: Pad::with_background(theme::BG), paragraphs_pad: Pad::with_background(theme::BG),
}) })
} }
pub fn value(&self) -> u32 { pub fn value(&self) -> u32 {
self.input.inner().value self.input.value
} }
} }
@ -69,7 +67,7 @@ impl Component for NumberInputDialog {
} }
if let Event::Swipe(SwipeEvent::End(SwipeDirection::Up)) = event { if let Event::Swipe(SwipeEvent::End(SwipeDirection::Up)) = event {
return Some(NumberInputDialogMsg::Confirmed(self.input.inner().value)); return Some(NumberInputDialogMsg::Confirmed(self.input.value));
} }
self.paragraphs.event(ctx, event); self.paragraphs.event(ctx, event);
None None
@ -101,8 +99,8 @@ pub enum NumberInputMsg {
pub struct NumberInput { pub struct NumberInput {
area: Rect, area: Rect,
dec: Child<Button>, dec: Button,
inc: Child<Button>, inc: Button,
min: u32, min: u32,
max: u32, max: u32,
value: u32, value: u32,
@ -110,12 +108,8 @@ pub struct NumberInput {
impl NumberInput { impl NumberInput {
pub fn new(min: u32, max: u32, value: u32) -> Self { pub fn new(min: u32, max: u32, value: u32) -> Self {
let dec = Button::with_icon(theme::ICON_MINUS) let dec = Button::with_icon(theme::ICON_MINUS).styled(theme::button_counter());
.styled(theme::button_counter()) let inc = Button::with_icon(theme::ICON_PLUS).styled(theme::button_counter());
.into_child();
let inc = Button::with_icon(theme::ICON_PLUS)
.styled(theme::button_counter())
.into_child();
let value = value.clamp(min, max); let value = value.clamp(min, max);
Self { Self {
area: Rect::zero(), area: Rect::zero(),
@ -150,10 +144,8 @@ impl Component for NumberInput {
changed = true; changed = true;
}; };
if changed { if changed {
self.dec self.dec.enable_if(ctx, self.value > self.min);
.mutate(ctx, |ctx, btn| btn.enable_if(ctx, self.value > self.min)); self.inc.enable_if(ctx, self.value < self.max);
self.inc
.mutate(ctx, |ctx, btn| btn.enable_if(ctx, self.value < self.max));
ctx.request_paint(); ctx.request_paint();
return Some(NumberInputMsg::Changed(self.value)); return Some(NumberInputMsg::Changed(self.value));
} }

View File

@ -3,7 +3,7 @@ use crate::{
strutil::{ShortString, TString}, strutil::{ShortString, TString},
translations::TR, translations::TR,
ui::{ ui::{
component::{base::ComponentExt, Child, Component, Event, EventCtx}, component::{Component, Event, EventCtx},
constant::screen, constant::screen,
display, display,
event::TouchEvent, event::TouchEvent,
@ -18,7 +18,7 @@ pub enum NumberInputSliderDialogMsg {
pub struct NumberInputSliderDialog { pub struct NumberInputSliderDialog {
area: Rect, area: Rect,
input: Child<NumberInputSlider>, input: NumberInputSlider,
footer: Footer<'static>, footer: Footer<'static>,
min: u16, min: u16,
max: u16, max: u16,
@ -30,7 +30,7 @@ impl NumberInputSliderDialog {
pub fn new(min: u16, max: u16, init_value: u16) -> Self { pub fn new(min: u16, max: u16, init_value: u16) -> Self {
Self { Self {
area: Rect::zero(), area: Rect::zero(),
input: NumberInputSlider::new(min, max, init_value).into_child(), input: NumberInputSlider::new(min, max, init_value),
footer: Footer::new::<TString<'static>>( footer: Footer::new::<TString<'static>>(
TR::instructions__swipe_horizontally.into(), TR::instructions__swipe_horizontally.into(),
Some(TR::setting__adjust.into()), Some(TR::setting__adjust.into()),
@ -43,7 +43,7 @@ impl NumberInputSliderDialog {
} }
pub fn value(&self) -> u16 { pub fn value(&self) -> u16 {
self.input.inner().value self.input.value
} }
} }
@ -79,7 +79,7 @@ impl Component for NumberInputSliderDialog {
if let Some(value) = self.input.event(ctx, event) { if let Some(value) = self.input.event(ctx, event) {
self.val = value; self.val = value;
if self.val == self.init_val || self.input.inner().touching { if self.val == self.init_val || self.input.touching {
self.footer self.footer
.update_instruction(ctx, TR::instructions__swipe_horizontally); .update_instruction(ctx, TR::instructions__swipe_horizontally);
self.footer.update_description(ctx, TR::setting__adjust); self.footer.update_description(ctx, TR::setting__adjust);

View File

@ -4,10 +4,9 @@ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{ component::{
base::ComponentExt,
paginated::Paginate, paginated::Paginate,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
Child, Component, Event, EventCtx, Label, Never, Pad, Component, Event, EventCtx, Label, Never, Pad,
}, },
display::{self, Font, LOADER_MAX}, display::{self, Font, LOADER_MAX},
geometry::{Insets, Offset, Rect}, geometry::{Insets, Offset, Rect},
@ -23,11 +22,11 @@ use crate::{
use super::theme; use super::theme;
pub struct Progress { pub struct Progress {
title: Child<Label<'static>>, title: Label<'static>,
value: u16, value: u16,
loader_y_offset: i16, loader_y_offset: i16,
indeterminate: bool, indeterminate: bool,
description: Child<Paragraphs<Paragraph<'static>>>, description: Paragraphs<Paragraph<'static>>,
description_pad: Pad, description_pad: Pad,
} }
@ -40,14 +39,13 @@ impl Progress {
description: TString<'static>, description: TString<'static>,
) -> Self { ) -> Self {
Self { Self {
title: Label::centered(title, theme::label_progress()).into_child(), title: Label::centered(title, theme::label_progress()),
value: 0, value: 0,
loader_y_offset: 0, loader_y_offset: 0,
indeterminate, indeterminate,
description: Paragraphs::new( description: Paragraphs::new(
Paragraph::new(&theme::TEXT_NORMAL, description).centered(), Paragraph::new(&theme::TEXT_NORMAL, description).centered(),
) ),
.into_child(),
description_pad: Pad::with_background(theme::BG), description_pad: Pad::with_background(theme::BG),
} }
} }
@ -60,10 +58,9 @@ impl Component for Progress {
let description_lines = 1 + self let description_lines = 1 + self
.description .description
.inner() .inner()
.inner()
.content() .content()
.map(|t| t.chars().filter(|c| *c == '\n').count() as i16); .map(|t| t.chars().filter(|c| *c == '\n').count() as i16);
let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y); let (title, rest) = Self::AREA.split_top(self.title.max_size().y);
let (loader, description) = let (loader, description) =
rest.split_bottom(Font::NORMAL.line_height() * description_lines); rest.split_bottom(Font::NORMAL.line_height() * description_lines);
let loader = loader.inset(Insets::top(theme::CONTENT_BORDER)); let loader = loader.inset(Insets::top(theme::CONTENT_BORDER));
@ -80,14 +77,12 @@ impl Component for Progress {
if !animation_disabled() { if !animation_disabled() {
ctx.request_paint(); ctx.request_paint();
} }
self.description.mutate(ctx, |ctx, para| { if self.description.inner_mut().content() != &new_description {
if para.inner_mut().content() != &new_description { self.description.inner_mut().update(new_description);
para.inner_mut().update(new_description); self.description.change_page(0); // Recompute bounding box.
para.change_page(0); // Recompute bounding box. ctx.request_paint();
ctx.request_paint(); self.description_pad.clear();
self.description_pad.clear(); }
}
});
} }
} }
None None

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{text::TextStyle, Child, Component, Event, EventCtx, Label, Never, Pad}, component::{text::TextStyle, Component, Event, EventCtx, Label, Never, Pad},
constant::screen, constant::screen,
display::{self, Color, Font, Icon}, display::{self, Color, Font, Icon},
geometry::{Alignment2D, Insets, Offset, Point, Rect}, geometry::{Alignment2D, Insets, Offset, Point, Rect},
@ -117,8 +117,8 @@ pub struct ResultScreen<'a> {
footer_pad: Pad, footer_pad: Pad,
style: &'a ResultStyle, style: &'a ResultStyle,
icon: Icon, icon: Icon,
message: Child<Label<'a>>, message: Label<'a>,
footer: Child<ResultFooter<'a>>, footer: ResultFooter<'a>,
} }
impl<'a> ResultScreen<'a> { impl<'a> ResultScreen<'a> {
@ -134,8 +134,8 @@ impl<'a> ResultScreen<'a> {
footer_pad: Pad::with_background(style.bg_color), footer_pad: Pad::with_background(style.bg_color),
style, style,
icon, icon,
message: Child::new(Label::centered(message, style.message_style())), message: Label::centered(message, style.message_style()),
footer: Child::new(ResultFooter::new(footer, style)), footer: ResultFooter::new(footer, style),
}; };
if complete_draw { if complete_draw {