From 6b218d049fc1ac951ab35ce47b5d466cfb525213 Mon Sep 17 00:00:00 2001 From: Simon Schneegans Date: Thu, 27 Jan 2022 05:07:30 +0100 Subject: [PATCH] :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