%PDF- %PDF-
| Direktori : /usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/extension/ |
| Current File : //usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/extension/activeWindowHint.js |
import { Clutter, GObject, Meta, St } from '../dependencies/gi.js';
import { Main } from '../dependencies/shell.js';
import { Settings } from '../common.js';
import { TilingWindowManager as Twm } from './tilingWindowManager.js';
export default class ActiveWindowHintHandler {
constructor() {
// On a fresh install no color is set for the hint yet. Use the bg color
// from the tile preview style by using a temporary widget.
if (Settings.getString(Settings.ACTIVE_WINDOW_HINT_COLOR) === '') {
const widget = new St.Widget({ style_class: 'tile-preview' });
global.stage.add_child(widget);
const color = widget.get_theme_node().get_background_color();
const { red, green, blue } = color;
Settings.setString(Settings.ACTIVE_WINDOW_HINT_COLOR, `rgb(${red},${green},${blue})`);
widget.destroy();
}
this._hint = null;
this._settingsId = 0;
this._setupHint();
this._settingsId = Settings.changed(Settings.ACTIVE_WINDOW_HINT,
() => this._setupHint());
}
destroy() {
Settings.disconnect(this._settingsId);
this._hint?.destroy();
this._hint = null;
}
_setupHint() {
switch (Settings.getInt(Settings.ACTIVE_WINDOW_HINT)) {
case 0: // Disabled
this._hint?.destroy();
this._hint = null;
break;
case 1: // Minimal
this._hint?.destroy();
this._hint = new MinimalHint();
break;
case 2: // Always
this._hint?.destroy();
this._hint = new AlwaysHint();
}
}
}
const Hint = GObject.registerClass(
class ActiveWindowHint extends St.Widget {
_init() {
super._init();
this._color = Settings.getString(Settings.ACTIVE_WINDOW_HINT_COLOR);
this._borderSize = Settings.getInt(Settings.ACTIVE_WINDOW_HINT_BORDER_SIZE);
this._innerBorderSize = Settings.getInt(Settings.ACTIVE_WINDOW_HINT_INNER_BORDER_SIZE); // 'Inner border' to cover rounded corners
this._settingsIds = [];
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_COLOR, () => {
this._color = Settings.getString(Settings.ACTIVE_WINDOW_HINT_COLOR);
}));
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_BORDER_SIZE, () => {
this._borderSize = Settings.getInt(Settings.ACTIVE_WINDOW_HINT_BORDER_SIZE);
}));
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_INNER_BORDER_SIZE, () => {
this._innerBorderSize = Settings.getInt(Settings.ACTIVE_WINDOW_HINT_INNER_BORDER_SIZE);
}));
global.window_group.add_child(this);
}
destroy() {
this._settingsIds.forEach(id => Settings.disconnect(id));
super.destroy();
}
});
const MinimalHint = GObject.registerClass(
class MinimalActiveWindowHint extends Hint {
_init() {
super._init();
this._windowClone = null;
this._updateStyle();
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_COLOR, () => {
this._updateStyle();
}));
global.workspace_manager.connectObject('workspace-switched',
() => this._onWsSwitched(), this);
}
destroy() {
this._reset();
super.destroy();
}
_reset() {
if (this._laterId) {
global.compositor.get_laters().remove(this._laterId);
delete this._laterId;
}
this._windowClone?.destroy();
this._windowClone = null;
this.hide();
}
_updateStyle() {
this.set_style(`background-color: ${this._color};`);
}
_onWsSwitched() {
// Reset in case multiple workspaces are switched at once.
this._reset();
// If we are in the overview, it's likely the user actively chose
// a window to focus. So the hint is unnecessary.
if (Main.overview.visible)
return;
const window = global.display.focus_window;
if (!window)
return;
// Maximized or fullscreen windows don't require a hint since they
// cover the entire screen.
if (window.is_fullscreen() || Twm.isMaximized(window))
return;
// Now figure out if the focused window is easily identifiable by
// checking (in stacking order) if all other windows are being
// overlapped by higher windows. If a window is not overlapped, the
// focused window is ambiguous.
const windows = Twm.getWindows();
const overlapping = windows.splice(windows.indexOf(window), 1);
const notOverlappedWindowExists = windows.some(w => {
if (!overlapping.some(o => o.get_frame_rect().overlap(w.get_frame_rect())))
return true;
overlapping.push(w);
return false;
});
if (notOverlappedWindowExists)
this._giveHint(window);
}
_giveHint(window) {
this._scaleClone(window);
this._rippleFade(window);
}
_scaleClone(window) {
const actor = window.get_compositor_private();
if (!actor)
return;
const { x, y, width, height } = actor;
const scaleAmount = 15;
this._windowClone = new Clutter.Clone({
source: actor,
x: x - scaleAmount,
y: y - scaleAmount,
width: width + 2 * scaleAmount,
height: height + 2 * scaleAmount
});
global.window_group.insert_child_above(this._windowClone, actor);
this._windowClone.ease({
x, y, width, height,
delay: 250,
duration: 250,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
// May already have been destroyed by a reset
this._windowClone?.destroy();
this._windowClone = null;
}
});
}
_rippleFade(window) {
const actor = window.get_compositor_private();
if (!actor)
return;
if (!this._laterId) {
this._laterId = global.compositor.get_laters().add(
Meta.LaterType.BEFORE_REDRAW,
() => {
global.window_group.set_child_below_sibling(this, actor);
delete this._laterId;
return false;
}
);
}
const { x, y, width, height } = window.get_frame_rect();
this.set({ x, y, width, height });
this.set_opacity(255);
this.show();
const rippleSize = 30;
this.ease({
x: x - rippleSize,
y: y - rippleSize,
width: width + 2 * rippleSize,
height: height + 2 * rippleSize,
opacity: 0,
delay: 250,
duration: 350,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this.hide()
});
}
});
// TODO a solid bg color looks better than a border when launching an app since
// the border will appear before the window is fully visible. However there was
// an issue with global.window_group.set_child_below_sibling not putting the hint
// below the window for some reason. laters-add solved it but I don't know
// why. So as to not potentially cover the entire window's content use the border
// style until I figure out if laters-add is the proper solution...
const AlwaysHint = GObject.registerClass(
class AlwaysActiveWindowHint extends Hint {
_init() {
super._init();
this._window = null;
this._signalIds = [];
this._updateGeometry();
this._updateStyle();
global.display.connectObject('notify::focus-window',
() => this._updateGeometry(), this);
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_COLOR, () => {
this._updateStyle();
this._updateGeometry();
}));
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_BORDER_SIZE, () => {
this._updateStyle();
this._updateGeometry();
}));
this._settingsIds.push(Settings.changed(Settings.ACTIVE_WINDOW_HINT_INNER_BORDER_SIZE, () => {
this._updateStyle();
this._updateGeometry();
}));
}
destroy() {
this._reset();
super.destroy();
}
vfunc_hide() {
this._cancelShowLater();
super.vfunc_hide();
}
_reset() {
this._cancelShowLater();
this._signalIds.forEach(id => this._window.disconnect(id));
this._signalIds = [];
this._window = null;
}
_cancelShowLater() {
if (!this._showLater)
return;
global.compositor.get_laters().remove(this._showLater);
delete this._showLater;
}
_updateGeometry() {
this._reset();
const window = global.display.focus_window;
const allowTypes = [Meta.WindowType.NORMAL, Meta.WindowType.DIALOG, Meta.WindowType.MODAL_DIALOG];
if (!window || !allowTypes.includes(window.get_window_type())) {
this.hide();
return;
}
this._window = window;
this._signalIds.push(window.connect('position-changed', () => this._updateGeometry()));
this._signalIds.push(window.connect('size-changed', () => this._updateGeometry()));
// Don't show hint on maximzed/fullscreen windows
if (window.is_fullscreen() || Twm.isMaximized(window)) {
this.hide();
return;
}
const { x, y, width, height } = window.get_frame_rect();
this.set({ x, y, width, height });
const actor = window.get_compositor_private();
if (!actor || this._showLater)
return;
this._showLater = global.compositor.get_laters().add(
Meta.LaterType.IDLE,
() => {
global.window_group.set_child_below_sibling(this, actor);
this.show();
delete this._showLater;
return false;
}
);
}
_updateStyle() {
this.set_style(`
border: ${this._innerBorderSize}px solid ${this._color};
outline: ${this._borderSize}px solid ${this._color};
`);
}
});