mirror of
https://github.com/gosticks/PaperWM.git
synced 2026-02-18 14:52:43 +00:00
205 lines
6.6 KiB
JavaScript
205 lines
6.6 KiB
JavaScript
const Extension = imports.misc.extensionUtils.extensions['paperwm@hedning:matrix.org']
|
|
const Tiling = Extension.imports.tiling;
|
|
const Clutter = imports.gi.Clutter;
|
|
const Tweener = imports.ui.tweener;
|
|
const Lang = imports.lang;
|
|
const Main = imports.ui.main;
|
|
const Shell = imports.gi.Shell;
|
|
const utils = Extension.imports.utils;
|
|
const debug = utils.debug;
|
|
const Minimap = Extension.imports.minimap;
|
|
|
|
/*
|
|
The stack overlay decorates the top stacked window with its icon and
|
|
captures mouse input such that a mouse click only _activates_ the
|
|
window. A very limited portion of the window is visible and due to
|
|
the animation the button-up event will be triggered at an
|
|
unpredictable position
|
|
|
|
See #10
|
|
*/
|
|
|
|
/*
|
|
Parent of the overlay?
|
|
|
|
Most natural parent is the window actor, but then the overlay
|
|
becomes visible in the clones too.
|
|
|
|
Since the stacked windows doesn't really move it's not a big problem
|
|
that the overlay doesn't track the window. The main challenge with
|
|
using a different parent becomes controlling the "z-index".
|
|
|
|
If I understand clutter correctly that can only be done by managing
|
|
the order of the scene graph nodes. Descendants of node A will thus
|
|
always be drawn in the same plane compared to a non-descendants.
|
|
|
|
The overlay thus have to be parented to `global.window_group`. One
|
|
would think that was ok, but unfortunately mutter keeps syncing the
|
|
window_group with the window stacking and in the process destroy the
|
|
stacking of any non-window actors.
|
|
|
|
Adding a "clutter restack" to the `MetaScreen` `restacked` signal
|
|
seems keep the stacking in sync (without entering into infinite
|
|
restack loops)
|
|
*/
|
|
|
|
function createAppIcon(metaWindow, size) {
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
let app = tracker.get_window_app(metaWindow);
|
|
let appIcon = app ? app.create_icon_texture(size)
|
|
: new St.Icon({ icon_name: 'icon-missing',
|
|
icon_size: size });
|
|
appIcon.x_expand = appIcon.y_expand = true;
|
|
appIcon.x_align = appIcon.y_align = Clutter.ActorAlign.END;
|
|
|
|
return appIcon;
|
|
}
|
|
|
|
var StackOverlay = new Lang.Class({
|
|
Name: 'Stackoverlay',
|
|
|
|
_init: function(showIcon) {
|
|
this.showIcon = showIcon;
|
|
|
|
let overlay = new Clutter.Actor({ reactive: true
|
|
, name: "stack-overlay" });
|
|
|
|
this.monitor = Main.layoutManager.primaryMonitor;
|
|
|
|
let panelBox = Main.layoutManager.panelBox;
|
|
|
|
overlay.y = panelBox.height;
|
|
// global.window_group is below the panel so not really necessary to adjust height?
|
|
overlay.height = this.monitor.height - panelBox.height;
|
|
overlay.width = Tiling.stack_margin;
|
|
|
|
overlay.hide();
|
|
|
|
this.pressId = overlay.connect('button-press-event', () => {
|
|
return true;
|
|
});
|
|
this.releaseId = overlay.connect('button-release-event', () => {
|
|
// this.fadeOut();
|
|
Main.activateWindow(this.target);
|
|
return true;
|
|
});
|
|
|
|
global.window_group.add_child(overlay);
|
|
Main.layoutManager._trackActor(overlay)
|
|
|
|
this.overlay = overlay;
|
|
},
|
|
updateIcon: function() {
|
|
if (this.icon) {
|
|
this.icon.destroy();
|
|
this.icon = null;
|
|
}
|
|
|
|
let iconMarginX = 2;
|
|
let iconSize = margin_lr;
|
|
let icon = createAppIcon(this.target, iconSize);
|
|
this.icon = icon;
|
|
|
|
let actor = this.target.get_compositor_private();
|
|
|
|
if (actor.x <= Tiling.stack_margin) {
|
|
icon.x = iconMarginX;
|
|
} else {
|
|
icon.x = this.overlay.width - iconMarginX - iconSize;
|
|
}
|
|
|
|
let [dx, dy] = Minimap.calcOffset(this.target);
|
|
icon.y = actor.y + dy + 4 - this.overlay.y;
|
|
|
|
this.overlay.add_child(icon);
|
|
},
|
|
setTarget: function(metaWindow) {
|
|
this.target = metaWindow;
|
|
|
|
let bail = () => {
|
|
this.target = null;
|
|
this.overlay.hide();
|
|
return false;
|
|
}
|
|
|
|
if (!metaWindow) {
|
|
// No target. Eg. if we're at the left- or right-most window
|
|
return bail();
|
|
}
|
|
|
|
let overlay = this.overlay;
|
|
let actor = metaWindow.get_compositor_private();
|
|
let frame = metaWindow.get_frame_rect();
|
|
let resizeBorderWidth = 5; // approx.
|
|
let space = Tiling.spaces.spaceOfWindow(metaWindow);
|
|
|
|
// Note: Atm. this can be called when the windows are moving. Therefore
|
|
// we must use destinationX and we might occationally get wrong y
|
|
// positions (icon) (since we don't track the y destination)
|
|
// We also assume window widths are are unchanging.
|
|
let destX = metaWindow.destinationX;
|
|
if (destX === undefined)
|
|
destX = frame.x;
|
|
if (destX < Tiling.stack_margin) {
|
|
let neighbour = space[space.indexOf(metaWindow) + 1]
|
|
if (!neighbour)
|
|
return bail(); // Should normally have a neighbour. Bail!
|
|
|
|
let neighbourX = neighbour.destinationX;
|
|
if (neighbourX === undefined)
|
|
neighbourX = neighbour.get_frame_rect().x;
|
|
|
|
overlay.x = 0;
|
|
overlay.width = Math.min(
|
|
Tiling.stack_margin,
|
|
Math.max(0, neighbourX - resizeBorderWidth)
|
|
);
|
|
} else {
|
|
let neighbour = space[space.indexOf(metaWindow) - 1]
|
|
if (!neighbour)
|
|
return bail(); // Should normally have a neighbour. Bail!
|
|
|
|
let neighbourFrame = neighbour.get_frame_rect();
|
|
let neighbourX = neighbour.destinationX;
|
|
if (neighbourX === undefined)
|
|
neighbourX = neighbourFrame.x;
|
|
|
|
overlay.x = Math.max(
|
|
this.monitor.width - Tiling.stack_margin,
|
|
neighbourX + neighbourFrame.width + resizeBorderWidth
|
|
);
|
|
overlay.width = Math.max(0, this.monitor.width - overlay.x);
|
|
}
|
|
|
|
if (this.showIcon) {
|
|
this.updateIcon();
|
|
}
|
|
|
|
global.window_group.set_child_above_sibling(overlay, actor);
|
|
|
|
// Tweener.addTween(this.overlay, { opacity: 255, time: 0.25 });
|
|
overlay.show();
|
|
return true;
|
|
},
|
|
fadeOut: function() {
|
|
Tweener.addTween(this.overlay, { opacity: 0, time: 0.25 });
|
|
}
|
|
});
|
|
|
|
var leftOverlay;
|
|
var rightOverlay;
|
|
function enable() {
|
|
leftOverlay = new StackOverlay();
|
|
rightOverlay = new StackOverlay();
|
|
}
|
|
|
|
function disable() {
|
|
// Disconnect the overlay
|
|
for (let overlay of [leftOverlay, rightOverlay]) {
|
|
let actor = overlay.overlay;
|
|
actor.disconnect(overlay.pressId);
|
|
actor.disconnect(overlay.releaseId);
|
|
actor.destroy();
|
|
}
|
|
}
|