diff --git a/docs/changelog.md b/docs/changelog.md index a8bd78d..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). @@ -13,6 +14,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/docs/how-to-create-new-effects.md b/docs/how-to-create-new-effects.md index c1a14f5..730c131 100644 --- a/docs/how-to-create-new-effects.md +++ b/docs/how-to-create-new-effects.md @@ -150,12 +150,20 @@ 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. + // 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 { + '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..dc33e09 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 { @@ -67,111 +67,138 @@ 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 - // closed. - this._effect = 0; + // 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 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; - } - }); - // We will use extensionThis to refer to the extension inside the patched methods of - // the WorkspacesView. - const extensionThis = this; + // ------------------------------------------------ patching the window-open animation - // 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... - if (WindowPreview) { - this._origDeleteAll = WindowPreview.prototype._deleteAll; - this._origRestack = WindowPreview.prototype._restack; - this._origInit = WindowPreview.prototype._init; + // 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(); - // 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); - } - }; + // 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); + }); - // 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); + } + // 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; - // 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); + extensionThis._setupEffect(actor, true); + }; } }); - // 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); + // 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)) { + clone = result[0]; + realWindow = clone.realWindow; + } else if (utils.shellVersionIs(3, 38)) { + clone = result._windowContainer; + realWindow = params[0].get_compositor_private(); + } else { + clone = result.window_container; + realWindow = params[0].get_compositor_private(); + } + + // 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; + } }); - }; - // 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); - } - }; - } + const yID = realWindow.connect('notify::scale-y', () => { + if (realWindow.scale_y > 0) { + clone.scale_y = realWindow.scale_y; + } + }); - // 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', () => { + realWindow.disconnect(xID); + realWindow.disconnect(yID); + }); + } + + // 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. + // 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 [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 - // 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. + return result; + }; + + + // ----------------------------------------------- patching the window-close animation + + // 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. + + // 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); + }); + + // 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)) { @@ -200,98 +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) => { - // 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; - } + // 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 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; + // We will monkey-patch these methods. + this._origDeleteAll = WindowPreview.prototype._deleteAll; + this._origRestack = WindowPreview.prototype._restack; + this._origInit = WindowPreview.prototype._init; - // 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'); + // 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); - 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; + // 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; + } }); - // 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`); + // 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); }); + }; - // And then choose a random effect. - if (enabled.length > 0) { - this._effect = enabled[Math.floor(Math.random() * enabled.length)]; + // 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); } - } + }; - // 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 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 @@ -301,12 +284,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 +307,174 @@ 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 || + 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 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'); + + if (isDialogWindow && !shouldDestroyDialogs && previewNick == '') { + this._fixAnimationTimes(isDialogWindow, forOpening, null); + return; + } + + // ------------------------------------------------------------------ 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; + }); + + // Only preview the effect once. + this._settings.set_string(action + '-preview-effect', ''); + + } + // Else we choose a random effect from all enabled effects. + else { + + // 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 + + // 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) { + 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(); + actor.set_easing_duration(1000); + 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(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; + actor.scale_y = 1.0; + }); + + // -------------------------------------------------------------------- add the shader + + // 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); + + // 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`); + }); + } + + // 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); + } + + // 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 +492,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/prefs.js b/prefs.js index 2182911..ffe9c17 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'); @@ -282,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', @@ -299,8 +322,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()}`, @@ -311,6 +338,12 @@ var PreferencesDialog = class PreferencesDialog { this._button.get_toplevel() }); + // Add a header bar to the window. + if (utils.isGTK4()) { + const header = Gtk.HeaderBar.new(); + window.set_titlebar(header); + } + const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, valign: Gtk.Align.CENTER, @@ -330,15 +363,16 @@ var PreferencesDialog = class PreferencesDialog { if (utils.isGTK4()) { window.set_child(box); + window.show(); } else { window.add(box); + window.show_all(); } - - window.show(); }); } }); - // clang-format on + // clang-format on + } } } 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..826c5ca 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,8 +105,14 @@ Use the matrix effect for window closing. + + false + Matrix Open Effect + Use the matrix effect for window opening. + + - 3000 + 1500 Matrix Animation Time The time the matrix effect takes. @@ -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,8 +185,14 @@ Use the trex effect for window closing. + + false + T-Rex Open Effect + Use the trex effect for window opening. + + - 1500 + 1000 T-Rex Animation Time The time the trex effect takes. @@ -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,8 +265,14 @@ Use the Energize B effect for window closing. + + false + Energize B Open Effect + Use the Energize B effect for window opening. + + - 2000 + 1500 Energize B Animation Time The time the Energize B effect takes. @@ -257,8 +299,14 @@ Use the wisps effect for window closing. + + false + Wisps Open Effect + Use the wisps effect for window opening. + + - 2000 + 1500 Wisps Animation Time The time the wisps effect takes. @@ -285,8 +333,14 @@ Use the broken glass effect for window closing. + + false + Broken Glass Open Effect + Use the broken glass effect for window opening. + + - 1500 + 1000 Broken Glass Animation Time The time the broken glass effect takes. 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