From ba79b5b611209232eb7912493a548209c2e712b5 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Mon, 24 Jan 2022 20:34:50 +0100 Subject: [PATCH 01/15] :tada: Make it possible to select window open effects --- prefs.js | 26 +++++++ resources/ui/gtk3/prefs.ui | 67 ++++++++++++++++++- resources/ui/gtk4/prefs.ui | 47 ++++++++++++- ...ell.extensions.burn-my-windows.gschema.xml | 54 +++++++++++++++ 4 files changed, 192 insertions(+), 2 deletions(-) diff --git a/prefs.js b/prefs.js index 2182911..83f14e7 100644 --- a/prefs.js +++ b/prefs.js @@ -138,6 +138,28 @@ var PreferencesDialog = class PreferencesDialog { // clang-format on } + // Populate the open-effects drop-down menu. + { + const menu = this._builder.get_object('open-effect-menu'); + const group = Gio.SimpleActionGroup.new(); + window.insert_action_group('open-effects', group); + + ALL_EFFECTS.forEach(Effect => { + const [minMajor, minMinor] = Effect.getMinShellVersion(); + if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { + const nick = Effect.getNick(); + const label = Effect.getLabel(); + const actionName = nick + '-open-effect'; + const fullName = 'open-effects.' + actionName; + + const action = this._settings.create_action(actionName); + group.add_action(action); + + menu.append_item(Gio.MenuItem.new(label, fullName)); + } + }); + } + // Populate the close-effects drop-down menu. { const menu = this._builder.get_object('close-effect-menu'); @@ -299,8 +321,12 @@ var PreferencesDialog = class PreferencesDialog { this._button.connect('clicked', () => { // Set the to-be-previewed effect. + dialog.getSettings().set_string('open-preview-effect', Effect.getNick()); dialog.getSettings().set_string('close-preview-effect', Effect.getNick()); + // Make sure that the window.show() firther below "sees" this change. + Gio.Settings.sync(); + // Create the preview-window. const window = new Gtk.Window({ title: `Preview for ${Effect.getLabel()}`, diff --git a/resources/ui/gtk3/prefs.ui b/resources/ui/gtk3/prefs.ui index 8b70434..708d1f5 100644 --- a/resources/ui/gtk3/prefs.ui +++ b/resources/ui/gtk3/prefs.ui @@ -3,6 +3,7 @@ + main-menu @@ -68,6 +69,70 @@ none + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + vertical + 3 + + + Open Window Animation + 0 + start + center + 1 + + + + + If multiple are selected, one is chosen randomly. + 0 + start + center + 1 + + + + + + + + open-effect-menu + + + 8 + + + Choose Effects + + + + + pan-down-symbolic + 1 + + + + + + + + + + + 10 @@ -144,7 +209,7 @@ 8 - Also Burn Dialogs + Also Add Effects to Dialogs 0 start center diff --git a/resources/ui/gtk4/prefs.ui b/resources/ui/gtk4/prefs.ui index 9d77674..a4182b1 100644 --- a/resources/ui/gtk4/prefs.ui +++ b/resources/ui/gtk4/prefs.ui @@ -3,6 +3,7 @@ + main-menu @@ -70,6 +71,50 @@ + + + 0 + + + + + vertical + 3 + + + Open Window Animation + 0 + start + center + 1 + + + + + If multiple are selected, one is chosen randomly. + 0 + start + center + 1 + + + + + + + + Choose Effects + open-effect-menu + + + + + + + 0 @@ -121,7 +166,7 @@ - Also Burn Dialogs + Also Add Effects to Dialogs 0 start center diff --git a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml index 73845d9..6637fbb 100644 --- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml +++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml @@ -19,6 +19,12 @@ The effect with this nick will be used for the next window-close animation. + + "" + Open Preview Effect + The effect with this nick will be used for the next window-open animation. + + @@ -29,6 +35,12 @@ Use the fire effect for window closing. + + false + Fire Open Effect + Use the fire effect for window opening. + + 1500 Fire Animation Time @@ -93,6 +105,12 @@ Use the matrix effect for window closing. + + false + Matrix Open Effect + Use the matrix effect for window opening. + + 3000 Matrix Animation Time @@ -139,6 +157,12 @@ Use the tv effect for window closing. + + false + TV Open Effect + Use the tv effect for window opening. + + 400 TV Animation Time @@ -161,6 +185,12 @@ Use the trex effect for window closing. + + false + T-Rex Open Effect + Use the trex effect for window opening. + + 1500 T-Rex Animation Time @@ -201,6 +231,12 @@ Use the Energize A effect for window closing. + + false + Energize A Open Effect + Use the Energize A effect for window opening. + + 2000 Energize A Animation Time @@ -229,6 +265,12 @@ Use the Energize B effect for window closing. + + false + Energize B Open Effect + Use the Energize B effect for window opening. + + 2000 Energize B Animation Time @@ -257,6 +299,12 @@ Use the wisps effect for window closing. + + false + Wisps Open Effect + Use the wisps effect for window opening. + + 2000 Wisps Animation Time @@ -285,6 +333,12 @@ Use the broken glass effect for window closing. + + false + Broken Glass Open Effect + Use the broken glass effect for window opening. + + 1500 Broken Glass Animation Time From 28754a93c2eed6a9727d04fa985c241053e14836 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Mon, 24 Jan 2022 20:36:23 +0100 Subject: [PATCH 02/15] :wrench: Reduce default animation time of some effects --- docs/changelog.md | 1 + ...org.gnome.shell.extensions.burn-my-windows.gschema.xml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index a8bd78d..cb33ec1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -13,6 +13,7 @@ * Added a menu entry with a link to the guide for creating new effect types. * Added a new option to the Matrix Effect: **Vertical Overshooting**: This can add some vertical variation to the start and end position of the letter drops. +* Thanks to improved timing, the default animation time of several effects could be reduced. ## [Burn My Window 9](https://github.com/schneegans/Burn-My-Windows/releases/tag/v9) diff --git a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml index 6637fbb..a889297 100644 --- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml +++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml @@ -112,7 +112,7 @@ - 3000 + 1500 Matrix Animation Time The time the matrix effect takes. @@ -192,7 +192,7 @@ - 1500 + 1000 T-Rex Animation Time The time the trex effect takes. @@ -306,7 +306,7 @@ - 2000 + 1500 Wisps Animation Time The time the wisps effect takes. @@ -340,7 +340,7 @@ - 1500 + 1000 Broken Glass Animation Time The time the broken glass effect takes. From b6cb78330d2100b76af11af5b8f81ca9025dfb03 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:07:00 +0100 Subject: [PATCH 03/15] :lipstick: Add headerbar to preview windows --- prefs.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prefs.js b/prefs.js index 83f14e7..068bbfc 100644 --- a/prefs.js +++ b/prefs.js @@ -337,6 +337,10 @@ var PreferencesDialog = class PreferencesDialog { this._button.get_toplevel() }); + // Add a header bar to the window. + const header = Gtk.HeaderBar.new(); + window.set_titlebar(header); + const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, valign: Gtk.Align.CENTER, From 6b218d049fc1ac951ab35ce47b5d466cfb525213 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:07:30 +0100 Subject: [PATCH 04/15] :hammer: Initial attempt on window-opening animation --- docs/how-to-create-new-effects.md | 19 +- extension.js | 392 +++++++++++++++++------------- src/BrokenGlass.js | 33 ++- src/EnergizeA.js | 28 ++- src/EnergizeB.js | 28 ++- src/Fire.js | 32 ++- src/Matrix.js | 29 ++- src/TRexAttack.js | 41 ++-- src/TVEffect.js | 34 ++- src/Wisps.js | 36 ++- 10 files changed, 405 insertions(+), 267 deletions(-) diff --git a/docs/how-to-create-new-effects.md b/docs/how-to-create-new-effects.md index c1a14f5..0d10d04 100644 --- a/docs/how-to-create-new-effects.md +++ b/docs/how-to-create-new-effects.md @@ -150,12 +150,19 @@ var SimpleFade = class SimpleFade { return new Shader(settings); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For each property, you can set a "from", "to", and a "mode". For this - // effect, windows should neither be scaled nor faded. - static getCloseTransition(actor, settings) { - return {'opacity': {to: 255}, 'scale-x': {to: 1.0}, 'scale-y': {to: 1.0}}; + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows should neither be scaled nor faded. + static tweakTransition(actor, settings, forOpening) { + return { + 'opacity': {from: 255, to: 255, mode: 4}, + 'scale-x': {from: 1.0, to: 1.0, mode: 4}, + 'scale-y': {from: 1.0, to: 1.0, mode: 4} + }; } } diff --git a/extension.js b/extension.js index 085491d..6cc0fec 100644 --- a/extension.js +++ b/extension.js @@ -15,6 +15,7 @@ const {Clutter, Gio, Meta} = imports.gi; +const Main = imports.ui.main; const Workspace = imports.ui.workspace.Workspace; const WindowManager = imports.ui.windowManager.WindowManager; @@ -43,13 +44,12 @@ const ALL_EFFECTS = [ ]; ////////////////////////////////////////////////////////////////////////////////////////// -// This extensions modifies the window-close animation to look like the window was set // -// on fire. There are also a few other effects available. All of them are implemented // -// using GLSL shaders which are applied to the window's Clutter.Actor. The extension is // -// actually very simple, most of the complexity comes from the fact that GNOME Shell // -// usually does not show an animation when a window is closed in the overview. Several // -// methods need to be monkey-patched to get this working. For more details, read the // -// other comments in this file... // +// This extensions modifies the window-close and window-open animations with all kinds // +// of effects. The effects are implemented using GLSL shaders which are applied to the // +// window's Clutter.Actor. The extension is actually very simple, much of the // +// complexity comes from the fact that GNOME Shell usually does not show an animation // +// when a window is closed in the overview. Several methods need to be monkey-patched // +// to get this working. For more details, read the other comments in this file... // ////////////////////////////////////////////////////////////////////////////////////////// class Extension { @@ -68,30 +68,82 @@ class Extension { this._settings = ExtensionUtils.getSettings(); // This will store an item of ALL_EFFECTS which was used the last time a window was - // closed. - this._effect = 0; + // opened / closed. + this._currentEffect = 0; - // We will monkey-patch these three methods. Let's store the original ones. + // We will use extensionThis to refer to the extension inside the patched methods. + const extensionThis = this; + + // We will monkey-patch these methods. Let's store the original ones. + this._origAddWindowClone = Workspace.prototype._addWindowClone; this._origWindowRemoved = Workspace.prototype._windowRemoved; this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow; - this._origAddWindowClone = Workspace.prototype._addWindowClone; this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor; // We will also override these animation times. this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME; this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME; - // Reset the dialog destroy time if the corresponding setting gets disabled. - this._settings.connect('changed::destroy-dialogs', () => { - if (!this._settings.get_boolean('destroy-dialogs')) { - imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = - this._origDialogTime; - } - }); + Workspace.prototype._addWindowClone = function(...params) { + const result = extensionThis._origAddWindowClone.apply(this, params); + + const clone = utils.shellVersionIs(3, 36) ? result[0] : result; + const window = params[0].get_compositor_private(); + + const xID = window.connect('notify::scale-x', () => { + if (window.scale_x > 0) { + clone.window_container.scale_x = window.scale_x; + } + }); + + const yID = window.connect('notify::scale-y', () => { + if (window.scale_y > 0) { + clone.window_container.scale_y = window.scale_y; + } + }); + + clone.window_container.connect('destroy', () => { + window.disconnect(xID); + window.disconnect(yID); + }); + + // On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone + // but not _doRemoveWindow. The latter is required to trigger the repositioning of + // the overview window layout. Therefore we call this method in addition. + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/workspace.js#L1877 + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1415 + if (utils.shellVersionIs(3, 36)) { + clone.connect('destroy', () => this._doRemoveWindow(clone.metaWindow)); + } + + return result; + }; + + this._windowCreatedConnection = + global.display.connect('window-created', (d, metaWin) => { + let actor = metaWin.get_compositor_private(); + + if (Main.overview.visible && !Main.overview.closing) { + utils.debug('window-created: setup effect on show'); + const id = actor.connect('show', () => { + extensionThis._setupEffect(actor, true); + actor.disconnect(id); + }); + + } else { + + utils.debug('window-created: setup effect on ease'); + const orig = actor.ease; + actor.ease = function(...params) { + orig.apply(actor, params); + actor.ease = orig; + + extensionThis._setupEffect(actor, true); + }; + } + }); + - // We will use extensionThis to refer to the extension inside the patched methods of - // the WorkspacesView. - const extensionThis = this; // This class is only available in GNOME Shell 3.38+. So no transition tweaking in // GNOME Shell 3.36, but this is not used by any effect available there anyways for @@ -111,26 +163,15 @@ class Extension { } }; - // When a window is removed from the overview, we need adjust the transitions of the - // window clone according to the chosen effect. We do this in the 'unmanaged' signal - // of the WindowPreview's Meta.Window. This is not ideal, as it does not work for - // dialogs which close themselves... Maybe there's a better way? WindowPreview.prototype._init = function(...params) { // Call the original method. extensionThis._origInit.apply(this, params); - // When the user clicks the X in the overview, the window is not deleted - // immediately. However, as soon as the window is really deleted, we need to - // adjust the transition of its clone. const connectionID = this.metaWindow.connect('unmanaged', () => { if (this.window_container) { // Hide the window's icon, name, and close button. this.overlayEnabled = false; this._icon.visible = false; - - const transitionConfig = extensionThis._effect.getCloseTransition( - this.window_container, extensionThis._settings); - extensionThis._tweakTransitions(this.window_container, transitionConfig); } }); @@ -151,19 +192,6 @@ class Extension { }; } - // On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone - // but not _doRemoveWindow. The latter is required to trigger the repositioning of the - // overview window layout. Therefore we call this method in addition. - // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/workspace.js#L1877 - // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1415 - if (utils.shellVersionIs(3, 36)) { - Workspace.prototype._addWindowClone = function(...params) { - const [clone, overlay] = extensionThis._origAddWindowClone.apply(this, params); - clone.connect('destroy', () => this._doRemoveWindow(clone.metaWindow)); - return [clone, overlay]; - }; - } - // These three method overrides are mega-hacky! They are only required to make the // fire animation work in the overview. Usually, windows are not faded when closed // from the overview (why?). With these overrides we make sure that they are actually @@ -205,92 +233,7 @@ class Extension { // As we cannot monkey-patch the _destroyWindow itself, we connect to the 'destroy' // signal of the window manager and tweak the animation to our needs. this._destroyConnection = global.window_manager.connect('destroy', (wm, actor) => { - // The _destroyWindow method of WindowManager, which was called right before this - // one, set up the window close animation. This usually fades-out the window and - // scales it a bit down. If no transition is in progress, something unexpected - // happened. We rather try not to burn the window! - const transition = actor.get_transition('scale-y'); - if (!transition) { - return; - } - - // We do nothing if a dialog got closed and we should not burn them. - const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs'); - const isDialogWindow = - actor.meta_window.window_type == Meta.WindowType.MODAL_DIALOG || - actor.meta_window.window_type == Meta.WindowType.DIALOG; - - // If an effect is to be previewed, we have to affect dialogs es well. This is - // because the preview window is a dialog window... - const previewNick = this._settings.get_string('close-preview-effect'); - - if (isDialogWindow && !shouldDestroyDialogs && previewNick == '') { - return; - } - - // Now we have to choose an effect. - this._effect = null; - - // First we check if an effect is to be previewed. - if (previewNick != '') { - this._effect = ALL_EFFECTS.find(Effect => { - return Effect.getNick() == previewNick; - }); - - // Only preview the effect once. - this._settings.set_string('close-preview-effect', ''); - - } else { - - // Else we choose a random effect from all enabled effects. Therefore, we first - // create a list of all currently enabled effects. - const enabled = ALL_EFFECTS.filter(Effect => { - return this._settings.get_boolean(`${Effect.getNick()}-close-effect`); - }); - - // And then choose a random effect. - if (enabled.length > 0) { - this._effect = enabled[Math.floor(Math.random() * enabled.length)]; - } - } - - // If nothing was enabled, we have to do nothing :) - if (this._effect == null) { - return; - } - - // The effect usually will choose to override the present transitions on the actor. - const transitionConfig = this._effect.getCloseTransition(actor, this._settings); - this._tweakTransitions(actor, transitionConfig); - - // Add a cool shader to our window actor! - const shader = this._effect.createShader(actor, this._settings); - - if (shader) { - actor.add_effect(shader); - - // Update uniforms at each frame. - transition.connect('new-frame', (t) => { - shader.set_uniform_value('uProgress', t.get_progress()); - shader.set_uniform_value('uTime', 0.001 * t.get_elapsed_time()); - shader.set_uniform_value('uSizeX', actor.width); - shader.set_uniform_value('uSizeY', actor.height); - }); - } - - // The code below is not necessary for Burn-My-Windows to function. However, there - // are some extensions such as "Show Application View When Workspace Empty" - // https://extensions.gnome.org/extension/2036/show-application-view-when-workspace-empty/ - // which do something *after* a window was closed. As the window-close animation - // duration depends on the used effect, this may vary each time a window is closed. - // We set the currently used time here, so that others can get an idea how long this - // will take... - const duration = transition.get_duration(); - if (isDialogWindow && shouldDestroyDialogs) { - imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = duration; - } else { - imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = duration; - } + this._setupEffect(actor, false); }); } @@ -301,12 +244,13 @@ class Extension { // Unregister our resources. Gio.resources_unregister(this._resources); - // Restore the original behavior. + // Restore the original window-open and window-close animations. global.window_manager.disconnect(this._destroyConnection); + global.display.disconnect(this._windowCreatedConnection); + Workspace.prototype._addWindowClone = this._origAddWindowClone; Workspace.prototype._windowRemoved = this._origWindowRemoved; Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow; - Workspace.prototype._addWindowClone = this._origAddWindowClone; WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor; imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = this._origWindowTime; @@ -323,6 +267,152 @@ class Extension { // ----------------------------------------------------------------------- private stuff + _setupEffect(actor, forOpening) { + const isNormalWindow = actor.meta_window.window_type == Meta.WindowType.NORMAL; + const isDialogWindow = + actor.meta_window.window_type == Meta.WindowType.MODAL_DIALOG || + actor.meta_window.window_type == Meta.WindowType.DIALOG; + + if (!isNormalWindow && !isDialogWindow) { + return; + } + + // We do nothing if a dialog got closed and we should not burn them. + const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs'); + + // If an effect is to be previewed, we have to affect dialogs es well. This is + // because the preview window is a dialog window... + const action = forOpening ? 'open' : 'close'; + const previewNick = this._settings.get_string(action + '-preview-effect'); + + if (isDialogWindow && !shouldDestroyDialogs && previewNick == '') { + this._fixAnimationTimes(isDialogWindow, forOpening, null); + return; + } + + // ------------------------------------------------------------------ choose an effect + + this._currentEffect = null; + + if (previewNick != '') { + this._currentEffect = ALL_EFFECTS.find(Effect => { + return Effect.getNick() == previewNick; + }); + + // Only preview the effect once. + this._settings.set_string(action + '-preview-effect', ''); + + } else { + + // Else we choose a random effect from all enabled effects. Therefore, we first + // create a list of all currently enabled effects. + const enabled = ALL_EFFECTS.filter(Effect => { + return this._settings.get_boolean(`${Effect.getNick()}-${action}-effect`); + }); + + // And then choose a random effect. + if (enabled.length > 0) { + this._currentEffect = enabled[Math.floor(Math.random() * enabled.length)]; + } + } + + // If nothing was enabled, we have to do nothing :) + if (this._currentEffect == null) { + this._fixAnimationTimes(isDialogWindow, forOpening, null); + return; + } + + // ----------------------------------------------------------- tweak actor transitions + + // This is used to tweak the ongoing transitions of a window actor. This is either the + // actual actor of the Meta.Window or a clone in the overview. Usually windows are + // faded in / out scaled up / down slightly by GNOME Shell. Here, we allow + // modifications to this behavior by the effects. The given config object is created + // by the effect's tweakTransition() method. + const config = this._currentEffect.tweakTransition(actor, this._settings, forOpening); + const duration = + this._settings.get_int(this._currentEffect.getNick() + '-animation-time'); + + actor.set_pivot_point(0.5, 0.5); + + for (const property in config) { + const from = config[property].from; + const to = config[property].to; + const mode = config[property].mode; + + let transition = actor.get_transition(property); + + if (!transition) { + actor.set_property(property, 0); + actor.save_easing_state(); + actor.set_easing_duration(1000); + actor.set_property(property, 1); + actor.restore_easing_state(); + + transition = actor.get_transition(property); + } + + if (!transition) { + this._fixAnimationTimes(isDialogWindow, forOpening, null); + return; + } + + transition.set_duration(duration); + transition.set_to(to); + transition.set_from(from); + transition.set_progress_mode(mode); + } + + const transition = actor.get_transition('scale-y'); + transition.connect('completed', () => { + actor.scale_x = 1.0; + actor.scale_y = 1.0; + }); + + // -------------------------------------------------------------------- add the shader + + // Add a cool shader to our window actor! + const shader = this._currentEffect.createShader(actor, this._settings, forOpening); + + if (shader) { + actor.remove_effect_by_name(`burn-my-windows-effect`); + actor.add_effect_with_name(`burn-my-windows-effect`, shader); + + // Update uniforms at each frame. + transition.connect('new-frame', (t) => { + shader.set_uniform_value('uProgress', t.get_progress()); + shader.set_uniform_value('uTime', 0.001 * t.get_elapsed_time()); + shader.set_uniform_value('uSizeX', actor.width); + shader.set_uniform_value('uSizeY', actor.height); + }); + + transition.connect('completed', () => { + actor.remove_effect_by_name(`burn-my-windows-effect`); + }); + } + + this._fixAnimationTimes(isDialogWindow, forOpening, duration); + } + + // The code below is not necessary for Burn-My-Windows to function. However, there + // are some extensions such as "Show Application View When Workspace Empty" + // https://extensions.gnome.org/extension/2036/show-application-view-when-workspace-empty/ + // which do something *after* a window was closed. As the window-close animation + // duration depends on the used effect, this may vary each time a window is + // closed. We set the currently used time here, so that others can get an idea how + // long this will take... + _fixAnimationTimes(isDialogWindow, forOpening, duration) { + if (!forOpening) { + if (isDialogWindow) { + imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = + duration != null ? duration : this._origDialogTime; + } else { + imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = + duration != null ? duration : this._origWindowTime; + } + } + } + // This is required to enable window-close animations in the overview. See the comment // for Workspace.prototype._windowRemoved above for an explanation. _shouldDestroy(workspace, metaWindow) { @@ -340,42 +430,6 @@ class Extension { return false; } - - // This is used to tweak the ongoing transitions of a window actor. This is either the - // actual actor of the Meta.Window or a clone in the overview. Usually windows are faded - // to transparency and scaled down slightly by GNOME Shell. Here, we allow modifications - // to this behavior by the effects. - _tweakTransitions(actor, config) { - const duration = this._settings.get_int(this._effect.getNick() + '-animation-time'); - - for (const property in config) { - const from = config[property].from; - const to = config[property].to; - const mode = config[property].mode; - - const transition = actor.get_transition(property); - - if (transition) { - transition.set_duration(duration); - - if (to != undefined) transition.set_to(to); - if (from != undefined) transition.set_from(from); - if (mode != undefined) transition.set_progress_mode(mode); - - } else { - - if (from != undefined) actor[property] = from; - - if (to != undefined) { - actor.save_easing_state(); - actor.set_easing_duration(duration) - if (mode != undefined) actor.set_easing_mode(mode); - actor[property] = to; - actor.restore_easing_state(); - } - } - } - } } // This function is called once when the extension is loaded, not enabled. diff --git a/src/BrokenGlass.js b/src/BrokenGlass.js index aa77df0..3646693 100644 --- a/src/BrokenGlass.js +++ b/src/BrokenGlass.js @@ -79,17 +79,21 @@ var BrokenGlass = class BrokenGlass { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(actor, settings); + static createShader(actor, settings, forOpening) { + return new Shader(actor, settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows are set to twice their original size, so that + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows are set to twice their original size, so that // we have some space to draw the shards. We also set the animation mode to "Linear". - static getCloseTransition(actor, settings) { + static tweakTransition(actor, settings, forOpening) { return { - 'opacity': {to: 255, mode: 1}, + 'opacity': {from: 255, to: 255, mode: 1}, 'scale-x': {from: 2.0, to: 2.0, mode: 1}, 'scale-y': {from: 2.0, to: 2.0, mode: 1} }; @@ -118,7 +122,7 @@ if (utils.isInShellProcess()) { // resources/img/shards.png. The red channel of the texture contains the distance to the // shard edges. This information is used to fade out the shards. Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(actor, settings) { + _init(actor, settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); // Load the shards texture. As the shader is re-created for each window animation, @@ -151,16 +155,19 @@ if (utils.isInShellProcess()) { const vec2 SEED = vec2(${Math.random()}, ${Math.random()}); const float SHARD_SCALE = ${settings.get_double('broken-glass-scale')}; const float SHARD_LAYERS = 5; - const float GRAVITY = ${settings.get_double('broken-glass-gravity')}; const float BLOW_FORCE = ${settings.get_double('broken-glass-blow-force')}; const float ACTOR_SCALE = 2.0; const float PADDING = ACTOR_SCALE / 2.0 - 0.5; const vec2 EPICENTER = vec2(${epicenterX}, ${epicenterY}); + const float GRAVITY = ${forOpening ? '-1.0' : '1.0'} * + ${settings.get_double('broken-glass-gravity')}; void main() { cogl_color_out = vec4(0, 0, 0, 0); + float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'}; + // Draw the individual shard layers. for (float i=0; i 0 ? 1: 0; + float dissolve = (shardMap.x - pow(progress+0.1, 2)) > 0 ? 1: 0; cogl_color_out = mix(cogl_color_out, windowColor, dissolve); } } diff --git a/src/EnergizeA.js b/src/EnergizeA.js index cbceeb7..a7ef50d 100644 --- a/src/EnergizeA.js +++ b/src/EnergizeA.js @@ -74,15 +74,23 @@ var EnergizeA = class EnergizeA { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows should neither be scaled nor faded. - static getCloseTransition(actor, settings) { - return {'opacity': {to: 255}, 'scale-x': {to: 1.0}, 'scale-y': {to: 1.0}}; + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows should neither be scaled nor faded. + static tweakTransition(actor, settings, forOpening) { + return { + 'opacity': {from: 255, to: 255, mode: 3}, + 'scale-x': {from: 1.0, to: 1.0, mode: 3}, + 'scale-y': {from: 1.0, to: 1.0, mode: 3} + }; } } @@ -99,7 +107,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); const color = Clutter.Color.from_string(settings.get_string('energize-a-color'))[1]; @@ -144,6 +152,10 @@ if (utils.isInShellProcess()) { // Compute fading window opacity. float windowMask = pow(1.0 - fadeOutProgress, 2.0); + #if ${forOpening ? '1' : '0'} + windowMask = 1.0 - windowMask; + #endif + return vec2(atomMask, windowMask); } diff --git a/src/EnergizeB.js b/src/EnergizeB.js index 4592679..0f0a6b7 100644 --- a/src/EnergizeB.js +++ b/src/EnergizeB.js @@ -74,15 +74,23 @@ var EnergizeB = class EnergizeB { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows should neither be scaled nor faded. - static getCloseTransition(actor, settings) { - return {'opacity': {to: 255}, 'scale-x': {to: 1.0}, 'scale-y': {to: 1.0}}; + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows should neither be scaled nor faded. + static tweakTransition(actor, settings, forOpening) { + return { + 'opacity': {from: 255, to: 255, mode: 3}, + 'scale-x': {from: 1.0, to: 1.0, mode: 3}, + 'scale-y': {from: 1.0, to: 1.0, mode: 3} + }; } } @@ -99,7 +107,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); const color = Clutter.Color.from_string(settings.get_string('energize-b-color'))[1]; @@ -162,6 +170,10 @@ if (utils.isInShellProcess()) { // Compute fading window opacity. float windowMask = pow(1.0 - fadeProgress, 2.0); + #if ${forOpening ? '1' : '0'} + windowMask = 1.0 - windowMask; + #endif + return vec4(showerMask, streakMask, atomMask, windowMask); } diff --git a/src/Fire.js b/src/Fire.js index a04310c..e7640e0 100644 --- a/src/Fire.js +++ b/src/Fire.js @@ -96,15 +96,23 @@ var Fire = class Fire { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows should neither be scaled nor faded. - static getCloseTransition(actor, settings) { - return {'opacity': {to: 255}, 'scale-x': {to: 1.0}, 'scale-y': {to: 1.0}}; + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows should neither be scaled nor faded. + static tweakTransition(actor, settings, forOpening) { + return { + 'opacity': {from: 255, to: 255, mode: 3}, + 'scale-x': {from: 1.0, to: 1.0, mode: 3}, + 'scale-y': {from: 1.0, to: 1.0, mode: 3} + }; } // ----------------------------------------------------------------------- private stuff @@ -210,7 +218,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); // Load the gradient values from the settings. We directly inject the values in the @@ -233,7 +241,7 @@ if (utils.isInShellProcess()) { ${shaderSnippets.edgeMask()} // These may be configurable in the future. - const float EDGE_FADE = 90; + const float EDGE_FADE = 70; const float FADE_WIDTH = 0.1; const float HIDE_TIME = 0.4; const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; @@ -295,6 +303,10 @@ if (utils.isInShellProcess()) { // Fade at window borders. effectMask *= getAbsoluteEdgeMask(edgeFadeWidth); + #if ${forOpening ? '1' : '0'} + windowMask = 1.0 - windowMask; + #endif + return vec2(windowMask, effectMask); } @@ -322,7 +334,7 @@ if (utils.isInShellProcess()) { cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; // Add the fire to the window. - cogl_color_out += fire; + cogl_color_out = mix(cogl_color_out, fire, fire.a); // These are pretty useful for understanding how this works. // cogl_color_out = vec4(vec3(noise), 1); diff --git a/src/Matrix.js b/src/Matrix.js index 678d5ce..329ea42 100644 --- a/src/Matrix.js +++ b/src/Matrix.js @@ -81,19 +81,24 @@ var Matrix = class Matrix { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows should neither be scaled nor faded. - static getCloseTransition(actor, settings) { + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows should not be faded but scaled vertically to allow for some + // overshooting. + static tweakTransition(actor, settings, forOpening) { const yScale = 1.0 + settings.get_double('matrix-overshoot'); return { - 'opacity': {to: 255}, - 'scale-x': {to: 1.0}, - 'scale-y': {from: yScale, to: yScale} + 'opacity': {from: 255, to: 255, mode: 1}, + 'scale-x': {from: 1.0, to: 1.0, mode: 1}, + 'scale-y': {from: yScale, to: yScale, mode: 1} }; } } @@ -111,7 +116,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); // Load the font texture. As the shader is re-created for each window animation, @@ -187,6 +192,10 @@ if (utils.isInShellProcess()) { rainAlpha *= smoothstep(0, 1, clamp(cogl_tex_coord_in[0].y / shorten, 0, 1)); rainAlpha *= smoothstep(0, 1, clamp((1.0 - cogl_tex_coord_in[0].y) / shorten, 0, 1)); + #if ${forOpening ? '1' : '0'} + windowAlpha = 1.0 - windowAlpha; + #endif + return vec2(rainAlpha, windowAlpha); } diff --git a/src/TRexAttack.js b/src/TRexAttack.js index 380c675..640ebb6 100644 --- a/src/TRexAttack.js +++ b/src/TRexAttack.js @@ -78,20 +78,23 @@ var TRexAttack = class TRexAttack { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, we slightly increase the window's scale as part of the - // warp effect. - static getCloseTransition(actor, settings) { - const warp = 0.5 * settings.get_double('claw-scratch-warp'); + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, we slightly increase the window's scale as part of the warp effect. + static tweakTransition(actor, settings, forOpening) { + const warp = 1.0 + 0.5 * settings.get_double('claw-scratch-warp'); return { - 'opacity': {to: 255}, - 'scale-x': {to: 1.0 + warp}, - 'scale-y': {to: 1.0 + warp} + 'opacity': {from: 255, to: 255, mode: 3}, + 'scale-x': {from: forOpening ? warp : 1.0, to: forOpening ? 1.0 : warp, mode: 3}, + 'scale-y': {from: forOpening ? warp : 1.0, to: forOpening ? 1.0 : warp, mode: 3} }; } } @@ -109,7 +112,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); // Load the claw texture. As the shader is re-created for each window animation, @@ -180,11 +183,13 @@ if (utils.isInShellProcess()) { void main() { + float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'}; + // Warp the texture coordinates to create a blow-up effect. vec2 coords = cogl_tex_coord_in[0].st * 2.0 - 1.0; float dist = length(coords); coords = (coords/dist * pow(dist, WARP_INTENSITY)) * 0.5 + 0.5; - coords = mix(cogl_tex_coord_in[0].st, coords, uProgress); + coords = mix(cogl_tex_coord_in[0].st, coords, progress); // Accumulate several random scratches. The color in the scratch map refers to the // relative time when the respective part will become invisible. Therefore we can @@ -198,11 +203,11 @@ if (utils.isInShellProcess()) { // Get the window texture. We shift the texture lookup by the local derivative of // the claw texture in order to mimic some folding distortion. - vec2 offset = vec2(dFdx(scratchMap), dFdy(scratchMap)) * uProgress * 0.5; + vec2 offset = vec2(dFdx(scratchMap), dFdy(scratchMap)) * progress * 0.5; cogl_color_out = texture2D(uTexture, coords + offset); // Add colorful flashes. - float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - uProgress) + 1; + float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - progress) + 1; if (flashIntensity < 0 || flashIntensity >= 1) { flashIntensity = 0; } @@ -211,16 +216,16 @@ if (utils.isInShellProcess()) { flashIntensity *= cogl_color_out.a; // Hide scratched out parts. - cogl_color_out *= (scratchMap > uProgress ? 1 : 0); + cogl_color_out *= (scratchMap > progress ? 1 : 0); vec3 flashColor = vec3(${color.red / 255}, ${color.green / 255}, ${color.blue / 255}) * ${color.alpha / 255}; - cogl_color_out.rgb += flashIntensity * mix(flashColor, vec3(0), uProgress); + cogl_color_out.rgb += flashIntensity * mix(flashColor, vec3(0), progress); // Fade out the remaining shards. - float fade = smoothstep(0, 1, 1 - clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); + float fade = smoothstep(0, 1, 1 - clamp((progress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); cogl_color_out *= fade; // These are pretty useful for understanding how this works. diff --git a/src/TVEffect.js b/src/TVEffect.js index b171caf..979252a 100644 --- a/src/TVEffect.js +++ b/src/TVEffect.js @@ -75,15 +75,23 @@ var TVEffect = class TVEffect { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows are scaled down vertically. - static getCloseTransition(actor, settings) { - return {'opacity': {to: 255}, 'scale-x': {to: 1.0}, 'scale-y': {to: 0.5}}; + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows are scaled down vertically. + static tweakTransition(actor, settings, forOpening) { + return { + 'opacity': {from: 255, to: 255, mode: 3}, + 'scale-x': {from: 1.0, to: 1.0, mode: 3}, + 'scale-y': {from: forOpening ? 0.5 : 1.0, to: forOpening ? 1.0 : 0.5, mode: 3} + }; } } @@ -100,7 +108,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); const color = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1]; @@ -118,13 +126,15 @@ if (utils.isInShellProcess()) { void main() { + float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'}; + // All of these are in [0..1] during the different stages of the animation. // tb refers to the top-bottom animation. // lr refers to the left-right animation. // ff refers to the final fade animation. - float tbProgress = smoothstep(0, 1, clamp(uProgress/TB_TIME, 0, 1)); - float lrProgress = smoothstep(0, 1, clamp((uProgress - LR_DELAY)/LR_TIME, 0, 1)); - float ffProgress = smoothstep(0, 1, clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); + float tbProgress = smoothstep(0, 1, clamp(progress/TB_TIME, 0, 1)); + float lrProgress = smoothstep(0, 1, clamp((progress - LR_DELAY)/LR_TIME, 0, 1)); + float ffProgress = smoothstep(0, 1, clamp((progress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); // This is a top-center-bottom gradient in [0..1..0] float tb = cogl_tex_coord_in[0].t * 2; @@ -147,7 +157,7 @@ if (utils.isInShellProcess()) { ${color.green / 255}, ${color.blue / 255}, 1.0) * windowColor.a; - windowColor.rgb = mix(windowColor.rgb, effectColor.rgb, smoothstep(0, 1, uProgress)); + windowColor.rgb = mix(windowColor.rgb, effectColor.rgb, smoothstep(0, 1, progress)); cogl_color_out = windowColor * mask; diff --git a/src/Wisps.js b/src/Wisps.js index 030339f..4722af9 100644 --- a/src/Wisps.js +++ b/src/Wisps.js @@ -75,15 +75,23 @@ var Wisps = class Wisps { // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. - static createShader(actor, settings) { - return new Shader(settings); + static createShader(actor, settings, forOpening) { + return new Shader(settings, forOpening); } - // This is also called from extension.js. It is used to tweak the ongoing transition of - // the actor - usually windows are faded to transparency and scaled down slightly by - // GNOME Shell. For this effect, windows should be scaled down slightly but not faded. - static getCloseTransition(actor, settings) { - return {'opacity': {to: 255}, 'scale-x': {to: 0.9}, 'scale-y': {to: 0.9}}; + // This is also called from extension.js. It is used to tweak a window's open / close + // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. + // forOpening is set to true if this is called for a window-open transition, for a + // window-close transition it is set to false. The modes can be set to any value from + // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also + // determines how the uProgress uniform value will progress in the shader. + // For this effect, windows should be scaled down slightly but not faded. + static tweakTransition(actor, settings, forOpening) { + return { + 'opacity': {from: 255, to: 255, mode: 3}, + 'scale-x': {from: forOpening ? 0.9 : 1.0, to: forOpening ? 1.0 : 0.9, mode: 3}, + 'scale-y': {from: forOpening ? 0.9 : 1.0, to: forOpening ? 1.0 : 0.9, mode: 3} + }; } } @@ -100,7 +108,7 @@ if (utils.isInShellProcess()) { const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { - _init(settings) { + _init(settings, forOpening) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); const color = Clutter.Color.from_string(settings.get_string('wisps-color'))[1]; @@ -159,6 +167,8 @@ if (utils.isInShellProcess()) { } void main() { + + float progress = ${forOpening ? '1.0-uProgress' : 'uProgress'}; // Get the color of the window. vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st); @@ -167,7 +177,7 @@ if (utils.isInShellProcess()) { ${color.blue / 255}, 1.0) * windowColor.a; // Compute several layers of moving wisps. - vec2 uv = (cogl_tex_coord_in[0].st-0.5) / mix(1.0, 0.5, uProgress) + 0.5; + vec2 uv = (cogl_tex_coord_in[0].st-0.5) / mix(1.0, 0.5, progress) + 0.5; uv /= ${settings.get_double('wisps-scale')}; float wisps = 0; for (int i=0; i Date: Thu, 27 Jan 2022 05:21:15 +0100 Subject: [PATCH 05/15] :beetle: Fix some GNOME 3.36 issues --- extension.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/extension.js b/extension.js index 6cc0fec..37e51dc 100644 --- a/extension.js +++ b/extension.js @@ -92,13 +92,25 @@ class Extension { const xID = window.connect('notify::scale-x', () => { if (window.scale_x > 0) { - clone.window_container.scale_x = window.scale_x; + if (utils.shellVersionIs(3, 36)) { + clone.scale_x = window.scale_x; + } else if (utils.shellVersionIs(3, 38)) { + clone._windowContainer.scale_x = window.scale_x; + } else { + clone.window_container.scale_x = window.scale_x; + } } }); const yID = window.connect('notify::scale-y', () => { if (window.scale_y > 0) { - clone.window_container.scale_y = window.scale_y; + if (utils.shellVersionIs(3, 36)) { + clone.scale_y = window.scale_y; + } else if (utils.shellVersionIs(3, 38)) { + clone._windowContainer.scale_y = window.scale_y; + } else { + clone.window_container.scale_y = window.scale_y; + } } }); From bf95e349da50bcd8f82e1d6528d3c6981cd8e3ae Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:24:32 +0100 Subject: [PATCH 06/15] :beetle: Fix some GNOME 3.36 issues --- extension.js | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/extension.js b/extension.js index 37e51dc..01f4ffe 100644 --- a/extension.js +++ b/extension.js @@ -85,38 +85,34 @@ class Extension { this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME; Workspace.prototype._addWindowClone = function(...params) { - const result = extensionThis._origAddWindowClone.apply(this, params); + const result = extensionThis._origAddWindowClone.apply(this, params); + const realWindow = params[0].get_compositor_private(); - const clone = utils.shellVersionIs(3, 36) ? result[0] : result; - const window = params[0].get_compositor_private(); + let clone; - const xID = window.connect('notify::scale-x', () => { - if (window.scale_x > 0) { - if (utils.shellVersionIs(3, 36)) { - clone.scale_x = window.scale_x; - } else if (utils.shellVersionIs(3, 38)) { - clone._windowContainer.scale_x = window.scale_x; - } else { - clone.window_container.scale_x = window.scale_x; - } + if (utils.shellVersionIs(3, 36)) { + clone = result[0]; + } else if (utils.shellVersionIs(3, 38)) { + clone = result._windowContainer; + } else { + clone = result.window_container; + } + + const xID = realWindow.connect('notify::scale-x', () => { + if (realWindow.scale_x > 0) { + clone.scale_x = realWindow.scale_x; } }); - const yID = window.connect('notify::scale-y', () => { - if (window.scale_y > 0) { - if (utils.shellVersionIs(3, 36)) { - clone.scale_y = window.scale_y; - } else if (utils.shellVersionIs(3, 38)) { - clone._windowContainer.scale_y = window.scale_y; - } else { - clone.window_container.scale_y = window.scale_y; - } + const yID = realWindow.connect('notify::scale-y', () => { + if (realWindow.scale_y > 0) { + clone.scale_y = realWindow.scale_y; } }); - clone.window_container.connect('destroy', () => { - window.disconnect(xID); - window.disconnect(yID); + clone.connect('destroy', () => { + realWindow.disconnect(xID); + realWindow.disconnect(yID); }); // On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone From f5e58b100f389ffad27d4ecc6360f6fc00032f94 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:31:38 +0100 Subject: [PATCH 07/15] :beetle: Fix some GNOME 3.36 issues --- extension.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extension.js b/extension.js index 01f4ffe..fd393f2 100644 --- a/extension.js +++ b/extension.js @@ -85,17 +85,19 @@ class Extension { this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME; Workspace.prototype._addWindowClone = function(...params) { - const result = extensionThis._origAddWindowClone.apply(this, params); - const realWindow = params[0].get_compositor_private(); + const result = extensionThis._origAddWindowClone.apply(this, params); - let clone; + let realWindow, clone; if (utils.shellVersionIs(3, 36)) { - clone = result[0]; + clone = result[0]; + realWindow = clone.realWindow; } else if (utils.shellVersionIs(3, 38)) { - clone = result._windowContainer; + clone = result._windowContainer; + realWindow = params[0].get_compositor_private(); } else { - clone = result.window_container; + clone = result.window_container; + realWindow = params[0].get_compositor_private(); } const xID = realWindow.connect('notify::scale-x', () => { From c107083cde73ecb9fb3aa9bafde76ee3f3405a7d Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:46:10 +0100 Subject: [PATCH 08/15] :beetle: Fix some GNOME 3.36 issues --- docs/how-to-create-new-effects.md | 9 ++++---- extension.js | 37 +++++++++++++++++-------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/docs/how-to-create-new-effects.md b/docs/how-to-create-new-effects.md index 0d10d04..730c131 100644 --- a/docs/how-to-create-new-effects.md +++ b/docs/how-to-create-new-effects.md @@ -152,10 +152,11 @@ var SimpleFade = class SimpleFade { // This is also called from extension.js. It is used to tweak a window's open / close // transitions - usually windows are faded in / out and scaled up / down by GNOME Shell. - // forOpening is set to true if this is called for a window-open transition, for a - // window-close transition it is set to false. The modes can be set to any value from - // here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. This also - // determines how the uProgress uniform value will progress in the shader. + // The parameter 'forOpening' is set to true if this is called for a window-open + // transition, for a window-close transition it is set to false. The modes can be set to + // any value from here: https://gjs-docs.gnome.org/clutter8~8_api/clutter.animationmode. + // This also determines how the uProgress uniform value will progress in the shader. + // Tweaking the actor's scale during the transition only works properly for GNOME 3.38+. // For this effect, windows should neither be scaled nor faded. static tweakTransition(actor, settings, forOpening) { return { diff --git a/extension.js b/extension.js index fd393f2..5115b3e 100644 --- a/extension.js +++ b/extension.js @@ -100,22 +100,26 @@ class Extension { realWindow = params[0].get_compositor_private(); } - const xID = realWindow.connect('notify::scale-x', () => { - if (realWindow.scale_x > 0) { - clone.scale_x = realWindow.scale_x; - } - }); + // Syncing the real window's scale with the scale of its clone only works on GNOME + // Shell 3.38+. So effects cannot scale windows in the overview of GNOME 3.36... + if (utils.shellVersionIsAtLeast(3, 38)) { + const xID = realWindow.connect('notify::scale-x', () => { + if (realWindow.scale_x > 0) { + clone.scale_x = realWindow.scale_x; + } + }); - const yID = realWindow.connect('notify::scale-y', () => { - if (realWindow.scale_y > 0) { - clone.scale_y = realWindow.scale_y; - } - }); + const yID = realWindow.connect('notify::scale-y', () => { + if (realWindow.scale_y > 0) { + clone.scale_y = realWindow.scale_y; + } + }); - clone.connect('destroy', () => { - realWindow.disconnect(xID); - realWindow.disconnect(yID); - }); + clone.connect('destroy', () => { + realWindow.disconnect(xID); + realWindow.disconnect(yID); + }); + } // On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone // but not _doRemoveWindow. The latter is required to trigger the repositioning of @@ -155,9 +159,8 @@ class Extension { - // This class is only available in GNOME Shell 3.38+. So no transition tweaking in - // GNOME Shell 3.36, but this is not used by any effect available there anyways for - // now... + // This class is only available in GNOME Shell 3.38+. So no overlay-hiding on + // GNOME Shell 3.36 for now. if (WindowPreview) { this._origDeleteAll = WindowPreview.prototype._deleteAll; this._origRestack = WindowPreview.prototype._restack; From 38458e4a4cba8aedc35e230c9bfb204f24083988 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:47:59 +0100 Subject: [PATCH 09/15] :beetle: Fix some GNOME 3.36 and 3.38 issues --- prefs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prefs.js b/prefs.js index 068bbfc..af5bc0d 100644 --- a/prefs.js +++ b/prefs.js @@ -360,11 +360,11 @@ var PreferencesDialog = class PreferencesDialog { if (utils.isGTK4()) { window.set_child(box); + window.show(); } else { window.add(box); + window.show_all(); } - - window.show(); }); } }); From a8d75de4c949f1f932f59ea85e36bf292d4ff9b8 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:52:49 +0100 Subject: [PATCH 10/15] :lipstick: Only add headerbar on GTK4 --- prefs.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prefs.js b/prefs.js index af5bc0d..e206641 100644 --- a/prefs.js +++ b/prefs.js @@ -338,8 +338,10 @@ var PreferencesDialog = class PreferencesDialog { }); // Add a header bar to the window. - const header = Gtk.HeaderBar.new(); - window.set_titlebar(header); + if (utils.isGTK4()) { + const header = Gtk.HeaderBar.new(); + window.set_titlebar(header); + } const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, From 1b79cd4aa49c15c2179d84dc6c17dcb89b68d1c2 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:53:13 +0100 Subject: [PATCH 11/15] :wrench: Remove debug print --- extension.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/extension.js b/extension.js index 5115b3e..eab1a01 100644 --- a/extension.js +++ b/extension.js @@ -138,15 +138,12 @@ class Extension { let actor = metaWin.get_compositor_private(); if (Main.overview.visible && !Main.overview.closing) { - utils.debug('window-created: setup effect on show'); const id = actor.connect('show', () => { extensionThis._setupEffect(actor, true); actor.disconnect(id); }); } else { - - utils.debug('window-created: setup effect on ease'); const orig = actor.ease; actor.ease = function(...params) { orig.apply(actor, params); From 5ff26324995bc7e3c185e047fa1ea67cad3c77e2 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 06:09:18 +0100 Subject: [PATCH 12/15] :wrench: Reduce default energize b time --- schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml index a889297..826c5ca 100644 --- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml +++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml @@ -272,7 +272,7 @@ - 2000 + 1500 Energize B Animation Time The time the Energize B effect takes. From 68712fb842673e342ee3515e9d6cbcb8e13ad2d5 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 06:16:07 +0100 Subject: [PATCH 13/15] :memo: Add changelog entry --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index cb33ec1..18b212e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,7 @@ #### New Features +* **Effects for Window Opening.** You can now select effects which are applied on newly opened windows! I expect that there are some bugs left, so please [report any issue you find](https://github.com/Schneegans/Burn-My-Windows/issues)! * **New Effect: Broken Glass.** Shatter your windows into a shower sharp shards! This effect can be configured so that the shards fly away from your mouse pointer position! * The effect-configuration pages of the preferences dialog now have **Preview Buttons** which con be used to, well, preview the current settings (without having to enable the effect first). From acff1da6c2426c02d396bedf97e306ae407f7f21 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 06:53:30 +0100 Subject: [PATCH 14/15] :beetle: Prevent registering the same type twice --- prefs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/prefs.js b/prefs.js index e206641..ffe9c17 100644 --- a/prefs.js +++ b/prefs.js @@ -304,6 +304,7 @@ var PreferencesDialog = class PreferencesDialog { // Each effect page is based on a template widget. This template contains the title // and the preview button. // clang-format off + if (GObject.type_from_name('BurnMyWindowsEffectPage') == null) { BurnMyWindowsEffectPage = GObject.registerClass({ GTypeName: 'BurnMyWindowsEffectPage', @@ -370,7 +371,8 @@ var PreferencesDialog = class PreferencesDialog { }); } }); - // clang-format on + // clang-format on + } } } From e2d9015490d694bf9d4b3e4bc8e8d7cacd7a6a10 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 20:59:02 +0100 Subject: [PATCH 15/15] :memo: Add plenty of comments --- extension.js | 250 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 151 insertions(+), 99 deletions(-) diff --git a/extension.js b/extension.js index eab1a01..dc33e09 100644 --- a/extension.js +++ b/extension.js @@ -67,8 +67,8 @@ class Extension { // Store a reference to the settings object. this._settings = ExtensionUtils.getSettings(); - // This will store an item of ALL_EFFECTS which was used the last time a window was - // opened / closed. + // This will store an item of ALL_EFFECTS array which was used the last time a window + // was opened / closed. this._currentEffect = 0; // We will use extensionThis to refer to the extension inside the patched methods. @@ -84,9 +84,52 @@ class Extension { this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME; this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME; + + // ------------------------------------------------ patching the window-open animation + + // Here we add an effect to the window-open animation. This is done whenever a new + // window is created. + this._windowCreatedConnection = + global.display.connect('window-created', (d, metaWin) => { + let actor = metaWin.get_compositor_private(); + + // If we are currently in the overview, we add the effect to the original window + // actor. The window preview in the overview is basically a Clutter.Clone which + // shows the original window. + if (Main.overview.visible && !Main.overview.closing) { + const id = actor.connect('show', () => { + extensionThis._setupEffect(actor, true); + actor.disconnect(id); + }); + + } + // If a window is created outside of the overview, the transitions are set up in + // the async _mapWindow of the WindowManager which can defer the actual showing + // of the window significantly, especially when currently leaving the overview: + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1449 + // AFAIK, overriding this method is not possible as it's called by a signal to + // which it is bound via the bind() method. To tweak the async transition + // anyways, we override the actors ease() method once - the next time it will be + // called by the _mapWindow(), we will intercept it! + else { + const orig = actor.ease; + actor.ease = function(...params) { + orig.apply(actor, params); + actor.ease = orig; + + extensionThis._setupEffect(actor, true); + }; + } + }); + + // Some of the effects require that the window's actor is enlarged to provide a bigger + // canvas to draw the effects. Outside the overview we can simply increase the scale + // of the actor. However, if we are in the overview, we have to enlarge the clone of + // the window as well. Workspace.prototype._addWindowClone = function(...params) { const result = extensionThis._origAddWindowClone.apply(this, params); + // The parameters of this method changed a bit through the versions... let realWindow, clone; if (utils.shellVersionIs(3, 36)) { @@ -121,6 +164,7 @@ class Extension { }); } + // This is actually need for the window-close animation on GNOME Shell 3.36. // On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone // but not _doRemoveWindow. The latter is required to trigger the repositioning of // the overview window layout. Therefore we call this method in addition. @@ -133,83 +177,28 @@ class Extension { return result; }; - this._windowCreatedConnection = - global.display.connect('window-created', (d, metaWin) => { - let actor = metaWin.get_compositor_private(); - if (Main.overview.visible && !Main.overview.closing) { - const id = actor.connect('show', () => { - extensionThis._setupEffect(actor, true); - actor.disconnect(id); - }); + // ----------------------------------------------- patching the window-close animation - } else { - const orig = actor.ease; - actor.ease = function(...params) { - orig.apply(actor, params); - actor.ease = orig; + // The signal handler below is all which is required outside of the overview. All + // other hacks further below are just required to defer the window-hiding in the + // overview until the effect is finished. - extensionThis._setupEffect(actor, true); - }; - } - }); + // The close animation is set up in WindowManager's _destroyWindow: + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1549 + // As we cannot monkey-patch the _destroyWindow itself, we connect to the 'destroy' + // signal of the window manager and tweak the animation to our needs. + this._destroyConnection = global.window_manager.connect('destroy', (wm, actor) => { + this._setupEffect(actor, false); + }); - - - // This class is only available in GNOME Shell 3.38+. So no overlay-hiding on - // GNOME Shell 3.36 for now. - if (WindowPreview) { - this._origDeleteAll = WindowPreview.prototype._deleteAll; - this._origRestack = WindowPreview.prototype._restack; - this._origInit = WindowPreview.prototype._init; - - // This is required, else WindowPreview's _restack() which is called by the - // "this.overlayEnabled = false", sometimes tries to access an already delete - // WindowPreview. - WindowPreview.prototype._restack = function() { - if (!this._closeRequested) { - // Call the original method. - extensionThis._origRestack.apply(this); - } - }; - - WindowPreview.prototype._init = function(...params) { - // Call the original method. - extensionThis._origInit.apply(this, params); - - const connectionID = this.metaWindow.connect('unmanaged', () => { - if (this.window_container) { - // Hide the window's icon, name, and close button. - this.overlayEnabled = false; - this._icon.visible = false; - } - }); - - // Make sure to not call the callback above if the Meta.Window was not unmanaged - // before leaving the overview. - this.connect('destroy', () => { - this.metaWindow.disconnect(connectionID); - }); - }; - - // The _deleteAll is called when the user clicks the X in the overview. We should - // not attempt to close windows twice. Due to the animation in the overview, the - // close button can be clicked twice which normally would lead to a crash. - WindowPreview.prototype._deleteAll = function() { - if (!this._closeRequested) { - extensionThis._origDeleteAll.apply(this); - } - }; - } - - // These three method overrides are mega-hacky! They are only required to make the - // fire animation work in the overview. Usually, windows are not faded when closed - // from the overview (why?). With these overrides we make sure that they are actually - // faded out. To do this, _windowRemoved and _doRemoveWindow now check whether there - // is a transition ongoing (via extensionThis._shouldDestroy). If that's the case, - // these methods do nothing. Are the actors removed in the end? I hope so. The - // _destroyWindow of the WindowManager sets the transitions up and should take care of - // removing the actors at the end of the transitions. + // These three method overrides are mega-hacky! Usually, windows are not faded when + // closed from the overview (why?). With these overrides we make sure that they are + // actually faded out. To do this, _windowRemoved and _doRemoveWindow now check + // whether there is a transition ongoing (via extensionThis._shouldDestroy). If that's + // the case, these methods do nothing. Are the actors removed in the end? I hope so. + // The _destroyWindow of the WindowManager sets the transitions up and should take + // care of removing the actors at the end of the transitions. // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1299 Workspace.prototype._windowRemoved = function(ws, metaWin) { if (extensionThis._shouldDestroy(this, metaWin)) { @@ -238,13 +227,54 @@ class Extension { return extensionThis._origShouldAnimateActor.apply(this, params); }; - // The close animation is set up in WindowManager's _destroyWindow: - // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1549 - // As we cannot monkey-patch the _destroyWindow itself, we connect to the 'destroy' - // signal of the window manager and tweak the animation to our needs. - this._destroyConnection = global.window_manager.connect('destroy', (wm, actor) => { - this._setupEffect(actor, false); - }); + // With the code below, we hide the window-overlay (icon, label, close button) in the + // overview once the close-animation is running. As the WindowPreview class is only + // available on GNOME 3.38 and beyond, we cannot hide the overlay on GNOME 3.36. + if (WindowPreview) { + + // We will monkey-patch these methods. + this._origDeleteAll = WindowPreview.prototype._deleteAll; + this._origRestack = WindowPreview.prototype._restack; + this._origInit = WindowPreview.prototype._init; + + // Whenever a WindowPreview is created, we connect to the referenced Meta.Window's + // 'unmanaged' signal to hide the overlay. + WindowPreview.prototype._init = function(...params) { + extensionThis._origInit.apply(this, params); + + // Hide the window's icon, name, and close button. + const connectionID = this.metaWindow.connect('unmanaged', () => { + if (this.window_container) { + this.overlayEnabled = false; + this._icon.visible = false; + } + }); + + // Make sure to not call the callback above if the Meta.Window was not unmanaged + // before leaving the overview. + this.connect('destroy', () => { + this.metaWindow.disconnect(connectionID); + }); + }; + + // The _deleteAll is called when the user clicks the X in the overview. We should + // not attempt to close windows twice. Due to the animation in the overview, the + // close button can be clicked twice which normally would lead to a crash. + WindowPreview.prototype._deleteAll = function() { + if (!this._closeRequested) { + extensionThis._origDeleteAll.apply(this); + } + }; + + // This is required, else WindowPreview's _restack() which is called by the + // "this.overlayEnabled = false", sometimes tries to access an already delete + // WindowPreview. + WindowPreview.prototype._restack = function() { + if (!this._closeRequested) { + extensionThis._origRestack.apply(this); + } + }; + } } // This function could be called after the extension is uninstalled, disabled in GNOME @@ -277,7 +307,13 @@ class Extension { // ----------------------------------------------------------------------- private stuff + // This method adds one of the configured effects to the given actor. If forOpening is + // set to true, a effect from the enabled window-open animations is chosen, else an + // enabled window-close animation is used. This will also tweak the transitions of the + // given actor (e.g. scale it up if required). _setupEffect(actor, forOpening) { + + // Only add effects to normal windows and dialog windows. const isNormalWindow = actor.meta_window.window_type == Meta.WindowType.NORMAL; const isDialogWindow = actor.meta_window.window_type == Meta.WindowType.MODAL_DIALOG || @@ -290,7 +326,7 @@ class Extension { // We do nothing if a dialog got closed and we should not burn them. const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs'); - // If an effect is to be previewed, we have to affect dialogs es well. This is + // If an effect is to be previewed however, we have to affect dialogs es well. This is // because the preview window is a dialog window... const action = forOpening ? 'open' : 'close'; const previewNick = this._settings.get_string(action + '-preview-effect'); @@ -302,8 +338,10 @@ class Extension { // ------------------------------------------------------------------ choose an effect + // Now we chose a random effect from all enabled effects. this._currentEffect = null; + // First we check if an effect is to be previewed. if (previewNick != '') { this._currentEffect = ALL_EFFECTS.find(Effect => { return Effect.getNick() == previewNick; @@ -312,10 +350,11 @@ class Extension { // Only preview the effect once. this._settings.set_string(action + '-preview-effect', ''); - } else { + } + // Else we choose a random effect from all enabled effects. + else { - // Else we choose a random effect from all enabled effects. Therefore, we first - // create a list of all currently enabled effects. + // Therefore, we first create a list of all currently enabled effects. const enabled = ALL_EFFECTS.filter(Effect => { return this._settings.get_boolean(`${Effect.getNick()}-${action}-effect`); }); @@ -334,24 +373,27 @@ class Extension { // ----------------------------------------------------------- tweak actor transitions - // This is used to tweak the ongoing transitions of a window actor. This is either the - // actual actor of the Meta.Window or a clone in the overview. Usually windows are - // faded in / out scaled up / down slightly by GNOME Shell. Here, we allow - // modifications to this behavior by the effects. The given config object is created - // by the effect's tweakTransition() method. + // The following is used to tweak the ongoing transitions of a window actor. Usually + // windows are faded in / out scaled up / down slightly by GNOME Shell. Here, we allow + // modifications to this behavior by the effects. const config = this._currentEffect.tweakTransition(actor, this._settings, forOpening); const duration = this._settings.get_int(this._currentEffect.getNick() + '-animation-time'); + // All animations are relative to the window's center. actor.set_pivot_point(0.5, 0.5); + // This goes through all properties given in the config object and tweaks any ongoing + // transitions accordingly. If there is no ongoing transition for a given property, a + // new one is set up. for (const property in config) { - const from = config[property].from; - const to = config[property].to; - const mode = config[property].mode; - let transition = actor.get_transition(property); + // If there is currently no ongoing transition, we create a new one. Clutter does + // not like to create transitions with the same start and end value - however, we + // need at least one transition for our progress value in the shader. So we trick + // Clutter by creating an arbitrary transition first and then modifying the start + // and end values according to our config object. if (!transition) { actor.set_property(property, 0); actor.save_easing_state(); @@ -359,20 +401,27 @@ class Extension { actor.set_property(property, 1); actor.restore_easing_state(); + // Now there should be a transition! transition = actor.get_transition(property); } + // For some reason, there are rare cases, where no transition is set up. We do not + // try to continue here... if (!transition) { + utils.debug('Failed to set up transitions.'); this._fixAnimationTimes(isDialogWindow, forOpening, null); return; } + // Tweak the transition according to the config object. transition.set_duration(duration); - transition.set_to(to); - transition.set_from(from); - transition.set_progress_mode(mode); + transition.set_to(config[property].to); + transition.set_from(config[property].from); + transition.set_progress_mode(config[property].mode); } + // There should always be a scale-y transitions. Once this is finished, we restore the + // original actor size. const transition = actor.get_transition('scale-y'); transition.connect('completed', () => { actor.scale_x = 1.0; @@ -381,10 +430,11 @@ class Extension { // -------------------------------------------------------------------- add the shader - // Add a cool shader to our window actor! + // Now add a cool shader to our window actor! const shader = this._currentEffect.createShader(actor, this._settings, forOpening); if (shader) { + // First remove any old effect. actor.remove_effect_by_name(`burn-my-windows-effect`); actor.add_effect_with_name(`burn-my-windows-effect`, shader); @@ -401,6 +451,8 @@ class Extension { }); } + // Finally, ensure that all animation times are set properly so that other extensions + // may guess how long it will take until windows are gone :) this._fixAnimationTimes(isDialogWindow, forOpening, duration); }