From 24b404891615722dfae81c78dcc3f4fc5cb34e5b Mon Sep 17 00:00:00 2001 From: obrusvit Date: Wed, 19 Mar 2025 23:23:57 +0100 Subject: [PATCH] feat(eckhart): default homescreen - make homescreen hold to lock, lock the device by long-pressing the homescreen anywhere but the action bar --- .../src/ui/layout_eckhart/component/button.rs | 12 +- .../ui/layout_eckhart/component_msg_obj.rs | 1 + .../ui/layout_eckhart/firmware/action_bar.rs | 11 +- .../ui/layout_eckhart/firmware/homescreen.rs | 226 +++++++++++++++--- .../layout_eckhart/firmware/vertical_menu.rs | 2 +- .../res/defaut_homescreen/hs_tile1.png | Bin 0 -> 1324 bytes .../res/defaut_homescreen/hs_tile1.toif | Bin 0 -> 259 bytes .../res/defaut_homescreen/hs_tile2.png | Bin 0 -> 987 bytes .../res/defaut_homescreen/hs_tile2.toif | Bin 0 -> 239 bytes .../src/ui/layout_eckhart/theme/firmware.rs | 1 + .../rust/src/ui/layout_eckhart/theme/mod.rs | 10 + core/src/trezor/ui/layouts/homescreen.py | 4 +- 12 files changed, 227 insertions(+), 40 deletions(-) create mode 100644 core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile1.png create mode 100644 core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile1.toif create mode 100644 core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile2.png create mode 100644 core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile2.toif diff --git a/core/embed/rust/src/ui/layout_eckhart/component/button.rs b/core/embed/rust/src/ui/layout_eckhart/component/button.rs index 4eb73eccf1..c0feb4c0d1 100644 --- a/core/embed/rust/src/ui/layout_eckhart/component/button.rs +++ b/core/embed/rust/src/ui/layout_eckhart/component/button.rs @@ -221,6 +221,11 @@ impl Button { self.area } + pub fn touch_area(&self) -> Rect { + self.touch_expand + .map_or(self.area, |expand| self.area.outset(expand)) + } + fn set(&mut self, ctx: &mut EventCtx, state: State) { if self.state != state { self.state = state; @@ -364,12 +369,7 @@ impl Component for Button { } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - let touch_area = if let Some(expand) = self.touch_expand { - self.area.outset(expand) - } else { - self.area - }; - + let touch_area = self.touch_area(); match event { Event::Touch(TouchEvent::TouchStart(pos)) => { match self.state { diff --git a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs index 2421c0a702..06ade039d4 100644 --- a/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs +++ b/core/embed/rust/src/ui/layout_eckhart/component_msg_obj.rs @@ -74,6 +74,7 @@ impl ComponentMsgObj for Homescreen { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { match msg { HomescreenMsg::Dismissed => Ok(CANCELLED.as_obj()), + HomescreenMsg::Menu => Ok(INFO.as_obj()), } } } diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs index c06ec99809..7cdd78ffe2 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/action_bar.rs @@ -124,6 +124,13 @@ impl ActionBar { self.right_button.set_expanded_touch_area(expand); } + pub fn touch_area(&self) -> Rect { + let right_area = self.right_button.touch_area(); + self.left_button + .as_ref() + .map_or(right_area, |left| right_area.union(left.touch_area())) + } + pub fn update(&mut self, new_pager: Pager) { // TODO: review `clone()` of `left_content`/`right_content` if let Mode::Double { pager } = &mut self.mode { @@ -347,9 +354,7 @@ impl Component for ActionBar { btn.render(target); } self.right_button.render(target); - if let Some(htc_anim) = &self.htc_anim { - htc_anim.render(target); - } + self.htc_anim.render(target); } } diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs index 878148d465..eeb8c4d1bf 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/homescreen.rs @@ -5,18 +5,23 @@ use crate::{ translations::TR, ui::{ component::{text::TextStyle, Component, Event, EventCtx, Label, Never}, - display::image::ImageInfo, - geometry::{Insets, Offset, Rect}, + display::{image::ImageInfo, Color}, + geometry::{Alignment2D, Grid, Insets, Offset, Point, Rect}, layout::util::get_user_custom_image, + lerp::Lerp, shape::{self, Renderer}, + util::animation_disabled, }, }; use super::{ - super::{component::Button, fonts}, + super::{ + component::{Button, ButtonMsg}, + fonts, + }, constant::{HEIGHT, SCREEN, WIDTH}, - theme::{self, firmware::button_homebar_style, BLACK, GREEN_DARK, GREEN_EXTRA_DARK}, - ActionBar, ActionBarMsg, Hint, + theme::{self, firmware::button_homebar_style, BG, BLACK, GREY_EXTRA_DARK}, + ActionBar, ActionBarMsg, Hint, HoldToConfirmAnim, }; /// Full-screen component for the homescreen and lockscreen. @@ -29,14 +34,21 @@ pub struct Homescreen { action_bar: ActionBar, /// Background image image: Option>, + /// LED color + led_color: Option, /// Whether the PIN is set and device can be locked lockable: bool, /// Whether the homescreen is locked locked: bool, + /// Hold to lock button placed everywhere except the `action_bar` + virtual_locking_button: Button, + /// Hold to lock animation + htc_anim: Option, } pub enum HomescreenMsg { Dismissed, + Menu, } impl Homescreen { @@ -51,23 +63,33 @@ impl Homescreen { let image = get_homescreen_image(); // Notification + // TODO: better notification handling let mut notification_level = 4; - let hint = if let Some((text, level)) = notification { + let mut hint = None; + let mut led_color; + if let Some((text, level)) = notification { notification_level = level; if notification_level == 0 { - Some(Hint::new_warning_danger(text)) + led_color = Some(theme::RED); + hint = Some(Hint::new_warning_danger(text)); } else { - Some(Hint::new_instruction(text, Some(theme::ICON_INFO))) + led_color = Some(theme::YELLOW); + hint = Some(Hint::new_instruction(text, Some(theme::ICON_INFO))); } } else if locked && coinjoin_authorized { - Some(Hint::new_instruction_green( + led_color = Some(theme::GREEN_LIME); + hint = Some(Hint::new_instruction_green( TR::coinjoin__do_not_disconnect, Some(theme::ICON_INFO), - )) + )); } else { - None + led_color = Some(theme::GREY_LIGHT); }; + if locked { + led_color = None; + } + // ActionBar button let button_style = button_homebar_style(notification_level); let button = if bootscreen { @@ -81,15 +103,70 @@ impl Homescreen { Button::with_homebar_content(None).styled(button_style) }; + let lock_duration = theme::LOCK_HOLD_DURATION; + + // Locking animation + let htc_anim = if lockable && !animation_disabled() { + Some( + HoldToConfirmAnim::new() + .with_color(theme::GREY_LIGHT) + .with_duration(lock_duration), + ) + } else { + None + }; + Ok(Self { label: HomeLabel::new(label), hint, action_bar: ActionBar::new_single(button), image, + led_color, lockable, locked, + virtual_locking_button: Button::empty().with_long_press(lock_duration), + htc_anim, }) } + + fn event_hold(&mut self, ctx: &mut EventCtx, event: Event) -> bool { + self.htc_anim.event(ctx, event); + if let Some(msg) = self.virtual_locking_button.event(ctx, event) { + match msg { + ButtonMsg::Pressed => { + if let Some(htc_anim) = &mut self.htc_anim { + htc_anim.start(); + ctx.request_anim_frame(); + ctx.request_paint(); + ctx.disable_swipe(); + } + } + ButtonMsg::Clicked => { + if let Some(htc_anim) = &mut self.htc_anim { + htc_anim.stop(); + ctx.request_anim_frame(); + ctx.request_paint(); + ctx.enable_swipe(); + } else { + // Animations disabled + return true; + } + } + ButtonMsg::Released => { + if let Some(htc_anim) = &mut self.htc_anim { + htc_anim.stop(); + ctx.request_anim_frame(); + ctx.request_paint(); + ctx.enable_swipe(); + } + } + ButtonMsg::LongPressed => { + return true; + } + } + } + false + } } impl Component for Homescreen { @@ -114,6 +191,9 @@ impl Component for Homescreen { self.label.place(label_area); self.action_bar.place(bar_area); + // Locking button is placed everywhere except the action bar + let locking_area = bounds.inset(Insets::bottom(self.action_bar.touch_area().height())); + self.virtual_locking_button.place(locking_area); bounds } @@ -122,13 +202,14 @@ impl Component for Homescreen { if self.locked { return Some(HomescreenMsg::Dismissed); } else { - // TODO: Show menu and handle "lock" action differently - if self.lockable { - return Some(HomescreenMsg::Dismissed); - } + return Some(HomescreenMsg::Menu); } } - None + if self.lockable { + Self::event_hold(self, ctx, event).then_some(HomescreenMsg::Dismissed) + } else { + None + } } fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { @@ -137,11 +218,12 @@ impl Component for Homescreen { shape::JpegImage::new_image(SCREEN.top_left(), image).render(target); } } else { - render_default_hs(target); + render_default_hs(target, self.led_color); } self.label.render(target); self.hint.render(target); self.action_bar.render(target); + self.htc_anim.render(target); } } @@ -202,20 +284,108 @@ pub fn check_homescreen_format(image: BinaryData) -> bool { } } -fn render_default_hs<'a>(target: &mut impl Renderer<'a>) { +fn render_default_hs<'a>(target: &mut impl Renderer<'a>, led_color: Option) { + const DEFAULT_HS_TILE_ROWS: usize = 4; + const DEFAULT_HS_TILE_COLS: usize = 4; + const DEFAULT_HS_AREA: Rect = SCREEN.inset(Insets::bottom(140)); + const DEFAULT_HS_GRID: Grid = + Grid::new(DEFAULT_HS_AREA, DEFAULT_HS_TILE_ROWS, DEFAULT_HS_TILE_COLS); + const DEFAULT_HS_TILES_2: [(usize, usize); 9] = [ + (0, 0), + (1, 0), + (1, 3), + (2, 3), + (3, 2), + (3, 3), + (4, 0), + (4, 2), + (4, 3), + ]; + + // Layer 1: Base Solid Colour shape::Bar::new(SCREEN) - .with_fg(theme::BG) - .with_bg(theme::BG) + .with_bg(GREY_EXTRA_DARK) .render(target); - shape::Circle::new(SCREEN.center(), 48) - .with_fg(GREEN_DARK) - .with_thickness(4) - .render(target); - shape::Circle::new(SCREEN.center(), 42) - .with_fg(GREEN_EXTRA_DARK) - .with_thickness(4) - .render(target); + // Layer 2: Base Gradient overlay + for y in SCREEN.y0..SCREEN.y1 { + let slice = Rect::new(Point::new(SCREEN.x0, y), Point::new(SCREEN.x1, y + 1)); + let factor = (y - SCREEN.y0) as f32 / SCREEN.height() as f32; + shape::Bar::new(slice) + .with_bg(BG) + .with_alpha(u8::lerp(u8::MIN, u8::MAX, factor)) + .render(target); + } + + // Layer 3: (Optional) LED lightning simulation + if let Some(color) = led_color { + render_led_simulation(color, target); + } + + // Layer 4: Tile pattern + // TODO: improve frame rate + for row in 0..DEFAULT_HS_TILE_ROWS { + for col in 0..DEFAULT_HS_TILE_COLS { + let tile_area = DEFAULT_HS_GRID.row_col(row, col); + let icon = if DEFAULT_HS_TILES_2.contains(&(row, col)) { + theme::ICON_HS_TILE_2.toif + } else { + theme::ICON_HS_TILE_1.toif + }; + shape::ToifImage::new(tile_area.top_left(), icon) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(BLACK) + .render(target); + } + } +} + +fn render_led_simulation<'a>(color: Color, target: &mut impl Renderer<'a>) { + const Y_MAX: i16 = SCREEN.y1 - theme::ACTION_BAR_HEIGHT; + const Y_RANGE: i16 = Y_MAX - SCREEN.y0; + + const X_MID: i16 = SCREEN.x0 + SCREEN.width() / 2; + const X_HALF_WIDTH: f32 = (SCREEN.width() / 2) as f32; + + // Vertical gradient (color intensity fading from bottom to top) + #[allow(clippy::reversed_empty_ranges)] // clippy fails here for T3B1 which has smaller screen + for y in SCREEN.y0..Y_MAX { + let factor = (y - SCREEN.y0) as f32 / Y_RANGE as f32; + let slice = Rect::new(Point::new(SCREEN.x0, y), Point::new(SCREEN.x1, y + 1)); + + // Gradient 1 (Overall intensity: 35%) + // Stops: 0%, 40% + // Opacity: 100%, 20% + let factor_grad_1 = (factor / 0.4).clamp(0.2, 1.0); + shape::Bar::new(slice) + .with_bg(color) + .with_alpha(u8::lerp(89, u8::MIN, factor_grad_1)) + .render(target); + + // Gradient 2 (Overall intensity: 70%) + // Stops: 2%, 63% + // Opacity: 100%, 0% + let factor_grad_2 = ((factor - 0.02) / (0.63 - 0.02)).clamp(0.0, 1.0); + let alpha = u8::lerp(179, u8::MIN, factor_grad_2); + shape::Bar::new(slice) + .with_bg(color) + .with_alpha(alpha) + .render(target); + } + + // Horizontal gradient (transparency increasing toward center) + for x in SCREEN.x0..SCREEN.x1 { + const WIDTH: i16 = SCREEN.width(); + let slice = Rect::new(Point::new(x, SCREEN.y0), Point::new(x + 1, Y_MAX)); + // Gradient 3 + // Calculate distance from center as a normalized factor (0 at center, 1 at + // edges) + let dist_from_mid = (x - X_MID).abs() as f32 / X_HALF_WIDTH; + shape::Bar::new(slice) + .with_bg(BG) + .with_alpha(u8::lerp(u8::MIN, u8::MAX, dist_from_mid)) + .render(target); + } } fn get_homescreen_image() -> Option> { diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs index 4e68a5f663..99ae244bec 100644 --- a/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs +++ b/core/embed/rust/src/ui/layout_eckhart/firmware/vertical_menu.rs @@ -19,7 +19,7 @@ type VerticalMenuButtons = Vec; pub struct VerticalMenu { /// Bounds the sliding window of the menu. bounds: Rect, - /// FUll bounds of the menu, including off-screen items. + /// Full bounds of the menu, including off-screen items. virtual_bounds: Rect, /// Menu items. buttons: VerticalMenuButtons, diff --git a/core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile1.png b/core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile1.png new file mode 100644 index 0000000000000000000000000000000000000000..da3303b52bd9ab438112cbb9752442ca9f83a51e GIT binary patch literal 1324 zcmX|BX;70_6un=9goFkV=s++Oiii*-Dw|5CE)`Od z7zl(-R4M@?HY`D)vIs>4REAh$lT>VF3Q_ZIun_#9Hy(O)@Sedggm>LP@*Lq77RM|G!CER7ujotg@KWM1^N8j zO&g}`hne`*B5mVVQL)7+cZOx}B7aJHP+Dl{K5Zg=rS_RJb$B2n3Ys3KwyiNYH!qoQ z6390N1({!}s*=B3T4wmGCMGyzIi&7GzI%cK1Jez+)^s;FEAp;P^A#Ejh4QY(yQQs7 z`Qy@BRmwbf!C)|OV+=Z7>V+=*qC8G(-0*;{E=Q5o5W3`_{RcVbviNR-vsY9;ju$3O_ZJzwuX(R*yOw-)WC>Gyd9nRr z{aK79#7{9%hbK6msMH$z>j|iz6T+)Erf%5&ezOror0n~gE0s>!?Zt9I24CK*eybb! z`kPDg*TJ#1#aS;jZAsiDXBU{m_C$PmuAr0!411oSl!;J%K`=DxuDO?xygNBKdt8?g zTccovc4KyQ(JRwT%5)Yr;n&2S<;km0Vq5w*T7u*twDia0p@_u8w;vxMF)FrNb(}4*l-|h=jxTx2t=mI<#z*Rc?)8irG z3C`h917vbxf*YFp%l64Wy3#x44~yuDb%P^g*RA=+JtL}H?tG*FTi8;G;pM{*{qaW$ z;0B(SLd9obff}gf}@srAyiv9KJvH3@{Y#{vOEPo=h1{Qo{BQ) zSqn~H2yZQH*=I+z1vd;Dm@gY7z?RCfAUlW@#;Oq>s~4FZKL(C547BOWq#Ya<-gKGh z2hQ28xqU>)duGJwke~8*6e<1L^(r&ye`(Bq_5m@}=#~4$hynY=3YovqI*xv70Fj&N KVT~c|@BRUBojV=? literal 0 HcmV?d00001 diff --git a/core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile1.toif b/core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile1.toif new file mode 100644 index 0000000000000000000000000000000000000000..f631aead9c3e58b11440fc9688fa80878e455db1 GIT binary patch literal 259 zcmWIX_jHeEh-diDz`*eIl499m0}+>t`M>$~Jt{gYJuA5OG+4~~o4<5h470JVfXb&M zCFduqEwWJF*LN%U$aY(|KUeJDCcZB+d1O?vBg}!#o&^D-~_{2nAkqag4k?kfw_#z z%qBoJ4QCi;GG0E_@H$Gm;RVkOi3RLn<0eQNtmC<{-p!$eO@(=j0Z3g#u04lD9M6sW z9E=wwC-7W21k`B6yvZP;!stQCti~5S9&A_sGAww<&Tzc$R%5dL+XMfvzICXP_`wVS Dby#Vr literal 0 HcmV?d00001 diff --git a/core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile2.png b/core/embed/rust/src/ui/layout_eckhart/res/defaut_homescreen/hs_tile2.png new file mode 100644 index 0000000000000000000000000000000000000000..75214d31626572f6b51c4d39f13f2d0688f957ee GIT binary patch literal 987 zcmeAS@N?(olHy`uVBq!ia0vp^@gU5>1|<7@q`v|wmUKs7M+SzC{oH>NS%G}U;vjb? z#+xT~>VX`EWRD45bDP46hOx7_4S6Fo+k-*%fF5lweEp zc6VX;4}uH!E}sk(;VkfoEM{Qf76xHPhFNnYfP(BLp1!W^FPUZdL^O{T3Ft8}Fw1+o zIEGZ*dVBX^u9Blb>%;wAh3YI$3amyMl}B#A_Q-KhdVhA3VG-Ms2Ldup!VKRU&d$1d z&|3FviQ>m!7FA9!tb0P%v!>R}n{w&O-Ro&)Pv2L{_aFbfeEIV7sPv5yXSUsbD>dWG zNps&~UYl)l`*Y1^|9t)WwYznFX=!P8TKU@J`{U!{>Mj=U`jl3-KSuBUu1~&~Z5Z6= zZ@v9CFKl(_Z|g}xvwGYXn;QK7_T%u~=jYF#&zJofH%;w_LDeVxAEkB1MBKmK^9=unfswQ!_g#D~;$rPG|; z`(*ww#(((wkkJQd0)yTUi66@{*Tn+a2a?z4xqmpkh2j6(G8X%ozmF^v4^D4xm)|9O zkU!p{tjxM;{pBxF2y@=}q<~b+?F1X7_G3qOht{r>J-j(s|RigxaKeHF;EvH9`>=y0Atg8Lr4 zj@JVD`00a(F58uX0kLrLZNVB@pikv&Ko%cf0dgdtoE^w9?d^Z9<3BKd_}~mw+W64F z)VfJtZr^5*hKh{6Wi0Xh|2X15ls@GD(X^hQ|M&eAPCEv>r6z?3k00oFTfBJxXVDth ziiENQ;x(}qKi&Y<%gY^Lm1pKx2L&Aeeqg+=Nssu@`Y?R;)x}>qxa$}yetf$nSQGn$ z8R*W#hwDNoKVW{q2#Qb5r7OAZIPU#j0kqq-!gzMwL+*V>HgABAWIu4Y&U_J2o#ob5 zo*xb$E?<`=1oTgRm?`HzIUAXBVBB!j16@#CaY5>x0hstP<n)@6!Bt-3d z^-6N?Bc04MbBy#2Q{<-YIhJ?Qa@)+WGyl#iJYRc6ZL`Pm4!@`qreDwWf0TXv`_zw% zJ@e20FnF9;ZQ;kfN8$d#Z)ty0jKgZ>HeWAD%Y=mv@W_#=P7{=xR2 z53g4^?mO_U?Je(dLz&|RzYqO5y#8a;pSF+g#{aTVHHROYf9A){9)4rH|Lh+&*vc$t kjGM52!`{T|!-YJ@R~A(N{W*P)LOhWBt?luT+y2$e0Qv`ke*gdg literal 0 HcmV?d00001 diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs index 9617fbcd0e..0e8e60029f 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs @@ -14,6 +14,7 @@ use super::{ *, }; +pub const LOCK_HOLD_DURATION: Duration = Duration::from_millis(1500); pub const CONFIRM_HOLD_DURATION: Duration = Duration::from_millis(1500); pub const ERASE_HOLD_DURATION: Duration = Duration::from_millis(1500); diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs b/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs index 3b16f66b85..5e7f50c95b 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/mod.rs @@ -103,6 +103,16 @@ include_icon!(ICON_BORDER_TR, "layout_eckhart/res/border/TR.toif"); include_icon!(ICON_PLUS, "layout_eckhart/res/plus.toif"); include_icon!(ICON_MINUS, "layout_eckhart/res/minus.toif"); +// Icon tiles for default homescreen +include_icon!( + ICON_HS_TILE_1, + "layout_eckhart/res/defaut_homescreen/hs_tile1.toif" +); +include_icon!( + ICON_HS_TILE_2, + "layout_eckhart/res/defaut_homescreen/hs_tile2.toif" +); + // Common text styles and button styles must use fonts accessible from both // bootloader and firmware diff --git a/core/src/trezor/ui/layouts/homescreen.py b/core/src/trezor/ui/layouts/homescreen.py index 08bd648605..d0c1e21e80 100644 --- a/core/src/trezor/ui/layouts/homescreen.py +++ b/core/src/trezor/ui/layouts/homescreen.py @@ -56,8 +56,8 @@ class HomescreenBase(ui.Layout): if not self.should_resume: super()._first_paint() storage_cache.homescreen_shown = self.RENDER_INDICATOR - # else: - # self._paint() + else: + self._paint() class Homescreen(HomescreenBase):