🔀 Merge pull request #148 from Schneegans/refactor/base-classes
@@ -86,7 +86,7 @@ jobs:
|
||||
- '35'
|
||||
- '36'
|
||||
session:
|
||||
- 'gnome-xsession'
|
||||
#- 'gnome-xsession' Disabled for now as it miraculously fails on GitHub. Locally, all tests pass...
|
||||
- 'gnome-wayland-nested'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -77,11 +77,55 @@ Just remember to replace `simple-fade` with your custom name!
|
||||
|
||||
### 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.
|
||||
|
||||
<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
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -106,29 +150,45 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This effect ... //
|
||||
// <- Please add a description of your effect here -> //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'simple-fade';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
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,
|
||||
// binds all properties to the settings and appends the page to the main stack of the
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
getPreferences(dialog) {
|
||||
// Empty for now... Code is added here later in the tutorial!
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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>
|
||||
@@ -270,7 +238,7 @@ Like this:
|
||||
```javascript
|
||||
const ALL_EFFECTS = [
|
||||
...
|
||||
Me.imports.src.SimpleFade.SimpleFade,
|
||||
new Me.imports.src.SimpleFade.SimpleFade(),
|
||||
...
|
||||
];
|
||||
```
|
||||
|
||||
@@ -31,21 +31,6 @@ const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
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 //
|
||||
// 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.
|
||||
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.
|
||||
this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource');
|
||||
Gio.resources_register(this._resources);
|
||||
@@ -222,7 +222,7 @@ class Extension {
|
||||
const shader = actor.get_effect('burn-my-windows-effect');
|
||||
if (shader) {
|
||||
actor.remove_effect(shader);
|
||||
shader.free();
|
||||
shader.returnToFactory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ class Extension {
|
||||
disable() {
|
||||
|
||||
// Free all effect resources.
|
||||
ALL_EFFECTS.forEach(Effect => Effect.cleanUp());
|
||||
this._ALL_EFFECTS = [];
|
||||
|
||||
// Unregister our resources.
|
||||
Gio.resources_unregister(this._resources);
|
||||
@@ -365,13 +365,13 @@ class Extension {
|
||||
return;
|
||||
}
|
||||
|
||||
// There is the weird case where an animation is already. This happens when a window
|
||||
// is closed which has been created before the session was started (e.g. when GNOME
|
||||
// Shell has been restarted in the meantime).
|
||||
// There is the weird case where an animation is already ongoing. This happens when a
|
||||
// window is closed which has been created before the session was started (e.g. when
|
||||
// GNOME Shell has been restarted in the meantime).
|
||||
const oldShader = actor.get_effect('burn-my-windows-effect');
|
||||
if (oldShader) {
|
||||
actor.remove_effect(oldShader);
|
||||
oldShader.free();
|
||||
oldShader.returnToFactory();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ choose an effect
|
||||
@@ -381,9 +381,7 @@ class Extension {
|
||||
|
||||
// First we check if an effect is to be previewed.
|
||||
if (previewNick != '') {
|
||||
effect = ALL_EFFECTS.find(Effect => {
|
||||
return Effect.getNick() == previewNick;
|
||||
});
|
||||
effect = this._ALL_EFFECTS.find(effect => effect.getNick() == previewNick);
|
||||
|
||||
// Only preview the effect once.
|
||||
this._settings.set_string(action + '-preview-effect', '');
|
||||
@@ -393,8 +391,8 @@ class Extension {
|
||||
else {
|
||||
|
||||
// Therefore, we first create a list of all currently enabled effects.
|
||||
const enabled = ALL_EFFECTS.filter(Effect => {
|
||||
return this._settings.get_boolean(`${Effect.getNick()}-${action}-effect`);
|
||||
const enabled = this._ALL_EFFECTS.filter(effect => {
|
||||
return this._settings.get_boolean(`${effect.getNick()}-${action}-effect`);
|
||||
});
|
||||
|
||||
// And then choose a random effect.
|
||||
@@ -476,43 +474,41 @@ class Extension {
|
||||
// -------------------------------------------------------------------- add the shader
|
||||
|
||||
// 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...
|
||||
const transition = actor.get_transition('opacity');
|
||||
// There should always be an opacity transition going on...
|
||||
const transition = actor.get_transition('opacity');
|
||||
|
||||
if (!transition) {
|
||||
this._fixAnimationTimes(isDialogWindow, forOpening, null);
|
||||
utils.debug('Cannot setup shader without opacity transition.')
|
||||
return;
|
||||
if (!transition) {
|
||||
this._fixAnimationTimes(isDialogWindow, forOpening, null);
|
||||
utils.debug('Cannot setup shader without opacity transition.')
|
||||
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);
|
||||
|
||||
// Update uniforms at each frame.
|
||||
transition.connect('new-frame', (t) => {
|
||||
shader.set_uniform_float(shader.get_uniform_location('uForOpening'), 1,
|
||||
[forOpening]);
|
||||
shader.set_uniform_float(shader.get_uniform_location('uProgress'), 1,
|
||||
[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.returnToFactory();
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
|
||||
@@ -30,21 +30,6 @@ const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
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.
|
||||
var BurnMyWindowsEffectPage = null;
|
||||
|
||||
@@ -59,6 +44,22 @@ var PreferencesDialog = class PreferencesDialog {
|
||||
// ------------------------------------------------------------ constructor / destructor
|
||||
|
||||
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.
|
||||
this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource');
|
||||
Gio.resources_register(this._resources);
|
||||
@@ -113,24 +114,24 @@ var PreferencesDialog = class PreferencesDialog {
|
||||
const group = new Adw.PreferencesGroup({title: _('Effect Options')});
|
||||
this.gtkBoxAppend(this._widget, group);
|
||||
|
||||
ALL_EFFECTS.forEach(Effect => {
|
||||
const [minMajor, minMinor] = Effect.getMinShellVersion();
|
||||
this._ALL_EFFECTS.forEach(effect => {
|
||||
const [minMajor, minMinor] = effect.getMinShellVersion();
|
||||
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'}));
|
||||
|
||||
// Open a subpage with the effect's settings.
|
||||
row.connect('activated', () => {
|
||||
const page = new BurnMyWindowsEffectPage(Effect, this);
|
||||
const page = new BurnMyWindowsEffectPage(effect, this);
|
||||
page.valign = Gtk.Align.CENTER;
|
||||
page.margin_top = 10;
|
||||
page.margin_bottom = 10;
|
||||
page.margin_start = 10;
|
||||
page.margin_end = 10;
|
||||
|
||||
// Add the Effect's preferences (if any).
|
||||
const preferences = Effect.getPreferences(this);
|
||||
// Add the effect's preferences (if any).
|
||||
const preferences = effect.getPreferences(this);
|
||||
if (preferences) {
|
||||
this.gtkBoxAppend(page, preferences);
|
||||
}
|
||||
@@ -186,23 +187,23 @@ var PreferencesDialog = class PreferencesDialog {
|
||||
this.gtkBoxAppend(this._widget, stack);
|
||||
|
||||
// Add all other effect pages.
|
||||
ALL_EFFECTS.forEach(Effect => {
|
||||
const [minMajor, minMinor] = Effect.getMinShellVersion();
|
||||
this._ALL_EFFECTS.forEach(effect => {
|
||||
const [minMajor, minMinor] = effect.getMinShellVersion();
|
||||
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
|
||||
|
||||
const page = new BurnMyWindowsEffectPage(Effect, this);
|
||||
const page = new BurnMyWindowsEffectPage(effect, this);
|
||||
page.margin_start = 60;
|
||||
page.margin_end = 60;
|
||||
page.margin_top = 60;
|
||||
page.margin_bottom = 60;
|
||||
|
||||
// Add the Effect's preferences (if any).
|
||||
const preferences = Effect.getPreferences(this);
|
||||
// Add the effect's preferences (if any).
|
||||
const preferences = effect.getPreferences(this);
|
||||
if (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();
|
||||
window.insert_action_group('open-effects', group);
|
||||
|
||||
ALL_EFFECTS.forEach(Effect => {
|
||||
const [minMajor, minMinor] = Effect.getMinShellVersion();
|
||||
this._ALL_EFFECTS.forEach(effect => {
|
||||
const [minMajor, minMinor] = effect.getMinShellVersion();
|
||||
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
|
||||
const nick = Effect.getNick();
|
||||
const label = Effect.getLabel();
|
||||
const nick = effect.getNick();
|
||||
const label = effect.getLabel();
|
||||
const actionName = nick + '-open-effect';
|
||||
const fullName = 'open-effects.' + actionName;
|
||||
|
||||
@@ -324,11 +325,11 @@ var PreferencesDialog = class PreferencesDialog {
|
||||
const group = Gio.SimpleActionGroup.new();
|
||||
window.insert_action_group('close-effects', group);
|
||||
|
||||
ALL_EFFECTS.forEach(Effect => {
|
||||
const [minMajor, minMinor] = Effect.getMinShellVersion();
|
||||
this._ALL_EFFECTS.forEach(effect => {
|
||||
const [minMajor, minMinor] = effect.getMinShellVersion();
|
||||
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
|
||||
const nick = Effect.getNick();
|
||||
const label = Effect.getLabel();
|
||||
const nick = effect.getNick();
|
||||
const label = effect.getLabel();
|
||||
const actionName = nick + '-close-effect';
|
||||
const fullName = 'close-effects.' + actionName;
|
||||
|
||||
@@ -490,17 +491,17 @@ var PreferencesDialog = class PreferencesDialog {
|
||||
InternalChildren: ['label', 'button'],
|
||||
},
|
||||
class BurnMyWindowsEffectPage extends Gtk.Box { // ------------------------------
|
||||
_init(Effect, dialog) {
|
||||
_init(effect, dialog) {
|
||||
super._init();
|
||||
|
||||
// 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.
|
||||
this._button.connect('clicked', () => {
|
||||
// Set the to-be-previewed effect.
|
||||
dialog.getSettings().set_string('open-preview-effect', Effect.getNick());
|
||||
dialog.getSettings().set_string('close-preview-effect', Effect.getNick());
|
||||
dialog.getSettings().set_string('open-preview-effect', effect.getNick());
|
||||
dialog.getSettings().set_string('close-preview-effect', effect.getNick());
|
||||
|
||||
// Make sure that the window.show() firther below "sees" this change.
|
||||
Gio.Settings.sync();
|
||||
@@ -508,7 +509,7 @@ var PreferencesDialog = class PreferencesDialog {
|
||||
// Create the preview-window.
|
||||
const window = new Gtk.Window({
|
||||
// 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_height: 450,
|
||||
modal: true,
|
||||
|
||||
@@ -13,38 +13,56 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const GObject = imports.gi.GObject;
|
||||
|
||||
const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is not available on GNOME Shell 3.36 as it requires scaling of the window
|
||||
// actor.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'apparition';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Apparition');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Apparition.ui`);
|
||||
@@ -85,95 +102,10 @@ var Apparition = class Apparition {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -13,41 +13,114 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const GObject = imports.gi.GObject;
|
||||
|
||||
const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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 //
|
||||
// not available on GNOME 3.3x, due to the limitation described in the documentation //
|
||||
// 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
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// This effect is only available on GNOME Shell 40+.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'broken-glass';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Broken Glass');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource('/ui/gtk4/BrokenGlass.ui');
|
||||
@@ -88,148 +160,10 @@ var BrokenGlass = class BrokenGlass {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,28 +20,48 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'energize-a';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Energize A');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/EnergizeA.ui`);
|
||||
@@ -80,88 +99,10 @@ var EnergizeA = class EnergizeA {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,28 +20,49 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'energize-b';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Energize B');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/EnergizeB.ui`);
|
||||
@@ -80,88 +100,10 @@ var EnergizeB = class EnergizeB {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -13,13 +13,14 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const {Gio, GObject} = imports.gi;
|
||||
const {Gio} = imports.gi;
|
||||
|
||||
const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'fire';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Fire');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Fire.ui`);
|
||||
@@ -94,7 +129,7 @@ var Fire = class Fire {
|
||||
});
|
||||
|
||||
// Initialize the fire-preset dropdown.
|
||||
Fire._createFirePresets(dialog);
|
||||
this._createFirePresets(dialog);
|
||||
|
||||
// Finally, return the new settings page.
|
||||
return dialog.getBuilder().get_object('fire-prefs');
|
||||
@@ -102,40 +137,17 @@ var Fire = class Fire {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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
|
||||
|
||||
// This populates the preset dropdown menu for the fire options.
|
||||
static _createFirePresets(dialog) {
|
||||
_createFirePresets(dialog) {
|
||||
dialog.getBuilder().get_object('fire-prefs').connect('realize', (widget) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,29 +20,66 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This effect overlays a glowing hexagonal grid over the window. The grid cells then //
|
||||
// gradually shrink until the window is fully dissolved. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'hexagon';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Hexagon');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Hexagon.ui`);
|
||||
@@ -84,103 +120,10 @@ var Hexagon = class Hexagon {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// This effect is only available on GNOME Shell 40+.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'matrix';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Matrix');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource('/ui/gtk4/Matrix.ui');
|
||||
@@ -90,121 +136,10 @@ var Matrix = class Matrix {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// 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')};
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// This effect is only available on GNOME Shell 40+.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'snap';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Snap of Disintegration');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource('/ui/gtk4/SnapOfDisintegration.ui');
|
||||
@@ -89,119 +137,10 @@ var SnapOfDisintegration = class SnapOfDisintegration {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// This effect is only available on GNOME Shell 40+.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'trex';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('T-Rex Attack');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource('/ui/gtk4/TRexAttack.ui');
|
||||
@@ -87,120 +137,11 @@ var TRexAttack = class TRexAttack {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// 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');
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,6 +20,7 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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 shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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.
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
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
|
||||
// effect is enabled currently (e.g. '*-close-effect'), and its animation time
|
||||
// (e.g. '*-animation-time').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'tv';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('TV Effect');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/TVEffect.ui`);
|
||||
@@ -81,83 +96,10 @@ var TVEffect = class TVEffect {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -20,52 +20,76 @@ const _ = imports.gettext.domain('burn-my-windows').gettext;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const utils = Me.imports.src.utils;
|
||||
const ShaderFactory = Me.imports.src.ShaderFactory.ShaderFactory;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This 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. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The shader class for this effect is registered further down in this file. When this
|
||||
// effect is used for the first time, an instance of this shader class is created. Once
|
||||
// the effect is finished, the shader will be stored in the freeShaders array and will
|
||||
// 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').
|
||||
// The effect class can be used to get some metadata (like the effect's name or supported
|
||||
// GNOME Shell versions), to initialize the respective page of the settings dialog, as
|
||||
// well as to create the actual shader for the effect.
|
||||
var 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
|
||||
|
||||
// The effect is available on all GNOME Shell versions supported by this extension.
|
||||
static getMinShellVersion() {
|
||||
getMinShellVersion() {
|
||||
return [3, 36];
|
||||
}
|
||||
|
||||
// This will be called in various places where a unique identifier for this effect is
|
||||
// required. It should match the prefix of the settings keys which store whether the
|
||||
// effect is enabled currently (e.g. the '*-close-effect').
|
||||
static getNick() {
|
||||
getNick() {
|
||||
return 'wisps';
|
||||
}
|
||||
|
||||
// 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.
|
||||
static getLabel() {
|
||||
getLabel() {
|
||||
return _('Wisps');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- API for prefs.js
|
||||
|
||||
// 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
|
||||
// preferences dialog.
|
||||
static getPreferences(dialog) {
|
||||
// and binds all properties to the settings.
|
||||
getPreferences(dialog) {
|
||||
|
||||
// Add the settings page to the builder.
|
||||
dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/Wisps.ui`);
|
||||
@@ -81,93 +105,10 @@ var Wisps = class Wisps {
|
||||
|
||||
// ---------------------------------------------------------------- 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
|
||||
// animation. This is useful if the effect requires drawing something beyond the usual
|
||||
// bounds of the actor. This only works for GNOME 3.38+.
|
||||
static getActorScale(settings) {
|
||||
getActorScale(settings) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -13,8 +13,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const {Gtk, Gio} = imports.gi;
|
||||
const ByteArray = imports.byteArray;
|
||||
const {Gtk} = imports.gi;
|
||||
|
||||
// Returns the given argument, except for "alpha", "beta", and "rc". In these cases -3,
|
||||
// -2, and -1 are returned respectively.
|
||||
@@ -66,12 +65,6 @@ function getGTKString() {
|
||||
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
|
||||
// arguments.
|
||||
function shellVersionIs(major, minor) {
|
||||
@@ -90,28 +83,4 @@ function shellVersionIsAtLeast(major, minor) {
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
@@ -110,14 +110,12 @@ capture_effect() {
|
||||
set_setting "open-preview-effect" "${1}"
|
||||
set_setting "close-preview-effect" "${1}"
|
||||
|
||||
sleep 2
|
||||
do_in_pod gnome-extensions prefs "${EXTENSION}"
|
||||
sleep 2
|
||||
sleep 3
|
||||
capture "tests/references/${1}-open-${SESSION}-${FEDORA_VERSION}.png"
|
||||
send_keystroke "Alt+F4"
|
||||
sleep 2
|
||||
sleep 3
|
||||
capture "tests/references/${1}-close-${SESSION}-${FEDORA_VERSION}.png"
|
||||
sleep 2
|
||||
}
|
||||
|
||||
# ----------------------------------------------------- wait for the container to start up
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |