diff --git a/Makefile b/Makefile index 7d49900..6dc34d4 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ burn-my-windows@schneegans.github.com.zip: schemas/gschemas.compiled resources/b @echo "Packing zip file..." @rm --force burn-my-windows@schneegans.github.com.zip - @zip -r burn-my-windows@schneegans.github.com.zip -- *.js src/*/*.js resources/burn-my-windows.gresource schemas/gschemas.compiled metadata.json LICENSE + @zip -r burn-my-windows@schneegans.github.com.zip -- *.js src/*.js resources/burn-my-windows.gresource schemas/gschemas.compiled metadata.json LICENSE @#Check if the zip size is too big to be uploaded @if [[ "$$(stat -c %s burn-my-windows@schneegans.github.com.zip)" -gt 4096000 ]]; then \ diff --git a/docs/changelog.md b/docs/changelog.md index 274b15e..cc601cd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,21 @@ # Changelog of the Burn-My-Windows Extension +## [Burn My Window 8](https://github.com/schneegans/Burn-My-Windows/releases/tag/v8) + +**Release Date:** TBD + +#### New Features + +* **Random effects!** You can now select a set of effects of which one will be chosen randomly whenever you close a window. +* Each effect has its own "Animation Time" setting now. The global animation time has been replaced by those, so **you may have to adjust the sliders to match your previous settings**. + +#### Other Enhancements + +* The source code has received a **major refactoring**. This will make the addition of new effects much easier in the future. +* The **layout of the settings dialog** has been reworked to make it easier to expand it in the future. +* The lower limit of the **animation time of the TV Effect** has been reduced. + + ## [Burn My Window 7](https://github.com/schneegans/Burn-My-Windows/releases/tag/v7) **Release Date:** 2022-01-03 diff --git a/extension.js b/extension.js index c920725..172755c 100644 --- a/extension.js +++ b/extension.js @@ -15,20 +15,22 @@ const {Clutter, Gio, Meta} = imports.gi; -const Config = imports.misc.config; -const [GS_MAJOR, GS_MINOR] = Config.PACKAGE_VERSION.split('.'); - const Workspace = imports.ui.workspace.Workspace; const WindowManager = imports.ui.windowManager.WindowManager; const WINDOW_REPOSITIONING_DELAY = imports.ui.workspace.WINDOW_REPOSITIONING_DELAY; const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const utils = Me.imports.src.common.utils; -const FireShader = Me.imports.src.extension.FireShader.FireShader; -const MatrixShader = Me.imports.src.extension.MatrixShader.MatrixShader; -const TVEffectShader = Me.imports.src.extension.TVEffectShader.TVEffectShader; -const TRexShader = Me.imports.src.extension.TRexShader.TRexShader; +const utils = Me.imports.src.utils; +const FireEffect = Me.imports.src.FireEffect.FireEffect; + +// New effects must be registered here and in prefs.js. +const ALL_EFFECTS = [ + Me.imports.src.FireEffect.FireEffect, + Me.imports.src.MatrixEffect.MatrixEffect, + Me.imports.src.TRexEffect.TRexEffect, + Me.imports.src.TVEffect.TVEffect, +]; ////////////////////////////////////////////////////////////////////////////////////////// // This extensions modifies the window-close animation to look like the window was set // @@ -61,25 +63,6 @@ class Extension { this._origAddWindowClone = Workspace.prototype._addWindowClone; this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor; - // We may also override these animation times. - this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME; - this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME; - - // Update animation times if the respective settings are changed. - const loadAnimationTimes = () => { - imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = - this._settings.get_int('destroy-animation-time'); - - imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = - this._settings.get_boolean('destroy-dialogs') ? - this._settings.get_int('destroy-animation-time') : - this._origDialogTime; - }; - - this._settings.connect('changed::destroy-animation-time', loadAnimationTimes); - this._settings.connect('changed::destroy-dialogs', loadAnimationTimes); - loadAnimationTimes(); - // We will use extensionThis to refer to the extension inside the patched methods of // the WorkspacesView. const extensionThis = this; @@ -89,7 +72,7 @@ class Extension { // overview window layout. Therefore we call this method in addition. // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/workspace.js#L1877 // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1415 - if (GS_MAJOR == 3 && GS_MINOR == 36) { + if (utils.shellVersionIs(3, 36)) { Workspace.prototype._addWindowClone = function(...params) { const [clone, overlay] = extensionThis._origAddWindowClone.apply(this, params); clone.connect('destroy', () => this._doRemoveWindow(clone.metaWindow)); @@ -154,38 +137,24 @@ class Extension { return; } - // If there's a transition in progress, we re-target these transitions so that the - // window is neither scaled nor faded. - const tweakTransition = (property, value) => { - const transition = actor.get_transition(property); - if (transition) { - transition.set_to(value); - transition.set_progress_mode(Clutter.AnimationMode.EASE_OUT_QUAD); - } - }; + // Create a list of all currently enabled effects. + const enabledEffects = ALL_EFFECTS.filter(Effect => { + return this._settings.get_boolean(`${Effect.getNick()}-close-effect`); + }); - tweakTransition('opacity', 255); - tweakTransition('scale-x', 1); - tweakTransition('scale-y', 1); + // Nothing is enabled... + if (enabledEffects.length == 0) { + return; + } - let shader = null; + // Choose a random effect. + const Effect = enabledEffects[Math.floor(Math.random() * enabledEffects.length)]; + + // The effect usually will choose to override the present transitions on the actor. + Effect.tweakTransitions(actor, this._settings); // Add a cool shader to our window actor! - const mode = this._settings.get_enum('close-animation'); - if (mode == 1) { - shader = new FireShader(this._settings); - } else if (mode == 2) { - shader = new MatrixShader(this._settings); - } else if (mode == 3) { - shader = new TVEffectShader(this._settings); - tweakTransition('scale-y', 0.5); - } else if (mode == 4) { - shader = new TRexShader(this._settings); - - const warp = 0.5 * this._settings.get_double('claw-scratch-warp'); - tweakTransition('scale-x', 1.0 + warp); - tweakTransition('scale-y', 1.0 + warp); - } + const shader = Effect.createShader(this._settings); if (shader) { actor.add_effect(shader); @@ -216,9 +185,6 @@ class Extension { Workspace.prototype._addWindowClone = this._origAddWindowClone; WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor; - imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME = this._origWindowTime; - imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME = this._origDialogTime; - this._settings = null; } @@ -233,8 +199,8 @@ class Extension { } // This was called "realWindow" in GNOME 3.36. - const GS_336 = GS_MAJOR == 3 && GS_MINOR == 36; - const actor = workspace._windows[index][GS_336 ? 'realWindow' : '_windowActor']; + const propertyName = utils.shellVersionIs(3, 36) ? 'realWindow' : '_windowActor'; + const actor = workspace._windows[index][propertyName]; if (!actor.get_transition('scale-y')) { return true; } diff --git a/prefs.js b/prefs.js index 9dc4ae4..4d67e1f 100644 --- a/prefs.js +++ b/prefs.js @@ -17,14 +17,24 @@ const {Gio, Gtk, Gdk} = imports.gi; const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); -const utils = Me.imports.src.common.utils; +const utils = Me.imports.src.utils; + +// New effects must be registered here and in extension.js. +const ALL_EFFECTS = [ + Me.imports.src.FireEffect.FireEffect, + Me.imports.src.MatrixEffect.MatrixEffect, + Me.imports.src.TRexEffect.TRexEffect, + Me.imports.src.TVEffect.TVEffect, +]; ////////////////////////////////////////////////////////////////////////////////////////// -// For now, the preferences dialog of this extension is very simple. In the future, if // -// we might consider to improve its layout... // +// The preferences dialog is organized in pages, each of which is loaded from a // +// separate ui file. There's one page with general options, all other paged are loaded // +// from the respective effects. // ////////////////////////////////////////////////////////////////////////////////////////// var PreferencesDialog = class PreferencesDialog { + // ------------------------------------------------------------ constructor / destructor constructor() { @@ -32,81 +42,88 @@ var PreferencesDialog = class PreferencesDialog { this._resources = Gio.Resource.load(Me.path + '/resources/burn-my-windows.gresource'); Gio.resources_register(this._resources); - // Load the user interface file. + // Store a reference to the settings object. + this._settings = ExtensionUtils.getSettings(); + + // Load the general user interface files. this._builder = new Gtk.Builder(); - this._builder.add_from_resource(`/ui/${utils.isGTK4() ? 'gtk4' : 'gtk3'}.ui`); + this._builder.add_from_resource(`/ui/common/main-menu.ui`); + this._builder.add_from_resource(`/ui/${utils.getGTKString()}/prefs.ui`); + this._builder.add_from_resource(`/ui/${utils.getGTKString()}/generalPage.ui`); + + // Bind general options properties. + this.bindSwitch('destroy-dialogs'); + + // Add the General Options page to the main stack. + const stack = this._builder.get_object('main-stack'); + stack.add_titled( + this._builder.get_object('general-prefs'), 'general', 'General Options'); + + // Add all other effect pages. + ALL_EFFECTS.forEach(Effect => { + const [minMajor, minMinor] = Effect.getMinShellVersion(); + if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { + Effect.initPreferences(this); + } + }); // This is our top-level widget which we will return later. this._widget = this._builder.get_object('settings-widget'); - // Store a reference to the settings object. - this._settings = ExtensionUtils.getSettings(); - - // Bind all properties. - this._bindCombobox('close-animation'); - this._bindAdjustment('destroy-animation-time'); - this._bindSwitch('destroy-dialogs'); - this._bindAdjustment('flame-movement-speed'); - this._bindAdjustment('flame-scale'); - this._bindSwitch('flame-3d-noise'); - this._bindColorButton('fire-color-1'); - this._bindColorButton('fire-color-2'); - this._bindColorButton('fire-color-3'); - this._bindColorButton('fire-color-4'); - this._bindColorButton('fire-color-5'); - this._bindAdjustment('matrix-scale'); - this._bindAdjustment('matrix-randomness'); - this._bindColorButton('matrix-trail-color'); - this._bindColorButton('matrix-tip-color'); - this._bindColorButton('tv-effect-color'); - this._bindColorButton('claw-scratch-color'); - this._bindAdjustment('claw-scratch-scale'); - this._bindAdjustment('claw-scratch-count'); - this._bindAdjustment('claw-scratch-warp'); - - // The fire-gradient-reset button needs to be bound explicitly. - this._builder.get_object('reset-fire-colors').connect('clicked', () => { - this._settings.reset('fire-color-1'); - this._settings.reset('fire-color-2'); - this._settings.reset('fire-color-3'); - this._settings.reset('fire-color-4'); - this._settings.reset('fire-color-5'); - }); - - // Initialize the fire-preset dropdown. - this._createFirePresets(); - - // Add a menu to the title bar of the preferences dialog. + // Some things can only be done once the widget is shown as we do not have access to + // the toplevel widget before. this._widget.connect('realize', (widget) => { const window = utils.isGTK4() ? widget.get_root() : widget.get_toplevel(); // Show the version number in the title bar. window.set_title(`Burn-My-Windows ${Me.metadata.version}`); - // Add the menu. - const menu = this._builder.get_object('menu-button'); - window.get_titlebar().pack_end(menu); + // Add the main menu to the title bar. + { + // Add the menu button to the title bar. + const menu = this._builder.get_object('menu-button'); + window.get_titlebar().pack_end(menu); - // Populate the actions. - const group = Gio.SimpleActionGroup.new(); + // Populate the menu with actions. + const group = Gio.SimpleActionGroup.new(); + window.insert_action_group('prefs', group); - const addAction = (name, uri) => { - const action = Gio.SimpleAction.new(name, null); - action.connect('activate', () => Gtk.show_uri(null, uri, Gdk.CURRENT_TIME)); - group.add_action(action); - }; + const addAction = (name, uri) => { + const action = Gio.SimpleAction.new(name, null); + action.connect('activate', () => Gtk.show_uri(null, uri, Gdk.CURRENT_TIME)); + group.add_action(action); + }; - addAction('homepage', 'https://github.com/Schneegans/Burn-My-Windows'); - addAction( - 'changelog', - 'https://github.com/Schneegans/Burn-My-Windows/blob/main/docs/changelog.md'); - addAction('bugs', 'https://github.com/Schneegans/Burn-My-Windows/issues'); - addAction( - 'donate-paypal', - 'https://www.paypal.com/donate/?hosted_button_id=3F7UFL8KLVPXE'); - addAction('donate-github', 'https://github.com/sponsors/Schneegans'); + // clang-format off + addAction('homepage', 'https://github.com/Schneegans/Burn-My-Windows'); + addAction('changelog', 'https://github.com/Schneegans/Burn-My-Windows/blob/main/docs/changelog.md'); + addAction('bugs', 'https://github.com/Schneegans/Burn-My-Windows/issues'); + addAction('donate-paypal', 'https://www.paypal.com/donate/?hosted_button_id=3F7UFL8KLVPXE'); + addAction('donate-github', 'https://github.com/sponsors/Schneegans'); + // clang-format on + } - window.insert_action_group('prefs', group); + // Populate the close-effects drop-down menu. + { + const menu = this._builder.get_object('close-effect-menu'); + const group = Gio.SimpleActionGroup.new(); + window.insert_action_group('close-effects', group); + + ALL_EFFECTS.forEach(Effect => { + const [minMajor, minMinor] = Effect.getMinShellVersion(); + if (utils.shellVersionIsAtLeast(minMajor, minMinor)) { + const nick = Effect.getNick(); + const label = Effect.getLabel(); + const actionName = nick + '-close-effect'; + const fullName = 'close-effects.' + actionName; + + const action = this._settings.create_action(actionName); + group.add_action(action); + + menu.append_item(Gio.MenuItem.new(label, fullName)); + } + }); + } }); // As we do not have something like a destructor, we just listen for the destroy @@ -124,35 +141,44 @@ var PreferencesDialog = class PreferencesDialog { // -------------------------------------------------------------------- public interface + // Returns the internally used Gtk.Builder. Effects can use this to modify the UI of the + // preferences dialog. + getBuilder() { + return this._builder; + } + + // Returns a Gio.Settings object for this extension. + getSettings() { + return this._settings; + } + // Returns the widget used for the settings of this extension. getWidget() { return this._widget; } - // ----------------------------------------------------------------------- private stuff - // Connects a Gtk.ComboBox (or anything else which has an 'active-id' property) to a // settings key. It also binds the corresponding reset button. - _bindCombobox(settingsKey) { + bindCombobox(settingsKey) { this._bind(settingsKey, 'active-id'); } // Connects a Gtk.Adjustment (or anything else which has a 'value' property) to a // settings key. It also binds the corresponding reset button. - _bindAdjustment(settingsKey) { + bindAdjustment(settingsKey) { this._bind(settingsKey, 'value'); } // Connects a Gtk.Switch (or anything else which has an 'active' property) to a settings // key. It also binds the corresponding reset button. - _bindSwitch(settingsKey) { + bindSwitch(settingsKey) { this._bind(settingsKey, 'active'); } // Colors are stored as strings like 'rgb(1, 0.5, 0)'. As Gio.Settings.bind_with_mapping // is not available yet, we need to do the color conversion manually. It also binds the // corresponding reset button. - _bindColorButton(settingsKey) { + bindColorButton(settingsKey) { const button = this._builder.get_object(settingsKey); @@ -179,17 +205,7 @@ var PreferencesDialog = class PreferencesDialog { this._bindResetButton(settingsKey); } - // Connects any widget's property to a settings key. The widget must have the same ID as - // the settings key. It also binds the corresponding reset button. - _bind(settingsKey, property) { - const object = this._builder.get_object(settingsKey); - - if (object) { - this._settings.bind(settingsKey, object, property, Gio.SettingsBindFlags.DEFAULT); - } - - this._bindResetButton(settingsKey); - } + // ----------------------------------------------------------------------- private stuff // Searches for a reset button for the given settings key and make it reset the settings // key when clicked. @@ -202,91 +218,16 @@ var PreferencesDialog = class PreferencesDialog { } } - // This populates the preset dropdown menu for the fire options. - _createFirePresets() { - this._widget.connect('realize', (widget) => { - const presets = [ - { - name: 'Default Fire', - scale: 1.0, - speed: 0.5, - color1: 'rgba(76, 51, 25, 0.0)', - color2: 'rgba(180, 55, 30, 0.7)', - color3: 'rgba(255, 76, 38, 0.9)', - color4: 'rgba(255, 166, 25, 1)', - color5: 'rgba(255, 255, 255, 1)' - }, - { - name: 'Hell Fire', - scale: 1.5, - speed: 0.2, - color1: 'rgba(0,0,0,0)', - color2: 'rgba(103,7,80,0.5)', - color3: 'rgba(150,0,24,0.9)', - color4: 'rgb(255,200,0)', - color5: 'rgba(255, 255, 255, 1)' - }, - { - name: 'Dark and Smutty', - scale: 1.0, - speed: 0.5, - color1: 'rgba(0,0,0,0)', - color2: 'rgba(36,3,0,0.5)', - color3: 'rgba(150,0,24,0.9)', - color4: 'rgb(255,177,21)', - color5: 'rgb(255,238,166)' - }, - { - name: 'Cold Breeze', - scale: 1.5, - speed: -0.1, - color1: 'rgba(0,110,255,0)', - color2: 'rgba(30,111,180,0.24)', - color3: 'rgba(38,181,255,0.54)', - color4: 'rgba(34,162,255,0.84)', - color5: 'rgb(97,189,255)' - }, - { - name: 'Santa is Coming', - scale: 0.4, - speed: -0.5, - color1: 'rgba(0,110,255,0)', - color2: 'rgba(208,233,255,0.24)', - color3: 'rgba(207,235,255,0.84)', - color4: 'rgb(208,243,255)', - color5: 'rgb(255,255,255)' - } - ]; + // Connects any widget's property to a settings key. The widget must have the same ID as + // the settings key. It also binds the corresponding reset button. + _bind(settingsKey, property) { + const object = this._builder.get_object(settingsKey); - const menu = Gio.Menu.new(); - const group = Gio.SimpleActionGroup.new(); - const groupName = 'presets'; + if (object) { + this._settings.bind(settingsKey, object, property, Gio.SettingsBindFlags.DEFAULT); + } - // Add all presets. - presets.forEach((preset, i) => { - const actionName = 'fire' + i; - menu.append(preset.name, groupName + '.' + actionName); - let action = Gio.SimpleAction.new(actionName, null); - - // Load the preset on activation. - action.connect('activate', () => { - this._settings.set_double('flame-movement-speed', preset.speed); - this._settings.set_double('flame-scale', preset.scale); - this._settings.set_string('fire-color-1', preset.color1); - this._settings.set_string('fire-color-2', preset.color2); - this._settings.set_string('fire-color-3', preset.color3); - this._settings.set_string('fire-color-4', preset.color4); - this._settings.set_string('fire-color-5', preset.color5); - }); - - group.add_action(action); - }); - - this._builder.get_object('fire-preset-button').set_menu_model(menu); - - const root = utils.isGTK4() ? widget.get_root() : widget.get_toplevel(); - root.insert_action_group(groupName, group); - }); + this._bindResetButton(settingsKey); } } diff --git a/resources/ui/common/main-menu.ui b/resources/ui/common/main-menu.ui new file mode 100644 index 0000000..4ba062b --- /dev/null +++ b/resources/ui/common/main-menu.ui @@ -0,0 +1,32 @@ + + + +
+ + Visit Homepage + prefs.homepage + + + View Changelog + prefs.changelog + + + Report a Bug + prefs.bugs + +
+
+ + Donate + + Via PayPal + prefs.donate-paypal + + + Via GitHub + prefs.donate-github + + +
+
+
\ No newline at end of file diff --git a/resources/ui/gtk3.ui b/resources/ui/gtk3.ui deleted file mode 100644 index a99b1c7..0000000 --- a/resources/ui/gtk3.ui +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - - - - - - - Fire - fire - - - TV Effect - tv - - - - - -
- - Visit Homepage - prefs.homepage - - - View Changelog - prefs.changelog - - - Report a Bug - prefs.bugs - -
-
- - Donate - - Via PayPal - prefs.donate-paypal - - - Via GitHub - prefs.donate-github - - -
-
- - - main-menu - 1 - - - open-menu-symbolic - 1 - 1 - - - - - - 10000 - 500 - 10 - 100 - - - - 3 - 0.1 - 0.01 - 0.1 - - - - 2.0 - -2.0 - 0.01 - 0.1 - - - - 50 - 5 - 1 - 5 - - - - 1 - 0 - 0.01 - 0.1 - - - - never - 200 - 0 - 1 - - - - - vertical - 60 - 60 - 30 - 30 - - - - 30 - 10 - General Options - 0 - - - - - - - - - - none - - - - 10 - 10 - 10 - 10 - 0 - - - 8 - - - Close Window Animation - 0 - start - center - 1 - - - - - center - animation-mode-list - 1 - fire - - - - 0 - - - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - 10 - 10 - 10 - 10 - 0 - - - 8 - - - Animation Time [ms] - 0 - start - center - 1 - - - - - end - center - 1 - 0 - left - 300 - destroy-animation-time - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - 10 - 10 - 10 - 10 - 0 - - - 8 - - - Also Burn Dialogs - 0 - start - center - 1 - - - - - end - center - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - - - - - - - 30 - 10 - - - Fire Animation Options - 0 - 1 - - - - - - none - - - 8 - - - Choose a Preset - - - - - pan-down-symbolic - 1 - - - - - - - - - - - - - - none - - - - - 10 - 10 - 10 - 10 - 0 - - - 8 - - - Scale - 0 - start - center - 1 - - - - - end - center - 1 - 2 - left - 300 - flame-scale - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - 10 - 10 - 10 - 10 - 0 - - - 8 - - - Speed - 0 - start - center - 1 - - - - - end - center - 1 - 2 - left - 300 - flame-movement-speed - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - 10 - 10 - 10 - 10 - 0 - 0 - - - 8 - - - vertical - 3 - - - 3D Noise - 0 - start - center - 1 - - - - - Creates a more dynamic fire but requires more GPU power. - 0 - start - center - 1 - - - - - - - - end - center - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - 10 - 10 - 10 - 10 - 0 - - - - 8 - - - Gradient - 0 - start - center - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - - - - - - - - 30 - 10 - - - TV Effect Options - 0 - 1 - - - - - - - - - - - none - - - - - 10 - 10 - 10 - 10 - 0 - - - 8 - - - Color - 0 - start - center - 1 - - - - - 1 - 1 - - - - - - - edit-clear-symbolic - 1 - - - Reset to Default Value - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/ui/gtk3/firePage.ui b/resources/ui/gtk3/firePage.ui new file mode 100644 index 0000000..688285c --- /dev/null +++ b/resources/ui/gtk3/firePage.ui @@ -0,0 +1,379 @@ + + + + + 10000 + 500 + 10 + 100 + + + + 3 + 0.1 + 0.01 + 0.1 + + + + 2.0 + -2.0 + 0.01 + 0.1 + + + + vertical + 60 + 60 + 60 + 60 + + + + 20 + + + none + + + + + 10 + 10 + 10 + 10 + 0 + + + + + Presets + 0 + start + center + 1 + + + + + + + 8 + + + Choose a Preset + + + + + pan-down-symbolic + 1 + + + + + + + + + + + + + + + + + + + + none + + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + fire-animation-time + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + + + Scale + 0 + start + center + 1 + + + + + end + center + 1 + 2 + left + 300 + flame-scale + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + + + Speed + 0 + start + center + 1 + + + + + end + center + 1 + 2 + left + 300 + flame-movement-speed + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + + + vertical + 3 + + + 3D Noise + 0 + start + center + 1 + + + + + Creates a more dynamic fire but requires more GPU power. + 0 + start + center + 1 + + + + + + + + end + center + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + Gradient + 0 + start + center + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk3/generalPage.ui b/resources/ui/gtk3/generalPage.ui new file mode 100644 index 0000000..9c952b8 --- /dev/null +++ b/resources/ui/gtk3/generalPage.ui @@ -0,0 +1,118 @@ + + + + + + + vertical + 60 + 60 + 30 + 30 + + + + + + none + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + vertical + 3 + + + Close Window Animation + 0 + start + center + 1 + + + + + If multiple are selected, one is chosen randomly. + 0 + start + center + 1 + + + + + + + + Choose Effects + close-effect-menu + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + Also Burn Dialogs + 0 + start + center + 1 + + + + + end + center + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk3/prefs.ui b/resources/ui/gtk3/prefs.ui new file mode 100644 index 0000000..5b79db1 --- /dev/null +++ b/resources/ui/gtk3/prefs.ui @@ -0,0 +1,35 @@ + + + + + + main-menu + 1 + + + open-menu-symbolic + 1 + 1 + + + + + + + + main-stack + + + + + slide-up-down + 1 + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk3/tvPage.ui b/resources/ui/gtk3/tvPage.ui new file mode 100644 index 0000000..b992120 --- /dev/null +++ b/resources/ui/gtk3/tvPage.ui @@ -0,0 +1,127 @@ + + + + + 5000 + 100 + 10 + 100 + + + + vertical + 60 + 60 + 60 + 60 + + + + + + none + + + + + 10 + 10 + 10 + 10 + 0 + + + 8 + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + tv-animation-time + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + 10 + 10 + 10 + 10 + 0 + + + + + Color + 0 + start + center + 1 + + + + + 0 + 1 + + + + + + + edit-clear-symbolic + 1 + + + Reset to Default Value + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4.ui b/resources/ui/gtk4.ui deleted file mode 100644 index 20b4805..0000000 --- a/resources/ui/gtk4.ui +++ /dev/null @@ -1,948 +0,0 @@ - - - - - - - - - - - - Fire - fire - - - Matrix - matrix - - - TV Effect - tv - - - T-Rex Attack - trex - - - - - -
- - Visit Homepage - prefs.homepage - - - View Changelog - prefs.changelog - - - Report a Bug - prefs.bugs - -
-
- - Donate - - Via PayPal - prefs.donate-paypal - - - Via GitHub - prefs.donate-github - - -
-
- - - main-menu - open-menu-symbolic - - - - never - 200 - 0 - 1 - - - 1 - - - vertical - 60 - 60 - 30 - 30 - - - - 30 - 10 - General Options - 0 - - - - - - - - - - none - 1 - - - - - - 0 - - - - - Close Window Animation - 0 - start - center - 1 - - - - - center - animation-mode-list - 1 - fire - - - - 0 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Animation Time [ms] - 0 - start - center - 1 - - - - - end - center - 1 - 0 - left - 300 - - - 10000 - 500 - 10 - 100 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Also Burn Dialogs - 0 - start - center - 1 - - - - - end - center - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - - - - - - - 30 - 10 - - - Fire Animation Options - 0 - 1 - - - - - - Choose a Preset - 0 - - - - - - - - - - none - 1 - - - - - 0 - - - - - Scale - 0 - start - center - 1 - - - - - end - center - 1 - 2 - left - 300 - - - 3 - 0.1 - 0.01 - 0.1 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Speed - 0 - start - center - 1 - - - - - end - center - 1 - 2 - left - 300 - - - 2.0 - -2.0 - 0.01 - 0.1 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - vertical - 3 - - - 3D Noise - 0 - start - center - 1 - - - - - Creates a more dynamic fire but requires more GPU power. - 0 - start - center - 1 - - - - - - - - end - center - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Gradient - 0 - start - center - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - 1 - 1 - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - - - - - - - 30 - 10 - - - Matrix Animation Options - 0 - 1 - - - - - - - - - - - none - 1 - - - - - 0 - - - - - Scale - 0 - start - center - 1 - - - - - end - center - 1 - 0 - left - 300 - - - 50 - 5 - 1 - 5 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Randomness - 0 - start - center - 1 - - - - - end - center - 1 - 2 - left - 300 - - - 1.0 - 0.0 - 0.01 - 0.1 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Trail Color - 0 - start - center - 1 - - - - - 0 - 1 - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Tip Color - 0 - start - center - 1 - - - - - 0 - 1 - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - - - - - - - - 30 - 10 - - - T-Rex Attack Options - 0 - 1 - - - - - - - - - - - none - 1 - - - - - 0 - - - - - Claw Scratch Scale - 0 - start - center - 1 - - - - - end - center - 1 - 1 - left - 300 - - - 10 - 0.1 - 0.1 - 1 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Claw Scratch Count - 0 - start - center - 1 - - - - - end - center - 1 - 0 - left - 300 - - - 50 - 1 - 1 - 10 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Warp Effect - 0 - start - center - 1 - - - - - end - center - 1 - 2 - left - 300 - - - 1 - 0 - 0.01 - 0.1 - - - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - 0 - - - - - Flash Color - 0 - start - center - 1 - - - - - 1 - 1 - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - - - - - - - 30 - 10 - - - TV Effect Options - 0 - 1 - - - - - - - - - - - none - 1 - - - - - 0 - - - - - Color - 0 - start - center - 1 - - - - - 0 - 1 - - - - - edit-clear-symbolic - Reset to Default Value - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/ui/gtk4/firePage.ui b/resources/ui/gtk4/firePage.ui new file mode 100644 index 0000000..346b2ee --- /dev/null +++ b/resources/ui/gtk4/firePage.ui @@ -0,0 +1,315 @@ + + + + + vertical + 60 + 60 + 60 + 60 + + + + 20 + + + none + 1 + + + + + 0 + + + + + Presets + 0 + start + center + 1 + + + + + Choose a Preset + + + + + + + + + + + + + + + + none + 1 + + + + + 0 + + + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 10000 + 500 + 10 + 100 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Scale + 0 + start + center + 1 + + + + + end + center + 1 + 2 + left + 300 + + + 3 + 0.1 + 0.01 + 0.1 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Speed + 0 + start + center + 1 + + + + + end + center + 1 + 2 + left + 300 + + + 2.0 + -2.0 + 0.01 + 0.1 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + vertical + 3 + + + 3D Noise + 0 + start + center + 1 + + + + + Creates a more dynamic fire but requires more GPU power. + 0 + start + center + 1 + + + + + + + + end + center + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Gradient + 0 + start + center + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + 1 + 1 + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4/generalPage.ui b/resources/ui/gtk4/generalPage.ui new file mode 100644 index 0000000..193c826 --- /dev/null +++ b/resources/ui/gtk4/generalPage.ui @@ -0,0 +1,109 @@ + + + + + + + vertical + 60 + 60 + 60 + 60 + + + + + + none + 1 + + + + + + 0 + + + + + vertical + 3 + + + Close Window Animation + 0 + start + center + 1 + + + + + If multiple are selected, one is chosen randomly. + 0 + start + center + 1 + + + + + + + + Choose Effects + close-effect-menu + + + + + + + + + + 0 + + + + + Also Burn Dialogs + 0 + start + center + 1 + + + + + end + center + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4/matrixPage.ui b/resources/ui/gtk4/matrixPage.ui new file mode 100644 index 0000000..7f5a570 --- /dev/null +++ b/resources/ui/gtk4/matrixPage.ui @@ -0,0 +1,234 @@ + + + + + vertical + 60 + 60 + 60 + 60 + + + + + + none + 1 + + + + + 0 + + + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 10000 + 500 + 10 + 100 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Scale + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 50 + 5 + 1 + 5 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Randomness + 0 + start + center + 1 + + + + + end + center + 1 + 2 + left + 300 + + + 1.0 + 0.0 + 0.01 + 0.1 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Trail Color + 0 + start + center + 1 + + + + + 0 + 1 + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Tip Color + 0 + start + center + 1 + + + + + 0 + 1 + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4/prefs.ui b/resources/ui/gtk4/prefs.ui new file mode 100644 index 0000000..92a5f39 --- /dev/null +++ b/resources/ui/gtk4/prefs.ui @@ -0,0 +1,27 @@ + + + + + + main-menu + open-menu-symbolic + + + + + + main-stack + + + + + slide-up-down + 1 + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4/trexPage.ui b/resources/ui/gtk4/trexPage.ui new file mode 100644 index 0000000..2bfe567 --- /dev/null +++ b/resources/ui/gtk4/trexPage.ui @@ -0,0 +1,246 @@ + + + + + vertical + 60 + 60 + 60 + 60 + + + + + + none + 1 + + + + + 0 + + + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 10000 + 500 + 10 + 100 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Claw Scratch Scale + 0 + start + center + 1 + + + + + end + center + 1 + 1 + left + 300 + + + 10 + 0.1 + 0.1 + 1 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Claw Scratch Count + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 50 + 1 + 1 + 10 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Warp Effect + 0 + start + center + 1 + + + + + end + center + 1 + 2 + left + 300 + + + 1 + 0 + 0.01 + 0.1 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Flash Color + 0 + start + center + 1 + + + + + 1 + 1 + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/ui/gtk4/tvPage.ui b/resources/ui/gtk4/tvPage.ui new file mode 100644 index 0000000..c64ecef --- /dev/null +++ b/resources/ui/gtk4/tvPage.ui @@ -0,0 +1,109 @@ + + + + + vertical + 60 + 60 + 60 + 60 + + + + + + none + 1 + + + + + 0 + + + + + Animation Time [ms] + 0 + start + center + 1 + + + + + end + center + 1 + 0 + left + 300 + + + 5000 + 100 + 10 + 100 + + + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + 0 + + + + + Color + 0 + start + center + 1 + + + + + 0 + 1 + + + + + edit-clear-symbolic + Reset to Default Value + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml index d19bbf3..be346ba 100644 --- a/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml +++ b/schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml @@ -1,27 +1,11 @@ - - - - - - - - - "fire" - Close Animation - The animation used for window closing. - - - - - 1500 - Destroy Animation Time - The time it takes to burn the windows. - + + + false @@ -29,22 +13,38 @@ Set to true to burn dialogs as well. + + + + + + true + Fire Close Effect + Use the fire effect for window closing. + + + + 1500 + Fire Animation Time + The time the fire effect takes. + + 0.5 - Flame Movement Speed - The speed at which the flames move. + Fire Movement Speed + The speed at which the fire moves. 1.0 - Flame Scale - The size of the flames. + Fire Scale + The size of the fire. false - Flame 3D Noise - Use a 3D noise for the flames. + Fire 3D Noise + Use a 3D noise for the fire. @@ -77,6 +77,21 @@ The fifth color of the fire gradient. + + + + + + false + Matrix Close Effect + Use the matrix effect for window closing. + + + + 2000 + Matrix Animation Time + The time the matrix effect takes. + 20 @@ -102,6 +117,21 @@ The color matrix trails. + + + + + + false + TV Close Effect + Use the tv effect for window closing. + + + + 400 + TV Animation Time + The time the tv effect takes. + "rgb(255, 255, 255)" @@ -109,6 +139,21 @@ The color of the TV effect. + + + + + + false + T-Rex Close Effect + Use the trex effect for window closing. + + + + 1500 + T-Rex Animation Time + The time the trex effect takes. + "rgb(170, 0, 0)" @@ -134,7 +179,6 @@ Number of claw scratches drawn on top of each other. - \ No newline at end of file diff --git a/src/FireEffect.js b/src/FireEffect.js new file mode 100644 index 0000000..e0c5458 --- /dev/null +++ b/src/FireEffect.js @@ -0,0 +1,355 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const {Gio, GObject} = imports.gi; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const utils = Me.imports.src.utils; + +////////////////////////////////////////////////////////////////////////////////////////// +// This effect is a homage to the good old Compiz days. However, it is implemented // +// quite differently. While Compiz used a particle system, this effect uses a noise // +// shader. The noise is moved vertically over time and mapped to a configurable color // +// gradient. It is faded to transparency towards the edges of the window. In addition, // +// there are a couple of moving gradients which fade-in or fade-out the fire effect. // +////////////////////////////////////////////////////////////////////////////////////////// + +// The shader class for this effect is registered further down in this file. +let FireShader = null; + +// The effect class is completely static. It can be used to get some metadata (like the +// effect's name or supported GNOME Shell versions), to initialize the respective page of +// the settings dialog, as well as to create the actual shader for the effect. +var FireEffect = class FireEffect { + + // ---------------------------------------------------------------------------- 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. the '*-close-effect'). + static getNick() { + return 'fire'; + } + + // This will be shown in the sidebar of the preferences dialog as well as in the + // drop-down menus where the user can choose the effect. + static getLabel() { + return 'Fire'; + } + + // -------------------------------------------------------------------- API for prefs.js + + // This is called by the preferences dialog. It loads the settings page for this effect, + // binds all properties to the settings and appends the page to the main stack of the + // preferences dialog. + static initPreferences(dialog) { + + // Add the settings page to the builder. + dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/firePage.ui`); + + // Bind all properties. + dialog.bindAdjustment('fire-animation-time'); + dialog.bindAdjustment('flame-movement-speed'); + dialog.bindAdjustment('flame-scale'); + dialog.bindSwitch('flame-3d-noise'); + dialog.bindColorButton('fire-color-1'); + dialog.bindColorButton('fire-color-2'); + dialog.bindColorButton('fire-color-3'); + dialog.bindColorButton('fire-color-4'); + dialog.bindColorButton('fire-color-5'); + + // The fire-gradient-reset button needs to be bound explicitly. + dialog.getBuilder().get_object('reset-fire-colors').connect('clicked', () => { + dialog.getSettings().reset('fire-color-1'); + dialog.getSettings().reset('fire-color-2'); + dialog.getSettings().reset('fire-color-3'); + dialog.getSettings().reset('fire-color-4'); + dialog.getSettings().reset('fire-color-5'); + }); + + // Initialize the fire-preset dropdown. + FireEffect._createFirePresets(dialog); + + // Finally, append the settings page to the main stack. + const stack = dialog.getBuilder().get_object('main-stack'); + stack.add_titled( + dialog.getBuilder().get_object('fire-prefs'), FireEffect.getNick(), + FireEffect.getLabel()); + } + + // ---------------------------------------------------------------- API for extension.js + + // This is called from extension.js whenever a window is closed with this effect. + static createShader(settings) { + return new FireShader(settings); + } + + // This is also called from extension.js. It is used to tweak the ongoing transitions of + // the actor - usually windows are faded to transparency and scaled down slightly by + // GNOME Shell. Here, we modify this behavior as well as the transition duration. + static tweakTransitions(actor, settings) { + const animationTime = settings.get_int('fire-animation-time'); + + const tweakTransition = (property, value) => { + const transition = actor.get_transition(property); + if (transition) { + transition.set_to(value); + transition.set_duration(animationTime); + } + }; + + // If there's a transition in progress, we re-target these transitions so that the + // window is neither scaled nor faded. + tweakTransition('opacity', 255); + tweakTransition('scale-x', 1); + tweakTransition('scale-y', 1); + } + + // ----------------------------------------------------------------------- private stuff + + // This populates the preset dropdown menu for the fire options. + static _createFirePresets(dialog) { + dialog.getBuilder().get_object('settings-widget').connect('realize', (widget) => { + const presets = [ + { + name: 'Default Fire', + scale: 1.0, + speed: 0.5, + color1: 'rgba(76, 51, 25, 0.0)', + color2: 'rgba(180, 55, 30, 0.7)', + color3: 'rgba(255, 76, 38, 0.9)', + color4: 'rgba(255, 166, 25, 1)', + color5: 'rgba(255, 255, 255, 1)' + }, + { + name: 'Hell Fire', + scale: 1.5, + speed: 0.2, + color1: 'rgba(0,0,0,0)', + color2: 'rgba(103,7,80,0.5)', + color3: 'rgba(150,0,24,0.9)', + color4: 'rgb(255,200,0)', + color5: 'rgba(255, 255, 255, 1)' + }, + { + name: 'Dark and Smutty', + scale: 1.0, + speed: 0.5, + color1: 'rgba(0,0,0,0)', + color2: 'rgba(36,3,0,0.5)', + color3: 'rgba(150,0,24,0.9)', + color4: 'rgb(255,177,21)', + color5: 'rgb(255,238,166)' + }, + { + name: 'Cold Breeze', + scale: 1.5, + speed: -0.1, + color1: 'rgba(0,110,255,0)', + color2: 'rgba(30,111,180,0.24)', + color3: 'rgba(38,181,255,0.54)', + color4: 'rgba(34,162,255,0.84)', + color5: 'rgb(97,189,255)' + }, + { + name: 'Santa is Coming', + scale: 0.4, + speed: -0.5, + color1: 'rgba(0,110,255,0)', + color2: 'rgba(208,233,255,0.24)', + color3: 'rgba(207,235,255,0.84)', + color4: 'rgb(208,243,255)', + color5: 'rgb(255,255,255)' + } + ]; + + const menu = Gio.Menu.new(); + const group = Gio.SimpleActionGroup.new(); + const groupName = 'presets'; + + // Add all presets. + presets.forEach((preset, i) => { + const actionName = 'fire' + i; + menu.append(preset.name, groupName + '.' + actionName); + let action = Gio.SimpleAction.new(actionName, null); + + // Load the preset on activation. + action.connect('activate', () => { + dialog.getSettings().set_double('flame-movement-speed', preset.speed); + dialog.getSettings().set_double('flame-scale', preset.scale); + dialog.getSettings().set_string('fire-color-1', preset.color1); + dialog.getSettings().set_string('fire-color-2', preset.color2); + dialog.getSettings().set_string('fire-color-3', preset.color3); + dialog.getSettings().set_string('fire-color-4', preset.color4); + dialog.getSettings().set_string('fire-color-5', preset.color5); + }); + + group.add_action(action); + }); + + dialog.getBuilder().get_object('fire-preset-button').set_menu_model(menu); + + const root = utils.isGTK4() ? widget.get_root() : widget.get_toplevel(); + root.insert_action_group(groupName, group); + }); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// The shader class for this effect will only be registered in GNOME Shell's process // +// (not in the preferences process). It's done this way as Clutter may not be installed // +// on the system and therefore the preferences would crash. // +////////////////////////////////////////////////////////////////////////////////////////// + +if (utils.isInShellProcess()) { + + const Clutter = imports.gi.Clutter; + const shaderSnippets = Me.imports.src.shaderSnippets; + + FireShader = GObject.registerClass({Properties: {}, Signals: {}}, + class FireShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the gradient values from the settings. We directly inject the values in the + // GLSL code below. The shader is compiled once for each window-closing anyways. In + // the future, we may want to prevent this frequent recompilations of shaders, + // though. + const gradient = []; + for (let i = 1; i <= 5; i++) { + const color = + Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; + gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ + color.blue / 255}, ${color.alpha / 255})`); + } + + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + // These may be configurable in the future. + const float EDGE_FADE = 90; + const float FADE_WIDTH = 0.1; + const float HIDE_TIME = 0.4; + const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; + const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')}; + + // This maps the input value from [0..1] to a color from the gradient. + vec4 getFireColor(float v) { + const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); + const vec4 colors[5] = vec4[]( + ${gradient[0]}, + ${gradient[1]}, + ${gradient[2]}, + ${gradient[3]}, + ${gradient[4]} + ); + + if (v < steps[0]) { + return colors[0]; + } + + for (int i=0; i<4; ++i) { + if (v <= steps[i+1]) { + return mix(colors[i], colors[i+1], vec4(v - steps[i])/(steps[i+1]-steps[i])); + } + } + + return colors[4]; + } + + // This method requires the uniforms from standardUniforms() to be available. + // It returns two values: The first is an alpha value which can be used for the window + // texture. This gradually dissolves the window from top to bottom. The second can be used + // to mask any effect, it will be most opaque where the window is currently fading and + // gradually dissolve to zero over time. + // hideTime: A value in [0..1]. It determines the percentage of the animation which + // is spent for hiding the window. 1-hideTime will be spent thereafter for + // dissolving the effect mask. + // fadeWidth: The relative size of the window-hiding gradient in [0..1]. + // edgeFadeWidth: The pixel width of the effect fading range at the edges of the window. + vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) { + float burnProgress = clamp(uProgress/hideTime, 0, 1); + float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1); + + // Gradient from top to bottom. + float t = cogl_tex_coord_in[0].t * (1 - fadeWidth); + + // Visible part of the window. Gradually dissolves towards the bottom. + float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1); + + // Gradient from top burning window. + float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1); + + // Fade-out when the window burned down. + if (uProgress > hideTime) { + float fade = sqrt(1-afterBurnProgress*afterBurnProgress); + effectMask *= mix(1, 1-t, afterBurnProgress) * fade; + } + + // Fade at window borders. + vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); + effectMask *= smoothstep(0, 1, clamp(pos.x / edgeFadeWidth, 0, 1)); + effectMask *= smoothstep(0, 1, clamp(pos.y / edgeFadeWidth, 0, 1)); + effectMask *= smoothstep(0, 1, clamp((uSizeX - pos.x) / edgeFadeWidth, 0, 1)); + effectMask *= smoothstep(0, 1, clamp((uSizeY - pos.y) / edgeFadeWidth, 0, 1)); + + return vec2(windowMask, effectMask); + } + + void main() { + + // Get a noise value which moves vertically in time. + vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; + uv.y += uTime * FIRE_SPEED; + + #if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} + float noise = noise3D(vec3(uv*7.5, uTime*FIRE_SPEED*3.0), 5); + #else + float noise = noise2D(uv * 7.5, 5); + #endif + + // Modulate noise by effect mask. + vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); + noise *= effectMask.y; + + // Map noise value to color. + vec4 fire = getFireColor(noise); + fire.rgb *= fire.a; + + // Get the window texture and fade it according to the effect mask. + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; + + // Add the fire to the window. + cogl_color_out += fire; + + // These are pretty useful for understanding how this works. + // cogl_color_out = vec4(vec3(noise), 1); + // cogl_color_out = vec4(vec3(effectMask.x), 1); + // cogl_color_out = vec4(vec3(effectMask.y), 1); + } + `); + }; + }); +} \ No newline at end of file diff --git a/src/MatrixEffect.js b/src/MatrixEffect.js new file mode 100644 index 0000000..bd23bd2 --- /dev/null +++ b/src/MatrixEffect.js @@ -0,0 +1,244 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const GObject = imports.gi.GObject; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const utils = Me.imports.src.utils; + +////////////////////////////////////////////////////////////////////////////////////////// +// The MatrixShader multiplies a grid of random letters with some gradients which are // +// moving from top to bottom. The speed of the moving gradients is chosen randomly. // +// Also, there is a random delay making the gradients not drop all at the same time. // +// This effect is not available on GNOME 3.3x, due to the limitation described in the // +// documentation of vfunc_paint_target further down in this file. // +////////////////////////////////////////////////////////////////////////////////////////// + +// The shader class for this effect is registered further down in this file. +let MatrixShader = null; + +// The effect class is completely static. It can be used to get some metadata (like the +// effect's name or supported GNOME Shell versions), to initialize the respective page of +// the settings dialog, as well as to create the actual shader for the effect. +var MatrixEffect = class MatrixEffect { + + // ---------------------------------------------------------------------------- metadata + + // This effect is only available on GNOME Shell 40+. + static getMinShellVersion() { + return [40, 0]; + } + + // This will be called in various places where a unique identifier for this effect is + // required. It should match the prefix of the settings keys which store whether the + // effect is enabled currently (e.g. the '*-close-effect'). + static getNick() { + return 'matrix'; + } + + // This will be shown in the sidebar of the preferences dialog as well as in the + // drop-down menus where the user can choose the effect. + static getLabel() { + return 'Matrix'; + } + + // -------------------------------------------------------------------- API for prefs.js + + // This is called by the preferences dialog. It loads the settings page for this effect, + // binds all properties to the settings and appends the page to the main stack of the + // preferences dialog. + static initPreferences(dialog) { + + // Add the settings page to the builder. + dialog.getBuilder().add_from_resource('/ui/gtk4/matrixPage.ui'); + + // Bind all properties. + dialog.bindAdjustment('matrix-animation-time'); + dialog.bindAdjustment('matrix-scale'); + dialog.bindAdjustment('matrix-randomness'); + dialog.bindColorButton('matrix-trail-color'); + dialog.bindColorButton('matrix-tip-color'); + + // Finally, append the settings page to the main stack. + const stack = dialog.getBuilder().get_object('main-stack'); + stack.add_titled( + dialog.getBuilder().get_object('matrix-prefs'), MatrixEffect.getNick(), + MatrixEffect.getLabel()); + } + + // ---------------------------------------------------------------- API for extension.js + + // This is called from extension.js whenever a window is closed with this effect. + static createShader(settings) { + return new MatrixShader(settings); + } + + // This is also called from extension.js. It is used to tweak the ongoing transitions of + // the actor - usually windows are faded to transparency and scaled down slightly by + // GNOME Shell. Here, we modify this behavior as well as the transition duration. + static tweakTransitions(actor, settings) { + const animationTime = settings.get_int('matrix-animation-time'); + + const tweakTransition = (property, value) => { + const transition = actor.get_transition(property); + if (transition) { + transition.set_to(value); + transition.set_duration(animationTime); + } + }; + + // If there's a transition in progress, we re-target these transitions so that the + // window is neither scaled nor faded. + tweakTransition('opacity', 255); + tweakTransition('scale-x', 1); + tweakTransition('scale-y', 1); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// The shader class for this effect will only be registered in GNOME Shell's process // +// (not in the preferences process). It's done this way as Clutter may not be installed // +// on the system and therefore the preferences would crash. // +////////////////////////////////////////////////////////////////////////////////////////// + +if (utils.isInShellProcess()) { + + const {Clutter, GdkPixbuf, Cogl} = imports.gi; + const shaderSnippets = Me.imports.src.shaderSnippets; + + + MatrixShader = GObject.registerClass({Properties: {}, Signals: {}}, + class MatrixShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the font texture. As the shader is re-created for each window animation, + // this texture is also re-created each time. This could be improved in the future! + const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png'); + this._fontTexture = new Clutter.Image(); + this._fontTexture.set_data( + fontData.get_pixels(), + fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888, + fontData.width, fontData.height, fontData.rowstride); + + const trailColor = + Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1]; + const tipColor = + Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1]; + + // The technique for this effect was inspired by + // https://www.shadertoy.com/view/ldccW4, however the implementation is quite + // different as the letters drop only once and there is no need for a noise texture. + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + uniform sampler2D uFontTexture; + + // These may be configurable in the future. + const float EDGE_FADE = 30; + const float FADE_WIDTH = 150; + const float TRAIL_LENGTH = 0.2; + const float FINAL_FADE_START_TIME = 0.8; + const float LETTER_TILES = 16.0; + const float LETTER_SIZE = ${settings.get_int('matrix-scale')}; + const float LETTER_FLICKER_SPEED = 2.0; + const float RANDOMNESS = ${settings.get_double('matrix-randomness')}; + + // This returns a flickering grid of random letters. + float getText(vec2 fragCoord) { + vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY); + vec2 uv = mod(pixelCoords.xy, LETTER_SIZE)/LETTER_SIZE; + vec2 block = pixelCoords/LETTER_SIZE - uv; + + // Choose random letter. + uv += floor(hash2D(floor(hash2D(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES); + + return texture2D(uFontTexture, uv/LETTER_TILES).r; + } + + // This returns two values: The first are gradients for the "raindrops" which move + // from top to bottom. This is used for fading the letters. The second value is set + // to one below each drop and to zero above it. This second value is used for fading + // the window texture. + vec2 getRain(vec2 fragCoord) { + vec2 coords = fragCoord * vec2(uSizeX, uSizeY); + coords.x -= mod(coords.x, LETTER_SIZE); + + float delay = fract(sin(coords.x)*78.233) * mix(0.0, 1.0, RANDOMNESS); + float speed = fract(cos(coords.x)*12.989) * mix(0.0, 0.3, RANDOMNESS) + 1.5; + + float distToDrop = (uProgress*2-delay)*speed - fragCoord.y; + + float rainAlpha = distToDrop >= 0 ? exp(-distToDrop/TRAIL_LENGTH) : 0; + float windowAlpha = 1 - clamp(uSizeY*distToDrop, 0, FADE_WIDTH) / FADE_WIDTH; + + return vec2(rainAlpha, windowAlpha); + } + + void main() { + + // Get a cool matrix effect. See comments for those methods above. + vec2 rain = getRain(cogl_tex_coord_in[0].st); + float text = getText(cogl_tex_coord_in[0].st); + + // Get the window texture and fade it according to the effect mask. + cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * rain.y; + + // This is used to fade out the remaining trails in the end. + float finalFade = 1-clamp((uProgress-FINAL_FADE_START_TIME)/ + (1-FINAL_FADE_START_TIME), 0, 1); + float rainAlpha = finalFade * rain.x; + + // Fade at window borders. + vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); + rainAlpha *= clamp(pos.x / EDGE_FADE, 0, 1); + rainAlpha *= clamp(pos.y / EDGE_FADE, 0, 1); + rainAlpha *= clamp((uSizeX - pos.x) / EDGE_FADE, 0, 1); + rainAlpha *= clamp((uSizeY - pos.y) / EDGE_FADE, 0, 1); + + // Add the matrix effect to the window. + vec3 trailColor = vec3(${trailColor.red / 255}, + ${trailColor.green / 255}, + ${trailColor.blue / 255}); + vec3 tipColor = vec3(${tipColor.red / 255}, + ${tipColor.green / 255}, + ${tipColor.blue / 255}); + cogl_color_out.rgb += mix(trailColor, tipColor, min(1, pow(rainAlpha+0.1, 4))) * rainAlpha * text; + + // These are pretty useful for understanding how this works. + // cogl_color_out = vec4(vec3(text), 1); + // cogl_color_out = vec4(vec3(rain.x), 1); + // cogl_color_out = vec4(vec3(rain.y), 1); + } + `); + }; + + // This is overridden to bind the font texture for drawing. Sadly, this seems to be + // impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called + // get_target() back then but this is not wrapped in GJS. + // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 + vfunc_paint_target(node, paint_context) { + const pipeline = this.get_pipeline(); + pipeline.set_layer_texture(1, this._fontTexture.get_texture()); + this.set_uniform_value('uFontTexture', 1); + super.vfunc_paint_target(node, paint_context); + } + }); +} \ No newline at end of file diff --git a/src/TRexEffect.js b/src/TRexEffect.js new file mode 100644 index 0000000..9850601 --- /dev/null +++ b/src/TRexEffect.js @@ -0,0 +1,254 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const GObject = imports.gi.GObject; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const utils = Me.imports.src.utils; + +////////////////////////////////////////////////////////////////////////////////////////// +// This effect tears your windows apart with a series of violent scratches! // +// This effect is not available on GNOME 3.3x, due to the limitation described in the // +// documentation of vfunc_paint_target further down in this file. // +////////////////////////////////////////////////////////////////////////////////////////// + +// The shader class for this effect is registered further down in this file. +let TRexShader = null; + +// The effect class is completely static. It can be used to get some metadata (like the +// effect's name or supported GNOME Shell versions), to initialize the respective page of +// the settings dialog, as well as to create the actual shader for the effect. +var TRexEffect = class TRexEffect { + + // ---------------------------------------------------------------------------- metadata + + // This effect is only available on GNOME Shell 40+. + static getMinShellVersion() { + return [40, 0]; + } + + // This will be called in various places where a unique identifier for this effect is + // required. It should match the prefix of the settings keys which store whether the + // effect is enabled currently (e.g. the '*-close-effect'). + static getNick() { + return 'trex'; + } + + // This will be shown in the sidebar of the preferences dialog as well as in the + // drop-down menus where the user can choose the effect. + static getLabel() { + return 'T-Rex Attack'; + } + + // -------------------------------------------------------------------- API for prefs.js + + // This is called by the preferences dialog. It loads the settings page for this effect, + // binds all properties to the settings and appends the page to the main stack of the + // preferences dialog. + static initPreferences(dialog) { + + // Add the settings page to the builder. + dialog.getBuilder().add_from_resource('/ui/gtk4/trexPage.ui'); + + // Bind all properties. + dialog.bindAdjustment('trex-animation-time'); + dialog.bindColorButton('claw-scratch-color'); + dialog.bindAdjustment('claw-scratch-scale'); + dialog.bindAdjustment('claw-scratch-count'); + dialog.bindAdjustment('claw-scratch-warp'); + + // Finally, append the settings page to the main stack. + const stack = dialog.getBuilder().get_object('main-stack'); + stack.add_titled( + dialog.getBuilder().get_object('trex-prefs'), TRexEffect.getNick(), + TRexEffect.getLabel()); + } + + // ---------------------------------------------------------------- API for extension.js + + // This is called from extension.js whenever a window is closed with this effect. + static createShader(settings) { + return new TRexShader(settings); + } + + // This is also called from extension.js. It is used to tweak the ongoing transitions of + // the actor - usually windows are faded to transparency and scaled down slightly by + // GNOME Shell. Here, we modify this behavior as well as the transition duration. + static tweakTransitions(actor, settings) { + const animationTime = settings.get_int('trex-animation-time'); + + const tweakTransition = (property, value) => { + const transition = actor.get_transition(property); + if (transition) { + transition.set_to(value); + transition.set_duration(animationTime); + } + }; + + // For this effect, we slightly increase the window's scale as part of the warp + // effect. + const warp = 0.5 * settings.get_double('claw-scratch-warp'); + tweakTransition('opacity', 255); + tweakTransition('scale-x', 1.0 + warp); + tweakTransition('scale-y', 1.0 + warp); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// The shader class for this effect will only be registered in GNOME Shell's process // +// (not in the preferences process). It's done this way as Clutter may not be installed // +// on the system and therefore the preferences would crash. // +////////////////////////////////////////////////////////////////////////////////////////// + +if (utils.isInShellProcess()) { + + const {Clutter, GdkPixbuf, Cogl} = imports.gi; + const shaderSnippets = Me.imports.src.shaderSnippets; + + + TRexShader = GObject.registerClass({Properties: {}, Signals: {}}, + class TRexShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + // Load the claw texture. As the shader is re-created for each window animation, + // this texture is also re-created each time. This could be improved in the future! + // See assets/README.md for how this texture was created. + const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png'); + this._clawTexture = new Clutter.Image(); + this._clawTexture.set_data( + clawData.get_pixels(), Cogl.PixelFormat.RGB_888, clawData.width, + clawData.height, clawData.rowstride); + + const color = + Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1]; + + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + ${shaderSnippets.noise()} + + // See assets/README.md for how this texture was created. + uniform sampler2D uClawTexture; + + const vec2 SEED = vec2(${Math.random()}, ${Math.random()}); + const float CLAW_SIZE = ${settings.get_double('claw-scratch-scale')}; + const float NUM_CLAWS = ${settings.get_int('claw-scratch-count')}; + const float WARP_INTENSITY = 1.0 + ${settings.get_double('claw-scratch-warp')}; + const float FLASH_INTENSITY = 0.1; + const float MAX_SPAWN_TIME = 0.5; // Scratches will only start in the first half of the animation. + const float FF_TIME = 0.5; // Relative time for the final fade to transparency. + + // This method generates a grid of randomly rotated, slightly shifted and scaled + // UV squares. It returns the texture coords of the UV square at the given actor + // coordinates. If these do not fall into one of the UV grids, the coordinates of + // the closest UV grid will be clamped and returned. + vec2 getClawUV(vec2 texCoords, float gridScale, vec2 seed) { + + // Shift coordinates by a random offset and make sure the have a 1:1 aspect ratio. + vec2 coords = texCoords + hash2D(seed); + coords *= uSizeX < uSizeY ? vec2(1.0, 1.0 * uSizeY / uSizeX) : vec2(1.0 * uSizeX / uSizeY, 1.0); + + // Apply global scale. + coords *= gridScale; + + // Get grid cell coordinates in [0..1]. + vec2 cellUV = mod(coords, vec2(1)); + + // This is unique for each cell. + vec2 cellID = coords-cellUV + vec2(362.456); + + // Add random rotation, scale and offset to each grid cell. + float scale = mix(0.8, 1.0, hash(cellID*seed*134.451)); + float offsetX = mix(0.0, 1.0 - scale, hash(cellID*seed*54.4129)); + float offsetY = mix(0.0, 1.0 - scale, hash(cellID*seed*25.3089)); + float rotation = mix(0.0, 2.0 * 3.141, hash(cellID*seed*2.99837)); + + cellUV -= vec2(offsetX, offsetY); + cellUV /= scale; + + cellUV -= 0.5; + cellUV = vec2(cellUV.x * cos(rotation) - cellUV.y * sin(rotation), + cellUV.x * sin(rotation) + cellUV.y * cos(rotation)); + cellUV += 0.5; + + // Clamp resulting coordinates. + return clamp(cellUV, vec2(0), vec2(1)); + } + + void main() { + + // Warp the texture coordinates to create a blow-up effect. + vec2 coords = cogl_tex_coord_in[0].st * 2.0 - 1.0; + float dist = length(coords); + coords = (coords/dist * pow(dist, WARP_INTENSITY)) * 0.5 + 0.5; + coords = mix(cogl_tex_coord_in[0].st, coords, uProgress); + + // Accumulate several random scratches. The color in the scratch map refers to the + // relative time when the respective part will become invisible. Therefore we can + // add a value to make the scratch appear later. + float scratchMap = 1.0; + for (int i=0; i uProgress ? 1 : 0); + + // Add colorful flashes. + float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - uProgress) + 1; + if (flashIntensity < 0 || flashIntensity >= 1) { + flashIntensity = 0; + } + + vec3 flashColor = vec3(${color.red / 255}, + ${color.green / 255}, + ${color.blue / 255}) * ${color.alpha / 255}; + + cogl_color_out.rgb += flashIntensity * mix(flashColor, vec3(0), uProgress); + + // Fade out the remaining shards. + float fade = smoothstep(0, 1, 1 - clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); + cogl_color_out *= fade; + + // These are pretty useful for understanding how this works. + // cogl_color_out = vec4(vec3(flashIntensity), 1); + // cogl_color_out = vec4(vec3(scratchMap), 1); + } + `); + }; + + // This is overridden to bind the claw texture for drawing. Sadly, this seems to be + // impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called + // get_target() back then but this is not wrapped in GJS. + // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 + vfunc_paint_target(node, paint_context) { + const pipeline = this.get_pipeline(); + pipeline.set_layer_texture(1, this._clawTexture.get_texture()); + this.set_uniform_value('uClawTexture', 1); + super.vfunc_paint_target(node, paint_context); + } + }); +} \ No newline at end of file diff --git a/src/TVEffect.js b/src/TVEffect.js new file mode 100644 index 0000000..b38f523 --- /dev/null +++ b/src/TVEffect.js @@ -0,0 +1,179 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +// Copyright (c) 2021 Simon Schneegans // +// Released under the GPLv3 or later. See LICENSE file for details. // +////////////////////////////////////////////////////////////////////////////////////////// + +'use strict'; + +const GObject = imports.gi.GObject; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const utils = Me.imports.src.utils; + +////////////////////////////////////////////////////////////////////////////////////////// +// The TVShader hides the actor by making it first transparent from top and // +// bottom towards the middle and then hiding the resulting line from left and right // +// towards the center. // +////////////////////////////////////////////////////////////////////////////////////////// + +// The shader class for this effect is registered further down in this file. +let TVShader = null; + +// The effect class is completely static. It can be used to get some metadata (like the +// effect's name or supported GNOME Shell versions), to initialize the respective page of +// the settings dialog, as well as to create the actual shader for the effect. +var TVEffect = class TVEffect { + + // ---------------------------------------------------------------------------- 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. the '*-close-effect'). + static getNick() { + return 'tv'; + } + + // This will be shown in the sidebar of the preferences dialog as well as in the + // drop-down menus where the user can choose the effect. + static getLabel() { + return 'TV Effect'; + } + + // -------------------------------------------------------------------- API for prefs.js + + // This is called by the preferences dialog. It loads the settings page for this effect, + // binds all properties to the settings and appends the page to the main stack of the + // preferences dialog. + static initPreferences(dialog) { + + // Add the settings page to the builder. + dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/tvPage.ui`); + + // Bind all properties. + dialog.bindAdjustment('tv-animation-time'); + dialog.bindColorButton('tv-effect-color'); + + // Finally, append the settings page to the main stack. + const stack = dialog.getBuilder().get_object('main-stack'); + stack.add_titled( + dialog.getBuilder().get_object('tv-prefs'), TVEffect.getNick(), + TVEffect.getLabel()); + } + + // ---------------------------------------------------------------- API for extension.js + + // This is called from extension.js whenever a window is closed with this effect. + static createShader(settings) { + return new TVShader(settings); + } + + // This is also called from extension.js. It is used to tweak the ongoing transitions of + // the actor - usually windows are faded to transparency and scaled down slightly by + // GNOME Shell. Here, we modify this behavior as well as the transition duration. + static tweakTransitions(actor, settings) { + const animationTime = settings.get_int('tv-animation-time'); + + const tweakTransition = (property, value) => { + const transition = actor.get_transition(property); + if (transition) { + transition.set_to(value); + transition.set_duration(animationTime); + } + }; + + // For this effect, we scale the window only vertically. + tweakTransition('opacity', 255); + tweakTransition('scale-x', 1); + tweakTransition('scale-y', 0.5); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// The shader class for this effect will only be registered in GNOME Shell's process // +// (not in the preferences process). It's done this way as Clutter may not be installed // +// on the system and therefore the preferences would crash. // +////////////////////////////////////////////////////////////////////////////////////////// + +if (utils.isInShellProcess()) { + + const Clutter = imports.gi.Clutter; + const shaderSnippets = Me.imports.src.shaderSnippets; + + TVShader = GObject.registerClass({Properties: {}, Signals: {}}, + class TVShader extends Clutter.ShaderEffect { + _init(settings) { + super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); + + const color = Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1]; + + this.set_shader_source(` + + // Inject some common shader snippets. + ${shaderSnippets.standardUniforms()} + + const float BLUR_WIDTH = 0.01; // Width of the gradients. + const float TB_TIME = 0.7; // Relative time for the top/bottom animation. + const float LR_TIME = 0.4; // Relative time for the left/right animation. + const float LR_DELAY = 0.6; // Delay after which the left/right animation starts. + const float FF_TIME = 0.2; // Relative time for the final fade to transparency. + + void main() { + + // All of these are in [0..1] during the different stages of the animation. + // tb refers to the top-bottom animation. + // lr refers to the left-right animation. + // ff refers to the final fade animation. + float tbProgress = smoothstep(0, 1, clamp(uProgress/TB_TIME, 0, 1)); + float lrProgress = smoothstep(0, 1, clamp((uProgress - LR_DELAY)/LR_TIME, 0, 1)); + float ffProgress = smoothstep(0, 1, clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); + + // This is a top-center-bottom gradient in [0..1..0] + float tb = cogl_tex_coord_in[0].t * 2; + tb = tb < 1 ? tb : 2 - tb; + + // This is a left-center-right gradient in [0..1..0] + float lr = cogl_tex_coord_in[0].s * 2; + lr = lr < 1 ? lr : 2 - lr; + + // Combine the progress values with the gradients to create the alpha masks. + float tbMask = 1 - smoothstep(0, 1, clamp((tbProgress - tb) / BLUR_WIDTH, 0, 1)); + float lrMask = 1 - smoothstep(0, 1, clamp((lrProgress - lr) / BLUR_WIDTH, 0, 1)); + float ffMask = 1 - smoothstep(0, 1, ffProgress); + + // Assemble the final alpha value. + float mask = tbMask * lrMask * ffMask; + + vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st); + vec4 effectColor = vec4(${color.red / 255}, + ${color.green / 255}, + ${color.blue / 255}, 1.0) * windowColor.a; + + windowColor.rgb = mix(windowColor.rgb, effectColor.rgb, smoothstep(0, 1, uProgress)); + + cogl_color_out = windowColor * mask; + + // These are pretty useful for understanding how this works. + // cogl_color_out = vec4(vec3(tbMask), 1); + // cogl_color_out = vec4(vec3(lrMask), 1); + // cogl_color_out = vec4(vec3(ffMask), 1); + // cogl_color_out = vec4(vec3(mask), 1); + } + `); + }; + }); +} \ No newline at end of file diff --git a/src/extension/FireShader.js b/src/extension/FireShader.js deleted file mode 100644 index 07644c4..0000000 --- a/src/extension/FireShader.js +++ /dev/null @@ -1,156 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// ) ( // -// ( /( ( ( ) ( ( ( ( )\ ) ( ( // -// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // -// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // -// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // -// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // -// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // -// |__/ // -// Copyright (c) 2021 Simon Schneegans // -// Released under the GPLv3 or later. See LICENSE file for details. // -////////////////////////////////////////////////////////////////////////////////////////// - -'use strict'; - -const {Clutter, GObject} = imports.gi; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const shaderSnippets = Me.imports.src.extension.shaderSnippets; - -////////////////////////////////////////////////////////////////////////////////////////// -// This effect is a homage to the good old Compiz days. However, it is implemented // -// quite differently. While Compiz used a particle system, this effect uses a noise // -// shader. The noise is moved vertically over time and mapped to a configurable color // -// gradient. It is faded to transparency towards the edges of the window. In addition, // -// there are a couple of moving gradients which fade-in or fade-out the fire effect. // -////////////////////////////////////////////////////////////////////////////////////////// - -var FireShader = GObject.registerClass({Properties: {}, Signals: {}}, - class FireShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - // Load the gradient values from the settings. We directly inject the values in the - // GLSL code below. The shader is compiled once for each window-closing anyways. In - // the future, we may want to prevent this frequent recompilations of shaders, - // though. - const gradient = []; - for (let i = 1; i <= 5; i++) { - const color = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1]; - gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${ - color.blue / 255}, ${color.alpha / 255})`); - } - - this.set_shader_source(` - - // Inject some common shader snippets. - ${shaderSnippets.standardUniforms()} - ${shaderSnippets.noise()} - - // These may be configurable in the future. - const float EDGE_FADE = 90; - const float FADE_WIDTH = 0.1; - const float HIDE_TIME = 0.4; - const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')}; - const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')}; - - // This maps the input value from [0..1] to a color from the gradient. - vec4 getFireColor(float v) { - const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8); - const vec4 colors[5] = vec4[]( - ${gradient[0]}, - ${gradient[1]}, - ${gradient[2]}, - ${gradient[3]}, - ${gradient[4]} - ); - - if (v < steps[0]) { - return colors[0]; - } - - for (int i=0; i<4; ++i) { - if (v <= steps[i+1]) { - return mix(colors[i], colors[i+1], vec4(v - - steps[i])/(steps[i+1]-steps[i])); - } - } - - return colors[4]; - } - - // This method requires the uniforms from standardUniforms() to be available. - // It returns two values: The first is an alpha value which can be used for the window - // texture. This gradually dissolves the window from top to bottom. The second can be used - // to mask any effect, it will be most opaque where the window is currently fading and - // gradually dissolve to zero over time. - // hideTime: A value in [0..1]. It determines the percentage of the animation which - // is spent for hiding the window. 1-hideTime will be spent thereafter for - // dissolving the effect mask. - // fadeWidth: The relative size of the window-hiding gradient in [0..1]. - // edgeFadeWidth: The pixel width of the effect fading range at the edges of the window. - vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) { - float burnProgress = clamp(uProgress/hideTime, 0, 1); - float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1); - - // Gradient from top to bottom. - float t = cogl_tex_coord_in[0].t * (1 - fadeWidth); - - // Visible part of the window. Gradually dissolves towards the bottom. - float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1); - - // Gradient from top burning window. - float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1); - - // Fade-out when the window burned down. - if (uProgress > hideTime) { - float fade = sqrt(1-afterBurnProgress*afterBurnProgress); - effectMask *= mix(1, 1-t, afterBurnProgress) * fade; - } - - // Fade at window borders. - vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); - effectMask *= smoothstep(0, 1, clamp(pos.x / edgeFadeWidth, 0, 1)); - effectMask *= smoothstep(0, 1, clamp(pos.y / edgeFadeWidth, 0, 1)); - effectMask *= smoothstep(0, 1, clamp((uSizeX - pos.x) / edgeFadeWidth, 0, 1)); - effectMask *= smoothstep(0, 1, clamp((uSizeY - pos.y) / edgeFadeWidth, 0, 1)); - - return vec2(windowMask, effectMask); - } - - void main() { - - // Get a noise value which moves vertically in time. - vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE; - uv.y += uTime * FIRE_SPEED; - - #if ${settings.get_boolean('flame-3d-noise') ? 1 : 0} - float noise = noise3D(vec3(uv*7.5, uTime*FIRE_SPEED*3.0), 5); - #else - float noise = noise2D(uv * 7.5, 5); - #endif - - // Modulate noise by effect mask. - vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE); - noise *= effectMask.y; - - // Map noise value to color. - vec4 fire = getFireColor(noise); - fire.rgb *= fire.a; - - // Get the window texture and fade it according to the effect mask. - cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x; - - // Add the fire to the window. - cogl_color_out += fire; - - // These are pretty useful for understanding how this works. - // cogl_color_out = vec4(vec3(noise), 1); - // cogl_color_out = vec4(vec3(effectMask.x), 1); - // cogl_color_out = vec4(vec3(effectMask.y), 1); - } - `); - }; -}); \ No newline at end of file diff --git a/src/extension/MatrixShader.js b/src/extension/MatrixShader.js deleted file mode 100644 index fe0ad38..0000000 --- a/src/extension/MatrixShader.js +++ /dev/null @@ -1,147 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// ) ( // -// ( /( ( ( ) ( ( ( ( )\ ) ( ( // -// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // -// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // -// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // -// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // -// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // -// |__/ // -// Copyright (c) 2021 Simon Schneegans // -// Released under the GPLv3 or later. See LICENSE file for details. // -////////////////////////////////////////////////////////////////////////////////////////// - -'use strict'; - -const {Clutter, GObject, GdkPixbuf, Cogl} = imports.gi; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const shaderSnippets = Me.imports.src.extension.shaderSnippets; - -////////////////////////////////////////////////////////////////////////////////////////// -// The MatrixShader multiplies a grid of random letters with some gradients which are // -// moving from top to bottom. The speed of the moving gradients is chosen randomly. // -// Also, there is a random delay making the gradients not drop all at the same time. // -// This effect is not available on GNOME 3.3x, due to the limitation described in the // -// documentation of vfunc_paint_target further down in this file. // -////////////////////////////////////////////////////////////////////////////////////////// - -var MatrixShader = GObject.registerClass({Properties: {}, Signals: {}}, - class MatrixShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - // Load the font texture. As the shader is re-created for each window animation, this - // texture is also re-created each time. This could be improved in the future! - const fontData = GdkPixbuf.Pixbuf.new_from_resource('/img/matrixFont.png'); - this._fontTexture = new Clutter.Image(); - this._fontTexture.set_data( - fontData.get_pixels(), - fontData.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888, - fontData.width, fontData.height, fontData.rowstride); - - const trailColor = - Clutter.Color.from_string(settings.get_string('matrix-trail-color'))[1]; - const tipColor = - Clutter.Color.from_string(settings.get_string('matrix-tip-color'))[1]; - - // The technique for this effect was inspired by - // https://www.shadertoy.com/view/ldccW4, however the implementation is quite - // different as the letters drop only once and there is no need for a noise texture. - this.set_shader_source(` - - // Inject some common shader snippets. - ${shaderSnippets.standardUniforms()} - ${shaderSnippets.noise()} - - uniform sampler2D uFontTexture; - - // These may be configurable in the future. - const float EDGE_FADE = 30; - const float FADE_WIDTH = 150; - const float TRAIL_LENGTH = 0.2; - const float FINAL_FADE_START_TIME = 0.8; - const float LETTER_TILES = 16.0; - const float LETTER_SIZE = ${settings.get_int('matrix-scale')}; - const float LETTER_FLICKER_SPEED = 2.0; - const float RANDOMNESS = ${settings.get_double('matrix-randomness')}; - - // This returns a flickering grid of random letters. - float getText(vec2 fragCoord) { - vec2 pixelCoords = fragCoord * vec2(uSizeX, uSizeY); - vec2 uv = mod(pixelCoords.xy, LETTER_SIZE)/LETTER_SIZE; - vec2 block = pixelCoords/LETTER_SIZE - uv; - - // Choose random letter. - uv += floor(hash2D(floor(hash2D(block)*vec2(12.9898,78.233) + LETTER_FLICKER_SPEED*uTime + 42.254))*LETTER_TILES); - - return texture2D(uFontTexture, uv/LETTER_TILES).r; - } - - // This returns two values: The first are gradients for the "raindrops" which move - // from top to bottom. This is used for fading the letters. The second value is set - // to one below each drop and to zero above it. This second value is used for fading - // the window texture. - vec2 getRain(vec2 fragCoord) { - vec2 coords = fragCoord * vec2(uSizeX, uSizeY); - coords.x -= mod(coords.x, LETTER_SIZE); - - float delay = fract(sin(coords.x)*78.233) * mix(0.0, 1.0, RANDOMNESS); - float speed = fract(cos(coords.x)*12.989) * mix(0.0, 0.3, RANDOMNESS) + 1.5; - - float distToDrop = (uProgress*2-delay)*speed - fragCoord.y; - float rainAlpha = distToDrop >= 0 ? exp(-distToDrop/TRAIL_LENGTH) : 0; - float windowAlpha = 1 - clamp(uSizeY*distToDrop, 0, FADE_WIDTH) / FADE_WIDTH; - return vec2(rainAlpha, windowAlpha); - } - - void main() { - - // Get a cool matrix effect. See comments for those methods above. - vec2 rain = getRain(cogl_tex_coord_in[0].st); - float text = getText(cogl_tex_coord_in[0].st); - - // Get the window texture and fade it according to the effect mask. - cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * rain.y; - - // This is used to fade out the remaining trails in the end. - float finalFade = 1-clamp((uProgress-FINAL_FADE_START_TIME)/ - (1-FINAL_FADE_START_TIME), 0, 1); - float rainAlpha = finalFade * rain.x; - - // Fade at window borders. - vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY); - rainAlpha *= clamp(pos.x / EDGE_FADE, 0, 1); - rainAlpha *= clamp(pos.y / EDGE_FADE, 0, 1); - rainAlpha *= clamp((uSizeX - pos.x) / EDGE_FADE, 0, 1); - rainAlpha *= clamp((uSizeY - pos.y) / EDGE_FADE, 0, 1); - - // Add the matrix effect to the window. - vec3 trailColor = vec3(${trailColor.red / 255}, - ${trailColor.green / 255}, - ${trailColor.blue / 255}); - vec3 tipColor = vec3(${tipColor.red / 255}, - ${tipColor.green / 255}, - ${tipColor.blue / 255}); - cogl_color_out.rgb += mix(trailColor, tipColor, min(1, pow(rainAlpha+0.1, 4))) * rainAlpha * text; - - // These are pretty useful for understanding how this works. - // cogl_color_out = vec4(vec3(text), 1); - // cogl_color_out = vec4(vec3(rain.x), 1); - // cogl_color_out = vec4(vec3(rain.y), 1); - } - `); - }; - - // This is overridden to bind the font texture for drawing. Sadly, this seems to be - // impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called - // get_target() back then but this is not wrapped in GJS. - // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 - vfunc_paint_target(node, paint_context) { - const pipeline = this.get_pipeline(); - pipeline.set_layer_texture(1, this._fontTexture.get_texture()); - this.set_uniform_value('uFontTexture', 1); - super.vfunc_paint_target(node, paint_context); - } -}); \ No newline at end of file diff --git a/src/extension/TRexShader.js b/src/extension/TRexShader.js deleted file mode 100644 index 14f4f5d..0000000 --- a/src/extension/TRexShader.js +++ /dev/null @@ -1,157 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// ) ( // -// ( /( ( ( ) ( ( ( ( )\ ) ( ( // -// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // -// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // -// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // -// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // -// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // -// |__/ // -// Copyright (c) 2021 Simon Schneegans // -// Released under the GPLv3 or later. See LICENSE file for details. // -////////////////////////////////////////////////////////////////////////////////////////// - -'use strict'; - -const {Clutter, GObject, GdkPixbuf, Cogl} = imports.gi; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const shaderSnippets = Me.imports.src.extension.shaderSnippets; - -////////////////////////////////////////////////////////////////////////////////////////// -// This effect tears your windows apart with a series of violent scratches! // -// This effect is not available on GNOME 3.3x, due to the limitation described in the // -// documentation of vfunc_paint_target further down in this file. // -////////////////////////////////////////////////////////////////////////////////////////// - -var TRexShader = GObject.registerClass({Properties: {}, Signals: {}}, - class TRexShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - // Load the claw texture. As the shader is re-created for each window animation, this - // texture is also re-created each time. This could be improved in the future! - // See assets/README.md for how this texture was created. - const clawData = GdkPixbuf.Pixbuf.new_from_resource('/img/claws.png'); - this._clawTexture = new Clutter.Image(); - this._clawTexture.set_data( - clawData.get_pixels(), Cogl.PixelFormat.RGB_888, clawData.width, clawData.height, - clawData.rowstride); - - const color = Clutter.Color.from_string(settings.get_string('claw-scratch-color'))[1]; - - this.set_shader_source(` - - // Inject some common shader snippets. - ${shaderSnippets.standardUniforms()} - ${shaderSnippets.noise()} - - // See assets/README.md for how this texture was created. - uniform sampler2D uClawTexture; - - const vec2 SEED = vec2(${Math.random()}, ${Math.random()}); - const float CLAW_SIZE = ${settings.get_double('claw-scratch-scale')}; - const float NUM_CLAWS = ${settings.get_int('claw-scratch-count')}; - const float WARP_INTENSITY = 1.0 + ${settings.get_double('claw-scratch-warp')}; - const float FLASH_INTENSITY = 0.1; - const float MAX_SPAWN_TIME = 0.5; // Scratches will only start in the first half of the animation. - const float FF_TIME = 0.5; // Relative time for the final fade to transparency. - - // This method generates a grid of randomly rotated, slightly shifted and scaled - // UV squares. It returns the texture coords of the UV square at the given actor - // coordinates. If these do not fall into one of the UV grids, the coordinates of - // the closest UV grid will be clamped and returned. - vec2 getClawUV(vec2 texCoords, float gridScale, vec2 seed) { - - // Shift coordinates by a random offset and make sure the have a 1:1 aspect ratio. - vec2 coords = texCoords + hash2D(seed); - coords *= uSizeX < uSizeY ? vec2(1.0, 1.0 * uSizeY / uSizeX) : vec2(1.0 * uSizeX / uSizeY, 1.0); - - // Apply global scale. - coords *= gridScale; - - // Get grid cell coordinates in [0..1]. - vec2 cellUV = mod(coords, vec2(1)); - - // This is unique for each cell. - vec2 cellID = coords-cellUV + vec2(362.456); - - // Add random rotation, scale and offset to each grid cell. - float scale = mix(0.8, 1.0, hash(cellID*seed*134.451)); - float offsetX = mix(0.0, 1.0 - scale, hash(cellID*seed*54.4129)); - float offsetY = mix(0.0, 1.0 - scale, hash(cellID*seed*25.3089)); - float rotation = mix(0.0, 2.0 * 3.141, hash(cellID*seed*2.99837)); - - cellUV -= vec2(offsetX, offsetY); - cellUV /= scale; - - cellUV -= 0.5; - cellUV = vec2(cellUV.x * cos(rotation) - cellUV.y * sin(rotation), - cellUV.x * sin(rotation) + cellUV.y * cos(rotation)); - cellUV += 0.5; - - // Clamp resulting coordinates. - return clamp(cellUV, vec2(0), vec2(1)); - } - - void main() { - - // Warp the texture coordinates to create a blow-up effect. - vec2 coords = cogl_tex_coord_in[0].st * 2.0 - 1.0; - float dist = length(coords); - coords = (coords/dist * pow(dist, WARP_INTENSITY)) * 0.5 + 0.5; - coords = mix(cogl_tex_coord_in[0].st, coords, uProgress); - - // Accumulate several random scratches. The color in the scratch map refers to the - // relative time when the respective part will become invisible. Therefore we can - // add a value to make the scratch appear later. - float scratchMap = 1.0; - for (int i=0; i uProgress ? 1 : 0); - - // Add colorful flashes. - float flashIntensity = 1.0 / FLASH_INTENSITY * (scratchMap - uProgress) + 1; - if (flashIntensity < 0 || flashIntensity >= 1) { - flashIntensity = 0; - } - - vec3 flashColor = vec3(${color.red / 255}, - ${color.green / 255}, - ${color.blue / 255}) * ${color.alpha / 255}; - - cogl_color_out.rgb += flashIntensity * mix(flashColor, vec3(0), uProgress); - - // Fade out the remaining shards. - float fade = smoothstep(0, 1, 1 - clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); - cogl_color_out *= fade; - - // These are pretty useful for understanding how this works. - // cogl_color_out = vec4(vec3(flashIntensity), 1); - // cogl_color_out = vec4(vec3(scratchMap), 1); - } - `); - }; - - // This is overridden to bind the claw texture for drawing. Sadly, this seems to be - // impossible under GNOME 3.3x as this.get_pipeline() is not available. It was called - // get_target() back then but this is not wrapped in GJS. - // https://gitlab.gnome.org/GNOME/mutter/-/blob/gnome-3-36/clutter/clutter/clutter-offscreen-effect.c#L598 - vfunc_paint_target(node, paint_context) { - const pipeline = this.get_pipeline(); - pipeline.set_layer_texture(1, this._clawTexture.get_texture()); - this.set_uniform_value('uClawTexture', 1); - super.vfunc_paint_target(node, paint_context); - } -}); \ No newline at end of file diff --git a/src/extension/TVEffectShader.js b/src/extension/TVEffectShader.js deleted file mode 100644 index f60db07..0000000 --- a/src/extension/TVEffectShader.js +++ /dev/null @@ -1,90 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// ) ( // -// ( /( ( ( ) ( ( ( ( )\ ) ( ( // -// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // -// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // -// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // -// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // -// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // -// |__/ // -// Copyright (c) 2021 Simon Schneegans // -// Released under the GPLv3 or later. See LICENSE file for details. // -////////////////////////////////////////////////////////////////////////////////////////// - -'use strict'; - -const {Clutter, GObject} = imports.gi; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const shaderSnippets = Me.imports.src.extension.shaderSnippets; - -////////////////////////////////////////////////////////////////////////////////////////// -// The TVEffectShader hides the actor by making it first transparent from top and // -// bottom towards the middle and then hiding the resulting line from left and right // -// towards the center. // -////////////////////////////////////////////////////////////////////////////////////////// - -var TVEffectShader = GObject.registerClass( - {Properties: {}, Signals: {}}, class TVEffectShader extends Clutter.ShaderEffect { - _init(settings) { - super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); - - const color = - Clutter.Color.from_string(settings.get_string('tv-effect-color'))[1]; - - this.set_shader_source(` - - // Inject some common shader snippets. - ${shaderSnippets.standardUniforms()} - - const float BLUR_WIDTH = 0.01; // Width of the gradients. - const float TB_TIME = 0.7; // Relative time for the top/bottom animation. - const float LR_TIME = 0.4; // Relative time for the left/right animation. - const float LR_DELAY = 0.6; // Delay after which the left/right animation starts. - const float FF_TIME = 0.2; // Relative time for the final fade to transparency. - - void main() { - - // All of these are in [0..1] during the different stages of the animation. - // tb refers to the top-bottom animation. - // lr refers to the left-right animation. - // ff refers to the final fade animation. - float tbProgress = smoothstep(0, 1, clamp(uProgress/TB_TIME, 0, 1)); - float lrProgress = smoothstep(0, 1, clamp((uProgress - LR_DELAY)/LR_TIME, 0, 1)); - float ffProgress = smoothstep(0, 1, clamp((uProgress - 1.0 + FF_TIME)/FF_TIME, 0, 1)); - - // This is a top-center-bottom gradient in [0..1..0] - float tb = cogl_tex_coord_in[0].t * 2; - tb = tb < 1 ? tb : 2 - tb; - - // This is a left-center-right gradient in [0..1..0] - float lr = cogl_tex_coord_in[0].s * 2; - lr = lr < 1 ? lr : 2 - lr; - - // Combine the progress values with the gradients to create the alpha masks. - float tbMask = 1 - smoothstep(0, 1, clamp((tbProgress - tb) / BLUR_WIDTH, 0, 1)); - float lrMask = 1 - smoothstep(0, 1, clamp((lrProgress - lr) / BLUR_WIDTH, 0, 1)); - float ffMask = 1 - smoothstep(0, 1, ffProgress); - - // Assemble the final alpha value. - float mask = tbMask * lrMask * ffMask; - - vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st); - vec4 effectColor = vec4(${color.red / 255}, - ${color.green / 255}, - ${color.blue / 255}, 1.0) * windowColor.a; - - windowColor.rgb = mix(windowColor.rgb, effectColor.rgb, smoothstep(0, 1, uProgress)); - - cogl_color_out = windowColor * mask; - - // These are pretty useful for understanding how this works. - // cogl_color_out = vec4(vec3(tbMask), 1); - // cogl_color_out = vec4(vec3(lrMask), 1); - // cogl_color_out = vec4(vec3(ffMask), 1); - // cogl_color_out = vec4(vec3(mask), 1); - } - `); - }; - }); \ No newline at end of file diff --git a/src/extension/shaderSnippets.js b/src/shaderSnippets.js similarity index 100% rename from src/extension/shaderSnippets.js rename to src/shaderSnippets.js diff --git a/src/common/utils.js b/src/utils.js similarity index 71% rename from src/common/utils.js rename to src/utils.js index 1dead0a..e230444 100644 --- a/src/common/utils.js +++ b/src/utils.js @@ -15,6 +15,9 @@ const {Gtk} = imports.gi; +const Config = imports.misc.config; +const [GS_MAJOR, GS_MINOR] = Config.PACKAGE_VERSION.split('.'); + const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); @@ -42,3 +45,34 @@ function debug(message) { function isGTK4() { return Gtk.get_major_version() == 4; } + +// This method returns 'gtk3' or 'gtk4' depending on the currently used gtk version. +function getGTKString() { + return isGTK4() ? 'gtk4' : 'gtk3'; +} + +// This method returns true if called in GNOME Shell's process, false if called in the +// preferences process. +function isInShellProcess() { + return window.global && global.stage; +} + +// This method returns true if the current GNOME Shell version matches the given +// arguments. +function shellVersionIs(major, minor) { + return GS_MAJOR == major && GS_MINOR == minor; +} + +// This method returns true if the current GNOME Shell version is at least as high as the +// given arguments. +function shellVersionIsAtLeast(major, minor) { + if (GS_MAJOR > major) { + return true; + } + + if (GS_MAJOR == major) { + return GS_MINOR >= minor; + } + + return false; +} \ No newline at end of file