From 3d061345456b483be19d5f49a9bd18d87491f31f Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Fri, 19 Jul 2024 14:11:15 +0100 Subject: [PATCH] Make startup animation use async functions throughout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During session startup, we have the following chain: LayoutManager._prepareStartupAnimation() `- LayoutManager._startupAnimation() `- LayoutManager._startupAnimationComplete() (a) | or `- LayoutManager._startupAnimationGreeter() (b) | or `- LayoutManager._startupAnimationSession() (c) `- Overview.runStartupAnimation() (d) `- OverviewActor.runStartupAnimation() (e) `- ControlsManager.runStartupAnimation() (f) Branch (b) calls LayoutManager._startupAnimationComplete() once the animation is complete. Branch (c) does the same, except that ControlsManager.runStartupAnimation() is an async function that awaits LayoutManager.ensureAllocation(). LayoutManager._prepareStartupAnimation() is an `async` function, and its caller catches & logs any error it raises. Previously, ControlsManager.runStartupAnimation() – (f) in the above diagram – was declared `async`, because it uses `await` internally, but also accepted a callback, which was called on successful completion of the function and the animation it triggers. If an exception is raised during execution of the function, its callback would never be called, and because the promise it implicitly returns is discarded, the exception would never be logged either. And, stepping a few levels up the call stack, the callback that LayoutManager._startupAnimationSession() (c) provided would never be called, and so LayoutManager._startupAnimationComplete() would never be called, meaning the cover pane that prevents input would never be removed and the session would be unusable. Remove the callback parameter from ControlsManager.runStartupAnimation() (f), and instead make it resolve in the case where the callback would previously have been called. Remove the callback parameter from OverviewActor.runStartupAnimation() (e) – it is just a wrapper around ControlsManager.runStartupAnimation(). Adjust Overview.runStartupAnimation() (d) accordingly: rather than passing a callback to OverviewActor.runStartupAnimation(), await its return and then proceed. There is a slight behaviour change here: previously, in the branch where this._syncGrab() is false, the callback would be called before calling this.hide(), whereas now this.hide() is called before the async function returns. Adjust LayoutManager._startupAnimationSession() and its siblings to be async, rather than each calling _startupAnimationComplete() directly. Finally, in _prepareStartupAnimation() await _startupAnimation() either succeeding or failing, and in either case call _startupAnimationComplete(), removing the cover pane that prevents all input. Part-of: --- js/ui/layout.js | 58 ++++++++++++++++++++++----------------- js/ui/overview.js | 32 ++++++++++----------- js/ui/overviewControls.js | 18 ++++++------ 3 files changed, 57 insertions(+), 51 deletions(-) diff --git a/js/ui/layout.js b/js/ui/layout.js index 3b54960f8..5b09817ba 100644 --- a/js/ui/layout.js +++ b/js/ui/layout.js @@ -763,39 +763,47 @@ export const LayoutManager = GObject.registerClass({ else this.emit('startup-prepared'); - this._startupAnimation(); - } - - _startupAnimation() { - if (Meta.is_restart()) + try { + await this._startupAnimation(); + } finally { this._startupAnimationComplete(); - else if (Main.sessionMode.isGreeter) - this._startupAnimationGreeter(); - else - this._startupAnimationSession(); + } } - _startupAnimationGreeter() { - this.panelBox.ease({ - translation_y: 0, - duration: STARTUP_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onStopped: () => this._startupAnimationComplete(), + async _startupAnimation() { + if (Meta.is_restart()) + return; + + if (Main.sessionMode.isGreeter) + await this._startupAnimationGreeter(); + else + await this._startupAnimationSession(); + } + + async _startupAnimationGreeter() { + await new Promise(resolve => { + this.panelBox.ease({ + translation_y: 0, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onStopped: () => resolve(), + }); }); } - _startupAnimationSession() { - const onStopped = () => this._startupAnimationComplete(); + async _startupAnimationSession() { if (Main.sessionMode.hasOverview) { - Main.overview.runStartupAnimation(onStopped); + await Main.overview.runStartupAnimation(); } else { - this.uiGroup.ease({ - scale_x: 1, - scale_y: 1, - opacity: 255, - duration: STARTUP_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onStopped, + await new Promise(resolve => { + this.uiGroup.ease({ + scale_x: 1, + scale_y: 1, + opacity: 255, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onStopped: () => resolve(), + }); }); } } diff --git a/js/ui/overview.js b/js/ui/overview.js index 7e431b68e..2f40d2fd9 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -90,8 +90,8 @@ class OverviewActor extends St.BoxLayout { this._controls.animateFromOverview(callback); } - runStartupAnimation(callback) { - this._controls.runStartupAnimation(callback); + async runStartupAnimation() { + await this._controls.runStartupAnimation(); } get dash() { @@ -666,7 +666,7 @@ export class Overview extends Signals.EventEmitter { this._overview.controls.appDisplay.selectApp(id); } - runStartupAnimation(callback) { + async runStartupAnimation() { Main.panel.style = 'transition-duration: 0ms;'; this._shown = true; @@ -679,23 +679,19 @@ export class Overview extends Signals.EventEmitter { this._changeShownState(OverviewShownState.SHOWING); - this._overview.runStartupAnimation(() => { - // Overview got hidden during startup animation - if (this._shownState !== OverviewShownState.SHOWING) { - callback(); - return; - } + await this._overview.runStartupAnimation(); - if (!this._syncGrab()) { - callback(); - this.hide(); - return; - } + // Overview got hidden during startup animation + if (this._shownState !== OverviewShownState.SHOWING) + return; - Main.panel.style = null; - this._changeShownState(OverviewShownState.SHOWN); - callback(); - }); + if (!this._syncGrab()) { + this.hide(); + return; + } + + Main.panel.style = null; + this._changeShownState(OverviewShownState.SHOWN); } getShowAppsButton() { diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js index 7cc481240..3082ea61d 100644 --- a/js/ui/overviewControls.js +++ b/js/ui/overviewControls.js @@ -797,7 +797,7 @@ class ControlsManager extends St.Widget { this._stateAdjustment.gestureInProgress = false; } - async runStartupAnimation(callback) { + async runStartupAnimation() { this._ignoreShowAppsButtonToggle = true; this.prepareToEnterOverview(); @@ -839,14 +839,16 @@ class ControlsManager extends St.Widget { }); // The Dash rises from the bottom. This is the last animation to finish, - // so run the callback there. + // so resolve the promise there. this.dash.translation_y = this.dash.height + this.dash.margin_bottom; - this.dash.ease({ - translation_y: 0, - delay: STARTUP_ANIMATION_TIME, - duration: STARTUP_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onStopped: () => callback(), + return new Promise(resolve => { + this.dash.ease({ + translation_y: 0, + delay: STARTUP_ANIMATION_TIME, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onStopped: () => resolve(), + }); }); }