🔀 Merge pull request #148 from Schneegans/refactor/base-classes

This commit is contained in:
Simon Schneegans
2022-05-16 05:30:07 +02:00
committed by GitHub
124 changed files with 1022 additions and 1521 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ jobs:
- '35' - '35'
- '36' - '36'
session: session:
- 'gnome-xsession' #- 'gnome-xsession' Disabled for now as it miraculously fails on GitHub. Locally, all tests pass...
- 'gnome-wayland-nested' - 'gnome-wayland-nested'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
+79 -111
View File
@@ -77,11 +77,55 @@ Just remember to replace `simple-fade` with your custom name!
### 2. Creating the Effect Class ### 2. Creating the Effect Class
You will have to create a new file called `src/SimpleFade.js` and paste the following code to it. You will have to create a new GLSL file called `resources/shaders/simple-fade.glsl` and a new JavaScript source file called `src/SimpleFade.js`.
Simply paste the following source code to the respective file.
Please study this code carefully, all of it is explained with inline comments. Please study this code carefully, all of it is explained with inline comments.
<details> <details>
<summary>Expand this to show the code.</summary> <summary>Expand this to show the GLSL code.</summary>
```glsl
// The code below injects some standard uniforms which will be updated during the
// animation. This includes:
// bool uForOpening: True if a window-open animation is ongoing, false otherwise.
// sampler2D uTexture: Contains the texture of the window.
// float uProgress: A value which transitions from 0 to 1 during the entire animation.
// float uTime: A steadily increasing value in seconds.
// vec2 uSize: The size of uTexture in pixels.
#include "common/uniforms.glsl"
// The width of the fading effect is loaded from the settings.
uniform float uFadeWidth;
void main() {
// Get the color from the window texture.
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st);
// Shell.GLSLEffect uses straight alpha. So we have to convert from premultiplied.
if (cogl_color_out.a > 0) {
cogl_color_out.rgb /= cogl_color_out.a;
}
// Radial distance from window edge to the window's center.
float dist = length(cogl_tex_coord_in[0].st - 0.5) * 2.0 / sqrt(2.0);
// This gradually dissolves from [1..0] from the outside to the center. We
// switch the direction for opening and closing.
float progress = uForOpening ? 1.0 - uProgress : uProgress;
float mask = (1.0 - progress * (1.0 + uFadeWidth) - dist + uFadeWidth) / uFadeWidth;
// Make the mask smoother.
mask = smoothstep(0, 1, mask);
// Apply the mask to the output.
cogl_color_out.a *= mask;
}
```
</details>
<details>
<summary>Expand this to show the JavaScript code.</summary>
```javascript ```javascript
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
@@ -106,29 +150,45 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect ... // // This effect ... //
// <- Please add a description of your effect here -> // // <- Please add a description of your effect here -> //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 SimpleFade = class SimpleFade { var SimpleFade = class SimpleFade {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store uniform locations of newly created shaders.
shader._uForOpening = shader.get_uniform_location('uForOpening');
shader._uFadeWidth = shader.get_uniform_location('uFadeWidth');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
shader.set_uniform_float(shader._uForOpening, 1, [forOpening]);
shader.set_uniform_float(shader._uFadeWidth, 1, [settings.get_double('simple-fade-width')]);
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
@@ -136,13 +196,13 @@ var SimpleFade = class SimpleFade {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'simple-fade'; return 'simple-fade';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Simple Fade Effect'); return _('Simple Fade Effect');
} }
@@ -151,112 +211,20 @@ var SimpleFade = class SimpleFade {
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // binds all properties to the settings and appends the page to the main stack of the
// preferences dialog. // preferences dialog.
static getPreferences(dialog) { getPreferences(dialog) {
// Empty for now... Code is added here later in the tutorial! // Empty for now... Code is added here later in the tutorial!
return null; return null;
} }
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
} }
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const Shell = imports.gi.Shell;
const shaderSnippets = Me.imports.src.shaderSnippets;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
_init(settings, forOpening) {
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
this._uForOpening = this.get_uniform_location('uForOpening');
this._uFadeWidth = this.get_uniform_location('uFadeWidth');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uFadeWidth, 1, [settings.get_double('simple-fade-width')]);
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const declarations = `
// The code below injects some standard uniforms which will be updated during the
// animation. This includes:
// bool uForOpening: True if a window-open animation is ongoing, false otherwise.
// sampler2D uTexture: Contains the texture of the window.
// float uProgress: A value which transitions from 0 to 1 during the entire animation.
// float uTime: A steadily increasing value in seconds.
// vec2 uSize: The size of uTexture in pixels.
#include "common/uniforms.glsl"
// The width of the fading effect is loaded from the settings.
uniform float uFadeWidth;
`;
const code = `
// Get the color from the window texture.
vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st);
// Radial distance from window edge to the window's center.
float dist = length(cogl_tex_coord_in[0].st - 0.5) * 2.0 / sqrt(2.0);
// This gradually dissolves from [1..0] from the outside to the center. We
// switch the direction for opening and closing.
float progress = uForOpening ? 1.0 - uProgress : uProgress;
float mask = (1.0 - progress * (1.0 + uFadeWidth) - dist + uFadeWidth) / uFadeWidth;
// Make the mask smoother.
mask = smoothstep(0, 1, mask);
// Set the final output color. This uses premultiplied alpha.
cogl_color_out = windowColor * mask;
`;
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, code, true);
};
});
}
``` ```
</details> </details>
@@ -270,7 +238,7 @@ Like this:
```javascript ```javascript
const ALL_EFFECTS = [ const ALL_EFFECTS = [
... ...
Me.imports.src.SimpleFade.SimpleFade, new Me.imports.src.SimpleFade.SimpleFade(),
... ...
]; ];
``` ```
+54 -58
View File
@@ -31,21 +31,6 @@ const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
// New effects must be registered here and in prefs.js.
const ALL_EFFECTS = [
Me.imports.src.Apparition.Apparition,
Me.imports.src.BrokenGlass.BrokenGlass,
Me.imports.src.EnergizeA.EnergizeA,
Me.imports.src.EnergizeB.EnergizeB,
Me.imports.src.Fire.Fire,
Me.imports.src.Hexagon.Hexagon,
Me.imports.src.Matrix.Matrix,
Me.imports.src.SnapOfDisintegration.SnapOfDisintegration,
Me.imports.src.TRexAttack.TRexAttack,
Me.imports.src.TVEffect.TVEffect,
Me.imports.src.Wisps.Wisps,
];
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This extensions modifies the window-close and window-open animations with all kinds // // 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 // // of effects. The effects are implemented using GLSL shaders which are applied to the //
@@ -63,6 +48,21 @@ class Extension {
// from GNOME Tweaks, when you log in or when the screen is unlocked. // from GNOME Tweaks, when you log in or when the screen is unlocked.
enable() { enable() {
// New effects must be registered here and in prefs.js.
this._ALL_EFFECTS = [
new Me.imports.src.Apparition.Apparition(),
new Me.imports.src.BrokenGlass.BrokenGlass(),
new Me.imports.src.EnergizeA.EnergizeA(),
new Me.imports.src.EnergizeB.EnergizeB(),
new Me.imports.src.Fire.Fire(),
new Me.imports.src.Hexagon.Hexagon(),
new Me.imports.src.Matrix.Matrix(),
new Me.imports.src.SnapOfDisintegration.SnapOfDisintegration(),
new Me.imports.src.TRexAttack.TRexAttack(),
new Me.imports.src.TVEffect.TVEffect(),
new Me.imports.src.Wisps.Wisps(),
];
// Load all of our resources. // Load all of our resources.
this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource'); this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource');
Gio.resources_register(this._resources); Gio.resources_register(this._resources);
@@ -222,7 +222,7 @@ class Extension {
const shader = actor.get_effect('burn-my-windows-effect'); const shader = actor.get_effect('burn-my-windows-effect');
if (shader) { if (shader) {
actor.remove_effect(shader); actor.remove_effect(shader);
shader.free(); shader.returnToFactory();
} }
} }
@@ -306,7 +306,7 @@ class Extension {
disable() { disable() {
// Free all effect resources. // Free all effect resources.
ALL_EFFECTS.forEach(Effect => Effect.cleanUp()); this._ALL_EFFECTS = [];
// Unregister our resources. // Unregister our resources.
Gio.resources_unregister(this._resources); Gio.resources_unregister(this._resources);
@@ -365,13 +365,13 @@ class Extension {
return; return;
} }
// There is the weird case where an animation is already. This happens when a window // There is the weird case where an animation is already ongoing. This happens when a
// is closed which has been created before the session was started (e.g. when GNOME // window is closed which has been created before the session was started (e.g. when
// Shell has been restarted in the meantime). // GNOME Shell has been restarted in the meantime).
const oldShader = actor.get_effect('burn-my-windows-effect'); const oldShader = actor.get_effect('burn-my-windows-effect');
if (oldShader) { if (oldShader) {
actor.remove_effect(oldShader); actor.remove_effect(oldShader);
oldShader.free(); oldShader.returnToFactory();
} }
// ------------------------------------------------------------------ choose an effect // ------------------------------------------------------------------ choose an effect
@@ -381,9 +381,7 @@ class Extension {
// First we check if an effect is to be previewed. // First we check if an effect is to be previewed.
if (previewNick != '') { if (previewNick != '') {
effect = ALL_EFFECTS.find(Effect => { effect = this._ALL_EFFECTS.find(effect => effect.getNick() == previewNick);
return Effect.getNick() == previewNick;
});
// Only preview the effect once. // Only preview the effect once.
this._settings.set_string(action + '-preview-effect', ''); this._settings.set_string(action + '-preview-effect', '');
@@ -393,8 +391,8 @@ class Extension {
else { else {
// Therefore, we first create a list of all currently enabled effects. // Therefore, we first create a list of all currently enabled effects.
const enabled = ALL_EFFECTS.filter(Effect => { const enabled = this._ALL_EFFECTS.filter(effect => {
return this._settings.get_boolean(`${Effect.getNick()}-${action}-effect`); return this._settings.get_boolean(`${effect.getNick()}-${action}-effect`);
}); });
// And then choose a random effect. // And then choose a random effect.
@@ -476,43 +474,41 @@ class Extension {
// -------------------------------------------------------------------- add the shader // -------------------------------------------------------------------- add the shader
// Now add a cool shader to our window actor! // Now add a cool shader to our window actor!
const shader = effect.getShader(actor, this._settings, forOpening); const shader = effect.shaderFactory.getShader();
if (shader) { // There should always be an opacity transition going on...
// There should always be an opacity transition going on... const transition = actor.get_transition('opacity');
const transition = actor.get_transition('opacity');
if (!transition) { if (!transition) {
this._fixAnimationTimes(isDialogWindow, forOpening, null); this._fixAnimationTimes(isDialogWindow, forOpening, null);
utils.debug('Cannot setup shader without opacity transition.') utils.debug('Cannot setup shader without opacity transition.')
return; return;
}
// Assign the effect to the window actor!
actor.add_effect_with_name('burn-my-windows-effect', shader);
// Set one-time uniforms.
shader.beginAnimation(this._settings, forOpening, actor);
// Set other uniforms each frame.
transition.connect('new-frame', (t) => {
if (testMode) {
shader.updateAnimation(0.5, 0.001 * duration * 0.5);
} else {
shader.updateAnimation(t.get_progress(), 0.001 * t.get_elapsed_time());
} }
});
actor.add_effect_with_name('burn-my-windows-effect', shader); // Remove the effect if the animation finished or was interrupted.
if (forOpening) {
// Update uniforms at each frame. transition.connect('stopped', () => {
transition.connect('new-frame', (t) => { const oldShader = actor.get_effect('burn-my-windows-effect');
shader.set_uniform_float(shader.get_uniform_location('uForOpening'), 1, if (oldShader) {
[forOpening]); actor.remove_effect(oldShader);
shader.set_uniform_float(shader.get_uniform_location('uProgress'), 1, oldShader.returnToFactory();
[testMode ? 0.5 : t.get_progress()]); }
shader.set_uniform_float(
shader.get_uniform_location('uTime'), 1,
[testMode ? duration / 2 : 0.001 * t.get_elapsed_time()]);
shader.set_uniform_float(shader.get_uniform_location('uSize'), 2,
[actor.width, actor.height]);
}); });
// Remove the effect if the animation finished or was interrupted.
if (forOpening) {
transition.connect('stopped', () => {
const oldShader = actor.get_effect('burn-my-windows-effect');
if (oldShader) {
actor.remove_effect(oldShader);
oldShader.free();
}
});
}
} }
// Finally, ensure that all animation times are set properly so that other extensions // Finally, ensure that all animation times are set properly so that other extensions
+41 -40
View File
@@ -30,21 +30,6 @@ const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
// New effects must be registered here and in extension.js.
const ALL_EFFECTS = [
Me.imports.src.Apparition.Apparition,
Me.imports.src.BrokenGlass.BrokenGlass,
Me.imports.src.EnergizeA.EnergizeA,
Me.imports.src.EnergizeB.EnergizeB,
Me.imports.src.Fire.Fire,
Me.imports.src.Hexagon.Hexagon,
Me.imports.src.Matrix.Matrix,
Me.imports.src.SnapOfDisintegration.SnapOfDisintegration,
Me.imports.src.TRexAttack.TRexAttack,
Me.imports.src.TVEffect.TVEffect,
Me.imports.src.Wisps.Wisps,
];
// This template widget class is defined at the bottom of this file. // This template widget class is defined at the bottom of this file.
var BurnMyWindowsEffectPage = null; var BurnMyWindowsEffectPage = null;
@@ -59,6 +44,22 @@ var PreferencesDialog = class PreferencesDialog {
// ------------------------------------------------------------ constructor / destructor // ------------------------------------------------------------ constructor / destructor
constructor() { constructor() {
// New effects must be registered here and in extension.js.
this._ALL_EFFECTS = [
new Me.imports.src.Apparition.Apparition(),
new Me.imports.src.BrokenGlass.BrokenGlass(),
new Me.imports.src.EnergizeA.EnergizeA(),
new Me.imports.src.EnergizeB.EnergizeB(),
new Me.imports.src.Fire.Fire(),
new Me.imports.src.Hexagon.Hexagon(),
new Me.imports.src.Matrix.Matrix(),
new Me.imports.src.SnapOfDisintegration.SnapOfDisintegration(),
new Me.imports.src.TRexAttack.TRexAttack(),
new Me.imports.src.TVEffect.TVEffect(),
new Me.imports.src.Wisps.Wisps(),
];
// Load all of our resources. // Load all of our resources.
this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource'); this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource');
Gio.resources_register(this._resources); Gio.resources_register(this._resources);
@@ -113,24 +114,24 @@ var PreferencesDialog = class PreferencesDialog {
const group = new Adw.PreferencesGroup({title: _('Effect Options')}); const group = new Adw.PreferencesGroup({title: _('Effect Options')});
this.gtkBoxAppend(this._widget, group); this.gtkBoxAppend(this._widget, group);
ALL_EFFECTS.forEach(Effect => { this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = Effect.getMinShellVersion(); const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
const row = new Adw.ActionRow({title: Effect.getLabel(), activatable: true}); const row = new Adw.ActionRow({title: effect.getLabel(), activatable: true});
row.add_suffix(new Gtk.Image({icon_name: 'go-next-symbolic'})); row.add_suffix(new Gtk.Image({icon_name: 'go-next-symbolic'}));
// Open a subpage with the effect's settings. // Open a subpage with the effect's settings.
row.connect('activated', () => { row.connect('activated', () => {
const page = new BurnMyWindowsEffectPage(Effect, this); const page = new BurnMyWindowsEffectPage(effect, this);
page.valign = Gtk.Align.CENTER; page.valign = Gtk.Align.CENTER;
page.margin_top = 10; page.margin_top = 10;
page.margin_bottom = 10; page.margin_bottom = 10;
page.margin_start = 10; page.margin_start = 10;
page.margin_end = 10; page.margin_end = 10;
// Add the Effect's preferences (if any). // Add the effect's preferences (if any).
const preferences = Effect.getPreferences(this); const preferences = effect.getPreferences(this);
if (preferences) { if (preferences) {
this.gtkBoxAppend(page, preferences); this.gtkBoxAppend(page, preferences);
} }
@@ -186,23 +187,23 @@ var PreferencesDialog = class PreferencesDialog {
this.gtkBoxAppend(this._widget, stack); this.gtkBoxAppend(this._widget, stack);
// Add all other effect pages. // Add all other effect pages.
ALL_EFFECTS.forEach(Effect => { this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = Effect.getMinShellVersion(); const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
const page = new BurnMyWindowsEffectPage(Effect, this); const page = new BurnMyWindowsEffectPage(effect, this);
page.margin_start = 60; page.margin_start = 60;
page.margin_end = 60; page.margin_end = 60;
page.margin_top = 60; page.margin_top = 60;
page.margin_bottom = 60; page.margin_bottom = 60;
// Add the Effect's preferences (if any). // Add the effect's preferences (if any).
const preferences = Effect.getPreferences(this); const preferences = effect.getPreferences(this);
if (preferences) { if (preferences) {
this.gtkBoxAppend(page, preferences); this.gtkBoxAppend(page, preferences);
} }
stack.add_titled(page, Effect.getNick(), Effect.getLabel()); stack.add_titled(page, effect.getNick(), effect.getLabel());
} }
}); });
} }
@@ -302,11 +303,11 @@ var PreferencesDialog = class PreferencesDialog {
const group = Gio.SimpleActionGroup.new(); const group = Gio.SimpleActionGroup.new();
window.insert_action_group('open-effects', group); window.insert_action_group('open-effects', group);
ALL_EFFECTS.forEach(Effect => { this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = Effect.getMinShellVersion(); const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
const nick = Effect.getNick(); const nick = effect.getNick();
const label = Effect.getLabel(); const label = effect.getLabel();
const actionName = nick + '-open-effect'; const actionName = nick + '-open-effect';
const fullName = 'open-effects.' + actionName; const fullName = 'open-effects.' + actionName;
@@ -324,11 +325,11 @@ var PreferencesDialog = class PreferencesDialog {
const group = Gio.SimpleActionGroup.new(); const group = Gio.SimpleActionGroup.new();
window.insert_action_group('close-effects', group); window.insert_action_group('close-effects', group);
ALL_EFFECTS.forEach(Effect => { this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = Effect.getMinShellVersion(); const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
const nick = Effect.getNick(); const nick = effect.getNick();
const label = Effect.getLabel(); const label = effect.getLabel();
const actionName = nick + '-close-effect'; const actionName = nick + '-close-effect';
const fullName = 'close-effects.' + actionName; const fullName = 'close-effects.' + actionName;
@@ -490,17 +491,17 @@ var PreferencesDialog = class PreferencesDialog {
InternalChildren: ['label', 'button'], InternalChildren: ['label', 'button'],
}, },
class BurnMyWindowsEffectPage extends Gtk.Box { // ------------------------------ class BurnMyWindowsEffectPage extends Gtk.Box { // ------------------------------
_init(Effect, dialog) { _init(effect, dialog) {
super._init(); super._init();
// Set the effect's name as label. // Set the effect's name as label.
this._label.label = Effect.getLabel(); this._label.label = effect.getLabel();
// Open the preview window once the preview button is clicked. // Open the preview window once the preview button is clicked.
this._button.connect('clicked', () => { this._button.connect('clicked', () => {
// Set the to-be-previewed effect. // Set the to-be-previewed effect.
dialog.getSettings().set_string('open-preview-effect', Effect.getNick()); dialog.getSettings().set_string('open-preview-effect', effect.getNick());
dialog.getSettings().set_string('close-preview-effect', Effect.getNick()); dialog.getSettings().set_string('close-preview-effect', effect.getNick());
// Make sure that the window.show() firther below "sees" this change. // Make sure that the window.show() firther below "sees" this change.
Gio.Settings.sync(); Gio.Settings.sync();
@@ -508,7 +509,7 @@ var PreferencesDialog = class PreferencesDialog {
// Create the preview-window. // Create the preview-window.
const window = new Gtk.Window({ const window = new Gtk.Window({
// Translators: %s will be replaced by the effect's name. // Translators: %s will be replaced by the effect's name.
title: _('Preview for %s').replace('%s', Effect.getLabel()), title: _('Preview for %s').replace('%s', effect.getLabel()),
default_width: 800, default_width: 800,
default_height: 450, default_height: 450,
modal: true, modal: true,
+39 -107
View File
@@ -13,38 +13,56 @@
'use strict'; 'use strict';
const GObject = imports.gi.GObject;
const _ = imports.gettext.domain('burn-my-windows').gettext; const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect hides the actor by violently sucking it into the void of magic. // // This effect hides the actor by violently sucking it into the void of magic. //
// towards the middle and then hiding the resulting line from left and right towards //
// the center. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 Apparition = class Apparition { var Apparition = class Apparition {
// 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/<nick>.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._uSeed = shader.get_uniform_location('uSeed');
shader._uShake = shader.get_uniform_location('uShake');
shader._uTwirl = shader.get_uniform_location('uTwirl');
shader._uSuction = shader.get_uniform_location('uSuction');
shader._uRandomness = shader.get_uniform_location('uRandomness');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
// If we are performing an integration tests, we use a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
shader.set_uniform_float(shader._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
shader.set_uniform_float(shader._uShake, 1, [settings.get_double('apparition-shake-intensity')]);
shader.set_uniform_float(shader._uTwirl, 1, [settings.get_double('apparition-twirl-intensity')]);
shader.set_uniform_float(shader._uSuction, 1, [settings.get_double('apparition-suction-intensity')]);
shader.set_uniform_float(shader._uRandomness, 1, [settings.get_double('apparition-randomness')]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is not available on GNOME Shell 3.36 as it requires scaling of the window // The effect is not available on GNOME Shell 3.36 as it requires scaling of the window
// actor. // actor.
static getMinShellVersion() { getMinShellVersion() {
return [3, 38]; return [3, 38];
} }
@@ -52,22 +70,21 @@ var Apparition = class Apparition {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'apparition'; return 'apparition';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Apparition'); return _('Apparition');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Apparition.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Apparition.ui`);
@@ -85,95 +102,10 @@ var Apparition = class Apparition {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 2.0, y: 2.0}; return {x: 2.0, y: 2.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const Shell = imports.gi.Shell;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uSeed = this.get_uniform_location('uSeed');
this._uShake = this.get_uniform_location('uShake');
this._uTwirl = this.get_uniform_location('uTwirl');
this._uSuction = this.get_uniform_location('uSuction');
this._uRandomness = this.get_uniform_location('uRandomness');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uShake, 1, [settings.get_double('apparition-shake-intensity')]);
this.set_uniform_float(this._uTwirl, 1, [settings.get_double('apparition-twirl-intensity')]);
this.set_uniform_float(this._uSuction, 1, [settings.get_double('apparition-suction-intensity')]);
this.set_uniform_float(this._uRandomness, 1, [settings.get_double('apparition-randomness')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${Apparition.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
} }
+95 -161
View File
@@ -13,41 +13,114 @@
'use strict'; 'use strict';
const GObject = imports.gi.GObject;
const _ = imports.gettext.domain('burn-my-windows').gettext; const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect shatters the window into pieces. For an explanation how this works, look // // This effect shatters the window into pieces. For an explanation how this works, look //
// further down in this file into the documentation of the Shader class. This effect is // // further down in this file into the documentation of the Shader class. This effect is //
// not available on GNOME 3.3x, due to the limitation described in the documentation // // not available on GNOME 3.3x, due to the limitation described in the documentation //
// of vfunc_paint_target further down in this file. // // of vfunc_paint_target further down in this file. //
// This shader creates a complex-looking effect with rather simple means. Here is how //
// it works: The window is drawn five times on top of each other (see the SHARD_LAYERS //
// constant in the GLSL code). Each layer only draws some of the shards, all layers //
// combined make up the entire window. The layers are then scaled, rotated, and moved //
// independently from each other - this creates the impression that all shards are //
// moving independently. In reality, there are only five groups of shards! Which shard //
// belongs to which layer is defined by the green channel of the texture //
// 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. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let shardTexture = null;
// The effect class is completely static. It 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 BrokenGlass = class BrokenGlass { var BrokenGlass = class BrokenGlass {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import these modules in this function as they are not available in the
// preferences process. This callback is only called within GNOME Shell's process.
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
// Create the texture in the first call.
if (!this._shardTexture) {
const shardData = GdkPixbuf.Pixbuf.new_from_resource('/img/shards.png');
this._shardTexture = new Clutter.Image();
this._shardTexture.set_data(shardData.get_pixels(), Cogl.PixelFormat.RGB_888,
shardData.width, shardData.height,
shardData.rowstride);
}
// Store all uniform locations.
shader._uShardTexture = shader.get_uniform_location('uShardTexture');
shader._uSeed = shader.get_uniform_location('uSeed');
shader._uEpicenter = shader.get_uniform_location('uEpicenter');
shader._uShardScale = shader.get_uniform_location('uShardScale');
shader._uBlowForce = shader.get_uniform_location('uBlowForce');
shader._uGravity = shader.get_uniform_location('uGravity');
// And update all uniforms at the start of each animation.
shader.connect('begin-animation', (shader, settings, forOpening, actor) => {
// Usually, the shards fly away from the center of the window.
let epicenterX = 0.5;
let epicenterY = 0.5;
// However, if this option is set, we use the mouse pointer position.
if (!forOpening && settings.get_boolean('broken-glass-use-pointer')) {
const [x, y] = global.get_pointer();
const [ok, localX, localY] = actor.transform_stage_point(x, y);
if (ok) {
epicenterX = localX / actor.width;
epicenterY = localY / actor.height;
}
}
// If we are currently performing integration test, the animation uses a fixed
// seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
shader.set_uniform_float(shader._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
shader.set_uniform_float(shader._uEpicenter, 2, [epicenterX, epicenterY]);
shader.set_uniform_float(shader._uShardScale, 1, [settings.get_double('broken-glass-scale')]);
shader.set_uniform_float(shader._uBlowForce, 1, [settings.get_double('broken-glass-blow-force')]);
shader.set_uniform_float(shader._uGravity, 1, [settings.get_double('broken-glass-gravity')]);
// clang-format on
});
// This is required to bind the shard texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was
// called get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
shader.connect('update-animation', (shader) => {
const pipeline = shader.get_pipeline();
// Use linear filtering for the window texture.
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
Cogl.PipelineFilter.LINEAR);
// Bind the shard texture.
pipeline.set_layer_texture(1, this._shardTexture.get_texture());
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
pipeline.set_uniform_1i(shader._uShardTexture, 1);
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// This effect is only available on GNOME Shell 40+. // This effect is only available on GNOME Shell 40+.
static getMinShellVersion() { getMinShellVersion() {
return [40, 0]; return [40, 0];
} }
@@ -55,22 +128,21 @@ var BrokenGlass = class BrokenGlass {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'broken-glass'; return 'broken-glass';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Broken Glass'); return _('Broken Glass');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource('/ui/gtk4/BrokenGlass.ui'); dialog.getBuilder().add_from_resource('/ui/gtk4/BrokenGlass.ui');
@@ -88,148 +160,10 @@ var BrokenGlass = class BrokenGlass {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 2.0, y: 2.0}; return {x: 2.0, y: 2.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
shardTexture = null;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
// This shader creates a complex-looking effect with rather simple means. Here is how it
// works: The window is drawn five times on top of each other (see the SHARD_LAYERS
// constant in the GLSL code). Each layer only draws some of the shards, all layers
// combined make up the entire window. The layers are then scaled, rotated, and moved
// independently from each other - this creates the impression that all shards are
// moving independently. In reality, there are only five groups of shards! Which shard
// belongs to which layer is defined by the green channel of the texture
// 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.
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the shards texture.
if (shardTexture == null) {
const shardData = GdkPixbuf.Pixbuf.new_from_resource('/img/shards.png');
shardTexture = new Clutter.Image();
shardTexture.set_data(shardData.get_pixels(), Cogl.PixelFormat.RGB_888,
shardData.width, shardData.height, shardData.rowstride);
}
this._uShardTexture = this.get_uniform_location('uShardTexture');
this._uSeed = this.get_uniform_location('uSeed');
this._uEpicenter = this.get_uniform_location('uEpicenter');
this._uShardScale = this.get_uniform_location('uShardScale');
this._uBlowForce = this.get_uniform_location('uBlowForce');
this._uGravity = this.get_uniform_location('uGravity');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// Usually, the shards fly away from the center of the window.
let epicenterX = 0.5;
let epicenterY = 0.5;
// However, if this option is set, we use the mouse pointer position.
if (!forOpening && settings.get_boolean('broken-glass-use-pointer')) {
const [x, y] = global.get_pointer();
const [ok, localX, localY] = actor.transform_stage_point(x, y);
if (ok) {
epicenterX = localX / actor.width;
epicenterY = localY / actor.height;
}
}
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uEpicenter, 2, [epicenterX, epicenterY]);
this.set_uniform_float(this._uShardScale, 1, [settings.get_double('broken-glass-scale')]);
this.set_uniform_float(this._uBlowForce, 1, [settings.get_double('broken-glass-blow-force')]);
this.set_uniform_float(this._uGravity, 1, [settings.get_double('broken-glass-gravity')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${BrokenGlass.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
// This is overridden to bind the shard texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
// get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
vfunc_paint_target(node, paint_context) {
const pipeline = this.get_pipeline();
// Use linear filtering for the window texture.
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
Cogl.PipelineFilter.LINEAR);
// Bind the shard texture.
pipeline.set_layer_texture(1, shardTexture.get_texture());
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
pipeline.set_uniform_1i(this._uShardTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});
} }
+37 -96
View File
@@ -20,28 +20,48 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect looks a bit like the transporter effect from TOS. // // This effect looks a bit like the transporter effect from TOS. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 EnergizeA = class EnergizeA { var EnergizeA = class EnergizeA {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store uniform locations of newly created shaders.
shader._uColor = shader.get_uniform_location('uColor');
shader._uScale = shader.get_uniform_location('uScale');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
const c = Clutter.Color.from_string(settings.get_string('energize-a-color'))[1];
// clang-format off
shader.set_uniform_float(shader._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
shader.set_uniform_float(shader._uScale, 1, [settings.get_double('energize-a-scale')]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
@@ -49,22 +69,21 @@ var EnergizeA = class EnergizeA {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'energize-a'; return 'energize-a';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Energize A'); return _('Energize A');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/EnergizeA.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/EnergizeA.ui`);
@@ -80,88 +99,10 @@ var EnergizeA = class EnergizeA {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uColor = this.get_uniform_location('uColor');
this._uScale = this.get_uniform_location('uScale');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c = Clutter.Color.from_string(settings.get_string('energize-a-color'))[1];
// clang-format off
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
this.set_uniform_float(this._uScale, 1, [settings.get_double('energize-a-scale')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${EnergizeA.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
} }
+38 -96
View File
@@ -20,28 +20,49 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect looks a bit like the transporter effect from TNG. // // This effect looks a bit like the transporter effect from TNG. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 EnergizeB = class EnergizeB { var EnergizeB = class EnergizeB {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store uniform locations of newly created shaders.
shader._uColor = shader.get_uniform_location('uColor');
shader._uScale = shader.get_uniform_location('uScale');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
const c = Clutter.Color.from_string(settings.get_string('energize-b-color'))[1];
// clang-format off
shader.set_uniform_float(shader._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
shader.set_uniform_float(shader._uScale, 1, [settings.get_double('energize-b-scale')]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
@@ -49,22 +70,21 @@ var EnergizeB = class EnergizeB {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'energize-b'; return 'energize-b';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Energize B'); return _('Energize B');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/EnergizeB.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/EnergizeB.ui`);
@@ -80,88 +100,10 @@ var EnergizeB = class EnergizeB {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uColor = this.get_uniform_location('uColor');
this._uScale = this.get_uniform_location('uScale');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c = Clutter.Color.from_string(settings.get_string('energize-b-color'))[1];
// clang-format off
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
this.set_uniform_float(this._uScale, 1, [settings.get_double('energize-b-scale')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${EnergizeB.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
} }
+56 -115
View File
@@ -13,13 +13,14 @@
'use strict'; 'use strict';
const {Gio, GObject} = imports.gi; const {Gio} = imports.gi;
const _ = imports.gettext.domain('burn-my-windows').gettext; const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect is a homage to the good old Compiz days. However, it is implemented // // This effect is a homage to the good old Compiz days. However, it is implemented //
@@ -29,23 +30,58 @@ const utils = Me.imports.src.utils;
// there are a couple of moving gradients which fade-in or fade-out the fire effect. // // there are a couple of moving gradients which fade-in or fade-out the fire effect. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 Fire = class Fire { var Fire = class Fire {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store all uniform locations.
shader._uGradient = [
shader.get_uniform_location('uGradient1'),
shader.get_uniform_location('uGradient2'),
shader.get_uniform_location('uGradient3'),
shader.get_uniform_location('uGradient4'),
shader.get_uniform_location('uGradient5'),
];
shader._u3DNoise = shader.get_uniform_location('u3DNoise');
shader._uScale = shader.get_uniform_location('uScale');
shader._uMovementSpeed = shader.get_uniform_location('uMovementSpeed');
// And update all uniforms at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
for (let i = 1; i <= 5; i++) {
const c = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1];
shader.set_uniform_float(
shader._uGradient[i - 1], 4,
[c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
}
// clang-format off
shader.set_uniform_float(shader._u3DNoise, 1, [settings.get_boolean('flame-3d-noise')]);
shader.set_uniform_float(shader._uScale, 1, [settings.get_double('flame-scale')]);
shader.set_uniform_float(shader._uMovementSpeed, 1, [settings.get_double('flame-movement-speed')]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
@@ -53,22 +89,21 @@ var Fire = class Fire {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'fire'; return 'fire';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Fire'); return _('Fire');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Fire.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Fire.ui`);
@@ -94,7 +129,7 @@ var Fire = class Fire {
}); });
// Initialize the fire-preset dropdown. // Initialize the fire-preset dropdown.
Fire._createFirePresets(dialog); this._createFirePresets(dialog);
// Finally, return the new settings page. // Finally, return the new settings page.
return dialog.getBuilder().get_object('fire-prefs'); return dialog.getBuilder().get_object('fire-prefs');
@@ -102,40 +137,17 @@ var Fire = class Fire {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
// ----------------------------------------------------------------------- private stuff // ----------------------------------------------------------------------- private stuff
// This populates the preset dropdown menu for the fire options. // This populates the preset dropdown menu for the fire options.
static _createFirePresets(dialog) { _createFirePresets(dialog) {
dialog.getBuilder().get_object('fire-prefs').connect('realize', (widget) => { dialog.getBuilder().get_object('fire-prefs').connect('realize', (widget) => {
const presets = [ const presets = [
{ {
@@ -221,74 +233,3 @@ var Fire = class Fire {
}); });
} }
} }
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uGradient = [
this.get_uniform_location('uGradient1'),
this.get_uniform_location('uGradient2'),
this.get_uniform_location('uGradient3'),
this.get_uniform_location('uGradient4'),
this.get_uniform_location('uGradient5'),
];
this._u3DNoise = this.get_uniform_location('u3DNoise');
this._uScale = this.get_uniform_location('uScale');
this._uMovementSpeed = this.get_uniform_location('uMovementSpeed');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// Load the gradient values from the settings.
for (let i = 1; i <= 5; i++) {
const c = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1];
this.set_uniform_float(this._uGradient[i - 1], 4,
[c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
}
// clang-format off
this.set_uniform_float(this._u3DNoise, 1, [settings.get_boolean('flame-3d-noise')]);
this.set_uniform_float(this._uScale, 1, [settings.get_double('flame-scale')]);
this.set_uniform_float(this._uMovementSpeed, 1, [settings.get_double('flame-movement-speed')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${Fire.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
}
+54 -111
View File
@@ -20,29 +20,66 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect overlays a glowing hexagonal grid over the window. The grid cells then // // This effect overlays a glowing hexagonal grid over the window. The grid cells then //
// gradually shrink until the window is fully dissolved. // // gradually shrink until the window is fully dissolved. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 Hexagon = class Hexagon { var Hexagon = class Hexagon {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store uniform locations of newly created shaders.
shader._uAdditiveBlending = shader.get_uniform_location('uAdditiveBlending');
shader._uSeed = shader.get_uniform_location('uSeed');
shader._uScale = shader.get_uniform_location('uScale');
shader._uLineWidth = shader.get_uniform_location('uLineWidth');
shader._uGlowColor = shader.get_uniform_location('uGlowColor');
shader._uLineColor = shader.get_uniform_location('uLineColor');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
// Get the two configurable colors. They are directly injected into the shader
// code below.
const gc =
Clutter.Color.from_string(settings.get_string('hexagon-glow-color'))[1];
const lc =
Clutter.Color.from_string(settings.get_string('hexagon-line-color'))[1];
// If we are currently performing integration test, the animation uses a fixed
// seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
shader.set_uniform_float(shader._uAdditiveBlending, 1, [settings.get_boolean('hexagon-additive-blending')]);
shader.set_uniform_float(shader._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
shader.set_uniform_float(shader._uScale, 1, [settings.get_double('hexagon-scale')]);
shader.set_uniform_float(shader._uLineWidth, 1, [settings.get_double('hexagon-line-width')]);
shader.set_uniform_float(shader._uGlowColor, 4, [gc.red / 255, gc.green / 255, gc.blue / 255, gc.alpha / 255]);
shader.set_uniform_float(shader._uLineColor, 4, [lc.red / 255, lc.green / 255, lc.blue / 255, lc.alpha / 255]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
@@ -50,22 +87,21 @@ var Hexagon = class Hexagon {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'hexagon'; return 'hexagon';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Hexagon'); return _('Hexagon');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Hexagon.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Hexagon.ui`);
@@ -84,103 +120,10 @@ var Hexagon = class Hexagon {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uAdditiveBlending = this.get_uniform_location('uAdditiveBlending');
this._uSeed = this.get_uniform_location('uSeed');
this._uScale = this.get_uniform_location('uScale');
this._uLineWidth = this.get_uniform_location('uLineWidth');
this._uGlowColor = this.get_uniform_location('uGlowColor');
this._uLineColor = this.get_uniform_location('uLineColor');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// Get the two configurable colors. They are directly injected into the shader code
// below.
const gc = Clutter.Color.from_string(settings.get_string('hexagon-glow-color'))[1];
const lc = Clutter.Color.from_string(settings.get_string('hexagon-line-color'))[1];
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
this.set_uniform_float(this._uAdditiveBlending, 1, [settings.get_boolean('hexagon-additive-blending')]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uScale, 1, [settings.get_double('hexagon-scale')]);
this.set_uniform_float(this._uLineWidth, 1, [settings.get_double('hexagon-line-width')]);
this.set_uniform_float(this._uGlowColor, 4, [gc.red / 255, gc.green / 255, gc.blue / 255, gc.alpha / 255]);
this.set_uniform_float(this._uLineColor, 4, [lc.red / 255, lc.green / 255, lc.blue / 255, lc.alpha / 255]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${Hexagon.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
} }
+67 -132
View File
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The Matrix shader multiplies a grid of random letters with some gradients which are // // The Matrix shader multiplies a grid of random letters with some gradients which are //
@@ -29,26 +30,72 @@ const utils = Me.imports.src.utils;
// documentation of vfunc_paint_target further down in this file. // // documentation of vfunc_paint_target further down in this file. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let fontTexture = null;
// The effect class is completely static. It 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 Matrix = class Matrix { var Matrix = class Matrix {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import these modules in this function as they are not available in the
// preferences process. This callback is only called within GNOME Shell's process.
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
// Create the texture in the first call.
if (!this._fontTexture) {
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);
}
// Store uniform locations of newly created shaders.
shader._uFontTexture = shader.get_uniform_location('uFontTexture');
shader._uTrailColor = shader.get_uniform_location('uTrailColor');
shader._uTipColor = shader.get_uniform_location('uTipColor');
shader._uLetterSize = shader.get_uniform_location('uLetterSize');
shader._uRandomness = shader.get_uniform_location('uRandomness');
shader._uOverShoot = shader.get_uniform_location('uOverShoot');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
const c1 =
Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1];
const c2 = Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1];
// clang-format off
shader.set_uniform_float(shader._uTrailColor, 3, [c1.red / 255, c1.green / 255, c1.blue / 255]);
shader.set_uniform_float(shader._uTipColor, 3, [c2.red / 255, c2.green / 255, c2.blue / 255]);
shader.set_uniform_float(shader._uLetterSize, 1, [settings.get_int('matrix-scale')]);
shader.set_uniform_float(shader._uRandomness, 1, [settings.get_double('matrix-randomness')]);
shader.set_uniform_float(shader._uOverShoot, 1, [settings.get_double('matrix-overshoot')]);
// clang-format on
});
// This is required to bind the font texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was
// called get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
shader.connect('update-animation', (shader) => {
const pipeline = shader.get_pipeline();
// Bind the font texture.
pipeline.set_layer_texture(1, this._fontTexture.get_texture());
pipeline.set_uniform_1i(shader._uFontTexture, 1);
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// This effect is only available on GNOME Shell 40+. // This effect is only available on GNOME Shell 40+.
static getMinShellVersion() { getMinShellVersion() {
return [40, 0]; return [40, 0];
} }
@@ -56,22 +103,21 @@ var Matrix = class Matrix {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'matrix'; return 'matrix';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Matrix'); return _('Matrix');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource('/ui/gtk4/Matrix.ui'); dialog.getBuilder().add_from_resource('/ui/gtk4/Matrix.ui');
@@ -90,121 +136,10 @@ var Matrix = class Matrix {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0 + settings.get_double('matrix-overshoot')}; return {x: 1.0, y: 1.0 + settings.get_double('matrix-overshoot')};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
fontTexture = null;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the font texture.
if (fontTexture == null) {
const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png');
fontTexture = new Clutter.Image();
fontTexture.set_data(
fontData.get_pixels(),
fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888,
fontData.width, fontData.height, fontData.rowstride);
}
this._uFontTexture = this.get_uniform_location('uFontTexture');
this._uTrailColor = this.get_uniform_location('uTrailColor');
this._uTipColor = this.get_uniform_location('uTipColor');
this._uLetterSize = this.get_uniform_location('uLetterSize');
this._uRandomness = this.get_uniform_location('uRandomness');
this._uOverShoot = this.get_uniform_location('uOverShoot');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c1 = Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1];
const c2 = Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1];
// clang-format off
this.set_uniform_float(this._uTrailColor, 3, [c1.red / 255, c1.green / 255, c1.blue / 255]);
this.set_uniform_float(this._uTipColor, 3, [c2.red / 255, c2.green / 255, c2.blue / 255]);
this.set_uniform_float(this._uLetterSize, 1, [settings.get_int('matrix-scale')]);
this.set_uniform_float(this._uRandomness, 1, [settings.get_double('matrix-randomness')]);
this.set_uniform_float(this._uOverShoot, 1, [settings.get_double('matrix-overshoot')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time. The technique for this effect was inspired by
// https://www.shadertoy.com/view/ldccW4, however the implementation is quite
// different as the letters drop only once and there is no need for a noise texture.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${Matrix.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
// This is overridden to bind the font texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
// get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
vfunc_paint_target(node, paint_context) {
const pipeline = this.get_pipeline();
pipeline.set_layer_texture(1, fontTexture.get_texture());
pipeline.set_uniform_1i(this._uFontTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});
} }
+145
View File
@@ -0,0 +1,145 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
// Copyright (c) 2021 Simon Schneegans //
// Released under the GPLv3 or later. See LICENSE file for details. //
//////////////////////////////////////////////////////////////////////////////////////////
'use strict';
const {Gio, Shell, GObject, Clutter} = imports.gi;
const ByteArray = imports.byteArray;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils;
//////////////////////////////////////////////////////////////////////////////////////////
// This is the base class for all shaders of Burn-My-Windows. It automagically loads //
// the shader's source code from the resource file resources/shaders/<nick>.glsl and //
// ensures that some standard uniforms are always updated. //
// Using Shell.GLSLEffect as a base class has some benefits and some drawbacks. The //
// main benefit when compared to Clutter.ShaderEffect is that setting uniforms of types //
// vec2, vec3 or vec4 is supported via the API (with Clutter.ShaderEffect the GJS //
// binding does not work properly). However, there are two drawbacks: On the one hand, //
// the shader source code is cached statically - this mean if we want to have a //
// different shader, we have to derive a new class. Therefore, each effect has to //
// derive its own class from the class below. This is encapsulated in the //
// ShaderFactory, however it is some really awkward code. The other drawback is the //
// hard-coded use of straight alpha (as opposed to premultiplied). This makes the //
// shaders a bit more complicated than required. //
// //
// The Shader fires two signals: //
// * begin-animation: This is called each time a new animation is started. It can //
// be used to set uniform values which do not change during the //
// animation. //
// * update-animation: This is called at each frame during the animation. It can be //
// used to set uniforms which change during the animation. //
//////////////////////////////////////////////////////////////////////////////////////////
var Shader = GObject.registerClass(
{
Signals: {
'begin-animation':
{param_types: [Gio.Settings.$gtype, GObject.TYPE_BOOLEAN, Clutter.Actor.$gtype]},
'update-animation': {param_types: [GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE]}
}
},
class Shader extends Shell.GLSLEffect { // --------------------------------------------
// The constructor automagically loads the shader's source code (in
// vfunc_build_pipeline()) from the resource file resources/shaders/<nick>.glsl
// resolving any #includes in this file.
_init(nick) {
this._nick = nick;
// This will call vfunc_build_pipeline().
super._init();
// These will be updated during the animation.
this._progress = 0;
this._time = 0;
// Store standard uniform locations.
this._uForOpening = this.get_uniform_location('uForOpening');
this._uProgress = this.get_uniform_location('uProgress');
this._uTime = this.get_uniform_location('uTime');
this._uSize = this.get_uniform_location('uSize');
}
// This is called once each time the shader is used.
beginAnimation(settings, forOpening, actor) {
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
this.emit('begin-animation', settings, forOpening, actor);
}
// This is called at each frame during the animation.
updateAnimation(progress, time) {
this.set_uniform_float(this._uProgress, 1, [progress]);
this.set_uniform_float(this._uTime, 1, [time]);
// Store the current time and progress values. The corresponding signal is emitted a
// but later in vfunc_paint_target.
this._progress = progress;
this._time = time;
}
// This is called by the constructor. This means, it's only called when the
// effect is used for the first time.
vfunc_build_pipeline() {
// Shell.GLSLEffect requires the declarations and the main source code as separate
// strings. As it's more convenient to store the in one GLSL file, we use a regex
// here to split the source code in two parts.
const code = this._loadGLSLResource(`/shaders/${this._nick}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
// We use this vfunc to trigger the update as it allows calling this.get_pipeline() in
// the handler. This could still be null if called from the updateAnimation() above.
vfunc_paint_target(...params) {
this.emit('update-animation', this._progress, this._time);
super.vfunc_paint_target(...params);
}
// --------------------------------------------------------------------- private stuff
// This loads the file at 'path' contained in the extension's resources to a
// JavaScript string.
_loadStringResource(path) {
const data = Gio.resources_lookup_data(path, 0);
return ByteArray.toString(ByteArray.fromGBytes(data));
}
// This loads a GLSL file from the extension's resources to a JavaScript string. Any
// #include statements in this file are replaced with the corresponding file contents.
_loadGLSLResource(path) {
let code = this._loadStringResource(path);
// This regex matches either #include "..." or #include <...>. The part between the
// brackets is captured in the capture group.
const regex = RegExp('#include ["<](.+)[">]', 'g');
code = code.replace(regex, (m, file) => {
return this._loadStringResource('/shaders/' + file);
});
// Add a trailing newline. Else the GLSL compiler complains...
return code + '\n';
}
});
+95
View File
@@ -0,0 +1,95 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ 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 ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension();
//////////////////////////////////////////////////////////////////////////////////////////
// Each effect of Burn-My-Windows owns an instance of this class. It is used to created //
// shaders whenever a new one is required. It tries to re-use old shaders as much as //
// possible in order to avoid memory leaks. //
//////////////////////////////////////////////////////////////////////////////////////////
var ShaderFactory = class ShaderFactory {
// Creates a new ShaderFactory. Requires the nick of the effect and a callback function
// which will be called whenever a new shader is created.
constructor(nick, setupFunc) {
// The _freeShaders array contains previously created shaders which are not currently
// in use.
this._freeShaders = [];
// The nick of the effect is required when creating new shaders as it's part of the
// GLSL source code file name.
this._nick = nick;
// Store the setup callback.
this._setupFunc = setupFunc;
}
// ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed. It returns an
// instance of the shader class, trying to reuse previously created shaders. If a new
// shader instance is required, it calls creates a new one and calls the setupFunc
// thereafter.
getShader() {
let shader;
// If there are currently no free shaders, we have to create a new one.
if (this._freeShaders.length == 0) {
// Since Shell.GLSLEffect caches the shader source per class (not per instance), we
// have to derive a new shader type for each effect. The type name contains the nick
// of the effect to make it unique.
const typeName = `BurnMyWindowsShader_${this._nick}`;
// Only try to register the new type once.
if (GObject.type_from_name(typeName) == null) {
const outerThis = this;
GObject.registerClass({GTypeName: typeName},
class Shader extends Me.imports.src.Shader.Shader {
// This will actually load the GLSL source code from the resources.
_init() {
super._init(outerThis._nick);
}
// This can be called to mark this instance to be re-usable.
returnToFactory() {
outerThis._freeShaders.push(this);
}
});
}
// Now create a niew instance of the newly registered shader type.
// GObject.Object.new is only available with newer versions of GJS.
if (GObject.Object.new) {
shader = GObject.Object.new(GObject.type_from_name(typeName), {});
} else {
shader = GObject.Object.newv(GObject.type_from_name(typeName), []);
}
// Call the provided setup callback.
this._setupFunc(shader);
} else {
shader = this._freeShaders.pop();
}
return shader;
}
}
+69 -130
View File
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effects dissolves your windows into a cloud of dust. For this, it uses an // // This effects dissolves your windows into a cloud of dust. For this, it uses an //
@@ -31,26 +32,74 @@ const utils = Me.imports.src.utils;
// documentation of vfunc_paint_target further down in this file. // // documentation of vfunc_paint_target further down in this file. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let dustTexture = null;
// The effect class is completely static. It 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 SnapOfDisintegration = class SnapOfDisintegration { var SnapOfDisintegration = class SnapOfDisintegration {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import these modules in this function as they are not available in the
// preferences process. This callback is only called within GNOME Shell's process.
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
// Create the texture in the first call.
if (!this._dustTexture) {
const dustData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png');
this._dustTexture = new Clutter.Image();
this._dustTexture.set_data(dustData.get_pixels(), Cogl.PixelFormat.RGB_888,
dustData.width, dustData.height, dustData.rowstride);
}
// Store uniform locations of newly created shaders.
shader._uDustTexture = shader.get_uniform_location('uDustTexture');
shader._uDustColor = shader.get_uniform_location('uDustColor');
shader._uSeed = shader.get_uniform_location('uSeed');
shader._uDustScale = shader.get_uniform_location('uDustScale');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
// The dust particles will fade to this color over time.
const c = Clutter.Color.from_string(settings.get_string('snap-color'))[1];
// If we are currently performing integration test, the animation uses a fixed
// seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
shader.set_uniform_float(shader._uDustColor, 4, [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
shader.set_uniform_float(shader._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
shader.set_uniform_float(shader._uDustScale, 1, [settings.get_double('snap-scale')]);
// clang-format on
});
// This is required to bind the dust texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was
// called get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
shader.connect('update-animation', (shader) => {
const pipeline = shader.get_pipeline();
// Use linear filtering for the window texture.
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
Cogl.PipelineFilter.LINEAR);
// Bind the dust texture.
pipeline.set_layer_texture(1, this._dustTexture.get_texture());
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
pipeline.set_uniform_1i(shader._uDustTexture, 1);
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// This effect is only available on GNOME Shell 40+. // This effect is only available on GNOME Shell 40+.
static getMinShellVersion() { getMinShellVersion() {
return [40, 0]; return [40, 0];
} }
@@ -58,22 +107,21 @@ var SnapOfDisintegration = class SnapOfDisintegration {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'snap'; return 'snap';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Snap of Disintegration'); return _('Snap of Disintegration');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource('/ui/gtk4/SnapOfDisintegration.ui'); dialog.getBuilder().add_from_resource('/ui/gtk4/SnapOfDisintegration.ui');
@@ -89,119 +137,10 @@ var SnapOfDisintegration = class SnapOfDisintegration {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.2, y: 1.2}; return {x: 1.2, y: 1.2};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
dustTexture = null;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the dust texture.
if (dustTexture == null) {
const dustData = GdkPixbuf.Pixbuf.new_from_resource('/img/dust.png');
dustTexture = new Clutter.Image();
dustTexture.set_data(dustData.get_pixels(), Cogl.PixelFormat.RGB_888,
dustData.width, dustData.height, dustData.rowstride);
}
this._uDustTexture = this.get_uniform_location('uDustTexture');
this._uDustColor = this.get_uniform_location('uDustColor');
this._uSeed = this.get_uniform_location('uSeed');
this._uDustScale = this.get_uniform_location('uDustScale');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
// The dust particles will fade to this color over time.
const c = Clutter.Color.from_string(settings.get_string('snap-color'))[1];
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
this.set_uniform_float(this._uDustColor, 4, [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uDustScale, 1, [settings.get_double('snap-scale')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code =
utils.loadGLSLResource(`/shaders/${SnapOfDisintegration.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
// This is overridden to bind the dust texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
// get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
vfunc_paint_target(node, paint_context) {
const pipeline = this.get_pipeline();
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
Cogl.PipelineFilter.LINEAR);
pipeline.set_layer_texture(1, dustTexture.get_texture());
pipeline.set_layer_wrap_mode(1, Cogl.PipelineWrapMode.REPEAT);
pipeline.set_uniform_1i(this._uDustTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});
} }
+71 -130
View File
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect tears your windows apart with a series of violent scratches! // // This effect tears your windows apart with a series of violent scratches! //
@@ -27,26 +28,76 @@ const utils = Me.imports.src.utils;
// documentation of vfunc_paint_target further down in this file. // // documentation of vfunc_paint_target further down in this file. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// This texture will be loaded when the effect is used for the first time.
let clawTexture = null;
// The effect class is completely static. It 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 TRexAttack = class TRexAttack { var TRexAttack = class TRexAttack {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import these modules in this function as they are not available in the
// preferences process. This callback is only called within GNOME Shell's process.
const {Clutter, GdkPixbuf, Cogl} = imports.gi;
// Create the texture in the first call.
if (!this._clawTexture) {
const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png');
this._clawTexture = new Clutter.Image();
this._clawTexture.set_data(clawData.get_pixels(), Cogl.PixelFormat.RGB_888,
clawData.width, clawData.height, clawData.rowstride);
}
// Store uniform locations of newly created shaders.
shader._uClawTexture = shader.get_uniform_location('uClawTexture');
shader._uFlashColor = shader.get_uniform_location('uFlashColor');
shader._uSeed = shader.get_uniform_location('uSeed');
shader._uClawSize = shader.get_uniform_location('uClawSize');
shader._uNumClaws = shader.get_uniform_location('uNumClaws');
shader._uWarpIntensity = shader.get_uniform_location('uWarpIntensity');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
const c = Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1];
// If we are currently performing integration test, the animation uses a fixed
// seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
shader.set_uniform_float(shader._uFlashColor, 4, [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
shader.set_uniform_float(shader._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
shader.set_uniform_float(shader._uClawSize, 1, [settings.get_double('claw-scratch-scale')]);
shader.set_uniform_float(shader._uNumClaws, 1, [settings.get_int('claw-scratch-count')]);
shader.set_uniform_float(shader._uWarpIntensity, 1, [settings.get_double('claw-scratch-warp')]);
// clang-format on
});
// This is required to bind the claw texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was
// called get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
shader.connect('update-animation', (shader) => {
const pipeline = shader.get_pipeline();
// Use linear filtering for the window texture.
pipeline.set_layer_filters(0, Cogl.PipelineFilter.LINEAR,
Cogl.PipelineFilter.LINEAR);
// Bind the claw texture.
pipeline.set_layer_texture(1, this._clawTexture.get_texture());
pipeline.set_uniform_1i(shader._uClawTexture, 1);
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// This effect is only available on GNOME Shell 40+. // This effect is only available on GNOME Shell 40+.
static getMinShellVersion() { getMinShellVersion() {
return [40, 0]; return [40, 0];
} }
@@ -54,22 +105,21 @@ var TRexAttack = class TRexAttack {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'trex'; return 'trex';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('T-Rex Attack'); return _('T-Rex Attack');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource('/ui/gtk4/TRexAttack.ui'); dialog.getBuilder().add_from_resource('/ui/gtk4/TRexAttack.ui');
@@ -87,120 +137,11 @@ var TRexAttack = class TRexAttack {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
const scale = 1.0 + 0.5 * settings.get_double('claw-scratch-warp'); const scale = 1.0 + 0.5 * settings.get_double('claw-scratch-warp');
return {x: scale, y: scale}; return {x: scale, y: scale};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
clawTexture = null;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, GdkPixbuf, Cogl, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
// Load the claw texture.
if (clawTexture == null) {
const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png');
clawTexture = new Clutter.Image();
clawTexture.set_data(clawData.get_pixels(), Cogl.PixelFormat.RGB_888,
clawData.width, clawData.height, clawData.rowstride);
}
this._uClawTexture = this.get_uniform_location('uClawTexture');
this._uFlashColor = this.get_uniform_location('uFlashColor');
this._uSeed = this.get_uniform_location('uSeed');
this._uClawSize = this.get_uniform_location('uClawSize');
this._uNumClaws = this.get_uniform_location('uNumClaws');
this._uWarpIntensity = this.get_uniform_location('uWarpIntensity');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c = Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1];
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
this.set_uniform_float(this._uFlashColor, 4, [c.red / 255, c.green / 255, c.blue / 255, c.alpha / 255]);
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uClawSize, 1, [settings.get_double('claw-scratch-scale')]);
this.set_uniform_float(this._uNumClaws, 1, [settings.get_int('claw-scratch-count')]);
this.set_uniform_float(this._uWarpIntensity, 1, [settings.get_double('claw-scratch-warp')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${TRexAttack.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
// This is overridden to bind the claw texture for drawing. Sadly, this seems to be
// impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called
// get_target() back then but this is not wrapped in GJS.
// https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598
vfunc_paint_target(node, paint_context) {
const pipeline = this.get_pipeline();
pipeline.set_layer_texture(1, clawTexture.get_texture());
pipeline.set_uniform_1i(this._uClawTexture, 1);
super.vfunc_paint_target(node, paint_context);
}
});
} }
+33 -91
View File
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect hides the actor by making it first transparent from top and bottom // // This effect hides the actor by making it first transparent from top and bottom //
@@ -27,23 +28,38 @@ const utils = Me.imports.src.utils;
// the center. // // the center. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// The effect class is completely static. It 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 TVEffect = class TVEffect { var TVEffect = class TVEffect {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store uniform locations of newly created shaders.
shader._uColor = shader.get_uniform_location('uColor');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
const c = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1];
shader.set_uniform_float(shader._uColor, 3,
[c.red / 255, c.green / 255, c.blue / 255]);
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
@@ -51,22 +67,21 @@ var TVEffect = class TVEffect {
// required. It should match the prefix of the settings keys which store whether the // 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 // effect is enabled currently (e.g. '*-close-effect'), and its animation time
// (e.g. '*-animation-time'). // (e.g. '*-animation-time').
static getNick() { getNick() {
return 'tv'; return 'tv';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('TV Effect'); return _('TV Effect');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/TVEffect.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/TVEffect.ui`);
@@ -81,83 +96,10 @@ var TVEffect = class TVEffect {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uColor = this.get_uniform_location('uColor');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1];
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${TVEffect.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
} }
+43 -102
View File
@@ -20,52 +20,76 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils; const utils = Me.imports.src.utils;
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// This effect lets your windows be carried to the realm of dreams by some little // // This effect lets your windows be carried to the realm of dreams by some little //
// fairies. It's implemented with several overlaid grids of randomly moving points. // // fairies. It's implemented with several overlaid grids of randomly moving points. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect is registered further down in this file. When this // The effect class can be used to get some metadata (like the effect's name or supported
// effect is used for the first time, an instance of this shader class is created. Once // GNOME Shell versions), to initialize the respective page of the settings dialog, as
// the effect is finished, the shader will be stored in the freeShaders array and will // well as to create the actual shader for the effect.
// then be reused if a new shader is requested. ShaderClass which will be used whenever
// this effect is used.
let ShaderClass = null;
let freeShaders = [];
// 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').
var Wisps = class Wisps { var Wisps = class Wisps {
// 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/<nick>.glsl. The callback will be called for each
// newly created shader instance.
constructor() {
this.shaderFactory = new ShaderFactory(this.getNick(), (shader) => {
// We import Clutter in this function as it is not available in the preferences
// process. This creator function of the ShaderFactory is only called within GNOME
// Shell's process.
const Clutter = imports.gi.Clutter;
// Store uniform locations of newly created shaders.
shader._uSeed = shader.get_uniform_location('uSeed');
shader._uColor = shader.get_uniform_location('uColor');
shader._uScale = shader.get_uniform_location('uScale');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
const c = Clutter.Color.from_string(settings.get_string('wisps-color'))[1];
// If we are currently performing integration test, the animation uses a fixed
// seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
shader.set_uniform_float(shader._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
shader.set_uniform_float(shader._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
shader.set_uniform_float(shader._uScale, 1, [settings.get_double('wisps-scale')]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata // ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension. // The effect is available on all GNOME Shell versions supported by this extension.
static getMinShellVersion() { getMinShellVersion() {
return [3, 36]; return [3, 36];
} }
// This will be called in various places where a unique identifier for this effect is // 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 // required. It should match the prefix of the settings keys which store whether the
// effect is enabled currently (e.g. the '*-close-effect'). // effect is enabled currently (e.g. the '*-close-effect').
static getNick() { getNick() {
return 'wisps'; return 'wisps';
} }
// This will be shown in the sidebar of the preferences dialog as well as in the // 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. // drop-down menus where the user can choose the effect.
static getLabel() { getLabel() {
return _('Wisps'); return _('Wisps');
} }
// -------------------------------------------------------------------- API for prefs.js // -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog. It loads the settings page for this effect, // This is called by the preferences dialog. It loads the settings page for this effect,
// binds all properties to the settings and appends the page to the main stack of the // and binds all properties to the settings.
// preferences dialog. getPreferences(dialog) {
static getPreferences(dialog) {
// Add the settings page to the builder. // Add the settings page to the builder.
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Wisps.ui`); dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Wisps.ui`);
@@ -81,93 +105,10 @@ var Wisps = class Wisps {
// ---------------------------------------------------------------- API for extension.js // ---------------------------------------------------------------- API for extension.js
// This is called from extension.js whenever a window is opened or closed with this
// effect. It returns an instance of the shader class, trying to reuse previously
// created shaders.
static getShader(actor, settings, forOpening) {
let shader;
if (freeShaders.length == 0) {
shader = new ShaderClass();
} else {
shader = freeShaders.pop();
}
shader.setUniforms(actor, settings, forOpening);
return shader;
}
// The getActorScale() is called from extension.js to adjust the actor's size during the // 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 // animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+. // bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings) { getActorScale(settings) {
return {x: 1.0, y: 1.0}; return {x: 1.0, y: 1.0};
} }
// This is called from extension.js if the extension is disabled. This should free all
// static resources.
static cleanUp() {
freeShaders = [];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// The shader class for this effect will only be registered in GNOME Shell's process //
// (not in the preferences process). It's done this way as Clutter may not be installed //
// on the system and therefore the preferences would crash. //
//////////////////////////////////////////////////////////////////////////////////////////
if (utils.isInShellProcess()) {
const {Clutter, Shell} = imports.gi;
ShaderClass = GObject.registerClass({}, class ShaderClass extends Shell.GLSLEffect {
// This is called when the effect is used for the first time. This can be used to
// store all required uniform locations.
_init() {
super._init();
this._uSeed = this.get_uniform_location('uSeed');
this._uColor = this.get_uniform_location('uColor');
this._uScale = this.get_uniform_location('uScale');
}
// This is called each time the effect is used. This can be used to retrieve the
// configuration from the settings and update all uniforms accordingly.
setUniforms(actor, settings, forOpening) {
const c = Clutter.Color.from_string(settings.get_string('wisps-color'))[1];
// If we are currently performing integration test, the animation uses a fixed seed.
const testMode = settings.get_boolean('test-mode');
// clang-format off
this.set_uniform_float(this._uSeed, 2, [testMode ? 0 : Math.random(), testMode ? 0 : Math.random()]);
this.set_uniform_float(this._uColor, 3, [c.red / 255, c.green / 255, c.blue / 255]);
this.set_uniform_float(this._uScale, 1, [settings.get_double('wisps-scale')]);
// clang-format on
}
// This is called by extension.js when the shader is not used anymore. We will store
// this instance of the shader so that it can be re-used in th future.
free() {
freeShaders.push(this);
}
// This is called by the constructor. This means, it's only called when the effect
// is used for the first time.
vfunc_build_pipeline() {
const code = utils.loadGLSLResource(`/shaders/${Wisps.getNick()}.glsl`);
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
const declarations = code.substr(0, match.index);
const main = match[1];
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
});
} }
+1 -32
View File
@@ -13,8 +13,7 @@
'use strict'; 'use strict';
const {Gtk, Gio} = imports.gi; const {Gtk} = imports.gi;
const ByteArray = imports.byteArray;
// Returns the given argument, except for "alpha", "beta", and "rc". In these cases -3, // Returns the given argument, except for "alpha", "beta", and "rc". In these cases -3,
// -2, and -1 are returned respectively. // -2, and -1 are returned respectively.
@@ -66,12 +65,6 @@ function getGTKString() {
return isGTK4() ? 'gtk4' : 'gtk3'; return isGTK4() ? 'gtk4' : 'gtk3';
} }
// This method returns true if called in GNOME Shell's process, false if called in the
// preferences process.
function isInShellProcess() {
return window.global && global.stage;
}
// This method returns true if the current GNOME Shell version matches the given // This method returns true if the current GNOME Shell version matches the given
// arguments. // arguments.
function shellVersionIs(major, minor) { function shellVersionIs(major, minor) {
@@ -91,27 +84,3 @@ function shellVersionIsAtLeast(major, minor) {
return false; return false;
} }
// This loads the file at 'path' contained in the extension's resources to a JavaScript
// string.
function loadStringResource(path) {
const data = Gio.resources_lookup_data(path, 0);
return ByteArray.toString(ByteArray.fromGBytes(data));
}
// This loads a GLSL file from the extension's resources to a JavaScript string. Any
// #include statements in this file are replaced with the corresponding file contents.
function loadGLSLResource(path) {
let code = loadStringResource(path);
// This regex matches either #include "..." or #include <...>. The part between the
// brackets is captured in the capture group.
const regex = RegExp('#include ["<](.+)[">]', 'g');
code = code.replace(regex, (m, file) => {
return loadStringResource('/shaders/' + file);
});
// Add a trailing newline. Else the GLSL compiler complains...
return code + '\n';
}
+2 -4
View File
@@ -110,14 +110,12 @@ capture_effect() {
set_setting "open-preview-effect" "${1}" set_setting "open-preview-effect" "${1}"
set_setting "close-preview-effect" "${1}" set_setting "close-preview-effect" "${1}"
sleep 2
do_in_pod gnome-extensions prefs "${EXTENSION}" do_in_pod gnome-extensions prefs "${EXTENSION}"
sleep 2 sleep 3
capture "tests/references/${1}-open-${SESSION}-${FEDORA_VERSION}.png" capture "tests/references/${1}-open-${SESSION}-${FEDORA_VERSION}.png"
send_keystroke "Alt+F4" send_keystroke "Alt+F4"
sleep 2 sleep 3
capture "tests/references/${1}-close-${SESSION}-${FEDORA_VERSION}.png" capture "tests/references/${1}-close-${SESSION}-${FEDORA_VERSION}.png"
sleep 2
} }
# ----------------------------------------------------- wait for the container to start up # ----------------------------------------------------- wait for the container to start up
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Some files were not shown because too many files have changed in this diff Show More