This commit is contained in:
2024-07-08 22:46:35 +02:00
parent 02f44c49d2
commit 27254d817a
56249 changed files with 808097 additions and 1 deletions

View File

@@ -0,0 +1,366 @@
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/* CoverflowAltTab::CoverflowSwitcher:
*
* Extends CoverflowAltTab::Switcher, switching tabs using a cover flow.
*/
import Graphene from 'gi://Graphene';
import {Switcher} from './switcher.js';
const BaseSwitcher = Switcher;
import {Preview, Placement, Direction, findUpperLeftFromCenter} from './preview.js'
const SIDE_ANGLE = 90;
const BLEND_OUT_ANGLE = 30;
const ALPHA = 1;
function appendParams(base, extra) {
for (let key in extra) {
base[key] = extra[key];
}
}
export class CoverflowSwitcher extends BaseSwitcher {
constructor(...args) {
super(...args);
}
_createPreviews() {
// TODO: Shouldn't monitor be set once per coverflow state?
let monitor = this._updateActiveMonitor();
let currentWorkspace = this._manager.workspace_manager.get_active_workspace();
this._previewsCenterPosition = {
x: this.actor.width / 2,
y: this.actor.height / 2 + this._settings.offset
};
let ratio = this._settings.preview_to_monitor_ratio;
this._xOffsetLeft = this.actor.width * (0.5 * (1 - ratio) - 0.1 * ratio)
this._xOffsetRight = this.actor.width - this._xOffsetLeft;
for (let windowActor of global.get_window_actors()) {
let metaWin = windowActor.get_meta_window();
let compositor = metaWin.get_compositor_private();
if (compositor) {
let texture = compositor.get_texture();
let width, height;
if (texture.get_size) {
[width, height] = texture.get_size();
} else {
// TODO: Check this OK!
let preferred_size_ok;
[preferred_size_ok, width, height] = texture.get_preferred_size();
}
let scale = 1.0;
let previewScale = this._settings.preview_to_monitor_ratio;
let previewWidth = this.actor.width * previewScale;
let previewHeight = this.actor.height * previewScale;
if (width > previewWidth || height > previewHeight)
scale = Math.min(previewWidth / width, previewHeight / height);
let preview = new Preview(metaWin, this, {
name: metaWin.title,
opacity: ALPHA * (!metaWin.minimized && metaWin.get_workspace() == currentWorkspace || metaWin.is_on_all_workspaces()) ? 255 : 0,
source: texture.get_size ? texture : compositor,
reactive: true,
x: metaWin.minimized ? 0 :
compositor.x - monitor.x,
y: metaWin.minimized ? 0 :
compositor.y - monitor.y,
translation_x: 0,
width: width,
height: height,
scale_x: metaWin.minimized ? 0 : 1,
scale_y: metaWin.minimized ? 0 : 1,
scale_z: metaWin.minimized ? 0 : 1,
rotation_angle_y: 0,
});
preview.scale = scale;
preview.set_pivot_point_placement(Placement.CENTER);
preview.center_position = {
x: findUpperLeftFromCenter(width,
this._previewsCenterPosition.x),
y: findUpperLeftFromCenter(height,
this._previewsCenterPosition.y)
};
if (this._windows.includes(metaWin)) {
this._previews[this._windows.indexOf(metaWin)] = preview;
}
this._allPreviews.push(preview);
this.previewActor.add_child(preview);
}
}
}
_usingCarousel() {
return (this._parent === null && this._settings.switcher_looping_method == "Carousel");
}
_previewNext() {
if (this._currentIndex == this._windows.length - 1) {
this._setCurrentIndex(0);
if (this._usingCarousel()) {
this._updatePreviews(false)
} else {
this._flipStack(Direction.TO_LEFT);
}
} else {
this._setCurrentIndex(this._currentIndex + 1);
this._updatePreviews(false);
}
}
_previewPrevious() {
if (this._currentIndex == 0) {
this._setCurrentIndex(this._windows.length-1);
if (this._usingCarousel()) {
this._updatePreviews(false)
} else {
this._flipStack(Direction.TO_RIGHT);
}
} else {
this._setCurrentIndex(this._currentIndex - 1);
this._updatePreviews(false);
}
}
_flipStack(direction) {
//this._looping = true;
let xOffset, angle;
this._updateActiveMonitor();
if (direction === Direction.TO_LEFT) {
xOffset = -this._xOffsetLeft;
angle = BLEND_OUT_ANGLE;
} else {
xOffset = this._activeMonitor.width + this._xOffsetLeft;
angle = -BLEND_OUT_ANGLE;
}
let animation_time = this._settings.animation_time * 2/3;
for (let [i, preview] of this._previews.entries()) {
this._onFlipIn(preview, i, direction);
}
}
_onFlipIn(preview, index, direction) {
let xOffsetStart, xOffsetEnd, angleStart, angleEnd;
let zeroIndexPreview = null;
this._updateActiveMonitor();
if (direction === Direction.TO_LEFT) {
xOffsetStart = this.actor.width + this._xOffsetLeft;
xOffsetEnd = this._xOffsetRight;
angleStart = -BLEND_OUT_ANGLE;
angleEnd = -SIDE_ANGLE + this._getPerspectiveCorrectionAngle(1);
} else {
xOffsetStart = -this._xOffsetLeft;
xOffsetEnd = this._xOffsetLeft;
angleStart = BLEND_OUT_ANGLE;
angleEnd = SIDE_ANGLE + this._getPerspectiveCorrectionAngle(0);
}
//let animation_time = this._settings.animation_time * 2;
let animation_time = this._settings.animation_time * 2 * (direction === Direction.TO_RIGHT ? ((index + 1) / this._previews.length) : (1 - index / this._previews.length));
this._updatePreview(index, zeroIndexPreview, preview, index, false, animation_time);
let translation_x;
if (direction === Direction.TO_RIGHT) {
translation_x = xOffsetStart - (this._previewsCenterPosition.x
- preview.width / 2) + 50 * (index - this._currentIndex);
} else {
translation_x = xOffsetStart - (this._previewsCenterPosition.x
+ preview.width / 2) + 50 * (index - this._currentIndex);
}
let lastExtraParams = {
transition: 'userChoice',
onCompleteParams: [direction],
onComplete: this._onFlipComplete,
onCompleteScope: this
};
this._manager.platform.tween(preview, {
transition: 'easeInOutQuint',
opacity: ALPHA * 255,
time: animation_time,
});
this._raiseIcons();
return;
}
_onFlipComplete(direction) {
this._looping = false;
this._updatePreviews(false);
}
// TODO: Remove unused direction variable
_animatePreviewToMid(preview, animation_time, extraParams = []) {
let pivot_point = preview.get_pivot_point_placement(Placement.CENTER);
let tweenParams = {
x: findUpperLeftFromCenter(preview.width, this._previewsCenterPosition.x),
y: findUpperLeftFromCenter(preview.height, this._previewsCenterPosition.y),
scale_x: preview.scale,
scale_y: preview.scale,
scale_z: preview.scale,
pivot_point: pivot_point,
translation_x: 0,
rotation_angle_y: 0,
time: animation_time,
transition: 'userChoice',
};
appendParams(tweenParams, extraParams);
this._manager.platform.tween(preview, tweenParams);
}
_animatePreviewToSide(preview, index, xOffset, extraParams, toChangePivotPoint = true) {
let [x, y] = preview.get_pivot_point();
let pivot_point = new Graphene.Point({ x: x, y: y });
let half_length = Math.floor(this._previews.length / 2);
let pivot_index = (this._usingCarousel()) ?
half_length : this._currentIndex;
if (toChangePivotPoint) {
if (index < pivot_index) {
let progress = pivot_index - index < 1 ? pivot_index - index : 1;
pivot_point = new Graphene.Point({ x: 0.5 - 0.5 * progress, y: 0.5});
} else {
let progress = index - pivot_index < 1 ? index - pivot_index : 1;
pivot_point = new Graphene.Point({ x: 0.5 + 0.5 * progress, y: 0.5});
}
}
let scale = Math.pow(this._settings.preview_scaling_factor, Math.abs(index - pivot_index));
scale = scale * preview.scale;
let tweenParams = {
x: findUpperLeftFromCenter(preview.width, this._previewsCenterPosition.x),
y: findUpperLeftFromCenter(preview.height, this._previewsCenterPosition.y),
scale_x: scale,
scale_y: scale,
scale_z: scale,
pivot_point: pivot_point,
};
if (index < pivot_index) {
tweenParams.translation_x = xOffset - (this._previewsCenterPosition.x
- preview.width / 2) + 50 * (index - pivot_index);
} else {
tweenParams.translation_x = xOffset - (this._previewsCenterPosition.x
+ preview.width / 2) + 50 * (index - pivot_index);
}
appendParams(tweenParams, extraParams);
this._manager.platform.tween(preview, tweenParams);
}
_getPerspectiveCorrectionAngle(side) {
if (this._settings.perspective_correction_method != "Adjust Angles") return 0;
if (this.num_monitors == 1) {
return 0;
} else if (this.num_monitors == 2) {
if (this.monitor_number == this.monitors_ltr[0].index) {
if (side == 0) return 508/1000 * 90;
else return 508/1000 *90;
} else {
if (side == 0) return -508/1000 * 90;
else return -508/1000 * 90;
}
} else if (this.num_monitors == 3) {
if (this.monitor_number == this.monitors_ltr[0].index) {
if (side == 0) return (666)/1000 * 90;
else return 750/1000 * 90;
} else if (this.monitor_number == this.monitors_ltr[1].index) {
return 0;
} else {
if (side == 0) return (-750)/1000 * 90;
else return -666/1000 * 90;
}
}
}
_updatePreviews(reorder_only=false) {
if (this._previews == null) return;
let half_length = Math.floor(this._previews.length / 2);
let previews = [];
for (let [i, preview] of this._previews.entries()) {
let idx = (this._usingCarousel()) ?
(i - this._currentIndex + half_length + this._previews.length) % this._previews.length :
i;
previews.push([i, idx, preview]);
}
previews.sort((a, b) => a[1] - b[1]);
let zeroIndexPreview = null;
for (let item of previews) {
let preview = item[2];
let i = item[0];
let idx = item[1];
let animation_time = this._settings.animation_time * (this._settings.randomize_animation_times ? this._getRandomArbitrary(0.0001, 1) : 1);
zeroIndexPreview = this._updatePreview(idx, zeroIndexPreview, preview, i, reorder_only, animation_time);
this._manager.platform.tween(preview, {
opacity: ALPHA * 255,
time: this._settings.animation_time,
transition: 'easeInOutQuint',
onComplete: () => {
preview.set_reactive(true);
}
});
}
if (zeroIndexPreview != null) zeroIndexPreview.make_bottom_layer(this.previewActor);
this._raiseIcons();
}
_updatePreview(idx, zeroIndexPreview, preview, i, reorder_only, animation_time) {
let half_length = Math.floor(this._previews.length / 2);
let pivot_index = (this._usingCarousel()) ?
half_length : this._currentIndex;
if (this._usingCarousel() && idx == 0) {
zeroIndexPreview = preview;
}
if (i == this._currentIndex) {
preview.make_top_layer(this.previewActor);
if (!reorder_only) {
this._animatePreviewToMid(preview, this._settings.animation_time);
}
} else if (idx < pivot_index) {
preview.make_top_layer(this.previewActor);
if (!reorder_only) {
let final_angle = SIDE_ANGLE + this._getPerspectiveCorrectionAngle(0);
let progress = pivot_index - idx < 1 ? pivot_index - idx : 1;
let center_offset = (this._xOffsetLeft + this._xOffsetRight) / 2;
this._animatePreviewToSide(preview, idx, center_offset - preview.width / 2 - progress * (center_offset - preview.width / 2 - this._xOffsetLeft), {
rotation_angle_y: progress * final_angle,
time: this.gestureInProgress ? 0 : animation_time,
transition: 'userChoice',
});
}
} else /* i > this._currentIndex */ {
preview.make_bottom_layer(this.previewActor);
if (!reorder_only) {
let final_angle = -SIDE_ANGLE + this._getPerspectiveCorrectionAngle(1);
let progress = idx - pivot_index < 1 ? idx - pivot_index : 1;
let center_offset = (this._xOffsetLeft + this._xOffsetRight) / 2;
this._animatePreviewToSide(preview, idx, center_offset + preview.width / 2 + progress * (this._xOffsetRight - center_offset - preview.width / 2), {
rotation_angle_y: progress * final_angle,
time: this.gestureInProgress ? 0 : animation_time,
transition: 'userChoice',
});
}
}
return zeroIndexPreview;
}
};

View File

@@ -0,0 +1,11 @@
uniform sampler2D tex;
uniform float red;
uniform float green;
uniform float blue;
uniform float blend;
void main() {
vec4 s = texture2D(tex, cogl_tex_coord_in[0].st);
vec4 dst = vec4(red, green, blue, blend);
cogl_color_out = vec4(mix(s.rgb, dst.rgb * s.a, blend), s.a);
}

View File

