|
|
|
@ -2,24 +2,29 @@ use core::mem;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
strutil::{ShortString, TString},
|
|
|
|
|
time::Duration,
|
|
|
|
|
time::{Duration, Stopwatch},
|
|
|
|
|
trezorhal::random,
|
|
|
|
|
ui::{
|
|
|
|
|
component::{
|
|
|
|
|
base::ComponentExt, text::TextStyle, Child, Component, Event, EventCtx, Label, Maybe,
|
|
|
|
|
Never, Pad, TimerToken,
|
|
|
|
|
base::{AttachType, ComponentExt},
|
|
|
|
|
text::TextStyle,
|
|
|
|
|
Child, Component, Event, EventCtx, Label, Never, Pad, SwipeDirection, TimerToken,
|
|
|
|
|
},
|
|
|
|
|
display::Font,
|
|
|
|
|
event::TouchEvent,
|
|
|
|
|
geometry::{Alignment, Alignment2D, Grid, Insets, Offset, Rect},
|
|
|
|
|
model_mercury::component::{
|
|
|
|
|
button::{
|
|
|
|
|
Button, ButtonContent,
|
|
|
|
|
ButtonMsg::{self, Clicked},
|
|
|
|
|
model_mercury::{
|
|
|
|
|
component::{
|
|
|
|
|
button::{
|
|
|
|
|
Button, ButtonContent,
|
|
|
|
|
ButtonMsg::{self, Clicked},
|
|
|
|
|
},
|
|
|
|
|
theme,
|
|
|
|
|
},
|
|
|
|
|
theme,
|
|
|
|
|
cshape,
|
|
|
|
|
},
|
|
|
|
|
shape::{self, Renderer},
|
|
|
|
|
util::animation_disabled,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -44,18 +49,217 @@ const HEADER_PADDING: Insets = Insets::new(
|
|
|
|
|
HEADER_PADDING_SIDE,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
#[derive(Default, Clone)]
|
|
|
|
|
struct AttachAnimation {
|
|
|
|
|
pub attach_top: bool,
|
|
|
|
|
pub timer: Stopwatch,
|
|
|
|
|
pub active: bool,
|
|
|
|
|
pub duration: Duration,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AttachAnimation {
|
|
|
|
|
const DURATION_MS: u32 = 750;
|
|
|
|
|
fn is_active(&self) -> bool {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.timer.is_running_within(self.duration)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eval(&self) -> f32 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return 1.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.timer.elapsed().to_millis() as f32 / 1000.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn opacity(&self, t: f32, pos_x: usize, pos_y: usize) -> u8 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return 255;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let diag = pos_x + pos_y;
|
|
|
|
|
|
|
|
|
|
let start = diag as f32 * 0.05;
|
|
|
|
|
|
|
|
|
|
let f = pareen::constant(0.0)
|
|
|
|
|
.seq_ease_in_out(
|
|
|
|
|
start,
|
|
|
|
|
easer::functions::Cubic,
|
|
|
|
|
0.1,
|
|
|
|
|
pareen::constant(1.0).eval(self.eval()),
|
|
|
|
|
)
|
|
|
|
|
.eval(t);
|
|
|
|
|
|
|
|
|
|
(f * 255.0) as u8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn header_opacity(&self, t: f32) -> u8 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return 255;
|
|
|
|
|
}
|
|
|
|
|
let f = pareen::constant(0.0)
|
|
|
|
|
.seq_ease_in_out(
|
|
|
|
|
0.65,
|
|
|
|
|
easer::functions::Linear,
|
|
|
|
|
0.1,
|
|
|
|
|
pareen::constant(1.0).eval(self.eval()),
|
|
|
|
|
)
|
|
|
|
|
.eval(t);
|
|
|
|
|
|
|
|
|
|
(f * 255.0) as u8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start(&mut self) {
|
|
|
|
|
self.active = true;
|
|
|
|
|
self.timer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn reset(&mut self) {
|
|
|
|
|
self.active = false;
|
|
|
|
|
self.timer = Stopwatch::new_stopped();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn lazy_start(&mut self, ctx: &mut EventCtx, event: Event) {
|
|
|
|
|
if let Event::Attach(_) = event {
|
|
|
|
|
if let Event::Attach(AttachType::Swipe(SwipeDirection::Up))
|
|
|
|
|
| Event::Attach(AttachType::Swipe(SwipeDirection::Down))
|
|
|
|
|
| Event::Attach(AttachType::Initial) = event
|
|
|
|
|
{
|
|
|
|
|
self.attach_top = true;
|
|
|
|
|
self.duration = Duration::from_millis(Self::DURATION_MS);
|
|
|
|
|
} else {
|
|
|
|
|
self.duration = Duration::from_millis(Self::DURATION_MS);
|
|
|
|
|
}
|
|
|
|
|
self.reset();
|
|
|
|
|
ctx.request_anim_frame();
|
|
|
|
|
}
|
|
|
|
|
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
|
|
|
|
if !self.timer.is_running() {
|
|
|
|
|
self.start();
|
|
|
|
|
}
|
|
|
|
|
if self.is_active() {
|
|
|
|
|
ctx.request_anim_frame();
|
|
|
|
|
ctx.request_paint();
|
|
|
|
|
} else if self.active {
|
|
|
|
|
self.active = false;
|
|
|
|
|
ctx.request_anim_frame();
|
|
|
|
|
ctx.request_paint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default, Clone)]
|
|
|
|
|
struct CloseAnimation {
|
|
|
|
|
pub attach_top: bool,
|
|
|
|
|
pub timer: Stopwatch,
|
|
|
|
|
pub duration: Duration,
|
|
|
|
|
}
|
|
|
|
|
impl CloseAnimation {
|
|
|
|
|
const DURATION_MS: u32 = 350;
|
|
|
|
|
fn is_active(&self) -> bool {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.timer.is_running_within(self.duration)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_finished(&self) -> bool {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.timer.is_running() && !self.timer.is_running_within(self.duration)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eval(&self) -> f32 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return 1.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.timer.elapsed().to_millis() as f32 / 1000.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn opacity(&self, t: f32, pos_x: usize, pos_y: usize) -> u8 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return 255;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let diag = pos_x + pos_y;
|
|
|
|
|
|
|
|
|
|
let start = diag as f32 * 0.05;
|
|
|
|
|
|
|
|
|
|
let f = pareen::constant(1.0)
|
|
|
|
|
.seq_ease_in_out(
|
|
|
|
|
start,
|
|
|
|
|
easer::functions::Cubic,
|
|
|
|
|
0.1,
|
|
|
|
|
pareen::constant(0.0).eval(self.eval()),
|
|
|
|
|
)
|
|
|
|
|
.eval(t);
|
|
|
|
|
|
|
|
|
|
(f * 255.0) as u8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn header_opacity(&self, t: f32) -> u8 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return 255;
|
|
|
|
|
}
|
|
|
|
|
let f = pareen::constant(1.0)
|
|
|
|
|
.seq_ease_in_out(
|
|
|
|
|
0.10,
|
|
|
|
|
easer::functions::Linear,
|
|
|
|
|
0.25,
|
|
|
|
|
pareen::constant(0.0).eval(self.eval()),
|
|
|
|
|
)
|
|
|
|
|
.eval(t);
|
|
|
|
|
|
|
|
|
|
(f * 255.0) as u8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn reset(&mut self) {
|
|
|
|
|
self.timer = Stopwatch::new_stopped();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start(&mut self, ctx: &mut EventCtx) {
|
|
|
|
|
self.duration = Duration::from_millis(Self::DURATION_MS);
|
|
|
|
|
self.reset();
|
|
|
|
|
self.timer.start();
|
|
|
|
|
ctx.request_anim_frame();
|
|
|
|
|
ctx.request_paint();
|
|
|
|
|
}
|
|
|
|
|
fn process(&mut self, ctx: &mut EventCtx, event: Event) {
|
|
|
|
|
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
|
|
|
|
if self.is_active() && !self.is_finished() {
|
|
|
|
|
ctx.request_anim_frame();
|
|
|
|
|
ctx.request_paint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct PinKeyboard<'a> {
|
|
|
|
|
allow_cancel: bool,
|
|
|
|
|
show_erase: bool,
|
|
|
|
|
show_cancel: bool,
|
|
|
|
|
major_prompt: Child<Label<'a>>,
|
|
|
|
|
minor_prompt: Child<Label<'a>>,
|
|
|
|
|
major_warning: Option<Child<Label<'a>>>,
|
|
|
|
|
keypad_area: Rect,
|
|
|
|
|
textbox_area: Rect,
|
|
|
|
|
textbox: Child<PinDots>,
|
|
|
|
|
textbox_pad: Pad,
|
|
|
|
|
erase_btn: Child<Maybe<Button>>,
|
|
|
|
|
cancel_btn: Child<Maybe<Button>>,
|
|
|
|
|
confirm_btn: Child<Button>,
|
|
|
|
|
digit_btns: [Child<Button>; DIGIT_COUNT],
|
|
|
|
|
erase_btn: Button,
|
|
|
|
|
cancel_btn: Button,
|
|
|
|
|
confirm_btn: Button,
|
|
|
|
|
digit_btns: [(Button, usize); DIGIT_COUNT],
|
|
|
|
|
warning_timer: Option<TimerToken>,
|
|
|
|
|
attach_animation: AttachAnimation,
|
|
|
|
|
close_animation: CloseAnimation,
|
|
|
|
|
close_confirm: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> PinKeyboard<'a> {
|
|
|
|
@ -70,34 +274,37 @@ impl<'a> PinKeyboard<'a> {
|
|
|
|
|
.styled(theme::button_keyboard_erase())
|
|
|
|
|
.with_long_press(theme::ERASE_HOLD_DURATION)
|
|
|
|
|
.initially_enabled(false);
|
|
|
|
|
let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child();
|
|
|
|
|
|
|
|
|
|
let cancel_btn =
|
|
|
|
|
Button::with_icon(theme::ICON_CLOSE).styled(theme::button_keyboard_cancel());
|
|
|
|
|
let cancel_btn = Maybe::new(theme::BG, cancel_btn, allow_cancel).into_child();
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
allow_cancel,
|
|
|
|
|
show_erase: false,
|
|
|
|
|
show_cancel: allow_cancel,
|
|
|
|
|
major_prompt: Label::left_aligned(major_prompt, theme::label_keyboard()).into_child(),
|
|
|
|
|
minor_prompt: Label::right_aligned(minor_prompt, theme::label_keyboard_minor())
|
|
|
|
|
.into_child(),
|
|
|
|
|
major_warning: major_warning.map(|text| {
|
|
|
|
|
Label::left_aligned(text, theme::label_keyboard_warning()).into_child()
|
|
|
|
|
}),
|
|
|
|
|
keypad_area: Rect::zero(),
|
|
|
|
|
textbox_area: Rect::zero(),
|
|
|
|
|
textbox: PinDots::new(theme::label_default()).into_child(),
|
|
|
|
|
textbox_pad: Pad::with_background(theme::label_default().background_color),
|
|
|
|
|
erase_btn,
|
|
|
|
|
cancel_btn,
|
|
|
|
|
confirm_btn: Button::with_icon(theme::ICON_SIMPLE_CHECKMARK24)
|
|
|
|
|
.styled(theme::button_pin_confirm())
|
|
|
|
|
.initially_enabled(false)
|
|
|
|
|
.into_child(),
|
|
|
|
|
.initially_enabled(false),
|
|
|
|
|
digit_btns: Self::generate_digit_buttons(),
|
|
|
|
|
warning_timer: None,
|
|
|
|
|
attach_animation: AttachAnimation::default(),
|
|
|
|
|
close_animation: CloseAnimation::default(),
|
|
|
|
|
close_confirm: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_digit_buttons() -> [Child<Button>; DIGIT_COUNT] {
|
|
|
|
|
fn generate_digit_buttons() -> [(Button, usize); DIGIT_COUNT] {
|
|
|
|
|
// Generate a random sequence of digits from 0 to 9.
|
|
|
|
|
let mut digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
|
|
|
|
random::shuffle(&mut digits);
|
|
|
|
@ -107,14 +314,13 @@ impl<'a> PinKeyboard<'a> {
|
|
|
|
|
b.styled(theme::button_keyboard())
|
|
|
|
|
.with_text_align(Alignment::Center)
|
|
|
|
|
})
|
|
|
|
|
.map(Child::new)
|
|
|
|
|
.map(|b| (b, 0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn pin_modified(&mut self, ctx: &mut EventCtx) {
|
|
|
|
|
let is_full = self.textbox.inner().is_full();
|
|
|
|
|
let is_empty = self.textbox.inner().is_empty();
|
|
|
|
|
|
|
|
|
|
self.textbox_pad.clear();
|
|
|
|
|
self.textbox.request_complete_repaint(ctx);
|
|
|
|
|
|
|
|
|
|
if is_empty {
|
|
|
|
@ -125,23 +331,32 @@ impl<'a> PinKeyboard<'a> {
|
|
|
|
|
|
|
|
|
|
let cancel_enabled = is_empty && self.allow_cancel;
|
|
|
|
|
for btn in &mut self.digit_btns {
|
|
|
|
|
btn.mutate(ctx, |ctx, btn| btn.enable_if(ctx, !is_full));
|
|
|
|
|
btn.0.enable_if(ctx, !is_full);
|
|
|
|
|
}
|
|
|
|
|
self.erase_btn.mutate(ctx, |ctx, btn| {
|
|
|
|
|
btn.show_if(ctx, !is_empty);
|
|
|
|
|
btn.inner_mut().enable_if(ctx, !is_empty);
|
|
|
|
|
});
|
|
|
|
|
self.cancel_btn.mutate(ctx, |ctx, btn| {
|
|
|
|
|
btn.show_if(ctx, cancel_enabled);
|
|
|
|
|
btn.inner_mut().enable_if(ctx, is_empty);
|
|
|
|
|
});
|
|
|
|
|
self.confirm_btn
|
|
|
|
|
.mutate(ctx, |ctx, btn| btn.enable_if(ctx, !is_empty));
|
|
|
|
|
|
|
|
|
|
self.show_erase = !is_empty;
|
|
|
|
|
self.show_cancel = cancel_enabled && is_empty;
|
|
|
|
|
|
|
|
|
|
self.erase_btn.enable_if(ctx, !is_empty);
|
|
|
|
|
self.cancel_btn.enable_if(ctx, is_empty);
|
|
|
|
|
self.confirm_btn.enable_if(ctx, !is_empty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn pin(&self) -> &str {
|
|
|
|
|
self.textbox.inner().pin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_button_alpha(&self, x: usize, y: usize, attach_time: f32, close_time: f32) -> u8 {
|
|
|
|
|
self.attach_animation
|
|
|
|
|
.opacity(attach_time, x, y)
|
|
|
|
|
.min(self.close_animation.opacity(close_time, x, y))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_textbox_alpha(&self, attach_time: f32, close_time: f32) -> u8 {
|
|
|
|
|
self.attach_animation
|
|
|
|
|
.header_opacity(attach_time)
|
|
|
|
|
.min(self.close_animation.header_opacity(close_time))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Component for PinKeyboard<'_> {
|
|
|
|
@ -153,11 +368,14 @@ impl Component for PinKeyboard<'_> {
|
|
|
|
|
bounds.split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING);
|
|
|
|
|
let prompt = header.inset(HEADER_PADDING);
|
|
|
|
|
|
|
|
|
|
// Keypad area.
|
|
|
|
|
self.keypad_area = keypad;
|
|
|
|
|
|
|
|
|
|
// Control buttons.
|
|
|
|
|
let grid = Grid::new(keypad, 4, 3).with_spacing(theme::BUTTON_SPACING);
|
|
|
|
|
|
|
|
|
|
// Prompts and PIN dots display.
|
|
|
|
|
self.textbox_pad.place(header);
|
|
|
|
|
self.textbox_area = header;
|
|
|
|
|
self.textbox.place(header);
|
|
|
|
|
self.major_prompt.place(prompt);
|
|
|
|
|
self.minor_prompt.place(prompt);
|
|
|
|
@ -172,19 +390,32 @@ impl Component for PinKeyboard<'_> {
|
|
|
|
|
// Digit buttons.
|
|
|
|
|
for (i, btn) in self.digit_btns.iter_mut().enumerate() {
|
|
|
|
|
// Assign the digits to buttons on a 4x3 grid, starting from the first row.
|
|
|
|
|
let area = grid.cell(if i < 9 {
|
|
|
|
|
let idx = if i < 9 {
|
|
|
|
|
i
|
|
|
|
|
} else {
|
|
|
|
|
// For the last key (the "0" position) we skip one cell.
|
|
|
|
|
i + 1
|
|
|
|
|
});
|
|
|
|
|
btn.place(area);
|
|
|
|
|
};
|
|
|
|
|
let area = grid.cell(idx);
|
|
|
|
|
btn.0.place(area);
|
|
|
|
|
btn.1 = idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bounds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
|
|
|
|
self.close_animation.process(ctx, event);
|
|
|
|
|
if self.close_animation.is_finished() && !animation_disabled() {
|
|
|
|
|
return Some(if self.close_confirm {
|
|
|
|
|
PinKeyboardMsg::Confirmed
|
|
|
|
|
} else {
|
|
|
|
|
PinKeyboardMsg::Cancelled
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.attach_animation.lazy_start(ctx, event);
|
|
|
|
|
|
|
|
|
|
match event {
|
|
|
|
|
// Set up timer to switch off warning prompt.
|
|
|
|
|
Event::Attach(_) if self.major_warning.is_some() => {
|
|
|
|
@ -193,19 +424,33 @@ impl Component for PinKeyboard<'_> {
|
|
|
|
|
// Hide warning, show major prompt.
|
|
|
|
|
Event::Timer(token) if Some(token) == self.warning_timer => {
|
|
|
|
|
self.major_warning = None;
|
|
|
|
|
self.textbox_pad.clear();
|
|
|
|
|
self.minor_prompt.request_complete_repaint(ctx);
|
|
|
|
|
ctx.request_paint();
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// do not process buttons when closing
|
|
|
|
|
if self.close_animation.is_active() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.textbox.event(ctx, event);
|
|
|
|
|
if let Some(Clicked) = self.confirm_btn.event(ctx, event) {
|
|
|
|
|
return Some(PinKeyboardMsg::Confirmed);
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return Some(PinKeyboardMsg::Confirmed);
|
|
|
|
|
} else {
|
|
|
|
|
self.close_animation.start(ctx);
|
|
|
|
|
self.close_confirm = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(Clicked) = self.cancel_btn.event(ctx, event) {
|
|
|
|
|
return Some(PinKeyboardMsg::Cancelled);
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return Some(PinKeyboardMsg::Cancelled);
|
|
|
|
|
} else {
|
|
|
|
|
self.close_animation.start(ctx);
|
|
|
|
|
self.close_confirm = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
match self.erase_btn.event(ctx, event) {
|
|
|
|
|
Some(ButtonMsg::Clicked) => {
|
|
|
|
@ -221,8 +466,8 @@ impl Component for PinKeyboard<'_> {
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
for btn in &mut self.digit_btns {
|
|
|
|
|
if let Some(Clicked) = btn.event(ctx, event) {
|
|
|
|
|
if let ButtonContent::Text(text) = btn.inner().content() {
|
|
|
|
|
if let Some(Clicked) = btn.0.event(ctx, event) {
|
|
|
|
|
if let ButtonContent::Text(text) = btn.0.content() {
|
|
|
|
|
text.map(|text| {
|
|
|
|
|
self.textbox.mutate(ctx, |ctx, t| t.push(ctx, text));
|
|
|
|
|
});
|
|
|
|
@ -239,8 +484,14 @@ impl Component for PinKeyboard<'_> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
|
|
|
|
self.erase_btn.render(target);
|
|
|
|
|
self.textbox_pad.render(target);
|
|
|
|
|
let t_attach = self.attach_animation.eval();
|
|
|
|
|
let t_close = self.close_animation.eval();
|
|
|
|
|
|
|
|
|
|
let erase_alpha = self.get_button_alpha(0, 3, t_attach, t_close);
|
|
|
|
|
|
|
|
|
|
if self.show_erase {
|
|
|
|
|
self.erase_btn.render_with_alpha(target, erase_alpha);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.textbox.inner().is_empty() {
|
|
|
|
|
if let Some(ref w) = self.major_warning {
|
|
|
|
@ -249,16 +500,28 @@ impl Component for PinKeyboard<'_> {
|
|
|
|
|
self.major_prompt.render(target);
|
|
|
|
|
}
|
|
|
|
|
self.minor_prompt.render(target);
|
|
|
|
|
self.cancel_btn.render(target);
|
|
|
|
|
if self.show_cancel {
|
|
|
|
|
self.cancel_btn.render_with_alpha(target, erase_alpha);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.textbox.render(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.confirm_btn.render(target);
|
|
|
|
|
shape::Bar::new(self.textbox_area)
|
|
|
|
|
.with_bg(theme::label_default().background_color)
|
|
|
|
|
.with_fg(theme::label_default().background_color)
|
|
|
|
|
.with_alpha(255 - self.get_textbox_alpha(t_attach, t_close))
|
|
|
|
|
.render(target);
|
|
|
|
|
|
|
|
|
|
let alpha = self.get_button_alpha(2, 3, t_attach, t_close);
|
|
|
|
|
self.confirm_btn.render_with_alpha(target, alpha);
|
|
|
|
|
|
|
|
|
|
for btn in &self.digit_btns {
|
|
|
|
|
btn.render(target);
|
|
|
|
|
let alpha = self.get_button_alpha(btn.1 % 3, btn.1 / 3, t_attach, t_close);
|
|
|
|
|
btn.0.render_with_alpha(target, alpha);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cshape::KeyboardOverlay::new(self.keypad_area).render(target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -439,7 +702,7 @@ impl crate::trace::Trace for PinKeyboard<'_> {
|
|
|
|
|
// So that debuglink knows the locations of the buttons
|
|
|
|
|
let mut digits_order = ShortString::new();
|
|
|
|
|
for btn in self.digit_btns.iter() {
|
|
|
|
|
let btn_content = btn.inner().content();
|
|
|
|
|
let btn_content = btn.0.content();
|
|
|
|
|
if let ButtonContent::Text(text) = btn_content {
|
|
|
|
|
text.map(|text| {
|
|
|
|
|
unwrap!(digits_order.push_str(text));
|
|
|
|
|