diff --git a/extension.js b/extension.js index 327c67e..8fbe62d 100644 --- a/extension.js +++ b/extension.js @@ -61,6 +61,7 @@ class Extension { new Me.imports.src.Matrix.Matrix(), new Me.imports.src.Pixelate.Pixelate(), new Me.imports.src.PixelWheel.PixelWheel(), + new Me.imports.src.PixelWipe.PixelWipe(), new Me.imports.src.SnapOfDisintegration.SnapOfDisintegration(), new Me.imports.src.TRexAttack.TRexAttack(), new Me.imports.src.TVEffect.TVEffect(), diff --git a/prefs.js b/prefs.js index 7fb21d1..7e0b499 100644 --- a/prefs.js +++ b/prefs.js @@ -57,6 +57,7 @@ var PreferencesDialog = class PreferencesDialog { new Me.imports.src.Matrix.Matrix(), new Me.imports.src.Pixelate.Pixelate(), new Me.imports.src.PixelWheel.PixelWheel(), + new Me.imports.src.PixelWipe.PixelWipe(), new Me.imports.src.SnapOfDisintegration.SnapOfDisintegration(), new Me.imports.src.TRexAttack.TRexAttack(), new Me.imports.src.TVEffect.TVEffect(), diff --git a/resources/shaders/pixel-wheel.frag b/resources/shaders/pixel-wheel.frag index b48ac03..3cbeb5a 100644 --- a/resources/shaders/pixel-wheel.frag +++ b/resources/shaders/pixel-wheel.frag @@ -37,7 +37,8 @@ void main() { } // Progressively hide the spokes. - if (mod(angle * uSpokeCount, 1.0) < progress) { + float threshold = mod(angle * uSpokeCount, 1.0); + if (progress > threshold) { oColor.a = 0.0; } diff --git a/resources/shaders/pixel-wipe.frag b/resources/shaders/pixel-wipe.frag new file mode 100644 index 0000000..1f2a8f5 --- /dev/null +++ b/resources/shaders/pixel-wipe.frag @@ -0,0 +1,51 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +// The content from common.glsl is automatically prepended to each shader effect. + +uniform float uPixelSize; +uniform vec2 uStartPos; + +const float FADE_WIDTH = 1.0; // Width of the transition. + +void main() { + + // Now we compute a 2D gradient in [0..1] which covers the entire window. The dark + // regions will be burned first, the bright regions in the end. We mix a radial gradient + // with some noise. The center of the radial gradient is positioned at uStartPos. + float circle = length(iTexCoord - uStartPos); + + float progress = easeOutQuad(uProgress); + progress = ((1.0 - progress) * (1.0 + FADE_WIDTH) - 1.0 + circle) / FADE_WIDTH; + progress = smoothstep(0.0, 1.0, progress); + + progress = uForOpening ? progress : 1.0 - progress; + + // The current level of pixelation increases with the progress. + float pixelSize = ceil(uPixelSize * progress + 1.0); + vec2 pixelGrid = vec2(pixelSize) / uSize; + vec2 texcoord = iTexCoord.st - mod(iTexCoord.st, pixelGrid) + pixelGrid * 0.5; + vec4 oColor = getInputColor(texcoord); + + // Hide pixels in the transition zone. + float random = simplex2DFractal(texcoord * uSize / 20.0) * 1.5 - 0.25; + if (progress > random) { + oColor.a *= max(0.0, 1.0 - (progress - random) * 10.0); + } + + // These are pretty useful for understanding how this works. + // oColor = vec4(progress, 0.0, 0.0, 1.0); + // oColor = vec4(random, 0.0, 0.0, 1.0); + + setOutputColor(oColor); +} \ No newline at end of file diff --git a/resources/shaders/pixelate.frag b/resources/shaders/pixelate.frag index 12e4557..5ef05ac 100644 --- a/resources/shaders/pixelate.frag +++ b/resources/shaders/pixelate.frag @@ -27,9 +27,9 @@ void main() { vec4 oColor = getInputColor(texcoord); // Hide selected pixels based on some random noise. - float random = simplex2DFractal(texcoord * uNoise * uSize / 1000.0); - if (random * 1.5 - 0.25 < progress) { - oColor.a = 0.0; + float random = simplex2DFractal(texcoord * uNoise * uSize / 1000.0) * 1.5 - 0.25; + if (progress > random) { + oColor.a *= max(0.0, 1.0 - (progress - random) * 20.0); } setOutputColor(oColor); diff --git a/resources/ui/gtk3/PixelWipe.ui b/resources/ui/gtk3/PixelWipe.ui new file mode 100644 index 0000000..f3830f2 --- /dev/null +++ b/resources/ui/gtk3/PixelWipe.ui @@ -0,0 +1,143 @@ + + + + + 5000 + 100 + 1 + 100 + + + + 360 + 0 + 1 + 10 + + + + 50 + 1 + 1 + 10 + + + + vertical + + + + + + none + + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + pixel-wipe-animation-time + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + Pixel Size + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + pixel-wipe-pixel-size + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4/PixelWipe.ui b/resources/ui/gtk4/PixelWipe.ui new file mode 100644 index 0000000..c7fca0b --- /dev/null +++ b/resources/ui/gtk4/PixelWipe.ui @@ -0,0 +1,117 @@ + + + + + vertical + + + + + + none + 1 + + + + + 0 + + + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 5000 + 100 + 1 + 100 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Pixel Size + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 50 + 1 + 1 + 10 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + + + + + + + \ No newline at end of file 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 b862806..8426023 100644 --- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml +++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml @@ -112,13 +112,41 @@ The noise of the effect. - + 10 Pixelate Pixel Size The size of the maximum pixelation. + + + + + + false + Pixel Wipe Close Effect + Use the Pixel Wipe effect for window closing. + + + + false + Pixel Wipe Open Effect + Use the Pixel Wipe effect for window opening. + + + + 1000 + Pixel Wipe Animation Time + The time the Pixel Wipe effect takes. + + + + 10 + Pixel Wipe Pixel Size + The size of the maximum pixelation. + + diff --git a/src/PixelWipe.js b/src/PixelWipe.js new file mode 100644 index 0000000..ec7a110 --- /dev/null +++ b/src/PixelWipe.js @@ -0,0 +1,127 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const GObject = imports.gi.GObject; + +const _ = imports.gettext.domain('burn-my-windows').gettext; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const utils = Me.imports.src.utils; +const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory; + +////////////////////////////////////////////////////////////////////////////////////////// +// This very simple effect pixelates the window texture and hides from left to right // +// (or any other direction). // +////////////////////////////////////////////////////////////////////////////////////////// + +// The effect class can be used to get some metadata (like the effect's name or supported +// GNOME Shell versions), to initialize the respective page of the settings dialog, as +// well as to create the actual shader for the effect. +var PixelWipe = class { + + // The constructor creates a ShaderFactory which will be used by extension.js to create + // shader instances for this effect. The shaders will be automagically created using the + // GLSL file in resources/shaders/.glsl. The callback will be called for each + // newly created shader instance. + constructor() { + this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => { + // Store uniform locations of newly created shaders. + shader._uPixelSize = shader.get_uniform_location('uPixelSize'); + shader._uStartPos = shader.get_uniform_location('uStartPos'); + + // Write all uniform values at the start of each animation. + shader.connect('begin-animation', (shader, settings, forOpening, actor) => { + // Because the actor position may change after the begin-animation signal is + // called, we set the uDirection uniform during the update callback. + this._startPointerPos = global.get_pointer(); + this._actor = actor; + + // clang-format off + shader.set_uniform_float(shader._uPixelSize, 1, [settings.get_int('pixel-wipe-pixel-size')]); + // clang-format on + }); + + // We set the uDirection uniform during the update callback as the actor position + // may not be set up properly before the begin animation callback. + shader.connect('update-animation', (shader) => { + if (this._startPointerPos) { + const [x, y] = this._startPointerPos; + const [ok, localX, localY] = this._actor.transform_stage_point(x, y); + + if (ok) { + let startPos = [ + Math.max(0.0, Math.min(1.0, localX / this._actor.width)), + Math.max(0.0, Math.min(1.0, localY / this._actor.height)) + ]; + shader.set_uniform_float(shader._uStartPos, 2, startPos); + } + } + }); + + // Make sure to drop the reference to the actor. + shader.connect('end-animation', (shader) => { + shader._actor = null; + }); + }); + } + + // ---------------------------------------------------------------------------- metadata + + // The effect is available on all GNOME Shell versions supported by this extension. + getMinShellVersion() { + return [3, 36]; + } + + // This will be called in various places where a unique identifier for this effect is + // required. It should match the prefix of the settings keys which store whether the + // effect is enabled currently (e.g. '*-close-effect'), and its animation time + // (e.g. '*-animation-time'). + getNick() { + return 'pixel-wipe'; + } + + // This will be shown in the sidebar of the preferences dialog as well as in the + // drop-down menus where the user can choose the effect. + getLabel() { + return _('Pixel Wipe'); + } + + // -------------------------------------------------------------------- API for prefs.js + + // This is called by the preferences dialog. It loads the settings page for this effect, + // and binds all properties to the settings. + getPreferences(dialog) { + + // Add the settings page to the builder. + dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/PixelWipe.ui`); + + // Bind all properties. + dialog.bindAdjustment('pixel-wipe-animation-time'); + dialog.bindAdjustment('pixel-wipe-pixel-size'); + + // Finally, return the new settings page. + return dialog.getBuilder().get_object('pixel-wipe-prefs'); + } + + // ---------------------------------------------------------------- API for extension.js + + // The getActorScale() is called from extension.js to adjust the actor's size during the + // animation. This is useful if the effect requires drawing something beyond the usual + // bounds of the actor. This only works for GNOME 3.38+. + getActorScale(settings) { + return {x: 1.0, y: 1.0}; + } +} diff --git a/src/Pixelate.js b/src/Pixelate.js index 248f39a..368e58a 100644 --- a/src/Pixelate.js +++ b/src/Pixelate.js @@ -46,7 +46,7 @@ var Pixelate = class { shader.connect('begin-animation', (shader, settings) => { // clang-format off shader.set_uniform_float(shader._uNoise, 1, [settings.get_double('pixelate-noise')]); - shader.set_uniform_float(shader._uPixelSize, 1, [settings.get_double('pixelate-pixel-size')]); + shader.set_uniform_float(shader._uPixelSize, 1, [settings.get_int('pixelate-pixel-size')]); // clang-format on }); });