@@ -0,0 +1,176 @@
// This code istaken from the blur-my-shell extension
//'use strict';
import Clutter from 'gi://Clutter';
import Shell from 'gi://Shell';
import GObject from 'gi://GObject';
import GLib from 'gi://GLib';
const SHADER_PATH = GLib.filename_from_uri(GLib.uri_resolve_relative(import.meta.url, 'color_effect.glsl', GLib.UriFlags.NONE))[0];
const get_shader_source = _ => {
try {
return Shell.get_file_contents_utf8_sync(SHADER_PATH);
} catch (e) {
log(`[Coverflow Alt-Tab] error loading shader from ${SHADER_PATH}: ${e}`);
return null;
}
};
/// New Clutter Shader Effect that simply mixes a color in, the class applies
/// the GLSL shader programmed into vfunc_get_static_shader_source and applies
/// it to an Actor.
///
/// Clutter Shader Source Code:
/// https://github.com/GNOME/clutter/blob/master/clutter/clutter-shader-effect.c
///
/// GJS Doc:
/// https://gjs-docs.gnome.org/clutter10~10_api/clutter.shadereffect
export const ColorEffect = new GObject.registerClass({
GTypeName: "CoverflowAltTabColorEffect",
Properties: {
'red': GObject.ParamSpec.double(
`red`,
`Red`,
`Red value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'green': GObject.ParamSpec.double(
`green`,
`Green`,
`Green value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'blue': GObject.ParamSpec.double(
`blue`,
`Blue`,
`Blue value in shader`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
'blend': GObject.ParamSpec.double(
`blend`,
`Blend`,
`Amount of blending between the colors`,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
0.4,
),
}
}, class CoverflowAltTabColorShader extends Clutter.ShaderEffect {
_init(params) {
this._red = null;
this._green = null;
this._blue = null;
this._blend = null;
// initialize without color as a parameter
let _color = params.color;
delete params.color;
super._init(params);
// set shader source
this._source = get_shader_source();
if (this._source)
this.set_shader_source(this._source);
// set shader color
if (_color)
this.color = _color;
this.update_enabled();
}
get red() {
return this._red;
}
set red(value) {
if (this._red !== value) {
this._red = value;
this.set_uniform_value('red', parseFloat(this._red - 1e-6));
}
}
get green() {
return this._green;
}
set green(value) {
if (this._green !== value) {
this._green = value;
this.set_uniform_value('green', parseFloat(this._green - 1e-6));
}
}
get blue() {
return this._blue;
}
set blue(value) {
if (this._blue !== value) {
this._blue = value;
this.set_uniform_value('blue', parseFloat(this._blue - 1e-6));
}
}
get blend() {
return this._blend;
}
set blend(value) {
if (this._blend !== value) {
this._blend = value;
this.set_uniform_value('blend', parseFloat(this._blend - 1e-6));
}
this.update_enabled();
}
set color(rgba) {
let [r, g, b, a] = rgba;
this.red = r;
this.green = g;
this.blue = b;
this.blend = a;
}
get color() {
return [this.red, this.green, this.blue, this.blend];
}
/// False set function, only cares about the color. Too hard to change.
set(params) {
this.color = params.color;
}
update_enabled() {
this.set_enabled(true);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
}
});

View File

@@ -0,0 +1,109 @@
//
// Description : Array and textureless GLSL 2D simplex noise function.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : stegu
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
// https://github.com/stegu/webgl-noise
//
uniform sampler2D tex;
uniform float time;
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec2 mod289(vec2 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec3 permute(vec3 x) {
return mod289(((x*34.0)+1.0)*x);
}
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
-0.577350269189626, // -1.0 + 2.0 * C.x
0.024390243902439); // 1.0 / 41.0
// First corner
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
// Other corners
vec2 i1;
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
//i1.y = 1.0 - i1.x;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
// x0 = x0 - 0.0 + 0.0 * C.xx ;
// x1 = x0 - i1 + 1.0 * C.xx ;
// x2 = x0 - 1.0 + 2.0 * C.xx ;
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
// Permutations
i = mod289(i); // Avoid truncation effects in permutation
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
// Gradients: 41 points uniformly over a line, mapped onto a diamond.
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
// Normalise gradients implicitly by scaling m
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
// Compute final noise value at P
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
float rand(vec2 co) {
return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453);
}
void main() {
vec2 uv = cogl_tex_coord_in[0].st;
float t = time * 2.0;
// Create large, incidental noise waves
float noise = max(0.0, snoise(vec2(t, uv.y * 0.3)) - 0.3) * (1.0 / 0.7);
// Offset by smaller, constant noise waves
noise = noise + (snoise(vec2(t*10.0, uv.y * 2.4)) - 0.5) * 0.15;
// Apply the noise as x displacement for every line
float xpos = uv.x - noise * noise * 0.25;
if (xpos < 0.0)
xpos = -xpos;
if (xpos > 1.0)
xpos = 1.0 - xpos;
cogl_color_out = texture2D(tex, vec2(xpos, uv.y));
// Mix in some random interference for lines
cogl_color_out.rgb = mix(cogl_color_out.rgb, vec3(rand(vec2(uv.y * t))), noise * 0.3).rgb;
// Apply a line pattern every 4 pixels
if (floor(mod(gl_FragCoord.y * 0.25, 2.0)) == 0.0)
{
cogl_color_out.rgb *= 1.0 - (0.15 * noise);
}
// Shift green/blue channels (using the red channel)
cogl_color_out.g = mix(cogl_color_out.r, texture2D(tex, vec2(xpos + noise * 0.05, uv.y)).g, 0.25);
cogl_color_out.b = mix(cogl_color_out.r, texture2D(tex, vec2(xpos - noise * 0.05, uv.y)).b, 0.25);
}

View File

@@ -0,0 +1,59 @@
// This code istaken from the blur-my-shell extension
//'use strict';
import Clutter from 'gi://Clutter';
import Shell from 'gi://Shell';
import GObject from 'gi://GObject';
import GLib from 'gi://GLib';
const SHADER_PATH = GLib.filename_from_uri(GLib.uri_resolve_relative(import.meta.url, 'glitch_effect.glsl', GLib.UriFlags.NONE))[0];
const get_shader_source = _ => {
try {
return Shell.get_file_contents_utf8_sync(SHADER_PATH);
} catch (e) {
log(`[Coverflow Alt-Tab] error loading shader from ${SHADER_PATH}: ${e}`);
return null;
}
};
/// New Clutter Shader Effect that simply mixes a color in, the class applies
/// the GLSL shader programmed into vfunc_get_static_shader_source and applies
/// it to an Actor.
///
/// Clutter Shader Source Code:
/// https://github.com/GNOME/clutter/blob/master/clutter/clutter-shader-effect.c
///
/// GJS Doc:
/// https://gjs-docs.gnome.org/clutter10~10_api/clutter.shadereffect
export const GlitchEffect = new GObject.registerClass({
GTypeName: "CoverflowAltTabGlitchEffect",
}, class CoverflowAltTabGlitchShader extends Clutter.ShaderEffect {
_init(params) {
super._init(params);
this._timeOffset = Math.random() * 1000000;
// set shader source
this._source = get_shader_source();
if (this._source)
this.set_shader_source(this._source);
this.set_enabled(true);
}
vfunc_paint_target(paint_node = null, paint_context = null) {
const time = this._timeOffset + GLib.get_monotonic_time() / GLib.USEC_PER_SEC;
this.set_uniform_value("time", time);
this.set_uniform_value("tex", 0);
if (paint_node && paint_context)
super.vfunc_paint_target(paint_node, paint_context);
else if (paint_node)
super.vfunc_paint_target(paint_node);
else
super.vfunc_paint_target();
this.queue_repaint();
}
});

View File

@@ -0,0 +1,67 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Gnome Shell extension specific routines.
*
* Create the correct manager and enable/disable it.
*/
import * as Manager from './manager.js';
import * as Platform from './platform.js';
import * as Keybinder from './keybinder.js';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
let manager = null;
export default class CoverflowAltTabExtension extends Extension {
constructor(metadata) {
super(metadata);
}
enable() {
if (!manager) {
/*
* As there are restricted Gnome versions the current extension support (that
* are specified in metadata.json file), only the API related to those supported
* versions must be used, not anything else. As a result, performing checks for
* keeping backward-compatiblity with old unsupported versions is a wrong
* decision.
*
* To support older versions of Gnome, first, add the version to the metadata
* file, then, if needed, include backward-compatible API here for each
* version.
*/
manager = new Manager.Manager(
new Platform.PlatformGnomeShell(this.getSettings()),
new Keybinder.Keybinder330Api()
);
}
manager.enable();
}
disable() {
if (manager) {
manager.disable();
manager = null;
}
}
}

View File

@@ -0,0 +1,84 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/* CoverflowAltTab::Keybinder
*
* Originally, created to be helper classes to handle the different keybinding APIs.
*/
import Shell from 'gi://Shell';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import {__ABSTRACT_METHOD__} from './lib.js'
class AbstractKeybinder {
enable() { __ABSTRACT_METHOD__(this, this.enable) }
disable() { __ABSTRACT_METHOD__(this, this.disable) }
}
export const Keybinder330Api = class Keybinder330Api extends AbstractKeybinder {
constructor(...args) {
super(...args);
this._startAppSwitcherBind = null;
}
enable(startAppSwitcherBind, platform) {
let mode = Shell.ActionMode ? Shell.ActionMode : Shell.KeyBindingMode;
this._startAppSwitcherBind = startAppSwitcherBind;
platform.addSettingsChangedCallback(this._onSettingsChanged.bind(this));
Main.wm.setCustomKeybindingHandler('switch-group', mode.NORMAL, startAppSwitcherBind);
Main.wm.setCustomKeybindingHandler('switch-group-backward', mode.NORMAL, startAppSwitcherBind);
}
disable() {
let mode = Shell.ActionMode ? Shell.ActionMode : Shell.KeyBindingMode;
Main.wm.setCustomKeybindingHandler('switch-applications', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-windows', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-group', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-applications-backward', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-windows-backward', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-group-backward', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
}
_onSettingsChanged(settings, key=null) {
let mode = Shell.ActionMode ? Shell.ActionMode : Shell.KeyBindingMode;
if (key == null || key == 'bind-to-switch-applications') {
if (settings.get_boolean('bind-to-switch-applications')) {
Main.wm.setCustomKeybindingHandler('switch-applications', mode.NORMAL, this._startAppSwitcherBind);
Main.wm.setCustomKeybindingHandler('switch-applications-backward', mode.NORMAL, this._startAppSwitcherBind);
} else {
Main.wm.setCustomKeybindingHandler('switch-applications', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-applications-backward', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
}
}
if (key == null || key == 'bind-to-switch-windows') {
if (settings.get_boolean('bind-to-switch-windows')) {
Main.wm.setCustomKeybindingHandler('switch-windows', mode.NORMAL, this._startAppSwitcherBind);
Main.wm.setCustomKeybindingHandler('switch-windows-backward', mode.NORMAL, this._startAppSwitcherBind);
} else {
Main.wm.setCustomKeybindingHandler('switch-windows', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
Main.wm.setCustomKeybindingHandler('switch-windows-backward', mode.NORMAL, Main.wm._startSwitcher.bind(Main.wm));
}
}
}
}

View File

@@ -0,0 +1,62 @@
/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the GNOME nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// This method can be used to write a message to GNOME Shell's log. This is enhances
// the standard log() functionality by prepending the extension's name and the location
// where the message was logged. As the extensions name is part of the location, you
// can more effectively watch the log output of GNOME Shell:
// journalctl -f -o cat | grep -E 'desktop-cube|'
// This method is based on a similar script from the Fly-Pie GNOME Shell extension which
// os published under the MIT License (https://github.com/Schneegans/Fly-Pie).
export function debug(message) {
const stack = new Error().stack.split('\n');
// Remove debug() function call from stack.
stack.shift();
// Find the index of the extension directory (e.g. desktopcube@schneegans.github.com)
// in the stack entry. We do not want to print the entire absolute file path.
const extensionRoot = stack[0].indexOf('CoverflowAltTab@palatis.blogspot.com');
log('[' + stack[0].slice(extensionRoot) + '] ' + message);
log(new Error().stack);
}
/**
* Make a method psuedo-abstract.
*
* @param {Object} object The current class instance, i.e. this.
* @param {Object} method The method object, e.g. this.enable.
* @return {void}
*/
export function __ABSTRACT_METHOD__(object, method) {
throw new Error(
"Abstract method " +
object.constructor.name + "." + method.name + "()" +
" not implemented"
);
}

View File

@@ -0,0 +1,148 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/* CoverflowAltTab::Manager
*
* This class is a helper class to start the actual switcher.
*/
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
function sortWindowsByUserTime(win1, win2) {
let t1 = win1.get_user_time();
let t2 = win2.get_user_time();
return (t2 > t1) ? 1 : -1;
}
function matchSkipTaskbar(win) {
return !win.is_skip_taskbar();
}
function matchWmClass(win) {
return win.get_wm_class() == this && !win.is_skip_taskbar();
}
function matchWorkspace(win) {
return win.get_workspace() == this && !win.is_skip_taskbar();
}
function matchOtherWorkspace(win) {
return win.get_workspace() != this && !win.is_skip_taskbar();
}
export const Manager = class Manager {
constructor(platform, keybinder) {
this.platform = platform;
this.keybinder = keybinder;
this.switcher = null;
if (global.workspace_manager && global.workspace_manager.get_active_workspace)
this.workspace_manager = global.workspace_manager;
else
this.workspace_manager = global.screen;
if (global.display && global.display.get_n_monitors)
this.display = global.display;
else
this.display = global.screen;
}
enable() {
this.platform.enable();
this.keybinder.enable(this._startWindowSwitcher.bind(this), this.platform);
}
disable() {
if (this.switcher != null)
this.switcher.destroy();
this.platform.disable();
this.keybinder.disable();
}
activateSelectedWindow(win) {
Main.activateWindow(win, global.get_current_time());
}
removeSelectedWindow(win) {
win.delete(global.get_current_time());
}
_startWindowSwitcher(display, window, binding) {
let windows = [];
let currentWorkspace = this.workspace_manager.get_active_workspace();
let isApplicationSwitcher = false;
// Construct a list with all windows
let windowActors = global.get_window_actors();
for (let windowActor of windowActors) {
if (typeof windowActor.get_meta_window === "function") {
windows.push(windowActor.get_meta_window());
}
}
windowActors = null;
switch (binding.get_name()) {
case 'switch-group':
// Switch between windows of same application from all workspaces
let focused = display.focus_window ? display.focus_window : windows[0];
windows = windows.filter(matchWmClass, focused.get_wm_class());
windows.sort(sortWindowsByUserTime);
break;
case 'switch-applications':
case 'switch-applications-backward':
isApplicationSwitcher = !this.platform.getSettings().switch_application_behaves_like_switch_windows
default:
let currentOnly = this.platform.getSettings().current_workspace_only;
if (currentOnly === 'all-currentfirst') {
// Switch between windows of all workspaces, prefer
// those from current workspace
let wins1 = windows.filter(matchWorkspace, currentWorkspace);
let wins2 = windows.filter(matchOtherWorkspace, currentWorkspace);
// Sort by user time
wins1.sort(sortWindowsByUserTime);
wins2.sort(sortWindowsByUserTime);
windows = wins1.concat(wins2);
wins1 = [];
wins2 = [];
} else {
let filter = currentOnly === 'current' ? matchWorkspace :
matchSkipTaskbar;
// Switch between windows of current workspace
windows = windows.filter(filter, currentWorkspace);
windows.sort(sortWindowsByUserTime);
}
break;
}
// filter by windows existing on the active monitor
if(this.platform.getSettings().switch_per_monitor)
{
windows = windows.filter ( (win) =>
win.get_monitor() == Main.layoutManager.currentMonitor.index );
}
if (windows.length) {
let mask = binding.get_mask();
let currentIndex = windows.indexOf(display.focus_window);
let switcher_class = this.platform.getSettings().switcher_class;
this.switcher = new switcher_class(windows, mask, currentIndex, this, null, isApplicationSwitcher, null);
}
}
}

View File

@@ -0,0 +1,18 @@
{
"_generated": "Generated by SweetTooth, do not edit",
"description": "Replacement of Alt-Tab, iterates through windows in a cover-flow manner.",
"donations": {
"github": "dsheeler",
"liberapay": "dsheeler",
"paypal": "DanielSheeler"
},
"gettext-domain": "CoverflowAltTab@palatis.blogspot.com",
"name": "Coverflow Alt-Tab",
"settings-schema": "org.gnome.shell.extensions.coverflowalttab",
"shell-version": [
"46"
],
"url": "https://github.com/dmo60/CoverflowAltTab",
"uuid": "CoverflowAltTab@palatis.blogspot.com",
"version": 72
}

View File

@@ -0,0 +1,685 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* CoverflowAltTab::Platform
*
* Originally, created to be helper classes to handle Gnome Shell and Cinnamon differences.
*/
import St from 'gi://St';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Meta from 'gi://Meta';
import Clutter from 'gi://Clutter';
import Shell from 'gi://Shell';
import GLib from 'gi://GLib';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Background from 'resource:///org/gnome/shell/ui/background.js';
import {__ABSTRACT_METHOD__} from './lib.js'
import {Switcher} from './switcher.js';
import {CoverflowSwitcher} from './coverflowSwitcher.js';
import {TimelineSwitcher} from './timelineSwitcher.js';
const POSITION_TOP = 1;
const POSITION_BOTTOM = 7;
const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor';
const TRANSITION_TYPE = 'easeOutQuad';
const modes = [
Clutter.AnimationMode.EASE_IN_BOUNCE,
Clutter.AnimationMode.EASE_OUT_BOUNCE,
Clutter.AnimationMode.EASE_IN_OUT_BOUNCE,
Clutter.AnimationMode.EASE_IN_BACK,
Clutter.AnimationMode.EASE_OUT_BACK,
Clutter.AnimationMode.EASE_IN_OUT_BACK,
Clutter.AnimationMode.EASE_IN_ELASTIC,
Clutter.AnimationMode.EASE_OUT_ELASTIC,
Clutter.AnimationMode.EASE_IN_OUT_ELASTIC,
Clutter.AnimationMode.EASE_IN_QUAD,
Clutter.AnimationMode.EASE_OUT_QUAD,
Clutter.AnimationMode.EASE_IN_OUT_QUAD,
Clutter.AnimationMode.EASE_IN_CUBIC,
Clutter.AnimationMode.EASE_OUT_CUBIC,
Clutter.AnimationMode.EASE_IN_OUT_CUBIC,
Clutter.AnimationMode.EASE_IN_QUART,
Clutter.AnimationMode.EASE_OUT_QUART,
Clutter.AnimationMode.EASE_IN_OUT_QUART,
Clutter.AnimationMode.EASE_IN_QUINT,
Clutter.AnimationMode.EASE_OUT_QUINT,
Clutter.AnimationMode.EASE_IN_OUT_QUINT,
Clutter.AnimationMode.EASE_IN_SINE,
Clutter.AnimationMode.EASE_OUT_SINE,
Clutter.AnimationMode.EASE_IN_OUT_SINE,
Clutter.AnimationMode.EASE_IN_EXPO,
Clutter.AnimationMode.EASE_OUT_EXPO,
Clutter.AnimationMode.EASE_IN_OUT_EXPO,
Clutter.AnimationMode.EASE_IN_CIRC,
Clutter.AnimationMode.EASE_OUT_CIRC,
Clutter.AnimationMode.EASE_IN_OUT_CIRC,
Clutter.AnimationMode.LINEAR
];
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
class AbstractPlatform {
enable() { __ABSTRACT_METHOD__(this, this.enable) }
disable() { __ABSTRACT_METHOD__(this, this.disable) }
getWidgetClass() { __ABSTRACT_METHOD__(this, this.getWidgetClass) }
getWindowTracker() { __ABSTRACT_METHOD__(this, this.getWindowTracker) }
getPrimaryModifier(mask) { __ABSTRACT_METHOD__(this, this.getPrimaryModifier) }
getSettings() { __ABSTRACT_METHOD__(this, this.getSettings) }
tween(actor, params) { __ABSTRACT_METHOD__(this, this.tween) }
removeTweens(actor) { __ABSTRACT_METHOD__(this, this.removeTweens) }
getDefaultSettings() {
return {
animation_time: 0.2,
randomize_animation_times: false,
dim_factor: 0,
title_position: POSITION_BOTTOM,
icon_style: 'Classic',
icon_has_shadow: false,
overlay_icon_opacity: 1,
text_scaling_factor: 1,
offset: 0,
hide_panel: true,
enforce_primary_monitor: true,
switcher_class: Switcher,
easing_function: 'ease-out-cubic',
current_workspace_only: '1',
switch_per_monitor: false,
preview_to_monitor_ratio: 0.5,
preview_scaling_factor: 0.75,
bind_to_switch_applications: true,
bind_to_switch_windows: true,
perspective_correction_method: "Move Camera",
highlight_mouse_over: false,
raise_mouse_over: true,
switcher_looping_method: 'Flip Stack',
switch_application_behaves_like_switch_windows: false,
blur_radius: 0,
desaturate_factor: 0.0,
tint_color: (0., 0., 0.),
switcher_background_color: (0., 0., 0.),
tint_blend: 0.0,
use_glitch_effect: false,
use_tint: false,
invert_swipes: false,
overlay_icon_size: 128,
};
}
initBackground() {
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
this._background.hide();
global.overlay_group.add_child(this._background);
}
dimBackground() {
this._background.show();
this.tween(this._background, {
dim_factor: this._settings.dim_factor,
time: this._settings.animation_time,
transition: TRANSITION_TYPE
});
}
removeBackground() {
global.overlay_group.remove_child(this._background);
}
}
export class PlatformGnomeShell extends AbstractPlatform {
constructor(settings, ...args) {
super(...args);
this._settings = null;
this._connections = null;
this._extensionSettings = settings;
this._desktopSettings = null;
this._backgroundColor = null;
this._settings_changed_callbacks = null;
this._themeContext = null;
}
_getSwitcherBackgroundColor() {
if (this._backgroundColor === null) {
let widgetClass = this.getWidgetClass();
let parent = new widgetClass({ visible: false, reactive: false, style_class: 'switcher-list'});
let actor = new widgetClass({ visible: false, reactive: false, style_class: 'item-box' });
parent.add_child(actor);
actor.add_style_pseudo_class('selected');
Main.uiGroup.add_child(parent);
this._backgroundColor = actor.get_theme_node().get_background_color();
Main.uiGroup.remove_child(parent);
parent = null;
let color = new GLib.Variant("(ddd)", [this._backgroundColor.red/255, this._backgroundColor.green/255, this._backgroundColor.blue/255]);
this._extensionSettings.set_value("switcher-background-color", color);
}
return this._backgroundColor;
}
enable() {
this._themeContext = St.ThemeContext.get_for_stage(global.stage);
this._themeContextChangedID = this._themeContext.connect("changed", (themeContext) => {
this._backgroundColor = null;
this._getSwitcherBackgroundColor();
});
this._settings_changed_callbacks = [];
if (this._desktopSettings == null)
this._desktopSettings = new Gio.Settings({ schema_id: DESKTOP_INTERFACE_SCHEMA });
let keys = [
"animation-time",
"randomize-animation-times",
"dim-factor",
"position",
"icon-style",
"icon-has-shadow",
"overlay-icon-size",
"overlay-icon-opacity",
"offset",
"hide-panel",
"enforce-primary-monitor",
"easing-function",
"current-workspace-only",
"switch-per-monitor",
"switcher-style",
"preview-to-monitor-ratio",
"preview-scaling-factor",
"bind-to-switch-applications",
"bind-to-switch-windows",
"perspective-correction-method",
"highlight-mouse-over",
"raise-mouse-over",
"desaturate-factor",
"blur-radius",
"switcher-looping-method",
"switch-application-behaves-like-switch-windows",
"use-tint",
"tint-color",
"tint-blend",
"switcher-background-color",
"use-glitch-effect",
"invert-swipes",
];
let dkeys = [
KEY_TEXT_SCALING_FACTOR,
];
this._connections = [];
for (let key of keys) {
let bind = this._onSettingsChanged.bind(this, key);
this._connections.push(this._extensionSettings.connect('changed::' + key, bind));
}
this._dconnections = [];
for (let dkey of dkeys) {
let bind = this._onSettingsChanged.bind(this, dkey);
this._dconnections.push(this._desktopSettings.connect('changed::' + dkey, bind));
}
this._settings = this._loadSettings();
}
disable() {
this.showPanels(0);
if (this._connections) {
for (let connection of this._connections) {
this._extensionSettings.disconnect(connection);
}
this._connections = null;
}
if (this._dconnections) {
for (let dconnection of this._dconnections) {
this._desktopSettings.disconnect(dconnection);
}
}
this._themeContext.disconnect(this._themeContextChangedID);
this._themeContext = null;
this._settings = null;
}
getWidgetClass() {
return St.Widget;
}
getWindowTracker() {
return Shell.WindowTracker.get_default();
}
getPrimaryModifier(mask) {
if (mask === 0)
return 0;
let primary = 1;
while (mask > 1) {
mask >>= 1;
primary <<= 1;
}
return primary;
}
getSettings() {
if (!this._settings) {
this._settings = this._loadSettings();
}
return this._settings;
}
addSettingsChangedCallback(cb) {
cb(this._extensionSettings);
this._settings_changed_callbacks.push(cb);
}
_onSettingsChanged(key) {
this._settings = null;
for (let cb of this._settings_changed_callbacks) {
cb(this._extensionSettings, key);
}
}
_loadSettings() {
try {
let settings = this._extensionSettings;
let dsettings = this._desktopSettings;
return {
animation_time: settings.get_double("animation-time"),
randomize_animation_times: settings.get_boolean("randomize-animation-times"),
dim_factor: clamp(settings.get_double("dim-factor"), 0, 1),
title_position: (settings.get_string("position") == 'Top' ? POSITION_TOP : POSITION_BOTTOM),
icon_style: (settings.get_string("icon-style")),
icon_has_shadow: settings.get_boolean("icon-has-shadow"),
overlay_icon_size: clamp(settings.get_double("overlay-icon-size"), 16, 1024),
overlay_icon_opacity: clamp(settings.get_double("overlay-icon-opacity"), 0, 1),
text_scaling_factor: dsettings.get_double(KEY_TEXT_SCALING_FACTOR),
offset: settings.get_int("offset"),
hide_panel: settings.get_boolean("hide-panel"),
enforce_primary_monitor: settings.get_boolean("enforce-primary-monitor"),
easing_function: settings.get_string("easing-function"),
switcher_class: settings.get_string("switcher-style") === 'Timeline'
? TimelineSwitcher : CoverflowSwitcher,
current_workspace_only: settings.get_string("current-workspace-only"),
switch_per_monitor: settings.get_boolean("switch-per-monitor"),
preview_to_monitor_ratio: clamp(settings.get_double("preview-to-monitor-ratio"), 0, 1),
preview_scaling_factor: clamp(settings.get_double("preview-scaling-factor"), 0, 1),
bind_to_switch_applications: settings.get_boolean("bind-to-switch-applications"),
bind_to_switch_windows: settings.get_boolean("bind-to-switch-windows"),
perspective_correction_method: settings.get_string("perspective-correction-method"),
highlight_mouse_over: settings.get_boolean("highlight-mouse-over"),
raise_mouse_over: settings.get_boolean("raise-mouse-over"),
desaturate_factor: settings.get_double("desaturate-factor") === 1.0 ? 0.999 : settings.get_double("desaturate-factor"),
blur_radius: settings.get_int("blur-radius"),
switcher_looping_method: settings.get_string("switcher-looping-method"),
switch_application_behaves_like_switch_windows: settings.get_boolean("switch-application-behaves-like-switch-windows"),
tint_color: settings.get_value("tint-color").deep_unpack(),
tint_blend: settings.get_double("tint-blend"),
switcher_background_color: settings.get_value("switcher-background-color").deep_unpack(),
use_glitch_effect: settings.get_boolean("use-glitch-effect"),
use_tint: settings.get_boolean("use-tint"),
invert_swipes: settings.get_boolean("invert-swipes"),
};
} catch (e) {
global.log(e);
}
return this.getDefaultSettings();
}
tween(actor, params) {
params.duration = params.time * 1000;
if (params.transition == 'userChoice' && this.getSettings().easing_function == 'random' ||
params.transition == 'Random') {
params.mode = modes[Math.floor(Math.random()*modes.length)];
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-bounce" ||
params.transition == 'easeInBounce') {
params.mode = Clutter.AnimationMode.EASE_IN_BOUNCE;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-bounce" ||
params.transition == 'easeOutBounce') {
params.mode = Clutter.AnimationMode.EASE_OUT_BOUNCE;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-bounce" ||
params.transition == 'easeInOutBounce') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_BOUNCE;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-back" ||
params.transition == 'easeInBack') {
params.mode = Clutter.AnimationMode.EASE_IN_BACK;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-back" ||
params.transition == 'easeOutBack') {
params.mode = Clutter.AnimationMode.EASE_OUT_BACK;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-back" ||
params.transition == 'easeInOutBack') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_BACK;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-elastic" ||
params.transition == 'easeInElastic') {
params.mode = Clutter.AnimationMode.EASE_IN_ELASTIC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-elastic" ||
params.transition == 'easeOutElastic') {
params.mode = Clutter.AnimationMode.EASE_OUT_ELASTIC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-elastic" ||
params.transition == 'easeInOutElastic') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_ELASTIC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-quad" ||
params.transition == 'easeInQuad') {
params.mode = Clutter.AnimationMode.EASE_IN_QUAD;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-quad" ||
params.transition == 'easeOutQuad') {
params.mode = Clutter.AnimationMode.EASE_OUT_QUAD;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-quad" ||
params.transition == 'easeInOutQuad') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_QUAD;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-cubic" ||
params.transition == 'easeInCubic') {
params.mode = Clutter.AnimationMode.EASE_IN_CUBIC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-cubic" ||
params.transition == 'easeOutCubic') {
params.mode = Clutter.AnimationMode.EASE_OUT_CUBIC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-cubic" ||
params.transition == 'easeInOutCubic') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_CUBIC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-quart" ||
params.transition == 'easeInQuart') {
params.mode = Clutter.AnimationMode.EASE_IN_QUART;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-quart" ||
params.transition == 'easeOutQuart') {
params.mode = Clutter.AnimationMode.EASE_OUT_QUART;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-quart" ||
params.transition == 'easeInOutQuart') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_QUART;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-quint" ||
params.transition == 'easeInQuint') {
params.mode = Clutter.AnimationMode.EASE_IN_QUINT;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-quint" ||
params.transition == 'easeOutQuint') {
params.mode = Clutter.AnimationMode.EASE_OUT_QUINT;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-quint" ||
params.transition == 'easeInOutQuint') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_QUINT;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-sine" ||
params.transition == 'easeInSine') {
params.mode = Clutter.AnimationMode.EASE_IN_SINE;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-sine" ||
params.transition == 'easeOutSine') {
params.mode = Clutter.AnimationMode.EASE_OUT_SINE;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-sine" ||
params.transition == 'easeInOutSine') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_SINE;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-expo" ||
params.transition == 'easeInExpo') {
params.mode = Clutter.AnimationMode.EASE_IN_EXPO;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-expo" ||
params.transition == 'easeOutExpo') {
params.mode = Clutter.AnimationMode.EASE_OUT_EXPO;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-expo" ||
params.transition == 'easeInOutExpo') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_EXPO;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-circ" ||
params.transition == 'easeInCirc') {
params.mode = Clutter.AnimationMode.EASE_IN_CIRC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-out-circ" ||
params.transition == 'easeOutCirc') {
params.mode = Clutter.AnimationMode.EASE_OUT_CIRC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-in-out-circ" ||
params.transition == 'easeInOutCirc') {
params.mode = Clutter.AnimationMode.EASE_IN_OUT_CIRC;
} else if (params.transition == 'userChoice' && this.getSettings().easing_function == "ease-linear" ||
params.transition == 'easeLinear') {
params.mode = Clutter.AnimationMode.LINEAR;
} else {
log("Could not find Clutter AnimationMode", params.transition, this.getSettings().easing_function);
}
if (params.onComplete) {
if (params.onCompleteParams && params.onCompleteScope) {
params.onComplete = params.onComplete.bind(params.onCompleteScope, ...params.onCompleteParams);
} else if (params.onCompleteParams) {
params.onComplete = params.onComplete.bind(null, params.onCompleteParams);
} else if (params.onCompleteScope) {
params.onComplete = params.onComplete.bind(params.onCompleteScope);
}
}
actor.ease(params);
}
removeTweens(actor) {
actor.remove_all_transitions();
}
initBackground() {
this._backgroundGroup = new Meta.BackgroundGroup();
Main.layoutManager.uiGroup.add_child(this._backgroundGroup);
if (this._backgroundGroup.lower_bottom) {
this._backgroundGroup.lower_bottom();
} else {
Main.uiGroup.set_child_below_sibling(this._backgroundGroup, null);
}
this._backgroundShade = new Clutter.Actor({
opacity: 0,
reactive: false
});
let constraint = Clutter.BindConstraint.new(this._backgroundGroup,
Clutter.BindCoordinate.ALL, 0);
this._backgroundShade.add_constraint(constraint);
let shade = new MyRadialShaderEffect({name: 'shade'});
shade.brightness = 1;
shade.sharpness = this._settings.dim_factor;
this._backgroundShade.add_effect(shade);
this._backgroundGroup.add_child(this._backgroundShade);
this._backgroundGroup.set_child_above_sibling(this._backgroundShade, null);
this._backgroundGroup.hide();
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
new Background.BackgroundManager({
container: this._backgroundGroup,
monitorIndex: i,
vignette: false,
});
}
}
hidePanels() {
let panels = this.getPanels();
for (let panel of panels) {
try {
let panelActor = (panel instanceof Clutter.Actor) ? panel : panel.actor;
panelActor.set_reactive(false);
this.tween(panelActor, {
opacity: 0,
time: this._settings.animation_time,
transition: 'easeInOutQuint'
});
} catch (e) {
log(e);
// ignore fake panels
}
}
}
dimBackground() {
if (this._settings.hide_panel) {
this.hidePanels();
}
// hide gnome-shell legacy tray
try {
if (Main.legacyTray) {
Main.legacyTray.actor.hide();
}
} catch (e) {
// ignore missing legacy tray
}
this._backgroundGroup.show();
this.tween(this._backgroundShade, {
opacity: 255,
time: this._settings.animation_time,
transition: 'easeInOutQuint',
});
}
showPanels(time) {
// panels
let panels = this.getPanels();
for (let panel of panels){
try {
let panelActor = (panel instanceof Clutter.Actor) ? panel : panel.actor;
panelActor.set_reactive(true);
if (this._settings.hide_panel) {
this.removeTweens(panelActor);
this.tween(panelActor, {
opacity: 255,
time: time,
transition: 'easeInOutQuint'
});
}
} catch (e) {
//ignore fake panels
}
}
}
lightenBackground() {
if (this._settings.hide_panel) {
this.showPanels(this._settings.animation_time);
}
// show gnome-shell legacy trayconn
try {
if (Main.legacyTray) {
Main.legacyTray.actor.show();
}
} catch (e) {
//ignore missing legacy tray
}
this.tween(this._backgroundShade, {
time: this._settings.animation_time * 0.95,
transition: 'easeInOutQuint',
opacity: 0,
});
}
removeBackground() {
this._backgroundGroup.destroy();
}
getPanels() {
let panels = [Main.panel];
if (Main.panel2)
panels.push(Main.panel2);
// gnome-shell dash
if (Main.overview._dash)
panels.push(Main.overview._dash);
return panels;
}
}
const VIGNETTE_DECLARATIONS = ' \
uniform float brightness; \n\
uniform float vignette_sharpness; \n\
float rand(vec2 p) { \n\
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123); \n\
} \n';
const VIGNETTE_CODE = ' \
cogl_color_out.a = cogl_color_in.a; \n\
cogl_color_out.rgb = vec3(0.0, 0.0, 0.0); \n\
vec2 position = cogl_tex_coord_in[0].xy - 0.5; \n\
float t = clamp(length(1.41421 * position), 0.0, 1.0); \n\
float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t); \n\
cogl_color_out.a *= 1.0 - pixel_brightness * brightness; \n\
cogl_color_out.a += (rand(position) - 0.5) / 100.0; \n';
const MyRadialShaderEffect = GObject.registerClass({
Properties: {
'brightness': GObject.ParamSpec.float(
'brightness', 'brightness', 'brightness',
GObject.ParamFlags.READWRITE,
0, 1, 1),
'sharpness': GObject.ParamSpec.float(
'sharpness', 'sharpness', 'sharpness',
GObject.ParamFlags.READWRITE,
0, 1, 0),
},
}, class MyRadialShaderEffect extends Shell.GLSLEffect {
_init(params) {
this._brightness = undefined;
this._sharpness = undefined;
super._init(params);
this._brightnessLocation = this.get_uniform_location('brightness');
this._sharpnessLocation = this.get_uniform_location('vignette_sharpness');
this.brightness = 1.0;
this.sharpness = 0.0;
}
vfunc_build_pipeline() {
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT,
VIGNETTE_DECLARATIONS, VIGNETTE_CODE, true);
}
get brightness() {
return this._brightness;
}
set brightness(v) {
if (this._brightness === v)
return;
this._brightness = v;
this.set_uniform_float(this._brightnessLocation,
1, [this._brightness]);
this.notify('brightness');
}
get sharpness() {
return this._sharpness;
}
set sharpness(v) {
if (this._sharpness === v)
return;
this._sharpness = v;
this.set_uniform_float(this._sharpnessLocation,
1, [this._sharpness]);
this.notify('sharpness');
}
});

