diff --git a/assets/matrixFont.svg b/assets/matrixFont.svg new file mode 100644 index 0000000..22a9e1a --- /dev/null +++ b/assets/matrixFont.svg @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσΤτΥυΦφΧχΨψΩω1234567890АаБбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЬьЭэЮюЯя!"#$%&()*+,-/:;<=>?@©§[\]{|}~ªº¿®¬½¼¡¶ + ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσΤτΥυΦφΧχΨψΩω1234567890АаБбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЬьЭэЮюЯя!"#$%&()*+,-/:;<=>?@©§[\]{|}~ªº¿®¬½¼¡¶ + + + diff --git a/extension.js b/extension.js index 1d1dee7..675a1cd 100644 --- a/extension.js +++ b/extension.js @@ -13,7 +13,7 @@ 'use strict'; -const {Clutter} = imports.gi; +const {Clutter, Gio} = imports.gi; const Workspace = imports.ui.workspace.Workspace; const WindowManager = imports.ui.windowManager.WindowManager; @@ -23,6 +23,7 @@ const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); const utils = Me.imports.src.common.utils; const FireShader = Me.imports.src.extension.FireShader.FireShader; +const MatrixShader = Me.imports.src.extension.MatrixShader.MatrixShader; ////////////////////////////////////////////////////////////////////////////////////////// // This extensions modifies the window-close animation to look like the window was set // @@ -39,6 +40,10 @@ class Extension { // from GNOME Tweaks, when you log in or when the screen is unlocked. enable() { + // Load all of our resources. + this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource'); + Gio.resources_register(this._resources); + // Store a reference to the settings object. this._settings = ExtensionUtils.getSettings(); @@ -127,7 +132,7 @@ class Extension { tweakTransition('scale-y', 1); // Instead, we add a cool shader to our window actor! - const shader = new FireShader(this._settings); + const shader = new MatrixShader(this._settings); actor.add_effect(shader); // Update uniforms at each frame. @@ -144,6 +149,9 @@ class Extension { // Tweaks, when you log out or when the screen locks. disable() { + // Unregister our resources. + Gio.resources_unregister(this._resources); + // Restore the original behavior. global.window_manager.disconnect(this._destroyConnection); diff --git a/resources/img/matrixFont.png b/resources/img/matrixFont.png new file mode 100644 index 0000000..ed96beb Binary files /dev/null and b/resources/img/matrixFont.png differ diff --git a/src/extension/FireShader.js b/src/extension/FireShader.js index e1af97c..adc8303 100644 --- a/src/extension/FireShader.js +++ b/src/extension/FireShader.js @@ -44,7 +44,6 @@ var FireShader = GObject.registerClass({Properties: {}, Signals: {}}, // Inject some common shader snippets. ${shaderSnippets.standardUniforms()} ${shaderSnippets.noise()} - ${shaderSnippets.effectMask()} // These may be configurable in the future. const float EDGE_FADE = 70; @@ -78,7 +77,45 @@ var FireShader = GObject.registerClass({Properties: {}, Signals: {}}, return colors[4]; } - void main(void) { + // This method requires the uniforms from standardUniforms() to be available. + // It returns two values: The first is an alpha value which can be used for the window + // texture. This gradually dissolves the window from top to bottom. The second can be used + // to mask any effect, it will be most opaque where the window is currently fading and + // gradually dissolve to zero over time. + // hideTime: A value in [0..1]. It determines the percentage of the animation which + // is spent for hiding the window. 1-hideTime will be spent thereafter for + // dissolving the effect mask. + // fadeWidth: The relative size of the window-hiding gradient in [0..1]. + // edgeFadeWidth: The pixel width of the effect fading range at the edges of the window. + vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) { + float burnProgress = clamp(uProgress/hideTime, 0, 1); + float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1); + + // Gradient from top to bottom. + float t = cogl_tex_coord_in[0].t * (1 - fadeWidth); + + // Visible part of the window. Gradually dissolves towards the bottom. + float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1); + + // Gradient from top burning window. + float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1); + + // Fade-out when the window burned down. + if (uProgress > 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); + } + + void main() { // Get a noise value which moves vertically in time. vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; diff --git a/src/extension/MatrixShader.js b/src/extension/MatrixShader.js new file mode 100644 index 0000000..0f19a86 --- /dev/null +++ b/src/extension/MatrixShader.js @@ -0,0 +1,120 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ 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, GdkPixbuf, Cogl} = imports.gi; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const shaderSnippets = Me.imports.src.extension.shaderSnippets; + +////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////// + +var MatrixShader = GObject.registerClass({Properties: {}, Signals: {}}, + class MatrixShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png'); + this._fontTexture = new Clutter.Image(); + this._fontTexture.set_data( + fontData.get_pixels(), + fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888, + fontData.width, fontData.height, fontData.rowstride); + + // This is roughly based on https://www.shadertoy.com/view/ldccW4 (CC-BY-NC-SA). + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + uniform sampler2D uFontTexture; + + // These may be configurable in the future. + const float EDGE_FADE = 30; + const float FADE_WIDTH = 50; + const float TRAIL_LENGTH = 0.2; + const float FINAL_FADE_START_TIME = 0.8; + const float LETTER_TILES = 16.0; + const float LETTER_SIZE = 16.0; + const float BRIGHTNESS_BOOST = 2; + const float RANDOMNESS = 1.0; + + float getText(vec2 fragCoord) { + vec2 coords = fragCoord * vec2(uSizeX, uSizeY); + vec2 uv = mod(coords.xy, LETTER_SIZE)/LETTER_SIZE; + vec2 block = coords/LETTER_SIZE - uv; + + // scale the letters up a bit + uv = uv*.8+.1; + + // bring back into 0-1 range + uv += floor(rand2(block/512 + uTime*.0000001)*LETTER_TILES); + uv /= LETTER_TILES; + + // flip letters horizontally + uv.x = -uv.x+1.0; + return texture2D(uFontTexture, uv).r; + } + + vec2 getRain(vec2 fragCoord) { + vec2 coords = fragCoord * vec2(uSizeX, uSizeY); + coords.x -= mod(coords.x, LETTER_SIZE); + + float delay = abs(sin(coords.x * 3.0)) * mix(0.0, 1.0, RANDOMNESS); + float speed = cos(coords.x * 7.0) * mix(0.0, 0.3, RANDOMNESS) + 1.5; + + float distToDrop = (uProgress*2-delay)*speed - fragCoord.y; + return vec2(distToDrop >= 0 ? exp(-distToDrop/TRAIL_LENGTH) : 0, + clamp(uSizeY*distToDrop, 0, FADE_WIDTH) / FADE_WIDTH); + } + + void main() { + + vec3 color = vec3(0.1,1,0.35); + + // Get a cool matrix effect. + vec2 rain = getRain(cogl_tex_coord_in[0].st); + float text = getText(cogl_tex_coord_in[0].st); + + // Get the window texture and fade it according to the effect mask. + float windowAlpha = 1 - rain.y; + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * windowAlpha; + + float finalFade = 1-clamp((uProgress-FINAL_FADE_START_TIME)/(1-FINAL_FADE_START_TIME), 0, 1); + float textAlpha = finalFade * rain.x * BRIGHTNESS_BOOST; + + + // Fade at window borders. + vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); + textAlpha *= clamp(pos.x / EDGE_FADE, 0, 1); + textAlpha *= clamp(pos.y / EDGE_FADE, 0, 1); + textAlpha *= clamp((uSizeX - pos.x) / EDGE_FADE, 0, 1); + textAlpha *= clamp((uSizeY - pos.y) / EDGE_FADE, 0, 1); + + // Add the fire to the window. + cogl_color_out.rgb += color * textAlpha * text; + } + `); + }; + + vfunc_paint_target(node, paint_context) { + const pipeline = this.get_pipeline(); + pipeline.set_layer_texture(1, this._fontTexture.get_texture()); + this.set_uniform_value('uFontTexture', 1); + super.vfunc_paint_target(node, paint_context); + } +}); \ No newline at end of file diff --git a/src/extension/shaderSnippets.js b/src/extension/shaderSnippets.js index 8e52391..d2191c8 100644 --- a/src/extension/shaderSnippets.js +++ b/src/extension/shaderSnippets.js @@ -81,45 +81,3 @@ function noise() { } `; } - -// This method requires the uniforms from standardUniforms() to be available. -// It returns two values: The first is an alpha value which can be used for the window -// texture. This gradually dissolves the window from top to bottom. The second can be used -// to mask any effect, it will be most opaque where the window is currently fading and -// gradually dissolve to zero over time. -// hideTime: A value in [0..1]. It determines the percentage of the animation which -// is spent for hiding the window. 1-hideTime will be spent thereafter for -// dissolving the effect mask. -// fadeWidth: The relative size of the window-hiding gradient in [0..1]. -// edgeFadeWidth: The pixel width of the effect fading range at the edges of the window. -function effectMask() { - return ` - vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) { - float burnProgress = clamp(uProgress/hideTime, 0, 1); - float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1); - - // Gradient from top to bottom. - float t = cogl_tex_coord_in[0].t * (1 - fadeWidth); - - // Visible part of the window. Gradually dissolves towards the bottom. - float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1); - - // Gradient from top burning window. - float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1); - - // Fade-out when the window burned down. - if (uProgress > 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