From 87d1248dc17ec5fd234860b11c90f775276e17d6 Mon Sep 17 00:00:00 2001 From: Evan Welsh Date: Sun, 9 Jul 2023 21:58:21 -0700 Subject: [PATCH] animationUtils: Group together various animation helpers The environment module is used to initialize the environment, yet it currently also defines the adjustAnimationTime() function. Ideally it should not export any utility functions, in particular once converted to ESM. The function cannot be moved to the existing Utils module, as that depends on an initialized environment, and can therefore not be imported from environment.js, so use that opportunity to group together several animation helpers in a new animationUtils module. Part-of: --- js/gdm/authPrompt.js | 6 +- js/js-resources.gresource.xml | 1 + js/misc/animationUtils.js | 117 ++++++++++++++++++++++++++++++++ js/misc/util.js | 85 +---------------------- js/ui/calendar.js | 4 +- js/ui/components/keyring.js | 4 +- js/ui/components/polkitAgent.js | 4 +- js/ui/environment.js | 15 +--- js/ui/screenShield.js | 2 +- js/ui/search.js | 6 +- js/ui/shellMountOperation.js | 4 +- 11 files changed, 136 insertions(+), 112 deletions(-) create mode 100644 js/misc/animationUtils.js diff --git a/js/gdm/authPrompt.js b/js/gdm/authPrompt.js index 78bf29dd9..b10c26788 100644 --- a/js/gdm/authPrompt.js +++ b/js/gdm/authPrompt.js @@ -15,7 +15,7 @@ const GdmUtil = imports.gdm.util; const Params = imports.misc.params; const ShellEntry = imports.ui.shellEntry; const UserWidget = imports.ui.userWidget; -const Util = imports.misc.util; +const {wiggle} = imports.misc.animationUtils; var DEFAULT_BUTTON_WELL_ICON_SIZE = 16; var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000; @@ -391,7 +391,7 @@ var AuthPrompt = GObject.registerClass({ this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED; if (wasQueryingService) - Util.wiggle(this._entry); + wiggle(this._entry); } _onVerificationComplete() { @@ -566,7 +566,7 @@ var AuthPrompt = GObject.registerClass({ this._message.opacity = 0; } - Util.wiggle(this._message, wiggleParameters); + wiggle(this._message, wiggleParameters); } updateSensitivity(sensitive) { diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index b1f537cc8..6aa88ce21 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -11,6 +11,7 @@ gdm/realmd.js gdm/util.js + misc/animationUtils.js misc/config.js misc/extensionUtils.js misc/fileUtils.js diff --git a/js/misc/animationUtils.js b/js/misc/animationUtils.js new file mode 100644 index 000000000..619d719bd --- /dev/null +++ b/js/misc/animationUtils.js @@ -0,0 +1,117 @@ +/* exported adjustAnimationTime, ensureActorVisibleInScrollView, wiggle */ + +const St = imports.gi.St; +const Clutter = imports.gi.Clutter; + +const Params = imports.misc.params; + +var SCROLL_TIME = 100; + +const WIGGLE_OFFSET = 6; +const WIGGLE_DURATION = 65; +const N_WIGGLES = 3; + +/** + * adjustAnimationTime: + * + * @param {number} msecs - time in milliseconds + * + * Adjust `msecs` to account for St's enable-animations + * and slow-down-factor settings + */ +function adjustAnimationTime(msecs) { + const settings = St.Settings.get(); + + if (!settings.enable_animations) + return 0; + return settings.slow_down_factor * msecs; +} + +/** + * Animate scrolling a scrollview until an actor is visible. + * + * @param {St.ScrollView} scrollView - the scroll view the actor is in + * @param {Clutter.Actor} actor - the actor + */ +function ensureActorVisibleInScrollView(scrollView, actor) { + const {adjustment} = scrollView.vscroll; + let [value, lower_, upper, stepIncrement_, pageIncrement_, pageSize] = adjustment.get_values(); + + let offset = 0; + const vfade = scrollView.get_effect('fade'); + if (vfade) + offset = vfade.fade_margins.top; + + let box = actor.get_allocation_box(); + let y1 = box.y1, y2 = box.y2; + + let parent = actor.get_parent(); + while (parent !== scrollView) { + if (!parent) + throw new Error('actor not in scroll view'); + + box = parent.get_allocation_box(); + y1 += box.y1; + y2 += box.y1; + parent = parent.get_parent(); + } + + if (y1 < value + offset) + value = Math.max(0, y1 - offset); + else if (y2 > value + pageSize - offset) + value = Math.min(upper, y2 + offset - pageSize); + else + return; + + adjustment.ease(value, { + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: SCROLL_TIME, + }); +} + +/** + * "Wiggles" a clutter actor. A "wiggle" is an animation the moves an actor + * back and forth on the X axis a specified amount of times. + * + * @param {Clutter.Actor} actor - an actor to animate + * @param {object} params - options for the animation + * @param {number} params.offset - the offset to move the actor by per-wiggle + * @param {number} params.duration - the amount of time to move the actor per-wiggle + * @param {number} params.wiggleCount - the number of times to wiggle the actor + */ +function wiggle(actor, params) { + if (!St.Settings.get().enable_animations) + return; + + params = Params.parse(params, { + offset: WIGGLE_OFFSET, + duration: WIGGLE_DURATION, + wiggleCount: N_WIGGLES, + }); + actor.translation_x = 0; + + // Accelerate before wiggling + actor.ease({ + translation_x: -params.offset, + duration: params.duration, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + // Wiggle + actor.ease({ + translation_x: params.offset, + duration: params.duration, + mode: Clutter.AnimationMode.LINEAR, + repeatCount: params.wiggleCount, + autoReverse: true, + onComplete: () => { + // Decelerate and return to the original position + actor.ease({ + translation_x: 0, + duration: params.duration, + mode: Clutter.AnimationMode.EASE_IN_QUAD, + }); + }, + }); + }, + }); +} diff --git a/js/misc/util.js b/js/misc/util.js index e4097acc1..8dc6b9905 100644 --- a/js/misc/util.js +++ b/js/misc/util.js @@ -1,9 +1,8 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported findUrls, spawn, spawnCommandLine, spawnApp, trySpawnCommandLine, - createTimeLabel, insertSorted, ensureActorVisibleInScrollView, - wiggle, lerp, GNOMEversionCompare, DBusSenderChecker, Highlighter */ + createTimeLabel, insertSorted, lerp, GNOMEversionCompare, + DBusSenderChecker, Highlighter */ -const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Shell = imports.gi.Shell; @@ -11,15 +10,8 @@ const St = imports.gi.St; const GnomeDesktop = imports.gi.GnomeDesktop; const Main = imports.ui.main; -const Params = imports.misc.params; const {formatTime} = imports.misc.dateUtils; -var SCROLL_TIME = 100; - -const WIGGLE_OFFSET = 6; -const WIGGLE_DURATION = 65; -const N_WIGGLES = 3; - // http://daringfireball.net/2010/07/improved_regex_for_matching_urls const _balancedParens = '\\([^\\s()<>]+\\)'; const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]'; @@ -241,79 +233,6 @@ function insertSorted(array, val, cmp) { return pos; } -function ensureActorVisibleInScrollView(scrollView, actor) { - let adjustment = scrollView.vscroll.adjustment; - let [value, lower_, upper, stepIncrement_, pageIncrement_, pageSize] = adjustment.get_values(); - - let offset = 0; - let vfade = scrollView.get_effect("fade"); - if (vfade) - offset = vfade.fade_margins.top; - - let box = actor.get_allocation_box(); - let y1 = box.y1, y2 = box.y2; - - let parent = actor.get_parent(); - while (parent != scrollView) { - if (!parent) - throw new Error("actor not in scroll view"); - - box = parent.get_allocation_box(); - y1 += box.y1; - y2 += box.y1; - parent = parent.get_parent(); - } - - if (y1 < value + offset) - value = Math.max(0, y1 - offset); - else if (y2 > value + pageSize - offset) - value = Math.min(upper, y2 + offset - pageSize); - else - return; - - adjustment.ease(value, { - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - duration: SCROLL_TIME, - }); -} - -function wiggle(actor, params) { - if (!St.Settings.get().enable_animations) - return; - - params = Params.parse(params, { - offset: WIGGLE_OFFSET, - duration: WIGGLE_DURATION, - wiggleCount: N_WIGGLES, - }); - actor.translation_x = 0; - - // Accelerate before wiggling - actor.ease({ - translation_x: -params.offset, - duration: params.duration, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => { - // Wiggle - actor.ease({ - translation_x: params.offset, - duration: params.duration, - mode: Clutter.AnimationMode.LINEAR, - repeatCount: params.wiggleCount, - autoReverse: true, - onComplete: () => { - // Decelerate and return to the original position - actor.ease({ - translation_x: 0, - duration: params.duration, - mode: Clutter.AnimationMode.EASE_IN_QUAD, - }); - }, - }); - }, - }); -} - function lerp(start, end, progress) { return start + progress * (end - start); } diff --git a/js/ui/calendar.js b/js/ui/calendar.js index aa597fc42..d87d5aff8 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -13,7 +13,7 @@ const MessageList = imports.ui.messageList; const MessageTray = imports.ui.messageTray; const Mpris = imports.ui.mpris; const PopupMenu = imports.ui.popupMenu; -const Util = imports.misc.util; +const {ensureActorVisibleInScrollView} = imports.misc.animationUtils; const {formatDateWithCFormatString, formatTimeSpan} = imports.misc.dateUtils; const {loadInterfaceXML} = imports.misc.fileUtils; @@ -1016,7 +1016,7 @@ class CalendarMessageList extends St.Widget { 'notify::can-clear', this._sync.bind(this), 'destroy', () => this._sectionList.remove_actor(section), 'message-focused', (_s, messageActor) => { - Util.ensureActorVisibleInScrollView(this._scrollView, messageActor); + ensureActorVisibleInScrollView(this._scrollView, messageActor); }, this); this._sectionList.add_actor(section); } diff --git a/js/ui/components/keyring.js b/js/ui/components/keyring.js index e4d0ad85f..fa96553bb 100644 --- a/js/ui/components/keyring.js +++ b/js/ui/components/keyring.js @@ -13,7 +13,7 @@ const Dialog = imports.ui.dialog; const ModalDialog = imports.ui.modalDialog; const ShellEntry = imports.ui.shellEntry; const CheckBox = imports.ui.checkBox; -const Util = imports.misc.util; +const {wiggle} = imports.misc.animationUtils; var KeyringDialog = GObject.registerClass( class KeyringDialog extends ModalDialog.ModalDialog { @@ -83,7 +83,7 @@ class KeyringDialog extends ModalDialog.ModalDialog { }); this.prompt.connect('notify::warning', () => { if (this._passwordEntry && this.prompt.warning !== '') - Util.wiggle(this._passwordEntry); + wiggle(this._passwordEntry); }); warningBox.add_child(warning); diff --git a/js/ui/components/polkitAgent.js b/js/ui/components/polkitAgent.js index 65c4dff71..de2f86270 100644 --- a/js/ui/components/polkitAgent.js +++ b/js/ui/components/polkitAgent.js @@ -16,7 +16,7 @@ const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; const ShellEntry = imports.ui.shellEntry; const UserWidget = imports.ui.userWidget; -const Util = imports.misc.util; +const {wiggle} = imports.misc.animationUtils; const DialogMode = { AUTH: 0, @@ -267,7 +267,7 @@ var AuthenticationDialog = GObject.registerClass({ this._infoMessageLabel.hide(); this._nullMessageLabel.hide(); - Util.wiggle(this._passwordEntry); + wiggle(this._passwordEntry); } /* Try and authenticate again */ diff --git a/js/ui/environment.js b/js/ui/environment.js index 8f75871c3..aee0f8ae5 100644 --- a/js/ui/environment.js +++ b/js/ui/environment.js @@ -44,6 +44,7 @@ const Shell = imports.gi.Shell; const St = imports.gi.St; const Gettext = imports.gettext; const SignalTracker = imports.misc.signalTracker; +const {adjustAnimationTime} = imports.misc.animationUtils; Gio._promisify(Gio.DataInputStream.prototype, 'fill_async'); Gio._promisify(Gio.DataInputStream.prototype, 'read_line_async'); @@ -389,17 +390,3 @@ function init() { // Prevent extensions from opening a display connection to ourselves Gdk.set_allowed_backends(''); } - -// adjustAnimationTime: -// @msecs: time in milliseconds -// -// Adjust @msecs to account for St's enable-animations -// and slow-down-factor settings -function adjustAnimationTime(msecs) { - let settings = St.Settings.get(); - - if (!settings.enable_animations) - return 0; - return settings.slow_down_factor * msecs; -} - diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js index 8a10d7fe3..39ae22648 100644 --- a/js/ui/screenShield.js +++ b/js/ui/screenShield.js @@ -22,7 +22,7 @@ const MessageTray = imports.ui.messageTray; const ShellDBus = imports.ui.shellDBus; const SmartcardManager = imports.misc.smartcardManager; -const { adjustAnimationTime } = imports.ui.environment; +const {adjustAnimationTime} = imports.misc.animationUtils; const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; const LOCK_ENABLED_KEY = 'lock-enabled'; diff --git a/js/ui/search.js b/js/ui/search.js index 348f39799..9e42c9bce 100644 --- a/js/ui/search.js +++ b/js/ui/search.js @@ -14,7 +14,7 @@ const IconGrid = imports.ui.iconGrid; const Main = imports.ui.main; const ParentalControlsManager = imports.misc.parentalControlsManager; const RemoteSearch = imports.ui.remoteSearch; -const Util = imports.misc.util; +const {ensureActorVisibleInScrollView} = imports.misc.animationUtils; const { Highlighter } = imports.misc.util; @@ -746,7 +746,7 @@ var SearchResultsView = GObject.registerClass({ } _focusChildChanged(provider) { - Util.ensureActorVisibleInScrollView(this._scrollView, provider.focusChild); + ensureActorVisibleInScrollView(this._scrollView, provider.focusChild); } _ensureProviderDisplay(provider) { @@ -877,7 +877,7 @@ var SearchResultsView = GObject.registerClass({ if (selected) { result.add_style_pseudo_class('selected'); - Util.ensureActorVisibleInScrollView(this._scrollView, result); + ensureActorVisibleInScrollView(this._scrollView, result); } else { result.remove_style_pseudo_class('selected'); } diff --git a/js/ui/shellMountOperation.js b/js/ui/shellMountOperation.js index 86ec781fc..591d88b1a 100644 --- a/js/ui/shellMountOperation.js +++ b/js/ui/shellMountOperation.js @@ -19,7 +19,7 @@ const Params = imports.misc.params; const ShellEntry = imports.ui.shellEntry; const { loadInterfaceXML } = imports.misc.fileUtils; -const Util = imports.misc.util; +const {wiggle} = imports.misc.animationUtils; var LIST_ITEM_ICON_SIZE = 48; var WORK_SPINNER_ICON_SIZE = 16; @@ -421,7 +421,7 @@ var ShellMountPasswordDialog = GObject.registerClass({ this._errorMessageLabel.text = _('Sorry, that didn’t work. Please try again.'); this._errorMessageLabel.opacity = 255; - Util.wiggle(this._passwordEntry); + wiggle(this._passwordEntry); } _onCancelButton() {