diff --git a/extension.js b/extension.js index b96262f..493caa8 100644 --- a/extension.js +++ b/extension.js @@ -15,6 +15,10 @@ const {Clutter} = imports.gi; +const Workspace = imports.ui.workspace.Workspace; +const WindowManager = imports.ui.windowManager.WindowManager; +const WINDOW_REPOSITIONING_DELAY = imports.ui.workspace.WINDOW_REPOSITIONING_DELAY; + const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); const utils = Me.imports.utils; @@ -23,8 +27,6 @@ const utils = Me.imports.utils; ////////////////////////////////////////////////////////////////////////////////////////// class Extension { - // The constructor is called once when the extension is loaded, not enabled. - constructor() {} // ------------------------------------------------------------------------ public stuff @@ -32,11 +34,40 @@ class Extension { // from GNOME Tweaks, when you log in or when the screen is unlocked. enable() { - // imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = 2000; + imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = 2000; // Store a reference to the settings object. // this._settings = ExtensionUtils.getSettings(); + // We will monkey-patch these three methods. Let's store the original ones. + this._origWindowRemoved = Workspace.prototype._windowRemoved; + this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow; + this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor; + + // We will use extensionThis to refer to the extension inside the patched methods of + // the WorkspacesView. + const extensionThis = this; + + Workspace.prototype._windowRemoved = function(ws, metaWin) { + if (extensionThis._shouldDestroy(this, metaWin)) { + extensionThis._origWindowRemoved.apply(this, [ws, metaWin]); + } + }; + + Workspace.prototype._doRemoveWindow = function(metaWin) { + if (extensionThis._shouldDestroy(this, metaWin)) { + extensionThis._origDoRemoveWindow.apply(this, [metaWin]); + } + }; + + WindowManager.prototype._shouldAnimateActor = function(actor, types) { + if ((new Error()).stack.split('\n')[1].includes('_destroyWindow@')) { + return true; + } + return extensionThis._origShouldAnimateActor.apply(this, [actor, types]); + }; + + this._destroyConnection = global.window_manager.connect('destroy', (wm, actor) => { const tweakTransition = (property, value) => { const transition = actor.get_transition(property); @@ -54,18 +85,105 @@ class Extension { shader_type: Clutter.ShaderType.FRAGMENT_SHADER, }); + // https://www.shadertoy.com/view/3tcBzH shader.set_shader_source(` uniform sampler2D texture; uniform float progress; + uniform float time; + uniform int sizeX; + uniform int sizeY; - const float BLEND_RANGE = 0.1; + const float EDGE_BLEND = 70; + const float BURN_RANGE = 0.1; + const float BURN_TIME = 0.2; + const vec2 FIRE_SCALE = vec2(400, 600); + const float FIRE_SPEED = 0.5; + + float rand(vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); + } + + float hermite(float t) { + return t * t * (3.0 - 2.0 * t); + } + + float noiseImpl(vec2 co, float frequency) { + vec2 v = vec2(co.x * frequency, co.y * frequency); + + float ix1 = floor(v.x); + float iy1 = floor(v.y); + float ix2 = floor(v.x + 1.0); + float iy2 = floor(v.y + 1.0); + + float fx = hermite(fract(v.x)); + float fy = hermite(fract(v.y)); + + float fade1 = mix(rand(vec2(ix1, iy1)), rand(vec2(ix2, iy1)), fx); + float fade2 = mix(rand(vec2(ix1, iy2)), rand(vec2(ix2, iy2)), fx); + + return mix(fade1, fade2, fy); + } + + float pNoise(vec2 co, float freq, int steps, float persistence) { + float value = 0.0; + float ampl = 1.0; + float sum = 0.0; + for(int i=0 ; i BURN_TIME) { + mask *= mix(1, 1-t, afterBurnProgress) * pow(1-afterBurnProgress, 2); + } + + vec2 pos = cogl_tex_coord_in[0].st * vec2(sizeX, sizeY); + mask *= clamp(pos.x / EDGE_BLEND, 0, 1); + mask *= clamp(pos.y / EDGE_BLEND, 0, 1); + mask *= clamp((sizeX - pos.x) / EDGE_BLEND, 0, 1); + mask *= clamp((sizeY - pos.y) / EDGE_BLEND, 0, 1); + + vec2 firePos = pos / FIRE_SCALE; + firePos.y += time * FIRE_SPEED; + + float noise = pNoise(firePos, 10.0, 5, 0.5); + + vec4 color = getFireColor(noise*mask); + color.rgb *= color.a; + + // cogl_color_out = vec4(mask); + cogl_color_out += color; } `); @@ -74,6 +192,9 @@ class Extension { const transition = actor.get_transition('opacity'); transition.connect('new-frame', (t) => { shader.set_uniform_value('progress', t.get_progress()); + shader.set_uniform_value('time', 0.001 * t.get_elapsed_time()); + shader.set_uniform_value('sizeX', actor.width); + shader.set_uniform_value('sizeY', actor.height); }); }); } @@ -85,10 +206,28 @@ class Extension { // Restore the original behavior. global.window_manager.disconnect(this._destroyConnection); + Workspace.prototype._windowRemoved = this._origWindowRemoved; + Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow; + WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor; + // this._settings = null; } // ----------------------------------------------------------------------- private stuff + + _shouldDestroy(workspace, metaWindow) { + const index = workspace._lookupIndex(metaWindow); + if (index == -1) { + return true; + } + + const actor = workspace._windows[index]._windowActor; + if (!actor.get_transition('opacity')) { + return true; + } + + return false; + } } // This function is called once when the extension is loaded, not enabled.