diff --git a/Makefile b/Makefile index 124369d..7d49900 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ burn-my-windows@schneegans.github.com.zip: schemas/gschemas.compiled resources/b @echo "Packing zip file..." @rm --force burn-my-windows@schneegans.github.com.zip - @zip -r burn-my-windows@schneegans.github.com.zip -- *.js resources/burn-my-windows.gresource schemas/gschemas.compiled metadata.json LICENSE + @zip -r burn-my-windows@schneegans.github.com.zip -- *.js src/*/*.js resources/burn-my-windows.gresource schemas/gschemas.compiled metadata.json LICENSE @#Check if the zip size is too big to be uploaded @if [[ "$$(stat -c %s burn-my-windows@schneegans.github.com.zip)" -gt 4096000 ]]; then \ diff --git a/extension.js b/extension.js index a1173a9..1d1dee7 100644 --- a/extension.js +++ b/extension.js @@ -21,7 +21,8 @@ const WINDOW_REPOSITIONING_DELAY = imports.ui.workspace.WINDOW_REPOSITIONING_DEL const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const utils = Me.imports.utils; +const utils = Me.imports.src.common.utils; +const FireShader = Me.imports.src.extension.FireShader.FireShader; ////////////////////////////////////////////////////////////////////////////////////////// // This extensions modifies the window-close animation to look like the window was set // @@ -125,149 +126,16 @@ class Extension { tweakTransition('scale-x', 1); tweakTransition('scale-y', 1); - // Instead, we add a cool fire shader to our window actor! The noise implementation - // is from https://www.shadertoy.com/view/3tcBzH (CC-BY-NC-SA). - const shader = new Clutter.ShaderEffect({ - shader_type: Clutter.ShaderType.FRAGMENT_SHADER, - }); - - // Load the gradient values from the settings. We directly inject the values in the - // GLSL code below. The shader is compiled once for each window-closing anyways. In - // the future, we my want to prevent this frequent recompilations of shaders, - // though. - const gradient = []; - for (let i = 1; i <= 5; i++) { - const color = - Clutter.Color.from_string(this._settings.get_string('fire-color-' + i))[1]; - gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ - color.blue / 255}, ${color.alpha / 255})`); - } - - shader.set_shader_source(` - uniform sampler2D texture; - uniform float progress; - uniform float time; - uniform int sizeX; - uniform int sizeY; - - // These may be configurable in the future. - const float EDGE_BLEND = 70; - const float BURN_RANGE = 0.1; - const float BURN_TIME = 0.2; - const vec2 FIRE_SCALE = vec2(400, 600) * ${ - this._settings.get_double('flame-scale')}; - const float FIRE_SPEED = ${this._settings.get_double('flame-movement-speed')}; - - 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); - } - - // Fade at window borders. - 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); - - // Modulate noise by mask and map to color. - vec4 color = getFireColor(noise*mask); - color.rgb *= color.a; - - // cogl_color_out = vec4(mask); - cogl_color_out += color; - } - `); - + // Instead, we add a cool shader to our window actor! + const shader = new FireShader(this._settings); actor.add_effect(shader); // Update uniforms at each frame. 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); + 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); }); }); } diff --git a/utils.js b/src/common/utils.js similarity index 100% rename from utils.js rename to src/common/utils.js diff --git a/src/extension/FireShader.js b/src/extension/FireShader.js new file mode 100644 index 0000000..ced910f --- /dev/null +++ b/src/extension/FireShader.js @@ -0,0 +1,101 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const {Clutter, GObject} = imports.gi; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const shaderSnippets = Me.imports.src.extension.shaderSnippets; + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// + +var FireShader = GObject.registerClass({Properties: {}, Signals: {}}, + class FireShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the gradient values from the settings. We directly inject the values in the + // GLSL code below. The shader is compiled once for each window-closing anyways. In + // the future, we my want to prevent this frequent recompilations of shaders, + // though. + const gradient = []; + for (let i = 1; i <= 5; i++) { + const color = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; + gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ + color.blue / 255}, ${color.alpha / 255})`); + } + + + this.set_shader_source(` + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + ${shaderSnippets.effectMask()} + + // These may be configurable in the future. + const float EDGE_FADE = 70; + const float FADE_WIDTH = 0.1; + const float HIDE_TIME = 0.2; + const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; + const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')}; + + vec4 getFireColor(float v) { + const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); + const vec4 colors[5] = vec4[]( + ${gradient[0]}, + ${gradient[1]}, + ${gradient[2]}, + ${gradient[3]}, + ${gradient[4]} + ); + + if (v < steps[0]) { + return colors[0]; + } + + for (int i=0; i<4; ++i) { + if (v <= steps[i+1]) { + return mix(colors[i], colors[i+1], vec4(v - + steps[i])/(steps[i+1]-steps[i])); + } + } + + return colors[4]; + } + + void main(void) { + + // Get a noise value. + vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; + uv.y += uTime * FIRE_SPEED; + float noise = perlinNoise(uv, 10.0, 5, 0.5); + + // Modulate noise by mask. + vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); + noise *= effectMask.y; + + // Map noise value to color. + vec4 fire = getFireColor(noise); + fire.rgb *= fire.a; + + // Get window texture. + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; + + // Add fire. + cogl_color_out += fire; + } + `); + }; +}); \ No newline at end of file diff --git a/src/extension/shaderSnippets.js b/src/extension/shaderSnippets.js new file mode 100644 index 0000000..2b0b48b --- /dev/null +++ b/src/extension/shaderSnippets.js @@ -0,0 +1,102 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +function standardUniforms() { + return ` + uniform sampler2D uTexture; + uniform float uProgress; + uniform float uTime; + uniform int uSizeX; + uniform int uSizeY; + `; +} + +// The noise implementation is from https://www.shadertoy.com/view/3tcBzH +// (CC-BY-NC-SA). +function noise() { + return ` + 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 perlinNoise(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 hideTime) { + effectMask *= mix(1, 1-t, afterBurnProgress) * pow(1-afterBurnProgress, 2); + } + + // Fade at window borders. + vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); + effectMask *= clamp(pos.x / edgeFadeWidth, 0, 1); + effectMask *= clamp(pos.y / edgeFadeWidth, 0, 1); + effectMask *= clamp((uSizeX - pos.x) / edgeFadeWidth, 0, 1); + effectMask *= clamp((uSizeY - pos.y) / edgeFadeWidth, 0, 1); + + return vec2(windowMask, effectMask); + } + `; +} \ No newline at end of file