This commit is contained in:
Justin Garza
2025-01-14 05:29:43 -05:00
359 changed files with 14577 additions and 13559 deletions
+140 -136
View File
@@ -47,166 +47,170 @@ import * as utils from './utils.js';
// used to clean up any resources.
//////////////////////////////////////////////////////////////////////////////////////////
export var Shader = GObject.registerClass(
{
Signals: {
'begin-animation': {
param_types: [
Gio.Settings.$gtype, GObject.TYPE_BOOLEAN, GObject.TYPE_BOOLEAN,
Clutter.Actor.$gtype
]
},
'update-animation': {param_types: [GObject.TYPE_DOUBLE]},
'end-animation': {}
}
},
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;
export var Shader = GObject.registerClass({
Signals: {
'begin-animation': {
param_types: [
Gio.Settings.$gtype, GObject.TYPE_BOOLEAN, GObject.TYPE_BOOLEAN,
Clutter.Actor.$gtype
]
},
'update-animation': {param_types: [GObject.TYPE_DOUBLE]},
'end-animation': {}
}
},
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) {
log(`Shader initialized with params: ${JSON.stringify(nick)}`);
// This will call vfunc_build_pipeline().
super._init();
this._nick = nick;
// These will be updated during the animation.
this._progress = 0;
this._time = 0;
// This will call vfunc_build_pipeline().
super._init();
// Store standard uniform locations.
this._uForOpening = this.get_uniform_location('uForOpening');
this._uIsFullscreen = this.get_uniform_location('uIsFullscreen');
this._uProgress = this.get_uniform_location('uProgress');
this._uDuration = this.get_uniform_location('uDuration');
this._uSize = this.get_uniform_location('uSize');
this._uPadding = this.get_uniform_location('uPadding');
// These will be updated during the animation.
this._progress = 0;
this._time = 0;
// Create a timeline to drive the animation.
this._timeline = new Clutter.Timeline();
// Store standard uniform locations.
this._uForOpening = this.get_uniform_location('uForOpening');
this._uIsFullscreen = this.get_uniform_location('uIsFullscreen');
this._uProgress = this.get_uniform_location('uProgress');
this._uDuration = this.get_uniform_location('uDuration');
this._uSize = this.get_uniform_location('uSize');
this._uPadding = this.get_uniform_location('uPadding');
// Call updateAnimation() once a frame.
this._timeline.connect('new-frame', (t) => {
if (this._testMode) {
this.updateAnimation(0.5);
} else {
this.updateAnimation(t.get_progress());
}
});
// Create a timeline to drive the animation.
this._timeline = new Clutter.Timeline();
// Clean up if the animation finished or was interrupted.
this._timeline.connect('stopped', (t, finished) => {
this.endAnimation();
});
}
// This is called once each time the shader is used.
beginAnimation(settings, forOpening, testMode, duration, actor) {
if (this._timeline.is_playing()) {
this._timeline.stop();
// Call updateAnimation() once a frame.
this._timeline.connect('new-frame', (t) => {
if (this._testMode) {
this.updateAnimation(0.5);
} else {
this.updateAnimation(t.get_progress());
}
});
// On GNOME 3.36 this method was not yet available.
if (this._timeline.set_actor) {
this._timeline.set_actor(actor);
}
// Clean up if the animation finished or was interrupted.
this._timeline.connect('stopped', (t, finished) => {
// log(`Shader initialized with params: ${JSON.stringify(t)}`);
this.endAnimation();
});
}
this._timeline.set_duration(duration);
this._timeline.start();
// Make sure that no fullscreen window is drawn over our animations.
Meta.disable_unredirect_for_display(global.display);
global.begin_work();
// Reset progress value.
this._progress = 0;
this._testMode = testMode;
// This is not necessarily symmetric, but I haven't figured out a way to
// get the actual values...
const padding = (actor.width - actor.meta_window.get_frame_rect().width) / 2;
const isFullscreen =
actor.meta_window.get_maximized() === Meta.MaximizeFlags.BOTH ||
actor.meta_window.fullscreen;
this.set_uniform_float(this._uPadding, 1, [padding]);
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uIsFullscreen, 1, [isFullscreen]);
this.set_uniform_float(this._uDuration, 1, [duration * 0.001]);
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
this.emit('begin-animation', settings, forOpening, testMode, actor);
// This is called once each time the shader is used.
beginAnimation(settings, forOpening, testMode, duration, actor) {
if (this._timeline.is_playing()) {
this._timeline.stop();
}
// This is called at each frame during the animation.
updateAnimation(progress) {
// Store the current progress value. The corresponding signal is emitted each frame
// in vfunc_paint_target. We do not emit it here, as the pipeline which may be used
// by handlers must not have been created yet.
this._progress = progress;
this.queue_repaint();
// On GNOME 3.36 this method was not yet available.
if (this._timeline.set_actor) {
this._timeline.set_actor(actor);
}
// This will stop any running animation and emit the end-animation signal.
endAnimation() {
// This will call endAnimation() again, so we can return for now.
if (this._timeline.is_playing()) {
this._timeline.stop();
return;
}
this._timeline.set_duration(duration);
this._timeline.start();
// Restore unredirecting behavior for fullscreen windows.
Meta.enable_unredirect_for_display(global.display);
global.end_work();
// Make sure that no fullscreen window is drawn over our animations.
Meta.disable_unredirect_for_display(global.display);
global.begin_work();
this.emit('end-animation');
// Reset progress value.
this._progress = 0;
this._testMode = testMode;
// This is not necessarily symmetric, but I haven't figured out a way to
// get the actual values...
const padding = (actor.width - actor.meta_window.get_frame_rect().width) / 2;
const isFullscreen = actor.meta_window.get_maximized() === Meta.MaximizeFlags.BOTH ||
actor.meta_window.fullscreen;
this.set_uniform_float(this._uPadding, 1, [padding]);
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
this.set_uniform_float(this._uIsFullscreen, 1, [isFullscreen]);
this.set_uniform_float(this._uDuration, 1, [duration * 0.001]);
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
this.emit('begin-animation', settings, forOpening, testMode, actor);
}
// This is called at each frame during the animation.
updateAnimation(progress) {
// Store the current progress value. The corresponding signal is emitted each frame
// in vfunc_paint_target. We do not emit it here, as the pipeline which may be used
// by handlers must not have been created yet.
this._progress = progress;
this.queue_repaint();
}
// This will stop any running animation and emit the end-animation signal.
endAnimation() {
// This will call endAnimation() again, so we can return for now.
if (this._timeline.is_playing()) {
// log(`Shader initialized with params:
// ${JSON.stringify(this._timeline.is_playing())}`);
this._timeline.stop();
return;
}
// This is called by the constructor. This means, it's only called when the
// effect is used for the first time.
vfunc_build_pipeline() {
// Restore unredirecting behavior for fullscreen windows.
Meta.enable_unredirect_for_display(global.display);
global.end_work();
// 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._loadShaderResource(`/shaders/${this._nick}.frag`);
this.emit('end-animation');
}
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
// 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._loadShaderResource(`/shaders/${this._nick}.frag`);
const declarations = code.substr(0, match.index);
const main = match[1];
// Match anything between the curly brackets of "void main() {...}".
const regex = RegExp('void main *\\(\\) *\\{([\\S\\s]+)\\}');
const match = regex.exec(code);
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
const declarations = code.substr(0, match.index);
const main = match[1];
// 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.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, declarations, main, true);
}
// Starting with GNOME 44.2, the alpha channel is not written to by default. We need
// to undo this. It is a pity that we have to do this here, as it is not really
// required to be done each frame. But it's the only place where we can do it.
// https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2650
this.get_pipeline().set_blend(
'RGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))');
// 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.set_uniform_float(this._uProgress, 1, [this._progress]);
super.vfunc_paint_target(...params);
}
// Starting with GNOME 44.2, the alpha channel is not written to by default. We need
// to undo this. It is a pity that we have to do this here, as it is not really
// required to be done each frame. But it's the only place where we can do it.
// https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2650
this.get_pipeline().set_blend(
'RGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))');
// --------------------------------------------------------------------- private stuff
this.set_uniform_float(this._uProgress, 1, [this._progress]);
super.vfunc_paint_target(...params);
}
// This loads a GLSL file from the extension's resources to a JavaScript string. The
// code from "common.glsl" is prepended automatically.
_loadShaderResource(path) {
let common = utils.getStringResource('/shaders/common.glsl');
let code = utils.getStringResource(path);
// --------------------------------------------------------------------- private stuff
// Add a trailing newline. Else the GLSL compiler complains...
return common + '\n' + code + '\n';
}
});
// This loads a GLSL file from the extension's resources to a JavaScript string. The
// code from "common.glsl" is prepended automatically.
_loadShaderResource(path) {
let common = utils.getStringResource('/shaders/common.glsl');
let code = utils.getStringResource(path);
// Add a trailing newline. Else the GLSL compiler complains...
return common + '\n' + code + '\n';
}
});
+1 -1
View File
@@ -61,7 +61,7 @@ export default class ShaderFactory {
// Only try to register the new type once.
if (GObject.type_from_name(typeName) == null) {
const outerThis = this;
GObject.registerClass({GTypeName: typeName}, class ShaderImp extends Shader {
GObject.registerClass({GTypeName: typeName}, class ShaderImp extends Shader{
// This will actually load the GLSL source code from the resources.
_init() {
super._init(outerThis._nick);
+254
View File
@@ -0,0 +1,254 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
//////////////////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: Justin Garza <JGarza9788@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
'use strict';
import * as utils from '../utils.js';
// We import the ShaderFactory only in the Shell process as it is not required in the
// preferences process. The preferences process does not create any shader instances, it
// only uses the static metadata of the effect.
const ShaderFactory = await utils.importInShellOnly('./ShaderFactory.js');
const _ = await utils.importGettext();
//////////////////////////////////////////////////////////////////////////////////////////
// This effect was inspired by apple's new siri 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.
export default class Effect {
// 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(Effect.getNick(), (shader) => {
// Store uniform locations of newly created shaders.
shader._uColorSpeed = shader.get_uniform_location('uColorSpeed');
shader._uRandomColorOffset = shader.get_uniform_location('uRandomColorOffset');
shader._uColorOffset = shader.get_uniform_location('uColorOffset');
shader._uColorSaturation = shader.get_uniform_location('uColorSaturation');
shader._uEdgeSize = shader.get_uniform_location('uEdgeSize');
shader._uEdgeShape = shader.get_uniform_location('uEdgeShape');
shader._uEdgeHardness = shader.get_uniform_location('uEdgeHardness');
shader._uBlur = shader.get_uniform_location('uBlur');
shader._uFadeOut = shader.get_uniform_location('uFadeOut');
shader._uSeed = shader.get_uniform_location('uSeed');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
shader.set_uniform_float(shader._uColorSpeed, 1, [
settings.get_double('aura-glow-color-speed'),
]);
shader.set_uniform_float(shader._uRandomColorOffset, 1,
[settings.get_boolean('aura-glow-random-color')]);
shader.set_uniform_float(shader._uColorOffset, 1, [
settings.get_double('aura-glow-color-offset'),
]);
shader.set_uniform_float(shader._uColorSaturation, 1, [
settings.get_double('aura-glow-color-saturation'),
]);
shader.set_uniform_float(shader._uEdgeSize, 1, [
settings.get_double('aura-glow-edge-size'),
]);
shader.set_uniform_float(shader._uEdgeShape, 1, [
settings.get_double('aura-glow-edge-shape'),
]);
shader.set_uniform_float(shader._uEdgeHardness, 1, [
settings.get_double('aura-glow-edge-hardness'),
]);
shader.set_uniform_float(shader._uBlur, 1, [
settings.get_double('aura-glow-blur'),
]);
shader.set_uniform_float(shader._uFadeOut, 1, [
settings.get_double('aura-glow-fade-out'),
]);
// shader.set_uniform_float(shader._uLightTheme, 1, [
// Effect.getCurrentThemeMode(),
// ]);
// this will be used with a has function to get a random number
// clang-format off
shader.set_uniform_float(shader._uSeed, 2, [Math.random(), Math.random()]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension.
static 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. '*-enable-effect'), and its animation time
// (e.g. '*-animation-time'). Also, the shader file and the settings UI files should be
// named likes this.
static getNick() {
return 'aura-glow';
}
// 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() {
return _('Aura Glow');
}
// -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog whenever a new effect profile is loaded. It
// binds all user interface elements to the respective settings keys of the profile.
static bindPreferences(dialog) {
// Empty for now... Code is added here later in the tutorial!
dialog.bindAdjustment('aura-glow-animation-time');
dialog.bindAdjustment('aura-glow-color-speed');
dialog.bindSwitch('aura-glow-random-color');
dialog.bindAdjustment('aura-glow-color-offset');
dialog.bindAdjustment('aura-glow-color-saturation');
dialog.bindAdjustment('aura-glow-edge-size');
dialog.bindAdjustment('aura-glow-edge-shape');
dialog.bindAdjustment('aura-glow-edge-hardness');
dialog.bindAdjustment('aura-glow-blur');
dialog.bindAdjustment('aura-glow-fade-out');
// enable and disable the one slider
function EnableDisablePref(dialog, state) {
dialog.getBuilder()
.get_object('aura-glow-color-offset-scale')
.set_sensitive(!state);
}
// if we use a checkbox instead of a switch
/*
const randColor = dialog.getBuilder().get_object('aura-glow-random-color');
if (randColor) {
// Connect to the "toggled" signal to update preferences dynamically
randColor.connect('toggled', (widget) => {
const state = widget.get_active(); // Retrieve the state directly
EnableDisablePref(dialog, state); // Update sensitivity when the state changes
});
// Manually call the update function on startup, using the initial state of the
// switch
const initialState =
randColor.get_active(); // Get the initial state of the check button
EnableDisablePref(dialog, initialState);
} else {
// Log an error if the switch widget is not found in the UI
log('Error: \'aura-glow-random-color\' switch widget not found.');
}
*/
// if we use a switch instead of a checkbox
const switchWidget = dialog.getBuilder().get_object('aura-glow-random-color');
if (switchWidget) {
// Connect to the "state-set" signal to update preferences dynamically based on
// the switch state.
switchWidget.connect('state-set', (widget, state) => {
EnableDisablePref(dialog, state); // Update sensitivity when the state changes.
});
// Manually call the update function on startup, using the initial state of the
// switch.
const initialState =
switchWidget.get_active(); // Get the current state of the switch.
EnableDisablePref(dialog, initialState);
} else {
// Log an error if the switch widget is not found in the UI.
log('Error: \'aura-glow-random-color\' switch widget not found.');
}
// Retrieve the necessary objects
const colorOffset = dialog.getBuilder().get_object('aura-glow-color-offset-scale');
const actionRow = dialog.getBuilder().get_object('aura-glow-action-row');
if (colorOffset == null) {
log('colorOffset is null');
}
// Define an array of color names based on the slider value
const colorNames = [
'Red', // 0.00
'Reddish-Orange', // 0.05
'Orange', // 0.10
'Yellow-Orange', // 0.15
'Yellow-Green', // 0.20
'Lime Green', // 0.25
'Green', // 0.30
'Greenish-Cyan', // 0.35
'Aqua', // 0.40
'Light Cyan', // 0.45
'Cyan', // 0.50
'Sky Cyan', // 0.55
'Sky Blue', // 0.60
'Light Blue', // 0.65
'Blue', // 0.70
'Indigo', // 0.75
'Purple', // 0.80
'Magenta', // 0.85
'Pinkish-Red', // 0.90
'Crimson', // 0.95
'Red' // 1.00 (wraps around)
];
// Function to update the subtitle based on the slider value
const updateSubtitle = () => {
const value = colorOffset.get_value(); // Get the current slider value
const index = Math.round(value * 20); // Map value [0.0, 1.0] to index [0, 10]
actionRow.set_subtitle(
colorNames[index]); // Update subtitle with the corresponding color
};
// Connect the value-changed signal to the updateSubtitle function
// Connect the value-changed signal to the updateSubtitle function
colorOffset.connect('value-changed', updateSubtitle);
// Initialize the subtitle on load
updateSubtitle();
}
// ---------------------------------------------------------------- API for extension.js
// The getActorScale() is called from extension.js to adjust the actor's size during the
// animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings, forOpening, actor) {
return {x: 1.0, y: 1.0};
}
}
+11
View File
@@ -199,6 +199,17 @@ export default class Effect {
color3: 'rgba(207,235,255,0.84)',
color4: 'rgb(208,243,255)',
color5: 'rgb(255,255,255)'
},
// i don't think he'll notice if i sneak this in
{
name: _('Nuclear ☢️'),
scale: 1.5,
speed: 0.5,
color1: 'rgba(0,0,0,0)',
color2: 'rgba(2, 40, 0, 0.3)',
color3: 'rgba(0, 200, 50, 0.9)',
color4: 'rgba(255, 255, 0, 1.0)',
color5: 'rgba(255, 255, 255, 1.0)'
}
];
+389
View File
@@ -0,0 +1,389 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
//////////////////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: Justin Garza JGarza9788@gmail.com
// SPDX-License-Identifier: GPL-3.0-or-later
'use strict';
// Import the Gio module from the GNOME platform (GObject Introspection).
// This module provides APIs for I/O operations, settings management, and other core
// features.
import Gio from 'gi://Gio';
// Import utility functions from the local utils.js file.
// These utilities likely contain helper functions or shared logic used across the
// application.
import * as utils from '../utils.js';
// We import the ShaderFactory only in the Shell process as it is not required in the
// preferences process. The preferences process does not create any shader instances, it
// only uses the static metadata of the effect.
const ShaderFactory = await utils.importInShellOnly('./ShaderFactory.js');
const _ = await utils.importGettext();
//////////////////////////////////////////////////////////////////////////////////////////
// This effect is inspired by the old 8bit mario video games and the New Super Mario //
// Bros 2 specifically when mario gets the mushroom. i hope you enjoy this little blast //
// from the past. //
//////////////////////////////////////////////////////////////////////////////////////////
// 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.
export default class Effect {
// 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(Effect.getNick(), (shader) => {
// very basic effect... so nothing here
shader._uGradient = [
shader.get_uniform_location('uStarColor0'),
shader.get_uniform_location('uStarColor1'),
shader.get_uniform_location('uStarColor2'),
shader.get_uniform_location('uStarColor3'),
shader.get_uniform_location('uStarColor4'),
shader.get_uniform_location('uStarColor5'),
];
shader._uScaleStyle = shader.get_uniform_location('uScaleStyle');
shader._uSparkCount = shader.get_uniform_location('uSparkCount');
shader._uSparkColor = shader.get_uniform_location('uSparkColor');
shader._uSparkRotation = shader.get_uniform_location('uSparkRotation');
shader._uRaysColor = shader.get_uniform_location('uRaysColor');
shader._uRingCount = shader.get_uniform_location('uRingCount');
shader._uRingRotation = shader.get_uniform_location('uRingRotation');
shader._uStarCount = shader.get_uniform_location('uStarCount');
shader._uSeed = shader.get_uniform_location('uSeed');
// And update all uniforms at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
for (let i = 0; i <= 5; i++) {
shader.set_uniform_float(
shader._uGradient[i], 4,
utils.parseColor(settings.get_string('mushroom-star-color-' + i)));
}
// clang-format off
shader.set_uniform_float(shader._uScaleStyle, 1, [settings.get_double('mushroom-scale-style')]);
shader.set_uniform_float(shader._uSparkCount, 1, [settings.get_int('mushroom-spark-count')]);
shader.set_uniform_float(shader._uSparkColor, 4, utils.parseColor(settings.get_string('mushroom-spark-color')));
shader.set_uniform_float(shader._uSparkRotation, 1, [settings.get_double('mushroom-spark-rotation')]);
shader.set_uniform_float(shader._uRaysColor, 4, utils.parseColor(settings.get_string('mushroom-rays-color')));
shader.set_uniform_float(shader._uRingCount, 1, [settings.get_int('mushroom-ring-count')]);
shader.set_uniform_float(shader._uRingRotation, 1, [settings.get_double('mushroom-ring-rotation')]);
shader.set_uniform_float(shader._uStarCount, 1, [settings.get_int('mushroom-star-count')]);
shader.set_uniform_float(shader._uSeed, 2, [Math.random(), Math.random()]);
// clang-format on
});
});
}
// ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension.
static 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. '*-enable-effect'), and its animation time
// (e.g. '*-animation-time'). Also, the shader file and the settings UI files should be
// named likes this.
static getNick() {
return 'mushroom';
}
// 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() {
return _('Mushroom');
}
// -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog whenever a new effect profile is loaded. It
// binds all user interface elements to the respective settings keys of the profile.
static bindPreferences(dialog) {
// Empty for now... Code is added here later in the tutorial!
dialog.bindAdjustment('mushroom-animation-time');
// dialog.bindSwitch('mushroom-8bit-enable');
dialog.bindAdjustment('mushroom-scale-style');
dialog.bindAdjustment('mushroom-spark-count');
dialog.bindColorButton('mushroom-spark-color');
dialog.bindAdjustment('mushroom-spark-rotation');
dialog.bindColorButton('mushroom-rays-color');
dialog.bindAdjustment('mushroom-ring-count');
dialog.bindAdjustment('mushroom-ring-rotation');
dialog.bindAdjustment('mushroom-star-count');
dialog.bindColorButton('mushroom-star-color-0');
dialog.bindColorButton('mushroom-star-color-1');
dialog.bindColorButton('mushroom-star-color-2');
dialog.bindColorButton('mushroom-star-color-3');
dialog.bindColorButton('mushroom-star-color-4');
dialog.bindColorButton('mushroom-star-color-5');
// Ensure the button connections and other bindings happen only once,
// even if the bindPreferences function is called multiple times.
if (!Effect._isConnected) {
Effect._isConnected = true;
// Bind the "reset-star-colors" button to reset all star colors to their default
// values.
dialog.getBuilder().get_object('reset-star-colors').connect('clicked', () => {
// Reset each mushroom star color setting.
dialog.getProfileSettings().reset('mushroom-star-color-0');
dialog.getProfileSettings().reset('mushroom-star-color-1');
dialog.getProfileSettings().reset('mushroom-star-color-2');
dialog.getProfileSettings().reset('mushroom-star-color-3');
dialog.getProfileSettings().reset('mushroom-star-color-4');
dialog.getProfileSettings().reset('mushroom-star-color-5');
});
// Initialize the preset dropdown menu for mushroom star colors.
Effect._createMushroomPresets(dialog);
}
}
// ---------------------------------------------------------------- API for extension.js
// The getActorScale() is called from extension.js to adjust the actor's size during the
// animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings, forOpening, actor) {
return {x: 1.0, y: 1.0};
}
// ---------------------------------------------------------------- Presets
// This function initializes the preset dropdown menu for configuring fire options.
// It defines multiple color presets for the "mushroom star" effect and sets up
// the logic to apply these presets when selected.
static _createMushroomPresets(dialog) {
// Retrieve the builder object for the dialog and connect to the "realize" event of
// the button.
dialog.getBuilder()
.get_object('mushroom-star-color-preset-button')
.connect('realize', (widget) => {
// Define an array of color presets, each with a name and six color values (RGBA
// format).
const presets = [
{
name: _('8Bit Plumber (Default)'),
ScaleStyle: 0.0,
SparkCount: 0,
SparkColor: 'rgba(255,255,255,0.0)',
SparkRotation: 0.33,
RayColor: 'rgba(255,255,255,0.0)',
RingCount: 0,
RingRotation: 0.0,
StarCount: 0,
color0: 'rgba(233,249,0,1.0)',
color1: 'rgba(233,249,0,1.0)',
color2: 'rgba(91,255,0,1.0)',
color3: 'rgba(91,255,0,1.0)',
color4: 'rgba(0,240,236,1.0)',
color5: 'rgba(0,240,236,1.0)',
},
{
name: _('New Bros 2 🍄'),
ScaleStyle: 1.0,
SparkCount: 4,
SparkColor: 'rgba(255,255,255,1.0)',
SparkRotation: 0.3,
RayColor: 'rgba(255,255,255,1.0)',
RingCount: 3,
RingRotation: 1.33,
StarCount: 5,
color0: 'rgba(233,249,0,1.0)',
color1: 'rgba(233,249,0,1.0)',
color2: 'rgba(91,255,0,1.0)',
color3: 'rgba(91,255,0,1.0)',
color4: 'rgba(0,240,236,1.0)',
color5: 'rgba(0,240,236,1.0)',
},
{
name:
_('Red White and Blue 🇺🇸'), // A patriotic palette of red, white, and blue
ScaleStyle: 1.0,
SparkCount: 4,
SparkColor: 'rgba(255,255,255,1.0)',
SparkRotation: 0.3,
RayColor: 'rgba(255,255,255,0.0)',
RingCount: 3,
RingRotation: 1.33,
StarCount: 5,
color0: 'rgba(255, 0, 0, 1.0)',
color1: 'rgba(255, 0, 0, 1.0)',
color2: 'rgba(255,255,255, 1.0)',
color3: 'rgba(255,255,255, 1.0)',
color4: 'rgba(0,0,255, 1.0)',
color5: 'rgba(0,0,255, 1.0)'
},
{
name: _('Rainbow 🌈'), // A vivid rainbow spectrum of colors
ScaleStyle: 1.0,
SparkCount: 4,
SparkColor: 'rgba(255,255,255,1.0)',
SparkRotation: 0.3,
RayColor: 'rgba(255,255,255,0.0)',
RingCount: 3,
RingRotation: 2.0,
StarCount: 7,
color0: 'rgba(255, 69, 58, 1.0)', // Bold Red
color1: 'rgba(255, 140, 0, 1.0)', // Bold Orange
color2: 'rgba(255, 223, 0, 1.0)', // Bold Yellow
color3: 'rgba(50, 205, 50, 1.0)', // Bold Green
color4: 'rgba(30, 144, 255, 1.0)', // Bold Blue
color5: 'rgba(148, 0, 211, 1.0)' // Bold Purple
},
{
name: _('Cattuccino 😺'), // A soft pastel palette inspired by a
// cappuccino theme
ScaleStyle: 1.0,
SparkCount: 4,
SparkColor: 'rgba(255,255,255,1.0)',
SparkRotation: 0.3,
RayColor: 'rgba(255,255,255,0.0)',
RingCount: 3,
RingRotation: 2.0,
StarCount: 7,
color0: 'rgba(239, 146, 160, 1.0)',
color1: 'rgba(246, 178, 138, 1.0)',
color2: 'rgba(240, 217, 169, 1.0)',
color3: 'rgba(175, 223, 159, 1.0)',
color4: 'rgba(149, 182, 246, 1.0)',
color5: 'rgba(205, 170, 247, 1.0)'
},
{
name: _('Dracula 🦇'), // A dark palette inspired by the Dracula theme
ScaleStyle: 1.0,
SparkCount: 0,
SparkColor: 'rgba(255,255,255,1.0)',
SparkRotation: 0.3,
RayColor: 'rgba(255,255,255,0.0)',
RingCount: 3,
RingRotation: 2.0,
StarCount: 7,
color0: 'rgba(40, 42, 54, 1.0)', // Dark Grey
color1: 'rgba(68, 71, 90, 1.0)', // Medium Grey
color2: 'rgba(90, 94, 119, 1.0)', // Light Grey
color3: 'rgba(90, 94, 119, 1.0)', // Light Grey
color4: 'rgba(68, 71, 90, 1.0)', // Medium Grey
color5: 'rgba(40, 42, 54, 1.0)' // Dark Grey
},
{
name: _('Sparkle ✨'), // A dark palette inspired by the Dracula theme
ScaleStyle: 1.0,
SparkCount: 25,
SparkColor: 'rgba(255,255,255,1.0)',
SparkRotation: 0.3,
RayColor: 'rgba(255,255,255,0.0)',
RingCount: 0,
RingRotation: 1.33,
StarCount: 7,
color0: 'rgba(255,255,255,0.0)',
color1: 'rgba(255,255,255,0.0)',
color2: 'rgba(255,255,255,0.0)',
color3: 'rgba(255,255,255,0.0)',
color4: 'rgba(255,255,255,0.0)',
color5: 'rgba(255,255,255,0.0)',
}
];
// Create a new menu model to hold the presets and an action group for handling
// selections.
const menu = Gio.Menu.new();
const group = Gio.SimpleActionGroup.new();
const groupName = 'presets';
// Iterate over the presets to populate the menu.
presets.forEach((preset, i) => {
// Define an action name based on the preset index.
const actionName = 'mushroom' + i;
// Append the preset name to the menu.
menu.append(preset.name, groupName + '.' + actionName);
// Create a new action for the preset.
let action = Gio.SimpleAction.new(actionName, null);
// Connect the action to a function that loads the preset colors into the
// dialog.
action.connect('activate', () => {
dialog.getProfileSettings().set_double('mushroom-scale-style',
preset.ScaleStyle);
dialog.getProfileSettings().set_int('mushroom-spark-count',
preset.SparkCount);
dialog.getProfileSettings().set_string('mushroom-spark-color',
preset.SparkColor);
dialog.getProfileSettings().set_double('mushroom-spark-rotation',
preset.SparkRotation);
dialog.getProfileSettings().set_string('mushroom-rays-color',
preset.RayColor);
dialog.getProfileSettings().set_int('mushroom-ring-count', preset.RingCount);
dialog.getProfileSettings().set_double('mushroom-ring-rotation',
preset.RingRotation);
dialog.getProfileSettings().set_int('mushroom-star-count', preset.StarCount);
dialog.getProfileSettings().set_string('mushroom-star-color-0',
preset.color0);
dialog.getProfileSettings().set_string('mushroom-star-color-1',
preset.color1);
dialog.getProfileSettings().set_string('mushroom-star-color-2',
preset.color2);
dialog.getProfileSettings().set_string('mushroom-star-color-3',
preset.color3);
dialog.getProfileSettings().set_string('mushroom-star-color-4',
preset.color4);
dialog.getProfileSettings().set_string('mushroom-star-color-5',
preset.color5);
});
// Add the action to the action group.
group.add_action(action);
});
// Assign the populated menu to the preset button.
dialog.getBuilder()
.get_object('mushroom-star-color-preset-button')
.set_menu_model(menu);
// Insert the action group into the root widget for handling the presets.
const root = widget.get_root();
root.insert_action_group(groupName, group);
});
}
}
+117
View File
@@ -0,0 +1,117 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
//////////////////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: Justin Garza JGarza9788@gmail.com
// SPDX-License-Identifier: GPL-3.0-or-later
'use strict';
import * as utils from '../utils.js';
// We import the ShaderFactory only in the Shell process as it is not required in the
// preferences process. The preferences process does not create any shader instances, it
// only uses the static metadata of the effect.
const ShaderFactory = await utils.importInShellOnly('./ShaderFactory.js');
const _ = await utils.importGettext();
//////////////////////////////////////////////////////////////////////////////////////////
// This effect warps the Red Green Blue values //
//////////////////////////////////////////////////////////////////////////////////////////
// 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.
export default class Effect {
// 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(Effect.getNick(), (shader) => {
// Store uniform locations of newly created shaders.
shader._uWavesize = shader.get_uniform_location('uWavesize');
shader._uBrightness = shader.get_uniform_location('uBrightness');
shader._uStretchR = shader.get_uniform_location('uStretchR');
shader._uStretchG = shader.get_uniform_location('uStretchG');
shader._uStretchB = shader.get_uniform_location('uStretchB');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
shader.set_uniform_float(shader._uWavesize, 1, [
settings.get_double('rgbwarp-wavesize'),
]);
shader.set_uniform_float(shader._uBrightness, 1, [
settings.get_double('rgbwarp-brightness'),
]);
shader.set_uniform_float(shader._uStretchR, 1, [
settings.get_double('rgbwarp-offset-r'),
]);
shader.set_uniform_float(shader._uStretchG, 1, [
settings.get_double('rgbwarp-offset-g'),
]);
shader.set_uniform_float(shader._uStretchB, 1, [
settings.get_double('rgbwarp-offset-b'),
]);
});
});
}
// ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension.
static 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. '*-enable-effect'), and its animation time
// (e.g. '*-animation-time'). Also, the shader file and the settings UI files should be
// named likes this.
static getNick() {
return 'rgbwarp';
}
// 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() {
return _('RGB Warp Effect');
}
// -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog whenever a new effect profile is loaded. It
// binds all user interface elements to the respective settings keys of the profile.
static bindPreferences(dialog) {
// Empty for now... Code is added here later in the tutorial!
dialog.bindAdjustment('rgbwarp-animation-time');
dialog.bindAdjustment('rgbwarp-wavesize');
dialog.bindAdjustment('rgbwarp-brightness');
dialog.bindAdjustment('rgbwarp-offset-r');
dialog.bindAdjustment('rgbwarp-offset-g');
dialog.bindAdjustment('rgbwarp-offset-b');
}
// ---------------------------------------------------------------- API for extension.js
// The getActorScale() is called from extension.js to adjust the actor's size during the
// animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings, forOpening, actor) {
return {x: 1.0, y: 1.0};
}
}
+131
View File
@@ -0,0 +1,131 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
//////////////////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: Justin Garza JGarza9788@gmail.com
// SPDX-License-Identifier: GPL-3.0-or-later
'use strict';
import * as utils from '../utils.js';
// We import the ShaderFactory only in the Shell process as it is not required in the
// preferences process. The preferences process does not create any shader instances, it
// only uses the static metadata of the effect.
const ShaderFactory = await utils.importInShellOnly('./ShaderFactory.js');
const _ = await utils.importGettext();
//////////////////////////////////////////////////////////////////////////////////////////
// This effect was inspired by Team Rocket Blasting Off again! //
// https://c.tenor.com/0ag0MVXUaQEAAAAd/tenor.gif //
//////////////////////////////////////////////////////////////////////////////////////////
// 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.
export default class Effect {
// 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(Effect.getNick(), (shader) => {
// Store uniform locations of newly created shaders.
shader._uWT = shader.get_uniform_location('uWT');
shader._uXpos = shader.get_uniform_location('uXpos');
shader._uYpos = shader.get_uniform_location('uYpos');
shader._uWinRot = shader.get_uniform_location('uWinRot');
shader._uSparkleRot = shader.get_uniform_location('uSparkleRot');
shader._uSparkleSize = shader.get_uniform_location('uSparkleSize');
shader._uSeed = shader.get_uniform_location('uSeed');
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
shader.set_uniform_float(shader._uWT, 1, [
settings.get_double('team-rocket-window-time'),
]);
shader.set_uniform_float(shader._uXpos, 1, [
settings.get_double('team-rocket-x'),
]);
shader.set_uniform_float(shader._uYpos, 1, [
settings.get_double('team-rocket-y'),
]);
shader.set_uniform_float(shader._uWinRot, 1,
[settings.get_boolean('team-rocket-win-rot')]);
shader.set_uniform_float(shader._uSparkleRot, 1, [
settings.get_double('team-rocket-sparkle-rot'),
]);
shader.set_uniform_float(shader._uSparkleSize, 1, [
settings.get_double('team-rocket-sparkle-size'),
]);
// this will be used with a has function to get a random number
// clang-format off
shader.set_uniform_float(shader._uSeed, 2, [Math.random(), Math.random()]);
});
});
}
// ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension.
static 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. '*-enable-effect'), and its animation time
// (e.g. '*-animation-time'). Also, the shader file and the settings UI files should be
// named likes this.
static getNick() {
return 'team-rocket';
}
// 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() {
return _('TeamRocket');
}
// -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog whenever a new effect profile is loaded. It
// binds all user interface elements to the respective settings keys of the profile.
static bindPreferences(dialog) {
// Empty for now... Code is added here later in the tutorial!
dialog.bindAdjustment('team-rocket-animation-time');
dialog.bindAdjustment('team-rocket-window-time');
dialog.bindAdjustment('team-rocket-x');
dialog.bindAdjustment('team-rocket-y');
dialog.bindSwitch('team-rocket-win-rot');
dialog.bindAdjustment('team-rocket-sparkle-rot');
dialog.bindAdjustment('team-rocket-sparkle-size');
}
// ---------------------------------------------------------------- API for extension.js
// The getActorScale() is called from extension.js to adjust the actor's size during the
// animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings, forOpening, actor) {
return {x: 1.0, y: 1.0};
}
}
+471
View File
@@ -0,0 +1,471 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
// |__/ //
//////////////////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: Justin Garza JGarza9788@gmail.com
// SPDX-License-Identifier: GPL-3.0-or-later
'use strict';
// Import the Gio module from the GNOME platform (GObject Introspection).
// This module provides APIs for I/O operations, settings management, and other core
// features.
import Gio from 'gi://Gio';
// Import utility functions from the local utils.js file.
// These utilities likely contain helper functions or shared logic used across the
// application.
import * as utils from '../utils.js';
// We import the ShaderFactory only in the Shell process as it is not required in the
// preferences process. The preferences process does not create any shader instances, it
// only uses the static metadata of the effect.
const ShaderFactory = await utils.importInShellOnly('./ShaderFactory.js');
const _ = await utils.importGettext();
//////////////////////////////////////////////////////////////////////////////////////////
// I thought to myself, i said "self what would a unicorn fart look like" and then i came
// up with this, and i don't care that i'm out of the box, i think outside of the box,
// yeah f*ck that box!
//////////////////////////////////////////////////////////////////////////////////////////
// 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.
export default class Effect {
// 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(Effect.getNick(), (shader) => {
// Store uniform locations of newly created shaders.
// shader._uParticles = shader.get_uniform_location('uParticles');
// shader._uParticleSize = shader.get_uniform_location('uParticleSize');
// shader._uParticleSpeed = shader.get_uniform_location('uParticleSpeed');
// shader._uParticleColorRandom =
// shader.get_uniform_location('uParticleColorRandom'); shader._uParticleColorSpeed
// = shader.get_uniform_location('uParticleColorSpeed');
shader._uSparkCount = shader.get_uniform_location('uSparkCount');
shader._uSparkStartEnd = shader.get_uniform_location('uSparkStartEnd');
shader._uSparkOffset = shader.get_uniform_location('uSparkOffset');
shader._uSparkCount = shader.get_uniform_location('uSparkCount');
shader._uStarCount = shader.get_uniform_location('uStarCount');
shader._uStarRot = shader.get_uniform_location('uStarRot');
shader._uStarSize = shader.get_uniform_location('uStarSize');
shader._uStarStartEnd = shader.get_uniform_location('uStarStartEnd');
shader._uBlurQuality = shader.get_uniform_location('uBlurQuality');
shader._uSeed = shader.get_uniform_location('uSeed');
shader._uStarColors = [
shader.get_uniform_location('uStarColor0'),
shader.get_uniform_location('uStarColor1'),
shader.get_uniform_location('uStarColor2'),
shader.get_uniform_location('uStarColor3'),
shader.get_uniform_location('uStarColor4'),
shader.get_uniform_location('uStarColor5'),
];
shader._uParticleColors = [
shader.get_uniform_location('uSparkColor0'),
shader.get_uniform_location('uSparkColor1'),
shader.get_uniform_location('uSparkColor2'),
shader.get_uniform_location('uSparkColor3'),
shader.get_uniform_location('uSparkColor4'),
shader.get_uniform_location('uSparkColor5'),
];
// Write all uniform values at the start of each animation.
shader.connect('begin-animation', (shader, settings) => {
for (let i = 0; i <= 5; i++) {
shader.set_uniform_float(
shader._uStarColors[i], 4,
utils.parseColor(settings.get_string('unicorn-fart-star-color-' + i)));
}
for (let i = 0; i <= 5; i++) {
shader.set_uniform_float(
shader._uParticleColors[i], 4,
utils.parseColor(settings.get_string('unicorn-fart-spark-color-' + i)));
}
shader.set_uniform_float(shader._uSparkCount, 1,
[settings.get_int('unicorn-fart-spark-count')]);
shader.set_uniform_float(shader._uSparkStartEnd, 2, [
settings.get_double('unicorn-fart-spark-start'),
settings.get_double('unicorn-fart-spark-end')
]);
shader.set_uniform_float(shader._uSparkOffset, 1,
[settings.get_double('unicorn-fart-spark-offset')]);
shader.set_uniform_float(shader._uStarCount, 1,
[settings.get_int('unicorn-fart-star-count')]);
shader.set_uniform_float(shader._uStarRot, 1,
[settings.get_double('unicorn-fart-star-rotation')]);
shader.set_uniform_float(shader._uStarSize, 1,
[settings.get_double('unicorn-fart-star-size')]);
shader.set_uniform_float(shader._uStarStartEnd, 2, [
settings.get_double('unicorn-fart-star-start'),
settings.get_double('unicorn-fart-star-end')
]);
shader.set_uniform_float(shader._uBlurQuality, 1,
[settings.get_double('unicorn-fart-blur-quality')]);
shader.set_uniform_float(shader._uSeed, 1, [Math.random()]);
});
});
}
// ---------------------------------------------------------------------------- metadata
// The effect is available on all GNOME Shell versions supported by this extension.
static 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. '*-enable-effect'), and its animation time
// (e.g. '*-animation-time'). Also, the shader file and the settings UI files should be
// named likes this.
static getNick() {
return 'unicorn-fart';
}
// 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() {
return _('UnicornFart');
}
// -------------------------------------------------------------------- API for prefs.js
// This is called by the preferences dialog whenever a new effect profile is loaded. It
// binds all user interface elements to the respective settings keys of the profile.
static bindPreferences(dialog) {
// Empty for now... Code is added here later in the tutorial!
dialog.bindAdjustment('unicorn-fart-animation-time');
dialog.bindAdjustment('unicorn-fart-spark-count');
dialog.bindAdjustment('unicorn-fart-spark-start');
dialog.bindAdjustment('unicorn-fart-spark-end');
dialog.bindAdjustment('unicorn-fart-spark-offset');
dialog.bindAdjustment('unicorn-fart-star-count');
dialog.bindAdjustment('unicorn-fart-star-rotation');
dialog.bindAdjustment('unicorn-fart-star-size');
dialog.bindAdjustment('unicorn-fart-star-start');
dialog.bindAdjustment('unicorn-fart-star-end');
dialog.bindAdjustment('unicorn-fart-blur-quality');
// switches
dialog.bindSwitch('unicorn-fart-particles-enable');
// star-color
dialog.bindColorButton('unicorn-fart-star-color-0');
dialog.bindColorButton('unicorn-fart-star-color-1');
dialog.bindColorButton('unicorn-fart-star-color-2');
dialog.bindColorButton('unicorn-fart-star-color-3');
dialog.bindColorButton('unicorn-fart-star-color-4');
dialog.bindColorButton('unicorn-fart-star-color-5');
// particle-colors
dialog.bindColorButton('unicorn-fart-spark-color-0');
dialog.bindColorButton('unicorn-fart-spark-color-1');
dialog.bindColorButton('unicorn-fart-spark-color-2');
dialog.bindColorButton('unicorn-fart-spark-color-3');
dialog.bindColorButton('unicorn-fart-spark-color-4');
dialog.bindColorButton('unicorn-fart-spark-color-5');
// Ensure the button connections and other bindings happen only once,
// even if the bindPreferences function is called multiple times.
if (!Effect._isConnected) {
Effect._isConnected = true;
// Bind the "reset-unicorn-fart-spark-colors" button to reset all star colors to
// their default values.
dialog.getBuilder()
.get_object('reset-unicorn-fart-spark-colors')
.connect('clicked', () => {
// Reset each mushroom star color setting.
dialog.getProfileSettings().reset('unicorn-fart-spark-color-0');
dialog.getProfileSettings().reset('unicorn-fart-spark-color-1');
dialog.getProfileSettings().reset('unicorn-fart-spark-color-2');
dialog.getProfileSettings().reset('unicorn-fart-spark-color-3');
dialog.getProfileSettings().reset('unicorn-fart-spark-color-4');
dialog.getProfileSettings().reset('unicorn-fart-spark-color-5');
});
dialog.getBuilder()
.get_object('reset-unicorn-fart-star-colors')
.connect('clicked', () => {
// Reset each mushroom star color setting.
dialog.getProfileSettings().reset('unicorn-fart-star-color-0');
dialog.getProfileSettings().reset('unicorn-fart-star-color-1');
dialog.getProfileSettings().reset('unicorn-fart-star-color-2');
dialog.getProfileSettings().reset('unicorn-fart-star-color-3');
dialog.getProfileSettings().reset('unicorn-fart-star-color-4');
dialog.getProfileSettings().reset('unicorn-fart-star-color-5');
});
// Initialize the preset dropdown menu for mushroom star colors.
Effect._createUnicornFartPresets(dialog);
}
}
// ---------------------------------------------------------------- API for extension.js
// The getActorScale() is called from extension.js to adjust the actor's size during the
// animation. This is useful if the effect requires drawing something beyond the usual
// bounds of the actor. This only works for GNOME 3.38+.
static getActorScale(settings, forOpening, actor) {
return {x: 2.0, y: 2.0};
}
// ---------------------------------------------------------------- Presets
// This function initializes the preset dropdown menu for configuring fire options.
// It defines multiple color presets for the "mushroom star" effect and sets up
// the logic to apply these presets when selected.
static _createUnicornFartPresets(dialog) {
// Retrieve the builder object for the dialog and connect to the "realize" event of
// the button.
dialog.getBuilder()
.get_object('unicorn-fart-preset-button')
.connect('realize', (widget) => {
// Define an array of color presets, each with a name and six color values (RGBA
// format).
const presets = [
{
name: _('Unicorn Fart (Default)'),
SparkCount: 25,
SparkStart: 0.0,
SparkEnd: 1.0,
SparkOffset: 1.0,
StarCount: 6,
StarRot: 0.33,
StarSize: 0.10,
StarStart: 0.0,
StarEnd: 1.0,
BlurQuality: 5.0,
Sparkcolor0: 'rgba(255,255,255,1.0)',
Sparkcolor1: 'rgba(255,255,255,1.0)',
Sparkcolor2: 'rgba(255,255,255,1.0)',
Sparkcolor3: 'rgba(255,255,255,1.0)',
Sparkcolor4: 'rgba(255,255,255,1.0)',
Sparkcolor5: 'rgba(255,255,255,1.0)',
Starcolor0: 'rgba(255,0,0,1.0)',
Starcolor1: 'rgba(255,255,0,1.0)',
Starcolor2: 'rgba(0,255,0,1.0)',
Starcolor3: 'rgba(0,255,255,1.0)',
Starcolor4: 'rgba(0,0,255,1.0)',
Starcolor5: 'rgba(255,0,255,1.0)',
},
{
name: _('Fairy🧚'),
SparkCount: 100,
SparkStart: 0.0,
SparkEnd: 1.0,
SparkOffset: 1.0,
StarCount: 0,
StarRot: 0.33,
StarSize: 0.10,
StarStart: 0.0,
StarEnd: 1.0,
BlurQuality: 5.0,
Sparkcolor0: 'rgba(255,255,255,1.0)',
Sparkcolor1: 'rgba(255,255,255,1.0)',
Sparkcolor2: 'rgba(255,255,255,1.0)',
Sparkcolor3: 'rgba(255,255,255,1.0)',
Sparkcolor4: 'rgba(255,255,255,1.0)',
Sparkcolor5: 'rgba(255,255,255,1.0)',
Starcolor0: 'rgba(255,0,0,1.0)',
Starcolor1: 'rgba(255,255,0,1.0)',
Starcolor2: 'rgba(0,255,0,1.0)',
Starcolor3: 'rgba(0,255,255,1.0)',
Starcolor4: 'rgba(0,0,255,1.0)',
Starcolor5: 'rgba(255,0,255,1.0)',
},
{
name: _('Ring of Fire ❤️‍🔥'),
SparkCount: 100,
SparkStart: 0.0,
SparkEnd: 1.0,
SparkOffset: 1.0,
StarCount: 25,
StarRot: 3.0,
StarSize: 0.10,
StarStart: 0.0,
StarEnd: 1.0,
BlurQuality: 5.0,
Sparkcolor0: 'rgba(255,0,0,1.0)', // Deep red
Sparkcolor1: 'rgba(255,69,0,1.0)', // Fiery orange-red
Sparkcolor2: 'rgba(255,140,0,1.0)', // Orange
Sparkcolor3: 'rgba(255,215,0,1.0)', // Bright yellow
Sparkcolor4: 'rgba(255,255,102,1.0)', // Light yellow
Sparkcolor5: 'rgba(255,255,255,1.0)', // White (hottest point)
Starcolor0: 'rgba(255,0,0,1.0)', // Deep red
Starcolor1: 'rgba(255,69,0,1.0)', // Fiery orange-red
Starcolor2: 'rgba(255,140,0,1.0)', // Orange
Starcolor3: 'rgba(255,215,0,1.0)', // Bright yellow
Starcolor4: 'rgba(255,255,102,1.0)', // Light yellow
Starcolor5: 'rgba(255,255,255,1.0)', // White (hottest point)
},
{
name: _('Mystic 🪄'),
SparkCount: 100,
SparkStart: 0.0,
SparkEnd: 1.0,
SparkOffset: 1.0,
StarCount: 25,
StarRot: 3.0,
StarSize: 0.10,
StarStart: 0.0,
StarEnd: 1.0,
BlurQuality: 5.0,
Sparkcolor0: 'rgba(75,0,130,1.0)', // Indigo
Sparkcolor1: 'rgba(138,43,226,1.0)', // Blue-violet
Sparkcolor2: 'rgba(0,191,255,1.0)', // Deep sky blue
Sparkcolor3: 'rgba(0,255,127,1.0)', // Spring green
Sparkcolor4: 'rgba(173,255,47,1.0)', // Green-yellow
Sparkcolor5: 'rgba(255,20,147,1.0)', // Deep pink
Starcolor0: 'rgba(75,0,130,1.0)', // Indigo
Starcolor1: 'rgba(138,43,226,1.0)', // Blue-violet
Starcolor2: 'rgba(0,191,255,1.0)', // Deep sky blue
Starcolor3: 'rgba(0,255,127,1.0)', // Spring green
Starcolor4: 'rgba(173,255,47,1.0)', // Green-yellow
Starcolor5: 'rgba(255,20,147,1.0)', // Deep pink
},
];
// Create a new menu model to hold the presets and an action group for handling
// selections.
const menu = Gio.Menu.new();
const group = Gio.SimpleActionGroup.new();
const groupName = 'presets';
// Iterate over the presets to populate the menu.
presets.forEach((preset, i) => {
// Define an action name based on the preset index.
const actionName = 'unicorn-fart' + i;
// Append the preset name to the menu.
menu.append(preset.name, groupName + '.' + actionName);
// Create a new action for the preset.
let action = Gio.SimpleAction.new(actionName, null);
// Connect the action to a function that loads the preset colors into the
// dialog.
action.connect('activate', () => {
dialog.getProfileSettings().set_int('unicorn-fart-spark-count',
preset.SparkCount);
dialog.getProfileSettings().set_double('unicorn-fart-spark-start',
preset.SparkStart);
dialog.getProfileSettings().set_double('unicorn-fart-spark-end',
preset.SparkEnd);
dialog.getProfileSettings().set_double('unicorn-fart-spark-offset',
preset.SparkOffset);
dialog.getProfileSettings().set_int('unicorn-fart-star-count',
preset.StarCount);
dialog.getProfileSettings().set_double('unicorn-fart-star-rotation',
preset.StarRot);
dialog.getProfileSettings().set_double('unicorn-fart-star-size',
preset.StarSize);
dialog.getProfileSettings().set_double('unicorn-fart-star-start',
preset.StarStart);
dialog.getProfileSettings().set_double('unicorn-fart-star-end',
preset.StarEnd);
dialog.getProfileSettings().set_double('unicorn-fart-blur-quality',
preset.BlurQuality);
dialog.getProfileSettings().set_string('unicorn-fart-spark-color-0',
preset.Sparkcolor0);
dialog.getProfileSettings().set_string('unicorn-fart-spark-color-1',
preset.Sparkcolor1);
dialog.getProfileSettings().set_string('unicorn-fart-spark-color-2',
preset.Sparkcolor2);
dialog.getProfileSettings().set_string('unicorn-fart-spark-color-3',
preset.Sparkcolor3);
dialog.getProfileSettings().set_string('unicorn-fart-spark-color-4',
preset.Sparkcolor4);
dialog.getProfileSettings().set_string('unicorn-fart-spark-color-5',
preset.Sparkcolor5);
dialog.getProfileSettings().set_string('unicorn-fart-star-color-0',
preset.Starcolor0);
dialog.getProfileSettings().set_string('unicorn-fart-star-color-1',
preset.Starcolor1);
dialog.getProfileSettings().set_string('unicorn-fart-star-color-2',
preset.Starcolor2);
dialog.getProfileSettings().set_string('unicorn-fart-star-color-3',
preset.Starcolor3);
dialog.getProfileSettings().set_string('unicorn-fart-star-color-4',
preset.Starcolor4);
dialog.getProfileSettings().set_string('unicorn-fart-star-color-5',
preset.Starcolor5);
});
// Add the action to the action group.
group.add_action(action);
});
// Assign the populated menu to the preset button.
dialog.getBuilder().get_object('unicorn-fart-preset-button').set_menu_model(menu);
// Insert the action group into the root widget for handling the presets.
const root = widget.get_root();
root.insert_action_group(groupName, group);
});
}
}