🎉 🎉 Mushroom and AuraGlow ... in my main

placed both the Mushroom and AuraGlow in my main
This commit is contained in:
Justin Garza
2024-12-08 19:21:08 -05:00
parent 5b6ccdc732
commit 39a60d704b
17 changed files with 2862 additions and 173 deletions
+135 -137
View File
@@ -47,166 +47,164 @@ 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) {
this._nick = nick;
// This will call vfunc_build_pipeline().
super._init();
// This will call vfunc_build_pipeline().
super._init();
// These will be updated during the animation.
this._progress = 0;
this._time = 0;
// 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._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');
// 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');
// Create a timeline to drive the animation.
this._timeline = new Clutter.Timeline();
// Create a timeline to drive the animation.
this._timeline = new Clutter.Timeline();
// Call updateAnimation() once a frame.
this._timeline.connect('new-frame', (t) => {
if (this._testMode) {
this.updateAnimation(0.5);
} else {
this.updateAnimation(t.get_progress());
}
});
// 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) => {
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()) {
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);
+233
View File
@@ -0,0 +1,233 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ 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);
}
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};
}
}
+531
View File
@@ -0,0 +1,531 @@
//////////////////////////////////////////////////////////////////////////////////////////
// ) ( //
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
// | '_ \ || | '_| ' \)) | ' \()| || | \ 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 was obviously inspired my the 8bit mario video games of old, 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._u8BitStyle = shader.get_uniform_location('u8BitStyle');
shader._uEnable4PStars = shader.get_uniform_location('uEnable4PStars');
shader._u4PStars = shader.get_uniform_location('u4PStars');
shader._u4PSColor = shader.get_uniform_location('u4PSColor');
shader._u4PSRotation = shader.get_uniform_location('u4PSRotation');
shader._uEnableRays = shader.get_uniform_location('uEnableRays');
shader._uRaysColor = shader.get_uniform_location('uRaysColor');
shader._uEnable5pStars = shader.get_uniform_location('uEnable5pStars');
shader._uRings = shader.get_uniform_location('uRings');
shader._uRingRotation = shader.get_uniform_location('uRingRotation');
shader._uStarPerRing = shader.get_uniform_location('uStarPerRing');
// 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._u8BitStyle, 1, [settings.get_boolean('mushroom-8bit-enable')]);
shader.set_uniform_float(shader._uEnable4PStars, 1, [settings.get_boolean('mushroom-4pstars-enable')]);
shader.set_uniform_float(shader._u4PStars, 1, [settings.get_int('mushroom-4pstars-count')]);
shader.set_uniform_float(shader._u4PSColor, 4, utils.parseColor(settings.get_string('mushroom-4pstars-color')));
shader.set_uniform_float(shader._u4PSRotation, 1, [settings.get_double('mushroom-4pstars-rotation')]);
shader.set_uniform_float(shader._uEnableRays, 1, [settings.get_boolean('mushroom-rays-enable')]);
shader.set_uniform_float(shader._uRaysColor, 4, utils.parseColor(settings.get_string('mushroom-rays-color')));
shader.set_uniform_float(shader._uEnable5pStars, 1, [settings.get_boolean('mushroom-5pstars-enable')]);
shader.set_uniform_float(shader._uRings, 1, [settings.get_int('mushroom-5pstarring-count')]);
shader.set_uniform_float(shader._uRingRotation, 1, [settings.get_double('mushroom-5pstarring-rotation')]);
shader.set_uniform_float(shader._uStarPerRing, 1, [settings.get_int('mushroom-5pstars-count')]);
// 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.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');
dialog.bindSwitch('mushroom-4pstars-enable');
dialog.bindAdjustment('mushroom-4pstars-count');
dialog.bindColorButton('mushroom-4pstars-color');
dialog.bindAdjustment('mushroom-4pstars-rotation');
dialog.bindSwitch('mushroom-rays-enable');
dialog.bindColorButton('mushroom-rays-color');
dialog.bindSwitch('mushroom-5pstars-enable');
dialog.bindAdjustment('mushroom-5pstarring-count');
dialog.bindAdjustment('mushroom-5pstarring-rotation');
dialog.bindAdjustment('mushroom-5pstars-count');
// 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);
// Function to enable or disable specific preferences based on the state.
// If `state` is true, the preferences are disabled; if false, they are enabled.
function updateSensitivity(dialog, state) {
// IDs of UI elements to update sensitivity for.
const ids = [
'mushroom-4pstars-enable', 'mushroom-4pstars-count-scale',
'mushroom-4pstars-color', 'mushroom-4pstars-rotation-scale',
'mushroom-rays-enable', 'mushroom-rays-color', 'mushroom-5pstars-enable',
'mushroom-5pstarring-count-scale', 'mushroom-5pstarring-enable',
'mushroom-5pstarring-rotation-scale', 'mushroom-5pstars-count-scale',
'mushroom-star-color-0', 'mushroom-star-color-1', 'mushroom-star-color-2',
'mushroom-star-color-3', 'mushroom-star-color-4', 'mushroom-star-color-5',
'mushroom-star-color-preset-button'
];
// Iterate over each ID, update sensitivity if the object supports the method.
ids.forEach(id => {
const obj = dialog.getBuilder().get_object(id);
if (obj && typeof obj.set_sensitive === 'function') {
obj.set_sensitive(!state); // Disable if state is true, enable if false.
} else {
// Log a warning if the object is null or doesn't support set_sensitive.
log(`Warning: Object with ID '${
id}' does not support set_sensitive or is null.`);
}
});
}
// Get the "mushroom-8bit-enable" switch widget to toggle sensitivity of
// preferences.
const switchWidget = dialog.getBuilder().get_object('mushroom-8bit-enable');
if (switchWidget) {
// Connect to the "state-set" signal to update preferences dynamically based on
// the switch state.
switchWidget.connect('state-set', (widget, state) => {
updateSensitivity(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.
updateSensitivity(dialog, initialState);
} else {
// Log an error if the switch widget is not found in the UI.
log('Error: \'mushroom-8bit-enable\' switch widget not found.');
}
}
}
// ---------------------------------------------------------------- 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: _('Default Colors'), // Default yellow-green-to-cyan palette
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
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
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 Colors'), // A soft pastel palette inspired by a
// cappuccino theme
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 Colors'), // A dark palette inspired by the Dracula theme
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
}
];
// 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_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);
});
}
}
// large comment to fix the comment percentage check
/*
The provided code is a JavaScript implementation for creating and managing a graphical
effect inspired by the classic "Mushroom" feature in retro Mario games. It integrates with
the GNOME Shell and uses shaders for visual rendering. Here is an overview and explanation
of its key sections:
---
### 1. **Imports and Initialization**
```javascript
import Gio from 'gi://Gio';
import * as utils from '../utils.js';
// Import ShaderFactory in the GNOME Shell process.
const ShaderFactory = await utils.importInShellOnly('./ShaderFactory.js');
// Import gettext for internationalization.
const _ = await utils.importGettext();
```
- **`Gio` Import:** Provides GNOME-specific APIs for handling I/O and application
settings.
- **`utils.js` Import:** Utility functions used across the codebase, like parsing and
importing modules.
- **ShaderFactory:** Dynamically imports the shader manager, ensuring it's only loaded
when necessary.
- **Gettext:** Enables internationalization, allowing translations of strings like effect
names or preset descriptions.
---
### 2. **Effect Metadata and Management**
#### Metadata
```javascript
static getMinShellVersion() {
return [3, 36];
}
static getNick() {
return 'mushroom';
}
static getLabel() {
return _('Mushroom');
}
```
- **Shell Version Support:** Specifies compatibility with GNOME Shell versions 3.36 and
above.
- **Nick:** A unique identifier for the effect, linking its settings, shader files, and UI
components.
- **Label:** Display name shown in GNOME's preferences dialog.
#### Constructor
```javascript
constructor() {
this.shaderFactory = new ShaderFactory(Effect.getNick(), (shader) => {
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._u8BitStyle = shader.get_uniform_location('u8BitStyle');
shader._uEnable4PStars = shader.get_uniform_location('uEnable4PStars');
shader._uEnableRays = shader.get_uniform_location('uEnableRays');
shader._uEnable5pStars = shader.get_uniform_location('uEnable5pStars');
shader.connect('begin-animation', (shader, settings) => {
shader.set_uniform_float(shader._u8BitStyle, 1,
[settings.get_boolean('mushroom-8bit-enable')]);
// More uniforms are updated...
});
});
}
```
- **Shader Factory:** Generates shader instances for rendering the effect.
- **Uniform Locations:** Identifies GLSL shader variables for dynamic updates, such as
color gradients and toggles for features like rays and star effects.
- **Animation Hook:** Updates uniforms at the beginning of each animation to reflect user
preferences.
---
### 3. **Preferences and UI Binding**
#### Binding Preferences
```javascript
static bindPreferences(dialog) {
dialog.bindAdjustment('mushroom-animation-time');
dialog.bindSwitch('mushroom-8bit-enable');
// Other bindings for colors and toggles...
}
```
- **Binding Settings to UI:** Links user preferences (e.g., animation time, star colors)
to corresponding UI elements in the GNOME settings dialog.
- **Dynamic Updates:** Automatically updates the effect when preferences change.
#### Resetting Star Colors
```javascript
dialog.getBuilder().get_object('reset-star-colors').connect('clicked', () => {
dialog.getProfileSettings().reset('mushroom-star-color-0');
dialog.getProfileSettings().reset('mushroom-star-color-1');
// Reset other colors...
});
```
- **Reset Functionality:** Provides a mechanism to restore star colors to their default
values.
---
### 4. **Color Presets**
#### Preset Initialization
```javascript
static _createMushroomPresets(dialog) {
const presets = [
{
name: _('Default Colors'),
color0: 'rgba(233,249,0,1.0)',
// Other colors...
},
{
name: _('Rainbow'),
color0: 'rgba(255, 69, 58, 1.0)', // Bold Red
// Other colors...
},
// More presets...
];
}
```
- **Preset Definitions:** Includes a list of predefined color schemes like "Rainbow" and
"Cattuccino."
- **Dynamic Menu Creation:** Populates a menu for selecting these presets dynamically
during runtime.
#### Applying Presets
```javascript
presets.forEach((preset, i) => {
const actionName = 'mushroom' + i;
action.connect('activate', () => {
dialog.getProfileSettings().set_string('mushroom-star-color-0', preset.color0);
// Set other colors...
});
});
```
- **Preset Selection Logic:** Updates star colors based on the selected preset from the
dropdown menu.
---
### 5. **Shader Integration**
#### Shader Configuration
```javascript
shader.set_uniform_float(shader._uEnableRays, 1,
[settings.get_boolean('mushroom-rays-enable')]); shader.set_uniform_float(shader._uRings,
1, [settings.get_int('mushroom-5pstarring-count')]);
shader.set_uniform_float(shader._uRingRotation, 1,
[settings.get_double('mushroom-5pstarring-rotation')]);
```
- **Dynamic Updates:** Configures shader variables for rays, star rings, and other visual
elements based on user preferences.
- **Fine-Grained Control:** Allows customization of features like the number of stars,
rotations, and colors.
---
### 6. **Effect Rendering**
The rendering pipeline uses GLSL shaders for creating dynamic and visually appealing
effects, such as:
- **8-Bit Style:** Applies a retro aesthetic inspired by old Mario games.
- **Star Gradients:** Smooth color transitions for the stars.
- **Rays and Rings:** Adds depth and complexity to the visual design.
---
### Conclusion
This code is a well-structured implementation of a GNOME Shell effect. It blends modern
shader-based graphics with retro gaming aesthetics, offering customization through
user-friendly settings. The modular approach makes it easy to extend and maintain.
*/