From bf83a91e140291aeb777b23608bf700ce8846d56 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Wed, 27 Sep 2023 21:47:04 +0200 Subject: [PATCH] fix(core/ui): draw hold-to-confirm loader over dialog title [no changelog] --- core/embed/rust/src/ui/component/base.rs | 11 ++++++++ core/embed/rust/src/ui/layout/obj.rs | 16 +++++++++--- .../rust/src/ui/model_tt/component/loader.rs | 26 ++++++++++--------- .../rust/src/ui/model_tt/component/page.rs | 18 +++++++++---- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index 222ccd3cd..4039e6d5f 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -396,6 +396,7 @@ pub struct EventCtx { paint_requested: bool, anim_frame_scheduled: bool, page_count: Option, + root_repaint_requested: bool, } impl EventCtx { @@ -421,6 +422,7 @@ impl EventCtx { * `Child::marked_for_paint` being true. */ anim_frame_scheduled: false, page_count: None, + root_repaint_requested: false, } } @@ -459,6 +461,14 @@ impl EventCtx { } } + pub fn request_repaint_root(&mut self) { + self.root_repaint_requested = true; + } + + pub fn needs_repaint_root(&self) -> bool { + self.root_repaint_requested + } + pub fn set_page_count(&mut self, count: usize) { #[cfg(feature = "ui_debug")] assert!(self.page_count.is_none()); @@ -478,6 +488,7 @@ impl EventCtx { self.paint_requested = false; self.anim_frame_scheduled = false; self.page_count = None; + self.root_repaint_requested = false; } fn register_timer(&mut self, token: TimerToken, deadline: Duration) { diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 07d4a3098..d993e6785 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -158,9 +158,6 @@ impl LayoutObj { // SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`. let msg = unsafe { Gc::as_mut(&mut inner.root) }.obj_event(&mut inner.event_ctx, event)?; - // All concerning `Child` wrappers should have already marked themselves for - // painting by now, and we're prepared for a paint pass. - // Drain any pending timers into the callback. while let Some((token, deadline)) = inner.event_ctx.pop_timer() { let token = token.try_into(); @@ -176,6 +173,19 @@ impl LayoutObj { inner.page_count = count as u16; } + // Mark whole component tree for repaint if requested by inner component. + if inner.event_ctx.needs_repaint_root() { + inner.event_ctx.clear(); + // SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`. + let paint_msg = unsafe { Gc::as_mut(&mut inner.root) } + .obj_event(&mut inner.event_ctx, Event::RequestPaint)?; + assert!(paint_msg == Obj::const_none()); + unsafe { Gc::as_mut(&mut inner.root) }.obj_request_clear(); + } + + // All concerning `Child` wrappers should have already marked themselves for + // painting by now, and we're prepared for a paint pass. + Ok(msg) } diff --git a/core/embed/rust/src/ui/model_tt/component/loader.rs b/core/embed/rust/src/ui/model_tt/component/loader.rs index a135154f9..2f6b9d62d 100644 --- a/core/embed/rust/src/ui/model_tt/component/loader.rs +++ b/core/embed/rust/src/ui/model_tt/component/loader.rs @@ -2,7 +2,7 @@ use crate::{ time::{Duration, Instant}, ui::{ animation::Animation, - component::{Component, Event, EventCtx}, + component::{Component, Event, EventCtx, Pad}, display::{self, toif::Icon, Color}, geometry::{Offset, Rect}, model_tt::constant, @@ -27,7 +27,7 @@ enum State { } pub struct Loader { - offset_y: i16, + pub pad: Pad, state: State, growing_duration: Duration, shrinking_duration: Duration, @@ -38,12 +38,13 @@ impl Loader { pub const SIZE: Offset = Offset::new(120, 120); pub fn new() -> Self { + let styles = theme::loader_default(); Self { - offset_y: 0, + pad: Pad::with_background(styles.normal.background_color), state: State::Initial, growing_duration: Duration::from_millis(GROWING_DURATION_MS), shrinking_duration: Duration::from_millis(SHRINKING_DURATION_MS), - styles: theme::loader_default(), + styles, } } @@ -130,13 +131,8 @@ impl Component for Loader { type Msg = LoaderMsg; fn place(&mut self, bounds: Rect) -> Rect { - // Current loader API only takes Y-offset relative to screen center, which we - // compute from the bounds center point. - // NOTE: SwipeHoldPage relies on Loader being X-centered regardless of bounds. - // If this changes then SwipeHoldPage needs to be changed too. - let screen_center = constant::screen().center(); - self.offset_y = bounds.center().y - screen_center.y; - Rect::from_center_and_size(screen_center + Offset::y(self.offset_y), Self::SIZE) + self.pad.place(bounds); + Rect::from_center_and_size(bounds.center(), Self::SIZE) } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { @@ -177,9 +173,15 @@ impl Component for Loader { self.styles.active }; + // Current loader API only takes Y-offset relative to screen center, which we + // compute from the bounds center point. + let screen_center = constant::screen().center(); + let offset_y = self.pad.area.center().y - screen_center.y; + + self.pad.paint(); display::loader( progress, - self.offset_y, + offset_y, style.loader_color, style.background_color, style.icon, diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index 4702133d0..1852038ff 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -2,6 +2,7 @@ use crate::{ time::Instant, ui::{ component::{paginated::PageMsg, Component, ComponentExt, Event, EventCtx, Pad, Paginate}, + constant, display::{self, Color}, geometry::{Insets, Rect}, util::animation_disabled, @@ -153,6 +154,14 @@ where self.fade = Some(theme::BACKLIGHT_NORMAL); } + /// Area for drawing loader (and black rectangle behind it). Can be outside + /// bounds as we repaint entire UI tree after hiding the loader. + const fn loader_area() -> Rect { + constant::screen() + .inset(theme::borders()) + .inset(Insets::bottom(theme::BUTTON_HEIGHT + theme::BUTTON_SPACING)) + } + fn handle_swipe( &mut self, ctx: &mut EventCtx, @@ -225,12 +234,12 @@ where let now = Instant::now(); if let Some(LoaderMsg::ShrunkCompletely) = loader.event(ctx, event) { - // Clear the remnants of the loader. - self.pad.clear(); // Switch it to the initial state, so we stop painting it. loader.reset(); // Re-draw the whole content tree. self.content.request_complete_repaint(ctx); + // Loader overpainted our bounds, repaint entire screen from scratch. + ctx.request_repaint_root() // This can be a result of an animation frame event, we should take // care to not short-circuit here and deliver the event to the // content as well. @@ -238,7 +247,7 @@ where match button_msg { Some(ButtonMsg::Pressed) => { loader.start_growing(ctx, now); - self.pad.clear(); // Clear the remnants of the content. + loader.pad.clear(); // Clear the remnants of the content. } Some(ButtonMsg::Released) => { loader.start_shrinking(ctx, now); @@ -312,8 +321,7 @@ where self.scrollbar.set_count_and_active_page(page_count, 0); self.setup_swipe(); - let content_area = layout.content_single_page.union(layout.scrollbar); - self.loader.place(content_area); + self.loader.place(Self::loader_area()); bounds }