View File

@@ -0,0 +1,641 @@
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* CoverflowAltTab
*
* Preferences dialog for "gnome-extensions prefs" tool
*
* Based on preferences in the following extensions: JustPerfection, dash2doc-lite, night theme switcher, and desktop cube
*
*/
import Adw from 'gi://Adw';
import Gdk from 'gi://Gdk';
import GLib from 'gi://GLib';
import Gtk from 'gi://Gtk';
import Gio from 'gi://Gio';
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'
const easing_options = [
{
id: 'ease-linear', name: 'easeLinear'
}, {
id: 'ease-in-quad', name: "easeInQuad"
}, {
id: 'ease-out-quad', name: "easeOutQuad"
}, {
id: 'ease-in-out-quad', name: "easeInOutQuad"
}, {
id: 'ease-in-cubic', name: "easeInCubic"
}, {
id: 'ease-out-cubic', name: "easeOutCubic"
}, {
id: 'ease-in-out-cubic', name: "easeInOutCubic"
}, {
id: 'ease-in-quart', name: "easeInQuart"
}, {
id: 'ease-out-quart', name: "easeOutQuart"
}, {
id: 'ease-in-out-quart', name: "easeInOutQuart"
}, {
id: 'ease-in-quint', name: "easeInQuint"
}, {
id: 'ease-out-quint', name: "easeOutQuint"
}, {
id: 'ease-in-out-quint', name: "easeInOutQuint"
}, {
id: 'ease-in-sine', name: "easeInSine"
}, {
id: 'ease-out-sine', name: "easeOutSine"
}, {
id: 'ease-in-out-sine', name: "easeInOutSine"
}, {
id: 'ease-in-expo', name: "easeInExpo"
}, {
id: 'ease-out-expo', name: "easeOutExpo"
}, {
id: 'ease-in-out-expo', name: "easeInOutExpo"
}, {
id: 'ease-in-circ', name: "easeInCirc"
}, {
id: 'ease-out-circ', name: "easeOutCirc"
}, {
id: 'ease-in-out-circ', name: "easeInOutCirc"
}, {
id: 'ease-in-back', name: "easeInBack"
}, {
id: 'ease-out-back', name: "easeOutBack"
}, {
id: 'ease-in-out-back', name: "easeInOutBack"
}, {
id: 'ease-in-elastic', name: "easeInElastic"
}, {
id: 'ease-out-elastic', name: "easeOutElastic"
}, {
id: 'ease-in-out-elastic', name: "easeInOutElastic"
}, {
id: 'ease-in-bounce', name: "easeInBounce"
}, {
id: 'ease-out-bounce', name: "easeOutBounce"
}, {
id: 'ease-in-out-bounce', name: "easeInOutBounce"
}, {
id: 'random', name: "Random"
}];
function getBaseString(translatedString) {
switch (translatedString) {
case _("Coverflow"): return "Coverflow";
case _("Timeline"): return "Timeline";
case _("Bottom"): return "Bottom";
case _("Top"): return "Top";
case _("Classic"): return "Classic";
case _("Overlay"): return "Overlay";
default: return translatedString;
}
}
function makeResetButton() {
return new Gtk.Button({
icon_name: "edit-clear-symbolic",
tooltip_text: _("Reset to default value"),
valign: Gtk.Align.CENTER,
});
}
export default class CoverflowAltTabPreferences extends ExtensionPreferences {
constructor(metadata) {
super(metadata);
let IconsPath = GLib.build_filenamev([this.path, 'ui', 'icons']);
let iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
iconTheme.add_search_path(IconsPath);
}
getVersionString(_page) {
return _('Version %d').format(this.metadata.version);
}
fillPreferencesWindow(window) {
let settings = this.getSettings();
let general_page = new Adw.PreferencesPage({
title: _('General'),
icon_name: 'general-symbolic',
});
let switcher_pref_group = new Adw.PreferencesGroup({
title: _('Switcher'),
});
let switcher_looping_method_buttons = new Map([ [_("Flip Stack"), [[],[]]], [_("Carousel"), [[],[]]]]);
let switcher_looping_method_row = buildRadioAdw(settings, "switcher-looping-method", switcher_looping_method_buttons, _("Looping Method"), _("How to cycle through windows."));
switcher_pref_group.add(buildRadioAdw(settings, "switcher-style", new Map([ [_("Coverflow"), [[switcher_looping_method_row], []]], [_("Timeline"), [[],[switcher_looping_method_row]] ]]), _("Style"), _("Pick the type of switcher.")))
switcher_pref_group.add(buildSpinAdw(settings, "offset", [-500, 500, 1, 10], _("Vertical Offset"), _("Positive value moves everything down, negative up.")));
switcher_pref_group.add(buildRadioAdw(settings, "position", new Map([ [_("Bottom"), [[], []]], [_("Top"), [[],[]]]]), _("Window Title Position"), _("Place window title above or below the switcher.")));
switcher_pref_group.add(buildSwitcherAdw(settings, "enforce-primary-monitor", [], [], _("Enforce Primary Monitor"), _("Always show on the primary monitor, otherwise, show on the active monitor.")));
switcher_pref_group.add(switcher_looping_method_row);
switcher_pref_group.add(buildSwitcherAdw(settings, "hide-panel", [], [], _("Hide Panel"), _("Hide panel when switching windows.")));
switcher_pref_group.add(buildSwitcherAdw(settings, "invert-swipes", [], [], _("Invert Swipes"), _("Swipe content instead of view.")));
let animation_pref_group = new Adw.PreferencesGroup({
title: _('Animation'),
});
animation_pref_group.add(buildDropDownAdw(settings, "easing-function", easing_options, "Easing Function", "Determine how windows move."));
animation_pref_group.add(buildRangeAdw(settings, "animation-time", [0.01, 20, 0.001, [0.5, 1, 1.5]], _("Duration [s]"), "", true));
animation_pref_group.add(buildSwitcherAdw(settings, "randomize-animation-times", [], [], _("Randomize Durations"), _("Each animation duration assigned randomly between 0 and configured duration.")));
let windows_pref_group = new Adw.PreferencesGroup({
title: _('Switcher Windows'),
});
let options = [{
id: 'current', name: _("Current workspace only")
}, {
id: 'all', name: _("All workspaces")
}, {
id: 'all-currentfirst', name: _("All workspaces, current first")
}];
windows_pref_group.add(buildDropDownAdw(settings, "current-workspace-only", options, _("Workspaces"), _("Switch between windows on current or on all workspaces.")));
windows_pref_group.add(buildSwitcherAdw(settings, "switch-per-monitor", [], [], _("Current Monitor"), _("Switch between windows on current monitor.")));
let icon_pref_group = new Adw.PreferencesGroup({
title: _("Icons"),
});
let size_row = buildRangeAdw(settings, "overlay-icon-size", [16, 1024, 1, [32, 64, 128, 256, 512]], _("Overlay Icon Size"), _("Set the overlay icon size in pixels."), true);
let opacity_row = buildRangeAdw(settings, "overlay-icon-opacity", [0, 1, 0.001, [0.25, 0.5, 0.75]], _("Overlay Icon Opacity"), _("Set the overlay icon opacity."), true);
let buttons = new Map([[_("Classic"), [[],[size_row, opacity_row]]], [_("Overlay"), [[size_row, opacity_row], []]], [_("Attached"), [[size_row, opacity_row], []]]]);
let style_row = buildRadioAdw(settings, "icon-style", buttons, _("Application Icon Style"));
icon_pref_group.add(style_row);
icon_pref_group.add(size_row);
icon_pref_group.add(opacity_row);
icon_pref_group.add(buildSwitcherAdw(settings, "icon-has-shadow", [], [], _("Icon Shadow")));
let window_size_pref_group = new Adw.PreferencesGroup({
title: _("Window Properties")
});
window_size_pref_group.add(buildRangeAdw(settings, "preview-to-monitor-ratio", [0, 1, 0.001, [0.250, 0.500, 0.750]], _("Window Preview Size to Monitor Size Ratio"), _("Maximum ratio of window preview size to monitor size."), true));
window_size_pref_group.add(buildRangeAdw(settings, "preview-scaling-factor", [0, 1, 0.001, [0.250, 0.500, 0.800]], _("Off-center Size Factor"), _("Factor by which to successively shrink previews off to the side."), true));
let background_application_switcher_pref_group = new Adw.PreferencesGroup({
title: _('Application Switcher'),
});
background_application_switcher_pref_group.add(buildSwitcherAdw(settings, "switch-application-behaves-like-switch-windows", [], [], _("Make the Application Switcher Behave Like the Window Switcher"), _("Don't group windows of the same application in a subswitcher.")));
background_application_switcher_pref_group.add(buildRangeAdw(settings, "desaturate-factor", [0, 1, 0.001, [0.25, 0.5, 0.75]], _("Desaturate"), _("Larger means more desaturation."), true));
background_application_switcher_pref_group.add(buildSpinAdw(settings, "blur-radius", [0, 20, 1, 1], _("Blur"), _("Larger means blurrier.")));
let color_row = new Adw.ExpanderRow({
title: _("Tint"),
});
background_application_switcher_pref_group.add(color_row);
let use_tint_switch = new Gtk.Switch({
valign: Gtk.Align.CENTER,
active: settings.get_boolean("use-tint"),
});
settings.bind("use-tint", use_tint_switch, "active", Gio.SettingsBindFlags.DEFAULT);
color_row.add_suffix(use_tint_switch);
let tint_chooser_row = new Adw.ActionRow({
title: _("Color")
});
let choose_tint_box = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 10,
valign: Gtk.Align.CENTER,
});
tint_chooser_row.add_suffix(choose_tint_box);
color_row.add_row(tint_chooser_row);
let color_dialog = new Gtk.ColorDialog({
with_alpha: false,
});
let color_button = new Gtk.ColorDialogButton({
valign: Gtk.Align.CENTER,
dialog: color_dialog,
});
let use_theme_color_button = new Gtk.Button({
label: _("Set to Theme Color"),
valign: Gtk.Align.CENTER,
});
use_theme_color_button.connect('clicked', () => {
let c = settings.get_value("switcher-background-color").deep_unpack();
let rgba = color_button.rgba;
rgba.red = c[0];
rgba.green = c[1];
rgba.blue = c[2];
rgba.alpha = 1
color_button.set_rgba(rgba);
});
choose_tint_box.append(use_theme_color_button);
choose_tint_box.append(color_button);
let c = settings.get_value("tint-color").deep_unpack();
let rgba = color_button.rgba;
rgba.red = c[0];
rgba.green = c[1];
rgba.blue = c[2];
rgba.alpha = 1
color_button.set_rgba(rgba);
color_button.connect('notify::rgba', _ => {
let c = color_button.rgba;
let val = new GLib.Variant("(ddd)", [c.red, c.green, c.blue]);
settings.set_value("tint-color", val);
});
use_tint_switch.connect('notify::active', function(widget) {
color_row.set_expanded(widget.get_active());
});
let reset_button = makeResetButton();
reset_button.connect("clicked", function (widget) {
settings.reset("use-tint");
});
color_row.add_suffix(reset_button);
color_row.add_row(buildRangeAdw(settings, "tint-blend", [0, 1, 0.001, [0.25, 0.5, 0.75]], _("Blend"), _("How much to blend the tint color; bigger means more tint color."), true));
background_application_switcher_pref_group.add(buildSwitcherAdw(settings, "use-glitch-effect", [], [], _("Glitch")));
let background_pref_group = new Adw.PreferencesGroup({
title: _('Background'),
});
background_pref_group.add(buildRangeAdw(settings, "dim-factor", [0, 1, 0.001, [0.25, 0.5, 0.75]], _("Dim-factor"), _("Bigger means darker."), true));
let keybinding_pref_group = new Adw.PreferencesGroup({
title: _("Keybindings"),
});
keybinding_pref_group.add(buildSwitcherAdw(settings, "bind-to-switch-windows", [], [], _("Bind to 'switch-windows'")));
keybinding_pref_group.add(buildSwitcherAdw(settings, "bind-to-switch-applications", [background_application_switcher_pref_group], [], _("Bind to 'switch-applications'")));
let pcorrection_pref_group = new Adw.PreferencesGroup({
title: _("Perspective Correction")
})
pcorrection_pref_group.add(buildDropDownAdw(settings, "perspective-correction-method", [
{ id: "None", name: _("None") },
{ id: "Move Camera", name: _("Move Camera") },
{ id: "Adjust Angles", name: _("Adjust Angles") }],
_("Perspective Correction"), ("Method to make off-center switcher look centered.")));
let highlight_mouse_over_pref_group = new Adw.PreferencesGroup({
title: _("Highlight Window Under Mouse"),
});
window_size_pref_group.add(buildSwitcherAdw(settings, "highlight-mouse-over", [], [], _("Highlight Window Under Mouse"), _("Draw embelishment on window under the mouse to know the effects of clicking.")));
window_size_pref_group.add(buildSwitcherAdw(settings, "raise-mouse-over", [], [], _("Raise Window Under Mouse"), _("Raise the window under the mouse above all others.")));
/*let tweaks_page = new Adw.PreferencesPage({
title: _('Tweaks'),
icon_name: 'applications-symbolic',
});
tweaks_page.add(pcorrection_pref_group);
tweaks_page.add(highlight_mouse_over_pref_group);*/
general_page.add(switcher_pref_group);
general_page.add(animation_pref_group);
general_page.add(icon_pref_group);
general_page.add(windows_pref_group);
general_page.add(window_size_pref_group);
general_page.add(background_pref_group);
general_page.add(background_application_switcher_pref_group);
general_page.add(pcorrection_pref_group);
general_page.add(keybinding_pref_group);
let contribution_page = new Adw.PreferencesPage({
title: _("Contribute"),
icon_name: 'contribute-symbolic',
});
let contribute_icon_pref_group = new Adw.PreferencesGroup();
let icon_box = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
margin_top: 24,
margin_bottom: 24,
spacing: 18,
});
let icon_image = new Gtk.Image({
icon_name: "coverflow-symbolic",
pixel_size: 128,
});
let label_box = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 6,
});
let label = new Gtk.Label({
label: "Coverflow Alt-Tab",
wrap: true,
});
let context = label.get_style_context();
context.add_class("title-1");
let another_label = new Gtk.Label({
label: this.getVersionString(),
});
let links_pref_group = new Adw.PreferencesGroup();
let code_row = new Adw.ActionRow({
icon_name: "code-symbolic",
title: _("Code (create pull requests, report issues, etc.)")
});
let github_link = new Gtk.LinkButton({
label: "Github",
uri: "https://github.com/dmo60/CoverflowAltTab",
});
let donate_row = new Adw.ActionRow({
title: _("Donate"),
icon_name: "support-symbolic",
})
let donate_link = new Gtk.LinkButton({
label: "Liberapay",
uri: "https://liberapay.com/dsheeler/donate",
});
let donate_link_paypal = new Gtk.LinkButton({
label: "PayPal",
uri: "https://paypal.me/DanielSheeler?country.x=US&locale.x=en_US",
});
let donate_link_github = new Gtk.LinkButton({
label: "Github",
uri: "https://github.com/sponsors/dsheeler",
});
let translate_row = new Adw.ActionRow({
title: _("Translate"),
icon_name: "translate-symbolic",
})
let translate_link = new Gtk.LinkButton({
label: "Weblate",
uri: "https://hosted.weblate.org/engage/coverflow-alt-tab/",
});
code_row.add_suffix(github_link);
code_row.set_activatable_widget(github_link);
translate_row.add_suffix(translate_link);
translate_row.set_activatable_widget(translate_link);
donate_row.add_suffix(donate_link);
donate_row.add_suffix(donate_link_paypal);
donate_row.add_suffix(donate_link_github);
links_pref_group.add(code_row);
links_pref_group.add(translate_row);
links_pref_group.add(donate_row);
label_box.append(label);
label_box.append(another_label);
icon_box.append(icon_image);
icon_box.append(label_box);
contribute_icon_pref_group.add(icon_box);
contribution_page.add(contribute_icon_pref_group);
contribution_page.add(links_pref_group)
window.add(general_page);
// window.add(appearance_page);
window.add(contribution_page);
window.set_search_enabled(true);
}
}
function buildSwitcherAdw(settings, key, dependant_widgets, inverse_dependant_widgets, title, subtitle=null) {
let pref = new Adw.ActionRow({
title: title,
});
if (subtitle != null) {
pref.set_subtitle(subtitle);
}
let switcher = new Gtk.Switch({
valign: Gtk.Align.CENTER,
active: settings.get_boolean(key)
});
switcher.expand = false;
switcher.connect('notify::active', function(widget) {
settings.set_boolean(key, widget.active);
});
pref.set_activatable_widget(switcher);
pref.add_suffix(switcher);
switcher.connect('notify::active', function(widget) {
for (let dep of dependant_widgets) {
dep.set_sensitive(widget.get_active());
}
});
for (let widget of dependant_widgets) {
widget.set_sensitive(switcher.get_active());
}
switcher.connect('notify::active', function(widget) {
for (let inv_dep of inverse_dependant_widgets) {
inv_dep.set_sensitive(!widget.get_active());
}
});
for (let widget of inverse_dependant_widgets) {
widget.set_sensitive(!switcher.get_active());
}
let reset_button = makeResetButton();
reset_button.connect("clicked", function(widget) {
settings.reset(key);
switcher.set_active(settings.get_boolean(key));
})
pref.add_suffix(reset_button);
return pref;
}
function buildRangeAdw(settings, key, values, title, subtitle="", draw_value=false) {
let [min, max, step, defvs] = values;
let pref = new Adw.ActionRow({
title: title, });
if (subtitle !== null && subtitle !== "") {
pref.set_subtitle(subtitle);
}
let range = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, min, max, step);
range.set_value(settings.get_double(key));
if (draw_value) {
range.set_draw_value(true);
range.set_value_pos(Gtk.PositionType.RIGHT)
}
for (let defv of defvs) {
range.add_mark(defv, Gtk.PositionType.BOTTOM, null);
}
range.set_size_request(200, -1);
range.connect('value-changed', function(slider) {
settings.set_double(key, slider.get_value());
});
pref.set_activatable_widget(range);
pref.add_suffix(range)
let reset_button = makeResetButton();
reset_button.connect("clicked", function(widget) {
settings.reset(key);
range.set_value(settings.get_double(key));
});
pref.add_suffix(reset_button);
return pref;
}
function buildRadioAdw(settings, key, buttons, title, subtitle=null) {
let pref = new Adw.ActionRow({
title: title,
});
if (subtitle != null) {
pref.set_subtitle(subtitle);
}
let hbox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 10,
valign: Gtk.Align.CENTER,
});
let radio = new Gtk.ToggleButton();
let radio_for_button = {};
for (let button_name of buttons.keys()) {
radio = new Gtk.ToggleButton({group: radio, label: button_name});
radio_for_button[button_name] = radio;
if (getBaseString(button_name) == settings.get_string(key)) {
radio.set_active(true);
for (let pref_row of buttons.get(button_name)[0]) {
pref_row.set_sensitive(radio_for_button[button_name].get_active());
}
for (let pref_row of buttons.get(button_name)[1]) {
pref_row.set_sensitive(!radio_for_button[button_name].get_active());
}
}
radio.connect('toggled', function(widget) {
if (widget.get_active()) {
settings.set_string(key, getBaseString(widget.get_label()));
}
for (let pref_row of buttons.get(button_name)[0]) {
pref_row.set_sensitive(widget.get_active());
}
for (let pref_row of buttons.get(button_name)[1]) {
pref_row.set_sensitive(!widget.get_active());
}
});
hbox.append(radio);
};
let reset_button = makeResetButton();
reset_button.connect("clicked", function(widget) {
settings.reset(key);
for (let button of buttons.keys()) {
if (getBaseString(button) == settings.get_string(key)) {
radio_for_button[button].set_active(true);
}
}
});
pref.set_activatable_widget(hbox);
pref.add_suffix(hbox);
pref.add_suffix(reset_button);
return pref;
};
function buildSpinAdw(settings, key, values, title, subtitle=null) {
let [min, max, step, page] = values;
let pref = new Adw.ActionRow({
title: title,
});
if (subtitle != null) {
pref.set_subtitle(subtitle);
}
let spin = new Gtk.SpinButton({ valign: Gtk.Align.CENTER });
spin.set_range(min, max);
spin.set_increments(step, page);
spin.set_value(settings.get_int(key));
spin.connect('value-changed', function(widget) {
settings.set_int(key, widget.get_value());
});
pref.set_activatable_widget(spin);
pref.add_suffix(spin);
let reset_button = makeResetButton();
reset_button.connect("clicked", function(widget) {
settings.reset(key);
spin.set_value(settings.get_int(key));
});
pref.add_suffix(reset_button);
return pref;
}
function buildDropDownAdw(settings, key, values, title, subtitle=null) {
let pref = new Adw.ActionRow({
title: title,
});
if (subtitle != null) {
pref.set_subtitle(subtitle);
}
let model = new Gtk.StringList();
let chosen_idx = 0;
for (let i = 0; i < values.length; i++) {
let item = values[i];
model.append(item.name);
if (item.id == settings.get_string(key)) {
chosen_idx = i;
}
}
let chooser = new Gtk.DropDown({
valign: Gtk.Align.CENTER,
model: model,
selected: chosen_idx,
});
chooser.connect('notify::selected-item', function(c) {
let idx = c.get_selected();
settings.set_string(key, values[idx].id);
});
pref.set_activatable_widget(chooser);
pref.add_suffix(chooser);
let reset_button = makeResetButton();
reset_button.connect("clicked", function(widget) {
settings.reset(key);
for (let i = 0; i < values.length; i++) {
let item = values[i];
if (item.id == settings.get_string(key)) {
chooser.set_selected(i);
break;
}
}
});
pref.add_suffix(reset_button);
return pref;
}

