mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-24 15:28:10 +00:00
feat(core/mercury): pin entry animation
[no changelog]
This commit is contained in:
parent
b99325a764
commit
ff869dd864
1
core/.changelog.d/3885.added
Normal file
1
core/.changelog.d/3885.added
Normal file
@ -0,0 +1 @@
|
||||
[T3T1] Added PIN keyboard animation
|
@ -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));
|
||||
|
@ -0,0 +1,71 @@
|
||||
use crate::ui::{
|
||||
display::Color,
|
||||
geometry::Rect,
|
||||
shape::{Canvas, DrawingCache, Mono8Canvas, Renderer, Shape, ShapeClone},
|
||||
};
|
||||
|
||||
use without_alloc::alloc::LocalAllocLeakExt;
|
||||
|
||||
/// A special shape for rendering keyboard overlay.
|
||||
/// Makes the corner buttons have rounded corners.
|
||||
pub struct KeyboardOverlay {
|
||||
/// Center of the overlay
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
impl KeyboardOverlay {
|
||||
pub const RADIUS: i16 = 6;
|
||||
|
||||
/// Create a new overlay with given area
|
||||
pub fn new(area: Rect) -> Self {
|
||||
Self { area }
|
||||
}
|
||||
|
||||
pub fn render<'a>(self, renderer: &mut impl Renderer<'a>) {
|
||||
renderer.render_shape(self);
|
||||
}
|
||||
|
||||
fn prepare_overlay(&self, canvas: &mut dyn Canvas) {
|
||||
let area = canvas.bounds();
|
||||
|
||||
let transp = Color::black();
|
||||
let opaque = Color::white();
|
||||
|
||||
canvas.fill_background(opaque);
|
||||
|
||||
canvas.fill_round_rect(area, 6, transp, 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Shape<'a> for KeyboardOverlay {
|
||||
fn bounds(&self) -> Rect {
|
||||
self.area
|
||||
}
|
||||
|
||||
fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) {
|
||||
let bounds = self.bounds();
|
||||
|
||||
let overlay_buff = &mut unwrap!(cache.image_buff(), "No image buffer");
|
||||
|
||||
let mut overlay_canvas = unwrap!(
|
||||
Mono8Canvas::new(bounds.size(), None, None, &mut overlay_buff[..]),
|
||||
"Too small buffer"
|
||||
);
|
||||
|
||||
self.prepare_overlay(&mut overlay_canvas);
|
||||
|
||||
canvas.blend_bitmap(bounds, overlay_canvas.view().with_fg(Color::black()));
|
||||
}
|
||||
|
||||
fn cleanup(&mut self, _cache: &DrawingCache<'a>) {}
|
||||
}
|
||||
|
||||
impl<'a> ShapeClone<'a> for KeyboardOverlay {
|
||||
fn clone_at_bump<T>(self, bump: &'a T) -> Option<&'a mut dyn Shape<'a>>
|
||||
where
|
||||
T: LocalAllocLeakExt<'a>,
|
||||
{
|
||||
let clone = bump.alloc_t()?;
|
||||
Some(clone.uninit.init(KeyboardOverlay { ..self }))
|
||||
}
|
||||
}
|
@ -2,6 +2,10 @@ mod loader;
|
||||
|
||||
mod unlock_overlay;
|
||||
|
||||
mod keyboard_overlay;
|
||||
|
||||
pub use unlock_overlay::UnlockOverlay;
|
||||
|
||||
pub use keyboard_overlay::KeyboardOverlay;
|
||||
|
||||
pub use loader::{render_loader, LoaderRange};
|
||||
|
@ -19,7 +19,7 @@ const ZLIB_CACHE_SLOTS: usize = 3;
|
||||
const RENDER_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD;
|
||||
|
||||
#[cfg(feature = "model_mercury")]
|
||||
const IMAGE_BUFF_SIZE: usize = 32768 + ALIGN_PAD;
|
||||
const IMAGE_BUFF_SIZE: usize = 240 * 240 + ALIGN_PAD;
|
||||
#[cfg(not(feature = "model_mercury"))]
|
||||
const IMAGE_BUFF_SIZE: usize = 2048 + ALIGN_PAD;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user