View File

@@ -0,0 +1,429 @@
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
import Clutter from 'gi://Clutter';
import GObject from 'gi://GObject';
import Graphene from 'gi://Graphene';
import St from 'gi://St';
/**
* Direction and Placement properties values are set to be compatible with deprecated
* Clutter.Gravity.
*/
export class Direction {}
Direction.TO_RIGHT = 3;
Direction.TO_LEFT = 7;
export class Placement {}
Placement.TOP = 1;
Placement.TOP_RIGHT = 2;
Placement.RIGHT = 3;
Placement.BOTTOM_RIGHT = 4;
Placement.BOTTOM = 5;
Placement.BOTTOM_LEFT = 6;
Placement.LEFT = 7;
Placement.TOP_LEFT = 8;
Placement.CENTER = 9;
export const Preview = GObject.registerClass({
GTypeName: "Preview",
Properties: {
'remove_icon_opacity': GObject.ParamSpec.double(
`remove_icon_opacity`,
`Revmove Icon Opacity`,
`Icon opacity when `,
GObject.ParamFlags.READWRITE,
0.0, 1.0,
1.0,
)
}
}, class Preview extends Clutter.Clone {
_init(window, switcher, ...args) {
super._init(...args);
this.metaWin = window;
this.switcher = switcher;
this._icon = null;
this._highlight = null;
this._flash = null;
this._entered = false;
this._effectNames = ['blur', 'glitch', 'desaturate', 'tint']
this._effectCounts = {};
for (let effect_name of this._effectNames) {
this._effectCounts[effect_name] = 0;
}
}
/**
* Make the preview above all other children layers in the given parent.
*
* @param {Object} parent The preview parent. If is not its real parent,then the
* behaviour is undefined.
* @return {void}
*/
make_top_layer(parent) {
if (this.raise_top) {
this.raise_top()
if (this._icon) this._icon.raise_top();
} else if (parent.set_child_above_sibling) {
parent.set_child_above_sibling(this, null);
if (this._icon) parent.set_child_above_sibling(this._icon, this);
} else {
// Don't throw anything here, it may cause unstabilities
logError("No method found for making preview the top layer");
}
}
/**
* Make the preview below all other children layers in the given parent.
*
* @param {Object} parent The preview parent. If is not its real parent,then the
* behaviour is undefined.
* @return {void}
*/
make_bottom_layer(parent) {
if (this.lower_bottom) {
if (this._icon) this._icon.lower_bottom();
this.lower_bottom()
} else if (parent.set_child_below_sibling) {
parent.set_child_below_sibling(this, null);
if (this._icon) parent.set_child_above_sibling(this._icon, this);
} else {
// Don't throw anything here, it may cause unstabilities
logError("No method found for making preview the bottom layer");
}
}
addEffect(effect_class, constructor_argument, name, parameter_name, from_param_value, param_value, duration) {
duration = 0.99 * 1000.0 * duration;
let effect_name = name + "-effect";
let add_transition_name = effect_name + "-add";
let remove_transition_name = effect_name + "-remove";
let property_transition_name = `@effects.${effect_name}.${parameter_name}`;
if (this.get_transition(remove_transition_name) !== null) {
this.remove_transition(remove_transition_name);
let transition = Clutter.PropertyTransition.new(property_transition_name);
transition.progress_mode = Clutter.AnimationMode.LINEAR;
transition.duration = duration;
transition.remove_on_complete = true;
transition.set_from(this.get_effect(effect_name)[parameter_name]);
transition.set_to(param_value);
this.get_effect(effect_name)[parameter_name] = 1.0;
this.add_transition(add_transition_name, transition);
transition.connect('new-frame', (timeline, msecs) => {
this.queue_redraw();
});
} else if (this._effectCounts[name] == 0) {
if (this.get_transition(add_transition_name) === null) {
let transition = Clutter.PropertyTransition.new(property_transition_name);
transition.progress_mode = Clutter.AnimationMode.LINEAR;
transition.duration = duration;
transition.remove_on_complete = true;
transition.set_to(param_value);
transition.set_from(from_param_value);
this._newFrameCount = 0;
this.add_effect_with_name(effect_name, new effect_class(constructor_argument));
this.add_transition(add_transition_name, transition);
this._effectCounts[name] = 1;
transition.connect('new-frame', (timeline, msecs) => {
this.queue_redraw();
});
}
} else {
this._effectCounts[name] += 1;
}
}
removeEffect(name, parameter_name, value, duration) {
duration = 0.99 * 1000.0 * duration;
let effect_name = name + "-effect";
let add_transition_name = effect_name + "-add";
let remove_transition_name = effect_name + "-remove";
let property_transition_name = `@effects.${effect_name}.${parameter_name}`;
if (this._effectCounts[name] > 0) {
if (this._effectCounts[name] == 1) {
this.remove_transition(add_transition_name);
if (this.get_transition(remove_transition_name) === null) {
let transition = Clutter.PropertyTransition.new(property_transition_name);
transition.progress_mode = Clutter.AnimationMode.LINEAR;
transition.duration = duration;
transition.remove_on_complete = true;
transition.set_from(this.get_effect(effect_name)[parameter_name]);
transition.set_to(value);
this.get_effect(effect_name)[parameter_name] = 1.0;
this.add_transition(remove_transition_name, transition);
transition.connect("completed", (trans) => {
this.remove_effect_by_name(effect_name);
this._effectCounts[name] = 0;
});
}
} else {
this._effectCounts[name] -= 1;
}
}
}
_pulse_highlight() {
if (this._highlight == null) return;
this._highlight.ease({
opacity: 255,
duration: 2000,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUINT,
onComplete: () => {
this._highlight.ease({
opacity: 80,
duration: 1400,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUINT,
onComplete: () => {
this._pulse_highlight();
},
});
},
});
}
remove_highlight() {
if (this._highlight != null) {
this._highlight.ease({
opacity: 0,
duration: 300,
mode: Clutter.AnimationMode.EASE_IN_OUT_QUINT,
onComplete: () => {
if (this._highlight != null) {
this._highlight.destroy()
this._highlight = null;
}
},
});
}
if (this._flash != null) {
this._flash.destroy();
this._flash = null;
}
}
_getHighlightStyle(alpha) {
let bgcolor = this.switcher._getSwitcherBackgroundColor();
let style =`background-color: rgba(${bgcolor.red}, ${bgcolor.green}, ${bgcolor.blue}, ${alpha})`;
return style;
}
vfunc_enter_event(crossingEvent) {
if (this.switcher._animatingClosed || this._entered == true) {
return Clutter.EVENT_PROPAGATE;
}
this._entered = true;
if (this.switcher._settings.raise_mouse_over) {
this.make_top_layer(this.switcher.previewActor);
this.switcher._raiseIcons();
}
if (this.switcher._settings.highlight_mouse_over) {
let window_actor = this.metaWin.get_compositor_private();
if (this._highlight == null) {
this._highlight = new St.Bin({
opacity: 0,
width: this.width,
height: this.height,
x: 0,
y: 0,
reactive: false,
});
this._highlight.set_style(this._getHighlightStyle(0.3));
let constraint = Clutter.BindConstraint.new(window_actor, Clutter.BindCoordinate.SIZE, 0);
this._highlight.add_constraint(constraint);
window_actor.add_child(this._highlight);
}
if (this._flash == null) {
this._flash = new St.Bin({
width: 1,
height: 1,
opacity: 255,
reactive: false,
x: 0,
y: 0,
});
this._flash.set_style(this._getHighlightStyle(1));
let constraint = Clutter.BindConstraint.new(window_actor, Clutter.BindCoordinate.SIZE, 0);
this._flash.add_constraint(constraint);
window_actor.add_child(this._flash);
this._flash.ease({
opacity: 0,
duration: 500,
mode: Clutter.AnimationMode.EASE_OUT_QUINT,
onComplete: () => {
this._pulse_highlight();
}
});
}
}
return Clutter.EVENT_PROPAGATE;
}
addIcon() {
let app = this.switcher._tracker.get_window_app(this.metaWin);
let icon_size = this.switcher._settings.overlay_icon_size;
this._icon = app ? app.create_icon_texture(Math.min(icon_size, this.width, this.height) / this.scale) : null;
if (this._icon == null) {
this._icon = new St.Icon({
icon_name: 'applications-other',
});
}
let constraint = Clutter.BindConstraint.new(this, Clutter.BindCoordinate.ALL, 0);
this._icon.add_constraint(constraint);
this.bind_property_full('opacity',
this._icon, 'opacity',
GObject.BindingFlags.SYNC_CREATE,
(bind, source) => {
/* So that the icon fades out 1) when the preview fades
out, such as in the timeline switcher, and
2) when the icon is being removed,
but also ensure the icon only goes as high as the setting
opacity, we take the minimum of those three as our opacity.
Seems there might be a better way, but I'm not sure.
*/
return [true, Math.min(source, 255 * this.remove_icon_opacity, 255 * this.switcher._settings.overlay_icon_opacity)];
},
null);
this.bind_property('rotation_angle_y', this._icon, 'rotation_angle_y',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('pivot_point', this._icon, 'pivot_point',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('translation_x', this._icon, 'translation_x',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('scale_x', this._icon, 'scale_x',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('scale_y', this._icon, 'scale_y',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('scale_z', this._icon, 'scale_z',
GObject.BindingFlags.SYNC_CREATE);
this.switcher.previewActor.add_child(this._icon);
if (this.switcher._settings.icon_has_shadow) {
this._icon.add_style_class_name("icon-dropshadow");
}
}
removeIcon(animation_time) {
if (this._icon != null) {
let transition = Clutter.PropertyTransition.new('remove_icon_opacity');
transition.duration = 1000.0 * animation_time;
this._icon.remove_icon_opacity_start = this._icon.opacity / 255.;
transition.set_from(this._icon.remove_icon_opacity_start);
transition.set_to(0);
transition.remove_on_complete = true;
transition.connect('new-frame', (timeline, msecs) => {
this._icon.opacity = 255 * this._icon.remove_icon_opacity_start * (1 -
timeline.get_progress());//(1 - msecs / transition.duration);
this._icon.queue_redraw();
})
transition.connect('completed', (timeline) => {
if (this._icon != null) {
this._icon.destroy()
this._icon = null;
}
});
this.add_transition('remove_icon_opacity_transition', transition);
}
}
vfunc_leave_event(crossingEvent) {
this.remove_highlight();
this._entered = false;
if (this.switcher._settings.raise_mouse_over && !this.switcher._animatingClosed) this.switcher._updatePreviews(true, 0);
return Clutter.EVENT_PROPAGATE;
}
/**
* Gets the pivot point relative to the preview.
*
* @param {Placement} placement
* @return {Graphene.Point}
*/
get_pivot_point_placement(placement) {
let xFraction = 0,
yFraction = 0;
// Set xFraction
switch (placement) {
case Placement.TOP_LEFT:
case Placement.LEFT:
case Placement.BOTTOM_LEFT:
xFraction = 0;
break;
case Placement.TOP:
case Placement.CENTER:
case Placement.BOTTOM:
xFraction = 0.5;
break;
case Placement.TOP_RIGHT:
case Placement.RIGHT:
case Placement.BOTTOM_RIGHT:
xFraction = 1;
break;
default:
throw new Error("Unknown placement given");
}
// Set yFraction
switch (placement) {
case Placement.TOP_LEFT:
case Placement.TOP:
case Placement.TOP_RIGHT:
yFraction = 0;
break;
case Placement.LEFT:
case Placement.CENTER:
case Placement.RIGHT:
yFraction = 0.5;
break;
case Placement.BOTTOM_LEFT:
case Placement.BOTTOM:
case Placement.BOTTOM_RIGHT:
yFraction = 1;
break;
default:
throw new Error("Unknown placement given");
}
return new Graphene.Point({ x: xFraction, y: yFraction });
}
/**
* Sets the pivot point placement, relative to the preview.
*
* @param {Placement} placement
* @return {void}
*/
set_pivot_point_placement(placement) {
let point = this.get_pivot_point_placement(placement);
this.set_pivot_point(point.x, point.y);
}
});
export function findUpperLeftFromCenter(sideSize, position) {
return position - sideSize / 2;
}

View File

@@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/org/gnome/shell/extensions/coverflowalttab/" id="org.gnome.shell.extensions.coverflowalttab">
<key type="b" name="hide-panel">
<default>true</default>
<summary>Hide the panel when showing coverflow</summary>
<description>Whether to show or hide the panel when CoverflowAltTab is active.</description>
</key>
<key type="b" name="enforce-primary-monitor">
<default>false</default>
<summary>Always show the switcher on the primary monitor</summary>
<description>Show the switcher on the primary monitor instead of detecting the active monitor.</description>
</key>
<key type="d" name="animation-time">
<range min="0.01" max="60"/>
<default>0.2</default>
<summary>The duration of coverflow animations in ms</summary>
<description>Define the duration of the animations.</description>
</key>
<key type="d" name="dim-factor">
<range min="0" max="1"/>
<default>1</default>
<summary>Dim factor for background</summary>
<description>Define dimming of the background. Bigger means darker. </description>
</key>
<key type="s" name="position">
<choices>
<choice value="Top"/>
<choice value="Bottom"/>
</choices>
<default>"Bottom"</default>
<summary>Position of icon and window title</summary>
<description>Whether the icon and window title should be placed below or above the window preview.</description>
</key>
<key type="i" name="offset">
<range min="-500" max="500"/>
<default>0</default>
<summary>Set a vertical offset</summary>
<description>Set a vertical offset. Positive values move the whole Coverflow up, negative down.</description>
</key>
<key type="s" name="icon-style">
<choices>
<choice value="Classic"/>
<choice value="Overlay"/>
<choice value="Attached"/>
</choices>
<default>"Classic"</default>
<summary>Icon style</summary>
<description>Whether the application icon should be displayed next to the title label or as an overlay.</description>
</key>
<key type="d" name="overlay-icon-opacity">
<range min="0" max="1"/>
<default>1</default>
<summary>The opacity of the overlay icon</summary>
<description>Define the opacity of the overlay icon.</description>
</key>
<key type="d" name="overlay-icon-size">
<range min="16" max="1024"/>
<default>128</default>
<summary>The icon size.</summary>
<description>Size in pixels.</description>
</key>
<key type="s" name="switcher-style">
<choices>
<choice value="Coverflow"/>
<choice value="Timeline"/>
</choices>
<default>"Coverflow"</default>
<summary>Switcher style</summary>
<description>The switcher display style.</description>
</key>
<key type="s" name="easing-function">
<choices>
<choice value="ease-linear"/>
<choice value="ease-in-quad"/>
<choice value="ease-out-quad"/>
<choice value="ease-in-out-quad"/>
<choice value="ease-in-cubic"/>
<choice value="ease-out-cubic"/>
<choice value="ease-in-out-cubic"/>
<choice value="ease-in-quart"/>
<choice value="ease-out-quart"/>
<choice value="ease-in-out-quart"/>
<choice value="ease-in-quint"/>
<choice value="ease-out-quint"/>
<choice value="ease-in-out-quint"/>
<choice value="ease-in-sine"/>
<choice value="ease-out-sine"/>
<choice value="ease-in-out-sine"/>
<choice value="ease-in-expo"/>
<choice value="ease-out-expo"/>
<choice value="ease-in-out-expo"/>
<choice value="ease-in-circ"/>
<choice value="ease-out-circ"/>
<choice value="ease-in-out-circ"/>
<choice value="ease-in-back"/>
<choice value="ease-out-back"/>
<choice value="ease-in-out-back"/>
<choice value="ease-in-elastic"/>
<choice value="ease-out-elastic"/>
<choice value="ease-in-out-elastic"/>
<choice value="ease-in-bounce"/>
<choice value="ease-out-bounce"/>
<choice value="ease-in-out-bounce"/>
<choice value="random"/>
</choices>
<default>"ease-out-cubic"</default>
<summary>Easing function used in animations</summary>
<description>Use this easing function for animations.</description>
</key>
<key type="s" name="current-workspace-only">
<choices>
<choice value="current"/>
<choice value="all"/>
<choice value="all-currentfirst"/>
</choices>
<default>'current'</default>
<summary>Show windows from current workspace only</summary>
<description>Whether to show all windows or windows from current workspace only.</description>
</key>
<key type="b" name="switch-per-monitor">
<default>false</default>
<summary>Per monitor window switch</summary>
<description>Switch between windows on current monitor (monitor with the mouse cursor)</description>
</key>
<key type="b" name="icon-has-shadow">
<default>false</default>
<summary>Icon has shadow switch</summary>
<description>Whether or not the icons have a drop shadow.</description>
</key>
<key type="b" name="randomize-animation-times">
<default>false</default>
<summary>Randomize animation times switch</summary>
<description>Whether or not to have each animation take a different, randomized time to complete.</description>
</key>
<key type="d" name="preview-to-monitor-ratio">
<range min="0" max="1"/>
<default>0.5</default>
<summary>The maximum ratio of the preview dimensions with the monitor dimensions</summary>
<description>Define the ratio of the preview to monitor sizes.</description>
</key>
<key type="d" name="preview-scaling-factor">
<range min="0" max="1"/>
<default>0.8</default>
<summary>In Coverflow Switcher, scales the previews as they spread out to the sides in </summary>
<description>Define the scale factor successively applied each step away from the current preview.</description>
</key>
<key type="b" name="bind-to-switch-applications">
<default>true</default>
<summary>Bind to 'switch-applications' keybinding</summary>
<description>Whether or not to bind to the 'switch-applications' keybinding.</description>
</key>
<key type="b" name="bind-to-switch-windows">
<default>true</default>
<summary>Bind to 'switch-windows' keybinding</summary>
<description>Whether or not to bind to the 'switch-windows' keybinding.</description>
</key>
<key type="b" name="highlight-mouse-over">
<default>false</default>
<summary>Highlight window under mouse</summary>
<description>Whether or not to draw some flare on the window under the mouse so you know.</description>
</key>
<key type="b" name="raise-mouse-over">
<default>true</default>
<summary>Raise window under mouse</summary>
<description>Whether or not to raise the window under the mouse above others.</description>
</key>
<key type="s" name="perspective-correction-method">
<choices>
<choice value="None"/>
<choice value="Move Camera"/>
<choice value="Adjust Angles"/>
</choices>
<default>"Move Camera"</default>
<summary>Method to correct off-center monitor perspective</summary>
<description>Way to make off-center monitor switcher to look like centered.</description>
</key>
<key type="d" name="desaturate-factor">
<default>0.0</default>
<range min="0" max="1"/>
<summary>Amount to Desaturate the Background Application Switcher</summary>
<description>0 for no desaturation, 1 for total desaturation.</description>
</key>
<key type="i" name="blur-radius">
<default>0</default>
<range min="0" max="20"/>
<summary>Radius of Blur Applied to the Background Application Switcher</summary>
<description>The bigger, the blurrier.</description>
</key>
<key type="s" name="switcher-looping-method">
<choices>
<choice value="Flip Stack"/>
<choice value="Carousel"/>
</choices>
<default>"Flip Stack"</default>
<summary>How the windows cycle through the coverflow</summary>
</key>
<key type="b" name="switch-application-behaves-like-switch-windows">
<default>false</default>
<summary>The application-switcher keybinding action behaves the same as the window-switcher</summary>
</key>
<key type="b" name="use-tint">
<default>true</default>
<summary>Whether to Use a Tint Color on the Background Application Switcher</summary>
</key>
<key type="(ddd)" name="tint-color">
<default>(0.,0.,0.)</default>
<summary></summary>
</key>
<key type="(ddd)" name="switcher-background-color">
<default>(0.,0.,0.)</default>
<summary></summary>
</key>
<key type="d" name="tint-blend">
<default>0.0</default>
<range min="0" max="1"/>
<summary>Amount to Blend Tint Color</summary>
</key>
<key type="b" name="use-glitch-effect">
<default>false</default>
<summary>Use a "glitch effect" on the background application switcher</summary>
</key>
<key type="b" name="invert-swipes">
<default>false</default>
<summary>Swipe content instead of view</summary>
</key>
</schema>
</schemalist>

View File

@@ -0,0 +1,786 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SwipeTracker */
import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import Mtk from 'gi://Mtk';
import GObject from 'gi://GObject';
import * as Params from 'resource:///org/gnome/shell/misc/params.js';
// FIXME: ideally these values matches physical touchpad size. We can get the
// correct values for gnome-shell specifically, since mutter uses libinput
// directly, but GTK apps cannot get it, so use an arbitrary value so that
// it's consistent with apps.
const TOUCHPAD_BASE_HEIGHT = 300;
const TOUCHPAD_BASE_WIDTH = 400;
const EVENT_HISTORY_THRESHOLD_MS = 150;
const SCROLL_MULTIPLIER = 10;
const MIN_ANIMATION_DURATION = 100;
const MAX_ANIMATION_DURATION = 400;
const VELOCITY_THRESHOLD_TOUCH = 0.3;
const VELOCITY_THRESHOLD_TOUCHPAD = 0.6;
const DECELERATION_TOUCH = 0.998;
const DECELERATION_TOUCHPAD = 0.997;
const VELOCITY_CURVE_THRESHOLD = 2;
const DECELERATION_PARABOLA_MULTIPLIER = 0.35;
const DRAG_THRESHOLD_DISTANCE = 16;
// Derivative of easeOutCubic at t=0
const DURATION_MULTIPLIER = 3;
const ANIMATION_BASE_VELOCITY = 0.002;
const EPSILON = 0.005;
const GESTURE_FINGER_COUNT = 3;
const State = {
NONE: 0,
SCROLLING: 1,
};
const TouchpadState = {
NONE: 0,
PENDING: 1,
HANDLING: 2,
IGNORED: 3,
};
const EventHistory = class {
constructor() {
this.reset();
}
reset() {
this._data = [];
}
trim(time) {
const thresholdTime = time - EVENT_HISTORY_THRESHOLD_MS;
const index = this._data.findIndex(r => r.time >= thresholdTime);
this._data.splice(0, index);
}
append(time, delta) {
this.trim(time);
this._data.push({ time, delta });
}
calculateVelocity() {
if (this._data.length < 2)
return 0;
const firstTime = this._data[0].time;
const lastTime = this._data[this._data.length - 1].time;
if (firstTime === lastTime)
return 0;
const totalDelta = this._data.slice(1).map(a => a.delta).reduce((a, b) => a + b);
const period = lastTime - firstTime;
return totalDelta / period;
}
};
const TouchpadSwipeGesture = GObject.registerClass({
Properties: {
'enabled': GObject.ParamSpec.boolean(
'enabled', 'enabled', 'enabled',
GObject.ParamFlags.READWRITE,
true),
'orientation': GObject.ParamSpec.enum(
'orientation', 'orientation', 'orientation',
GObject.ParamFlags.READWRITE,
Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
},
Signals: {
'begin': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'update': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'end': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
},
}, class TouchpadSwipeGesture extends GObject.Object {
_init(allowedModes) {
super._init();
this._allowedModes = allowedModes;
this._state = TouchpadState.NONE;
this._cumulativeX = 0;
this._cumulativeY = 0;
this._touchpadSettings = new Gio.Settings({
schema_id: 'org.gnome.desktop.peripherals.touchpad',
});
global.stage.connectObject(
'captured-event::touchpad', this._handleEvent.bind(this), this);
}
_handleEvent(actor, event) {
if (event.type() !== Clutter.EventType.TOUCHPAD_SWIPE)
return Clutter.EVENT_PROPAGATE;
if (event.get_gesture_phase() === Clutter.TouchpadGesturePhase.BEGIN)
this._state = TouchpadState.NONE;
if (event.get_touchpad_gesture_finger_count() !== GESTURE_FINGER_COUNT)
return Clutter.EVENT_PROPAGATE;
/* if ((this._allowedModes & Main.actionMode) === 0)
return Clutter.EVENT_PROPAGATE; */
if (!this.enabled)
return Clutter.EVENT_PROPAGATE;
if (this._state === TouchpadState.IGNORED)
return Clutter.EVENT_PROPAGATE;
let time = event.get_time();
const [x, y] = event.get_coords();
const [dx, dy] = event.get_gesture_motion_delta_unaccelerated();
if (this._state === TouchpadState.NONE) {
if (dx === 0 && dy === 0)
return Clutter.EVENT_PROPAGATE;
this._cumulativeX = 0;
this._cumulativeY = 0;
this._state = TouchpadState.PENDING;
}
if (this._state === TouchpadState.PENDING) {
this._cumulativeX += dx;
this._cumulativeY += dy;
const cdx = this._cumulativeX;
const cdy = this._cumulativeY;
const distance = Math.sqrt(cdx * cdx + cdy * cdy);
if (distance >= DRAG_THRESHOLD_DISTANCE) {
const gestureOrientation = Math.abs(cdx) > Math.abs(cdy)
? Clutter.Orientation.HORIZONTAL
: Clutter.Orientation.VERTICAL;
this._cumulativeX = 0;
this._cumulativeY = 0;
if (gestureOrientation === this.orientation) {
this._state = TouchpadState.HANDLING;
this.emit('begin', time, x, y);
} else {
this._state = TouchpadState.IGNORED;
return Clutter.EVENT_PROPAGATE;
}
} else {
return Clutter.EVENT_PROPAGATE;
}
}
const vertical = this.orientation === Clutter.Orientation.VERTICAL;
let delta = vertical ? dy : dx;
const distance = vertical ? TOUCHPAD_BASE_HEIGHT : TOUCHPAD_BASE_WIDTH;
switch (event.get_gesture_phase()) {
case Clutter.TouchpadGesturePhase.BEGIN:
case Clutter.TouchpadGesturePhase.UPDATE:
if (this._touchpadSettings.get_boolean('natural-scroll'))
delta = -delta;
this.emit('update', time, delta, distance);
break;
case Clutter.TouchpadGesturePhase.END:
case Clutter.TouchpadGesturePhase.CANCEL:
this.emit('end', time, distance);
this._state = TouchpadState.NONE;
break;
}
return this._state === TouchpadState.HANDLING
? Clutter.EVENT_STOP
: Clutter.EVENT_PROPAGATE;
}
destroy() {
global.stage.disconnectObject(this);
}
});
const TouchSwipeGesture = GObject.registerClass({
Properties: {
'distance': GObject.ParamSpec.double(
'distance', 'distance', 'distance',
GObject.ParamFlags.READWRITE,
0, Infinity, 0),
'orientation': GObject.ParamSpec.enum(
'orientation', 'orientation', 'orientation',
GObject.ParamFlags.READWRITE,
Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
},
Signals: {
'begin': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'update': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'end': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
'cancel': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
},
}, class TouchSwipeGesture extends Clutter.GestureAction {
_init(allowedModes, nTouchPoints, thresholdTriggerEdge) {
super._init();
this.set_n_touch_points(nTouchPoints);
this.set_threshold_trigger_edge(thresholdTriggerEdge);
this._allowedModes = allowedModes;
this._distance = global.screen_height;
this._lastPosition = 0;
}
get distance() {
return this._distance;
}
set distance(distance) {
if (this._distance === distance)
return;
this._distance = distance;
this.notify('distance');
}
vfunc_gesture_prepare(actor) {
if (!super.vfunc_gesture_prepare(actor))
return false;
/* if ((this._allowedModes & Main.actionMode) === 0)
return false; */
let time = this.get_last_event(0).get_time();
let [xPress, yPress] = this.get_press_coords(0);
let [x, y] = this.get_motion_coords(0);
const [xDelta, yDelta] = [x - xPress, y - yPress];
const swipeOrientation = Math.abs(xDelta) > Math.abs(yDelta)
? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL;
if (swipeOrientation !== this.orientation)
return false;
this._lastPosition =
this.orientation === Clutter.Orientation.VERTICAL ? y : x;
this.emit('begin', time, xPress, yPress);
return true;
}
vfunc_gesture_progress(_actor) {
let [x, y] = this.get_motion_coords(0);
let pos = this.orientation === Clutter.Orientation.VERTICAL ? y : x;
let delta = pos - this._lastPosition;
this._lastPosition = pos;
let time = this.get_last_event(0).get_time();
this.emit('update', time, -delta, this._distance);
return true;
}
vfunc_gesture_end(_actor) {
let time = this.get_last_event(0).get_time();
this.emit('end', time, this._distance);
}
vfunc_gesture_cancel(_actor) {
let time = Clutter.get_current_event_time();
this.emit('cancel', time, this._distance);
}
});
const ScrollGesture = GObject.registerClass({
Properties: {
'enabled': GObject.ParamSpec.boolean(
'enabled', 'enabled', 'enabled',
GObject.ParamFlags.READWRITE,
true),
'orientation': GObject.ParamSpec.enum(
'orientation', 'orientation', 'orientation',
GObject.ParamFlags.READWRITE,
Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
'scroll-modifiers': GObject.ParamSpec.flags(
'scroll-modifiers', 'scroll-modifiers', 'scroll-modifiers',
GObject.ParamFlags.READWRITE,
Clutter.ModifierType, 0),
},
Signals: {
'begin': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'update': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
'end': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
},
}, class ScrollGesture extends GObject.Object {
_init(actor, allowedModes, inverted=false) {
super._init();
this._inverted = inverted;
this._allowedModes = allowedModes;
this._began = false;
this._enabled = true;
actor.connect('scroll-event', this._handleEvent.bind(this));
}
get enabled() {
return this._enabled;
}
set enabled(enabled) {
if (this._enabled === enabled)
return;
this._enabled = enabled;
this._began = false;
this.notify('enabled');
}
canHandleEvent(event) {
if (event.type() !== Clutter.EventType.SCROLL)
return false;
if (event.get_scroll_source() !== Clutter.ScrollSource.FINGER &&
event.get_source_device().get_device_type() !== Clutter.InputDeviceType.TOUCHPAD_DEVICE)
return false;
if (!this.enabled)
return false;
if (!this._began && this.scrollModifiers !== 0 &&
(event.get_state() & this.scrollModifiers) === 0)
return false;
return true;
}
_handleEvent(actor, event) {
if (!this.canHandleEvent(event))
return Clutter.EVENT_PROPAGATE;
if (event.get_scroll_direction() !== Clutter.ScrollDirection.SMOOTH)
return Clutter.EVENT_PROPAGATE;
const vertical = this.orientation === Clutter.Orientation.VERTICAL;
const distance = vertical ? TOUCHPAD_BASE_HEIGHT : TOUCHPAD_BASE_WIDTH;
let time = event.get_time();
let [dx, dy] = event.get_scroll_delta();
if (this._inverted) {
dx = -dx; dy = -dy;
}
if (dx === 0 && dy === 0) {
this.emit('end', time, distance);
this._began = false;
return Clutter.EVENT_STOP;
}
if (!this._began) {
let [x, y] = event.get_coords();
this.emit('begin', time, x, y);
this._began = true;
}
const delta = (vertical ? dy : dx) * SCROLL_MULTIPLIER;
this.emit('update', time, delta, distance);
return Clutter.EVENT_STOP;
}
});
// USAGE:
//
// To correctly implement the gesture, there must be handlers for the following
// signals:
//
// begin(tracker, monitor)
// The handler should check whether a deceleration animation is currently
// running. If it is, it should stop the animation (without resetting
// progress). Then it should call:
// tracker.confirmSwipe(distance, snapPoints, currentProgress, cancelProgress)
// If it's not called, the swipe would be ignored.
// The parameters are:
// * distance: the page size;
// * snapPoints: an (sorted with ascending order) array of snap points;
// * currentProgress: the current progress;
// * cancelprogress: a non-transient value that would be used if the gesture
// is cancelled.
// If no animation was running, currentProgress and cancelProgress should be
// same. The handler may set 'orientation' property here.
//
// update(tracker, progress)
// The handler should set the progress to the given value.
//
// end(tracker, duration, endProgress)
// The handler should animate the progress to endProgress. If endProgress is
// 0, it should do nothing after the animation, otherwise it should change the
// state, e.g. change the current page or switch workspace.
// NOTE: duration can be 0 in some cases, in this case it should finish
// instantly.
/** A class for handling swipe gestures */
export const MySwipeTracker = GObject.registerClass({
Properties: {
'enabled': GObject.ParamSpec.boolean(
'enabled', 'enabled', 'enabled',
GObject.ParamFlags.READWRITE,
true),
'orientation': GObject.ParamSpec.enum(
'orientation', 'orientation', 'orientation',
GObject.ParamFlags.READWRITE,
Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
'distance': GObject.ParamSpec.double(
'distance', 'distance', 'distance',
GObject.ParamFlags.READWRITE,
0, Infinity, 0),
'allow-long-swipes': GObject.ParamSpec.boolean(
'allow-long-swipes', 'allow-long-swipes', 'allow-long-swipes',
GObject.ParamFlags.READWRITE,
false),
'scroll-modifiers': GObject.ParamSpec.flags(
'scroll-modifiers', 'scroll-modifiers', 'scroll-modifiers',
GObject.ParamFlags.READWRITE,
Clutter.ModifierType, 0),
},
Signals: {
'begin': { param_types: [GObject.TYPE_UINT] },
'update': { param_types: [GObject.TYPE_DOUBLE] },
'end': { param_types: [GObject.TYPE_UINT64, GObject.TYPE_DOUBLE] },
},
}, class MySwipeTracker extends GObject.Object {
_init(actor, orientation, allowedModes, params, inverted=false) {
super._init();
params = Params.parse(params, { allowDrag: true, allowScroll: true });
this.orientation = orientation;
this._inverted = inverted;
this._allowedModes = allowedModes;
this._enabled = true;
this._distance = global.screen_height;
this._history = new EventHistory();
this._reset();
this._touchpadGesture = new TouchpadSwipeGesture(allowedModes);
this._touchpadGesture.connect('begin', this._beginGesture.bind(this));
this._touchpadGesture.connect('update', this._updateGesture.bind(this));
this._touchpadGesture.connect('end', this._endTouchpadGesture.bind(this));
this.bind_property('enabled', this._touchpadGesture, 'enabled', 0);
this.bind_property('orientation', this._touchpadGesture, 'orientation',
GObject.BindingFlags.SYNC_CREATE);
this._touchGesture = new TouchSwipeGesture(allowedModes,
GESTURE_FINGER_COUNT,
Clutter.GestureTriggerEdge.AFTER);
this._touchGesture.connect('begin', this._beginTouchSwipe.bind(this));
this._touchGesture.connect('update', this._updateGesture.bind(this));
this._touchGesture.connect('end', this._endTouchGesture.bind(this));
this._touchGesture.connect('cancel', this._cancelTouchGesture.bind(this));
this.bind_property('enabled', this._touchGesture, 'enabled', 0);
this.bind_property('orientation', this._touchGesture, 'orientation',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('distance', this._touchGesture, 'distance', 0);
global.stage.add_action_full('swipe', Clutter.EventPhase.CAPTURE, this._touchGesture);
if (params.allowDrag) {
this._dragGesture = new TouchSwipeGesture(allowedModes, 1,
Clutter.GestureTriggerEdge.AFTER);
this._dragGesture.connect('begin', this._beginGesture.bind(this));
this._dragGesture.connect('update', this._updateGesture.bind(this));
this._dragGesture.connect('end', this._endTouchGesture.bind(this));
this._dragGesture.connect('cancel', this._cancelTouchGesture.bind(this));
this.bind_property('enabled', this._dragGesture, 'enabled', 0);
this.bind_property('orientation', this._dragGesture, 'orientation',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('distance', this._dragGesture, 'distance', 0);
actor.add_action_full('drag', Clutter.EventPhase.CAPTURE, this._dragGesture);
} else {
this._dragGesture = null;
}
if (params.allowScroll) {
this._scrollGesture = new ScrollGesture(actor, allowedModes, this._inverted);
this._scrollGesture.connect('begin', this._beginGesture.bind(this));
this._scrollGesture.connect('update', this._updateGesture.bind(this));
this._scrollGesture.connect('end', this._endTouchpadGesture.bind(this));
this.bind_property('enabled', this._scrollGesture, 'enabled', 0);
this.bind_property('orientation', this._scrollGesture, 'orientation',
GObject.BindingFlags.SYNC_CREATE);
this.bind_property('scroll-modifiers',
this._scrollGesture, 'scroll-modifiers', 0);
} else {
this._scrollGesture = null;
}
}
/**
* canHandleScrollEvent:
* @param {Clutter.Event} scrollEvent: an event to check
* @returns {bool} whether the event can be handled by the tracker
*
* This function can be used to combine swipe gesture and mouse
* scrolling.
*/
canHandleScrollEvent(scrollEvent) {
if (!this.enabled || this._scrollGesture === null) {
return false;
}
return this._scrollGesture.canHandleEvent(scrollEvent);
}
get enabled() {
return this._enabled;
}
set enabled(enabled) {
if (this._enabled === enabled)
return;
this._enabled = enabled;
if (!enabled && this._state === State.SCROLLING)
this._interrupt();
this.notify('enabled');
}
get distance() {
return this._distance;
}
set distance(distance) {
if (this._distance === distance)
return;
this._distance = distance;
this.notify('distance');
}
_reset() {
this._state = State.NONE;
this._snapPoints = [];
this._initialProgress = 0;
this._cancelProgress = 0;
this._prevOffset = 0;
this._progress = 0;
this._cancelled = false;
this._history.reset();
}
_interrupt() {
this.emit('end', 0, this._cancelProgress);
this._reset();
}
_beginTouchSwipe(gesture, time, x, y) {
if (this._dragGesture)
this._dragGesture.cancel();
this._beginGesture(gesture, time, x, y);
}
_beginGesture(gesture, time, x, y) {
if (this._state === State.SCROLLING)
return;
this._history.append(time, 0);
let rect = new Mtk.Rectangle({ x, y, width: 1, height: 1 });
let monitor = global.display.get_monitor_index_for_rect(rect);
this.emit('begin', monitor);
}
_findClosestPoint(pos) {
const distances = this._snapPoints.map(x => Math.abs(x - pos));
const min = Math.min(...distances);
return distances.indexOf(min);
}
_findNextPoint(pos) {
return this._snapPoints.findIndex(p => p >= pos);
}
_findPreviousPoint(pos) {
const reversedIndex = this._snapPoints.slice().reverse().findIndex(p => p <= pos);
return this._snapPoints.length - 1 - reversedIndex;
}
_findPointForProjection(pos, velocity) {
const initial = this._findClosestPoint(this._initialProgress);
const prev = this._findPreviousPoint(pos);
const next = this._findNextPoint(pos);
if ((velocity > 0 ? prev : next) === initial)
return velocity > 0 ? next : prev;
return this._findClosestPoint(pos);
}
_getBounds(pos) {
if (this.allowLongSwipes)
return [this._snapPoints[0], this._snapPoints[this._snapPoints.length - 1]];
const closest = this._findClosestPoint(pos);
let prev, next;
if (Math.abs(this._snapPoints[closest] - pos) < EPSILON) {
prev = next = closest;
} else {
prev = this._findPreviousPoint(pos);
next = this._findNextPoint(pos);
}
const lowerIndex = Math.max(prev - 1, 0);
const upperIndex = Math.min(next + 1, this._snapPoints.length - 1);
return [this._snapPoints[lowerIndex], this._snapPoints[upperIndex]];
}
_updateGesture(gesture, time, delta, distance) {
if (this._state !== State.SCROLLING)
return;
if (!this.enabled) {
this._interrupt();
return;
}
if (this.orientation === Clutter.Orientation.HORIZONTAL &&
Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
delta = -delta;
this._progress += delta / distance;
this._history.append(time, delta);
this._progress = Math.clamp(this._progress, ...this._getBounds(this._initialProgress));
this.emit('update', this._progress);
}
_getEndProgress(velocity, distance, isTouchpad) {
if (this._cancelled)
return this._cancelProgress;
const threshold = isTouchpad ? VELOCITY_THRESHOLD_TOUCHPAD : VELOCITY_THRESHOLD_TOUCH;
if (Math.abs(velocity) < threshold)
return this._snapPoints[this._findClosestPoint(this._progress)];
const decel = isTouchpad ? DECELERATION_TOUCHPAD : DECELERATION_TOUCH;
const slope = decel / (1.0 - decel) / 1000.0;
let pos;
if (Math.abs(velocity) > VELOCITY_CURVE_THRESHOLD) {
const c = slope / 2 / DECELERATION_PARABOLA_MULTIPLIER;
const x = Math.abs(velocity) - VELOCITY_CURVE_THRESHOLD + c;
pos = slope * VELOCITY_CURVE_THRESHOLD +
DECELERATION_PARABOLA_MULTIPLIER * x * x -
DECELERATION_PARABOLA_MULTIPLIER * c * c;
} else {
pos = Math.abs(velocity) * slope;
}
pos = pos * Math.sign(velocity) + this._progress;
pos = Math.clamp(pos, ...this._getBounds(this._initialProgress));
const index = this._findPointForProjection(pos, velocity);
return this._snapPoints[index];
}
_endTouchGesture(_gesture, time, distance) {
this._endGesture(time, distance, false);
}
_endTouchpadGesture(_gesture, time, distance) {
this._endGesture(time, distance, true);
}
_endGesture(time, distance, isTouchpad) {
if (this._state !== State.SCROLLING)
return;
/* if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
this._interrupt();
return;
} */
this._history.trim(time);
let velocity = this._history.calculateVelocity();
const endProgress = this._getEndProgress(velocity, distance, isTouchpad);
velocity /= distance;
if ((endProgress - this._progress) * velocity <= 0)
velocity = ANIMATION_BASE_VELOCITY;
const nPoints = Math.max(1, Math.ceil(Math.abs(this._progress - endProgress)));
const maxDuration = MAX_ANIMATION_DURATION * Math.log2(1 + nPoints);
let duration = Math.abs((this._progress - endProgress) / velocity * DURATION_MULTIPLIER);
if (duration > 0)
duration = Math.clamp(duration, MIN_ANIMATION_DURATION, maxDuration);
this._reset();
this.emit('end', duration, endProgress);
}
_cancelTouchGesture(_gesture, time, distance) {
if (this._state !== State.SCROLLING)
return;
this._cancelled = true;
this._endGesture(time, distance, false);
}
/**
* confirmSwipe:
* @param {number} distance: swipe distance in pixels
* @param {number[]} snapPoints:
* An array of snap points, sorted in ascending order
* @param {number} currentProgress: initial progress value
* @param {number} cancelProgress: the value to be used on cancelling
*
* Confirms a swipe. User has to call this in 'begin' signal handler,
* otherwise the swipe wouldn't start. If there's an animation running,
* it should be stopped first.
*
* @cancel_progress must always be a snap point, or a value matching
* some other non-transient state.
*/
confirmSwipe(distance, snapPoints, currentProgress, cancelProgress) {
this.distance = distance;
this._snapPoints = snapPoints;
this._initialProgress = currentProgress;
this._progress = currentProgress;
this._cancelProgress = cancelProgress;
this._state = State.SCROLLING;
}
destroy() {
if (this._touchpadGesture) {
this._touchpadGesture.destroy();
delete this._touchpadGesture;
}
if (this._touchGesture) {
global.stage.remove_action(this._touchGesture);
delete this._touchGesture;
}
}
});

View File

@@ -0,0 +1,293 @@
/*
This file is part of CoverflowAltTab.
CoverflowAltTab is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CoverflowAltTab is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CoverflowAltTab. If not, see <http://www.gnu.org/licenses/>.
*/
/* CoverflowAltTab::TimelineSwitcher:
*
* Extends CoverflowAltTab::Switcher, switching tabs using a timeline
*/
import {Switcher} from './switcher.js';
import {Preview, Placement, findUpperLeftFromCenter} from './preview.js'
let TRANSITION_TYPE;
let IN_BOUNDS_TRANSITION_TYPE;
const TILT_ANGLE = 45;
export class TimelineSwitcher extends Switcher {
constructor(...args) {
super(...args);
TRANSITION_TYPE = 'userChoice';
IN_BOUNDS_TRANSITION_TYPE = 'easeInOutQuint';
}
_createPreviews() {
let monitor = this._updateActiveMonitor();
let currentWorkspace = this._manager.workspace_manager.get_active_workspace();
this._previewsCenterPosition = {
x: this.actor.width / 2,
y: this.actor.height / 2 + this._settings.offset
};
for (let windowActor of global.get_window_actors()) {
let metaWin = windowActor.get_meta_window();
let compositor = metaWin.get_compositor_private();
if (compositor) {
let texture = compositor.get_texture();
let width, height;
if (texture.get_size) {
[width, height] = texture.get_size()
} else {
let preferred_size_ok;
[preferred_size_ok, width, height] = texture.get_preferred_size();
}
let previewScale = this._settings.preview_to_monitor_ratio;
let scale = 1.0;
let previewWidth = this.actor.width * previewScale;
let previewHeight = this.actor.height * previewScale;
if (width > previewWidth || height > previewHeight)
scale = Math.min(previewWidth / width, previewHeight / height);
let preview = new Preview(metaWin, this, {
opacity: (!metaWin.minimized && metaWin.get_workspace() == currentWorkspace || metaWin.is_on_all_workspaces()) ? 255: 0,
source: texture.get_size ? texture : compositor,
reactive: true,
name: metaWin.title,
x: (metaWin.minimized ? -(compositor.x + compositor.width / 2) :
compositor.x) - monitor.x,
y: (metaWin.minimized ? -(compositor.y + compositor.height / 2) :
compositor.y) - monitor.y,
rotation_angle_y: 0,
width: width,
height: height,
});
preview.scale = scale;
preview.target_x = findUpperLeftFromCenter(preview.width * preview.scale,
this._previewsCenterPosition.x);
preview.target_y = findUpperLeftFromCenter(preview.height,
this._previewsCenterPosition.y);
preview.set_pivot_point_placement(Placement.LEFT);
if (this._windows.includes(metaWin)) {
this._previews[this._windows.indexOf(metaWin)] = preview;
}
this._allPreviews.push(preview);
this.previewActor.add_child(preview);
preview.make_bottom_layer(this.previewActor);
}
}
}
_previewNext() {
this._setCurrentIndex((this._currentIndex + 1) % this._windows.length);
this._updatePreviews(false, 1);
}
_previewPrevious() {
this._setCurrentIndex((this._windows.length + this._currentIndex - 1) % this._windows.length);
this._updatePreviews(false, -1);
}
_updatePreviews(reorder_only=false, direction=0) {
if (this._previews == null || this._previews.length == 0)
return;
let animation_time = this._settings.animation_time * (this._settings.randomize_animation_times ? this._getRandomArbitrary(0.25, 1) : 1);
if (this._previews.length == 1) {
if (reorder_only) return;
let preview = this._previews[0];
this._manager.platform.tween(preview, {
x: preview.target_x,
y: preview.target_y,
scale_x: preview.scale,
scale_y: preview.scale,
scale_z: preview.scale,
time: animation_time / 2,
transition: TRANSITION_TYPE,
rotation_angle_y: TILT_ANGLE,
});
this._manager.platform.tween(preview, {
opacity: 255,
time: animation_time / 2,
transition: IN_BOUNDS_TRANSITION_TYPE,
onComplete: () => {
preview.set_reactive(true);
}
});
return;
}
for (let i = this._currentIndex; i < this._currentIndex + this._previews.length; i++) {
this._previews[i%this._previews.length].make_bottom_layer(this.previewActor);
}
if (reorder_only) return;
// preview windows
for (let [i, preview] of this._previews.entries()) {
animation_time = this._settings.animation_time * (this._settings.randomize_animation_times ? this._getRandomArbitrary(0.0001, 1) : 1);
let distance = (this._currentIndex > i) ? this._previews.length - this._currentIndex + i : i - this._currentIndex;
if (distance === this._previews.length - 1 && direction > 0) {
preview.__looping = true;
animation_time = this._settings.animation_time;
preview.make_top_layer(this.previewActor);
this._raiseIcons();
let scale = preview.scale * Math.pow(this._settings.preview_scaling_factor, -1);
this._manager.platform.tween(preview, {
x: preview.target_x + 150,
y: preview.target_y + 100,
time: animation_time / 2,
transition: TRANSITION_TYPE,
rotation_angle_y: TILT_ANGLE,
onCompleteParams: [preview, distance, animation_time],
onComplete: this._onFadeForwardComplete,
onCompleteScope: this,
});
this._manager.platform.tween(preview, {
opacity: 0,
scale_x: scale,
scale_y: scale,
scale_z: scale,
time: animation_time / 2,
transition: IN_BOUNDS_TRANSITION_TYPE,
});
} else if (distance === 0 && direction < 0) {
preview.__looping = true;
animation_time = this._settings.animation_time;
let scale = preview.scale * Math.pow(this._settings.preview_scaling_factor, this._previews.length);
preview.make_bottom_layer(this.previewActor);
this._manager.platform.tween(preview, {
time: animation_time / 2,
x: preview.target_x - Math.sqrt(this._previews.length) * 150,
y: preview.target_y - Math.sqrt(this._previews.length) * 100,
transition: TRANSITION_TYPE,
rotation_angle_y: TILT_ANGLE,
onCompleteParams: [preview, distance, animation_time],
onComplete: this._onFadeBackwardsComplete,
onCompleteScope: this,
});
this._manager.platform.tween(preview, {
time: animation_time / 2,
transition: IN_BOUNDS_TRANSITION_TYPE,
scale_x: scale,
scale_y: scale,
scale_x: scale,
opacity: 0,
});
} else {
let scale = preview.scale * Math.pow(this._settings.preview_scaling_factor, distance);//Math.max(preview.scale * ((20 - 2 * distance) / 20), 0);
let tweenparams = {
x: preview.target_x - Math.sqrt(distance) * 150,
y: preview.target_y - Math.sqrt(distance) * 100,
scale_x: scale,
scale_y: scale,
scale_z: scale,
time: animation_time,
rotation_angle_y: TILT_ANGLE,
transition: TRANSITION_TYPE,
onComplete: () => { preview.set_reactive(true); },
};
let opacitytweenparams = {
opacity: 255,
time: animation_time,
transition: IN_BOUNDS_TRANSITION_TYPE,
};
if (preview.__looping || preview.__finalTween)
preview.__finalTween = [tweenparams, opacitytweenparams];
else
this._manager.platform.tween(preview, tweenparams);
this._manager.platform.tween(preview, opacitytweenparams);
}
}
}
_onFadeBackwardsComplete(preview, distance, animation_time) {
preview.__looping = false;
preview.make_top_layer(this.previewActor);
this._raiseIcons();
preview.x = preview.target_x + 150;
preview.y = preview.target_y + 100;
let scale_start = preview.scale * Math.pow(this._settings.preview_scaling_factor, -1);
preview.scale_x = scale_start;
preview.scale_y = scale_start;
preview.scale_z = scale_start;
this._manager.platform.tween(preview, {
x: preview.target_x,
y: preview.target_y,
time: animation_time / 2,
transition: TRANSITION_TYPE,
onCompleteParams: [preview],
onComplete: this._onFinishMove,
onCompleteScope: this,
});
this._manager.platform.tween(preview, {
opacity: 255,
scale_x: preview.scale,
scale_y: preview.scale,
scale_z: preview.scale,
time: animation_time / 2,
transition: IN_BOUNDS_TRANSITION_TYPE,
});
}
_onFadeForwardComplete(preview, distance, animation_time) {
preview.__looping = false;
preview.make_bottom_layer(this.previewActor);
log(distance);
preview.x = preview.target_x - Math.sqrt(distance + 1) * 150;
preview.y = preview.target_y - Math.sqrt(distance + 1) * 100;
let scale_start = preview.scale * Math.pow(this._settings.preview_scaling_factor, distance + 1);
preview.scale_x = scale_start;
preview.scale_y = scale_start;
preview.scale_z = scale_start;
this._manager.platform.tween(preview, {
x: preview.target_x - Math.sqrt(distance) * 150,
y: preview.target_y - Math.sqrt(distance) * 100,
time: animation_time / 2,
transition: TRANSITION_TYPE,
onCompleteParams: [preview],
onComplete: this._onFinishMove,
onCompleteScope: this,
});
let scale_end = preview.scale * Math.pow(this._settings.preview_scaling_factor, distance);
this._manager.platform.tween(preview, {
opacity: 255,
scale_x: scale_end,
scale_y: scale_end,
scale_z: scale_end,
time: animation_time / 2,
transition: IN_BOUNDS_TRANSITION_TYPE,
});
}
_onFinishMove(preview) {
this._updatePreviews(true)
if (preview.__finalTween) {
for (let tween of preview.__finalTween) {
this._manager.platform.tween(preview, tween);
}
preview.__finalTween = null;
}
}
};

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg7"
sodipodi:docname="applications.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="44.6875"
inkscape:cx="5.5944056"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path
d="M 2,1 C 0.892,1 0,1.892 0,3 v 9 c 0,1.108 0.892,2 2,2 h 12 c 1.108,0 2,-0.892 2,-2 V 3 C 16,1.892 15.108,1 14,1 Z m 0,1 h 12 c 0.554,0 1,0.446 1,1 H 1 C 1,2.446 1.446,2 2,2 Z M 1,3 h 14 v 9 c 0,0.554 -0.446,1 -1,1 H 2 C 1.446,13 1,12.554 1,12 Z"
style="fill:currentColor"
class="ColorScheme-Text"
id="path5"
sodipodi:nodetypes="sssssssssssccsccssssc" />
<g
id="g68"
transform="translate(0.3993007,0.15314685)">
<path
d="M 9.5,4 C 9.223,4 9,4.223 9,4.5 v 2 A 0.499,0.499 0 0 0 9.5,7 h 2 C 11.777,7 12,6.777 12,6.5 v -2 C 12,4.223 11.777,4 11.5,4 Z M 10,5 h 1 v 1 h -1 z"
fill="#363636"
id="path2" />
<path
d="M 4,6 C 3.446,6 3,6.446 3,7 v 3 c 0,0.554 0.446,1 1,1 h 3 c 0.554,0 1,-0.446 1,-1 V 7 C 8,6.446 7.554,6 7,6 Z m 0,1 h 3 v 3 H 4 Z"
fill="#363636"
id="path8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2021 Romain Vigier <contact AT romainvigier.fr>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m3.7578 2.0293a1 1 0 0 0-0.61523 0.45703l-3 5a1.0001 1.0001 0 0 0 0 1.0273l3 5a1 1 0 0 0 1.3711 0.34375 1 1 0 0 0 0.34375-1.3711l-2.6914-4.4863 2.6914-4.4863a1 1 0 0 0-0.34375-1.3711 1 1 0 0 0-0.75586-0.11328z"/>
<path d="m12.242 2.0293a1 1 0 0 0-0.75586 0.11328 1 1 0 0 0-0.34375 1.3711l2.6914 4.4863-2.6914 4.4863a1 1 0 0 0 0.34375 1.3711 1 1 0 0 0 1.3711-0.34375l3-5a1.0001 1.0001 0 0 0 0-1.0273l-3-5a1 1 0 0 0-0.61523-0.45703z"/>
<path d="m9.1953 2.0195a1 1 0 0 0-1.1758 0.78516l-2 10a1 1 0 0 0 0.78516 1.1758 1 1 0 0 0 1.1758-0.78516l2-10a1 1 0 0 0-0.78516-1.1758z"/>
</svg>

After

Width:  |  Height:  |  Size: 848 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2021 Romain Vigier <contact AT romainvigier.fr>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m4.5 1c-2.4853 0-4.5 2.0147-4.5 4.5 0.0032498 1.2489 0.75291 2.3099 1.4414 3.2891 1.7318 2.463 6.5586 6.2109 6.5586 6.2109s4.8267-3.7479 6.5586-6.2109c0.68851-0.97919 1.4382-2.0402 1.4414-3.2891 0-2.4853-2.0147-4.5-4.5-4.5-2.5 0-3.101 2.001-3.5 2s-1-2-3.5-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
sodipodi:docname="coverflow-symbolic.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
id="svg3466"
version="1.1"
viewBox="0 0 16.933333 16.933334"
height="64"
width="64"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview3468"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
inkscape:zoom="2.8293902"
inkscape:cx="-31.808975"
inkscape:cy="124.23172"
inkscape:window-width="2560"
inkscape:window-height="1351"
inkscape:window-x="2560"
inkscape:window-y="53"
inkscape:window-maximized="1"
inkscape:current-layer="layer2"
inkscape:showpageshadow="0"
inkscape:deskcolor="#d1d1d1"
inkscape:lockguides="false">
<inkscape:grid
type="xygrid"
id="grid1030" />
</sodipodi:namedview>
<defs
id="defs3463">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 8.466667 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="16.933333 : 8.466667 : 1"
inkscape:persp3d-origin="8.4666665 : 5.6444447 : 1"
id="perspective4871" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect130490"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,2,0,1 @ F,0,0,1,0,2,0,1 @ F,0,0,1,0,2,0,1 @ F,0,0,1,0,2,0,1"
unit="px"
method="auto"
mode="F"
radius="2"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false"
nodesatellites_param="F,0,0,1,0,2,0,1 @ F,0,0,1,0,2,0,1 @ F,0,0,1,0,2,0,1 @ F,0,0,1,0,2,0,1" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="matrix(0.99774183,0,0,0.77115443,0.71193726,0.91633828)">
<g
id="g2666"
transform="matrix(1.2594292,0,0,1.5615248,-2.0148681,-6.7303432)"
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.559234;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g27602"
transform="matrix(0.99277462,0,0,0.99277462,0.05616722,0.07606922)"
style="stroke-width:0.858316;stroke-dasharray:none">
<g
id="g2652"
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.710035;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(1.0822937,0,0,1.3501739,-0.63816917,-4.4362993)">
<g
id="g1028"
transform="matrix(0.83956608,0,0,1.0356734,1.1973153,1.6060768)"
style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.761449;stroke-dasharray:none;stroke-opacity:1">
<g
id="g1034"
transform="matrix(1.0572353,0,0,-0.71455331,-21.791344,225.59896)"
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.876068;stroke-dasharray:none;stroke-opacity:0">
<path
style="color:#000000;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.876068;stroke-dasharray:none;stroke-opacity:0"
d="m 20.191152,299.66796 c -0.152119,0.002 -0.283123,0.0654 -0.371094,0.13281 -0.17594,0.13474 -0.242446,0.27997 -0.304687,0.42969 -0.124482,0.29943 -0.185547,0.65031 -0.185547,1.00585 v 6.3379 c 0,0.35554 0.06106,0.70642 0.185547,1.00585 0.06224,0.14972 0.128748,0.29495 0.304687,0.42969 0.08797,0.0674 0.218979,0.12916 0.371094,0.13086 0.152116,0.002 0.296554,-0.0626 0.392578,-0.13867 l 1.53125,-1.21094 c 0.232498,-0.18412 0.525391,-0.58185 0.525391,-1.125 v -4.52148 c 0,-0.54315 -0.292885,-0.94087 -0.525391,-1.125 l -1.53125,-1.21289 c -0.09602,-0.0761 -0.240459,-0.14038 -0.392578,-0.13867 z m 0.158203,1.1621 1.496146,0.93165 c 0.128075,0.10142 0.166015,0.091 0.166015,0.38281 v 4.52148 c 0,0.2918 -0.03793,0.27943 -0.166015,0.38086 l -1.496146,0.9336 c -0.0307,-0.13528 -0.07031,-0.2605 -0.07031,-0.40625 v -6.3379 c 0,-0.14532 0.03975,-0.27119 0.07031,-0.40625 z"
id="path14264"
sodipodi:nodetypes="cccsscccccsscccccssccssc" />
</g>
<g
id="g1038"
transform="matrix(1.0572353,0,0,-0.71455331,-21.795226,225.54112)"
style="fill:#000000;fill-opacity:0.965186;stroke:#000000;stroke-width:0.876068;stroke-dasharray:none;stroke-opacity:1">
<path
style="color:#000000;fill:#000000;fill-opacity:0.965186;stroke:#000000;stroke-width:0.876068;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0"
d="m 35.850097,299.5658 c -0.152119,-0.002 -0.296555,0.0626 -0.392578,0.13867 l -1.53125,1.21289 c -0.232506,0.18413 -0.52539,0.58185 -0.52539,1.125 v 4.52148 c 0,0.54315 0.292893,0.94088 0.52539,1.125 l 1.53125,1.21094 c 0.09602,0.0761 0.240463,0.14038 0.392578,0.13867 0.152116,-0.002 0.285078,-0.0635 0.373047,-0.13086 0.175939,-0.13474 0.242447,-0.27997 0.304688,-0.42969 0.124483,-0.29943 0.183594,-0.65031 0.183594,-1.00585 v -6.3379 c 0,-0.35554 -0.05911,-0.70642 -0.183594,-1.00585 -0.06224,-0.14972 -0.128747,-0.29495 -0.304688,-0.42969 -0.08797,-0.0674 -0.220927,-0.13111 -0.373047,-0.13281 z m -0.158203,1.1621 c 0.03057,0.13508 0.07227,0.26089 0.07227,0.40625 v 6.3379 c 0,0.14579 -0.04155,0.27095 -0.07227,0.40625 l -1.460883,-0.9336 c -0.128084,-0.10143 -0.167969,-0.0891 -0.167969,-0.38086 v -4.52148 c 0,-0.29181 0.03989,-0.28139 0.167969,-0.38281 z"
id="path14266"
sodipodi:nodetypes="cccsscccccssccccssccsscc" />
</g>
<rect
style="display:inline;opacity:1;fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.761449;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:52.5354;stroke-opacity:1;paint-order:normal"
id="rect7847"
width="9.6742821"
height="6.4741611"
x="2.9982321"
y="4.8527794"
ry="0.40096685"
rx="0.76381439"
inkscape:label="rect7847" />
</g>
<path
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:0.710035;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9667305,15.136593 5.6340845,-0.01302"
id="path1273" />
</g>
<rect
style="opacity:1;fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:0.858316;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:39.9874;stroke-opacity:1;paint-order:normal"
id="rect27562"
width="2.0045266"
height="2.5935142"
x="6.7461357"
y="7.786335"
rx="0.40091178"
ry="0.51871145" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg7"
sodipodi:docname="top-panel.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="44.6875"
inkscape:cx="5.5944056"
inkscape:cy="8"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path
d="M 2,1 C 0.892,1 0,1.892 0,3 v 9 c 0,1.108 0.892,2 2,2 h 12 c 1.108,0 2,-0.892 2,-2 V 3 C 16,1.892 15.108,1 14,1 Z m 0,1 h 12 c 0.554,0 1,0.446 1,1 H 1 C 1,2.446 1.446,2 2,2 Z M 1,3 h 14 v 9 c 0,0.554 -0.446,1 -1,1 H 2 C 1.446,13 1,12.554 1,12 Z m 3,7 c -0.554,0 -1,0.446 -1,1 0,0.554 0.446,1 1,1 h 8 c 0.554,0 1,-0.446 1,-1 0,-0.554 -0.446,-1 -1,-1 z"
style="fill:currentColor"
class="ColorScheme-Text"
id="path5"
sodipodi:nodetypes="sssssssssssccsccsssscsssssss" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
enable-background="new"
version="1.1"
id="svg16"
sodipodi:docname="general-symbolic.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs20" />
<sodipodi:namedview
id="namedview18"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="24.174578"
inkscape:cx="13.98163"
inkscape:cy="8.3765683"
inkscape:window-width="1500"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g14" />
<g
fill="#363636"
id="g14"
transform="rotate(90,7.75,8.25)">
<path
d="m 2.9500144,1 c -0.277,0 -0.5,0.223 -0.5,0.5 v 6.5547 a 2.5,2.5 0 0 1 0.5,-0.054688 2.5,2.5 0 0 1 0.5,0.050781 v -6.5508 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z m 0.5,11.945 a 2.5,2.5 0 0 1 -0.5,0.05469 2.5,2.5 0 0 1 -0.5,-0.05078 v 1.5508 c 0,0.277 0.223,0.5 0.5,0.5 0.277,0 0.5,-0.223 0.5,-0.5 v -1.5547 z"
id="path2" />
<path
d="M 7.5,1 C 7.223,1 7,1.223 7,1.5 V 3.0547 A 2.5,2.5 0 0 1 7.5,3.000012 2.5,2.5 0 0 1 8,3.050793 v -1.5508 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z M 8,7.9453 A 2.5,2.5 0 0 1 7.5,7.999988 2.5,2.5 0 0 1 7,7.949207 v 6.5508 c 0,0.277 0.223,0.5 0.5,0.5 0.277,0 0.5,-0.223 0.5,-0.5 v -6.5547 z"
id="path4" />
<path
d="m 12.049986,1.0001482 c -0.277,0 -0.5,0.223 -0.5,0.5 v 6.5547 a 2.5,2.5 0 0 1 0.5,-0.054688 2.5,2.5 0 0 1 0.5,0.050781 v -6.5508 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z m 0.5,11.9450008 a 2.5,2.5 0 0 1 -0.5,0.05469 2.5,2.5 0 0 1 -0.5,-0.05078 v 1.5508 c 0,0.276999 0.223,0.5 0.5,0.5 0.277,0 0.5,-0.223001 0.5,-0.5 v -1.5547 z"
id="path6" />
<circle
cx="2.9500144"
cy="10.5"
id="circle8"
r="1.5" />
<circle
cx="7.5"
cy="5.5"
r="1.5"
id="circle10" />
<circle
cx="12.049986"
cy="10.500149"
id="circle12"
r="1.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2021 Romain Vigier <contact AT romainvigier.fr>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m4.5 1c-2.4853 0-4.5 2.0147-4.5 4.5 0.0032498 1.2489 0.75291 2.3099 1.4414 3.2891 1.7318 2.463 6.5586 6.2109 6.5586 6.2109s4.8267-3.7479 6.5586-6.2109c0.68851-0.97919 1.4382-2.0402 1.4414-3.2891 0-2.4853-2.0147-4.5-4.5-4.5-2.5 0-3.101 2.001-3.5 2s-1-2-3.5-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: 2021 Romain Vigier <contact AT romainvigier.fr>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m3 11c0 1.6509 1.3491 3 3 3v-1c-1.1105 0-2-0.88951-2-2h-1z"/>
<path d="m10 2v1c1.1105 0 2 0.88951 2 2h1c0-1.6509-1.3491-3-3-3z"/>
<path d="m2 0c-1.108 0-2 0.892-2 2v6c0 1.108 0.892 2 2 2h4v-2l-0.33398-1h-2.332l-0.33398 1h-1l2-6h1l1.4668 4.4023c0.53433-0.84006 1.4736-1.4023 2.5332-1.4023v-3c0-1.108-0.892-2-2-2h-5zm2.5 3.5-0.83398 2.5h1.668l-0.83398-2.5z"/>
<path d="m9 6c-1.108 0-2 0.892-2 2v6c0 1.108 0.892 2 2 2h5c1.108 0 2-0.892 2-2v-6c0-1.108-0.892-2-2-2zm2 2h1v1h2v1h-0.53906c-0.15383 0.98264-0.74444 1.7805-1.3945 2.3926 0.79475 0.45608 1.5312 0.61719 1.5312 0.61719a0.5 0.5 0 0 1 0.39258 0.58789 0.5 0.5 0 0 1-0.58789 0.39258s-1.0838-0.19928-2.168-0.91797c-0.81221 0.5775-1.5371 0.88672-1.5371 0.88672a0.5 0.5 0 0 1-0.65625-0.26172 0.5 0.5 0 0 1 0.26172-0.65625s0.50729-0.21982 1.1211-0.625c-0.33276-0.3295-0.63597-0.72311-0.87109-1.1934a0.5 0.5 0 0 1 0.22461-0.66992 0.5 0.5 0 0 1 0.38086-0.02734 0.5 0.5 0 0 1 0.28906 0.25195c0.20204 0.40409 0.48152 0.74022 0.79102 1.0195 0.55892-0.49663 1.0381-1.1094 1.2012-1.7969h-3.4395v-1h2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB