2020-06-15 21:03:06 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Atk from 'gi://Atk';
|
|
|
|
import Clutter from 'gi://Clutter';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import GObject from 'gi://GObject';
|
|
|
|
import Graphene from 'gi://Graphene';
|
|
|
|
import Meta from 'gi://Meta';
|
|
|
|
import Pango from 'gi://Pango';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import St from 'gi://St';
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as DND from './dnd.js';
|
|
|
|
import * as OverviewControls from './overviewControls.js';
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const WINDOW_DND_SIZE = 256;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
|
|
|
|
const WINDOW_OVERLAY_FADE_TIME = 200;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const WINDOW_SCALE_TIME = 200;
|
|
|
|
const WINDOW_ACTIVE_SIZE_INC = 5; // in each direction
|
2021-01-08 12:27:10 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const DRAGGING_WINDOW_OPACITY = 100;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2020-10-12 11:27:55 +00:00
|
|
|
const ICON_SIZE = 64;
|
|
|
|
const ICON_OVERLAP = 0.7;
|
|
|
|
|
2021-02-09 11:18:29 +00:00
|
|
|
const ICON_TITLE_SPACING = 6;
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const WindowPreview = GObject.registerClass({
|
2020-06-26 15:01:31 +00:00
|
|
|
Properties: {
|
|
|
|
'overlay-enabled': GObject.ParamSpec.boolean(
|
|
|
|
'overlay-enabled', 'overlay-enabled', 'overlay-enabled',
|
|
|
|
GObject.ParamFlags.READWRITE,
|
|
|
|
true),
|
|
|
|
},
|
2020-06-15 21:03:06 +00:00
|
|
|
Signals: {
|
|
|
|
'drag-begin': {},
|
|
|
|
'drag-cancelled': {},
|
|
|
|
'drag-end': {},
|
2023-08-06 22:40:20 +00:00
|
|
|
'selected': {param_types: [GObject.TYPE_UINT]},
|
2020-06-15 21:03:06 +00:00
|
|
|
'show-chrome': {},
|
|
|
|
'size-changed': {},
|
|
|
|
},
|
2021-02-25 15:04:30 +00:00
|
|
|
}, class WindowPreview extends Shell.WindowPreview {
|
2021-02-04 20:14:42 +00:00
|
|
|
_init(metaWindow, workspace, overviewAdjustment) {
|
2020-06-15 21:03:06 +00:00
|
|
|
this.metaWindow = metaWindow;
|
|
|
|
this.metaWindow._delegate = this;
|
|
|
|
this._windowActor = metaWindow.get_compositor_private();
|
|
|
|
this._workspace = workspace;
|
2021-02-04 20:14:42 +00:00
|
|
|
this._overviewAdjustment = overviewAdjustment;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
super._init({
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
accessible_role: Atk.Role.PUSH_BUTTON,
|
|
|
|
offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY,
|
|
|
|
});
|
|
|
|
|
2021-02-25 15:04:30 +00:00
|
|
|
const windowContainer = new Clutter.Actor({
|
2023-08-06 22:40:20 +00:00
|
|
|
pivot_point: new Graphene.Point({x: 0.5, y: 0.5}),
|
2021-01-08 12:27:10 +00:00
|
|
|
});
|
2021-02-25 15:04:30 +00:00
|
|
|
this.window_container = windowContainer;
|
|
|
|
|
|
|
|
windowContainer.connect('notify::scale-x',
|
2021-01-08 12:27:10 +00:00
|
|
|
() => this._adjustOverlayOffsets());
|
2020-06-15 21:03:06 +00:00
|
|
|
// gjs currently can't handle setting an actors layout manager during
|
|
|
|
// the initialization of the actor if that layout manager keeps track
|
|
|
|
// of its container, so set the layout manager after creating the
|
|
|
|
// container
|
2021-02-25 15:04:30 +00:00
|
|
|
windowContainer.layout_manager = new Shell.WindowPreviewLayout();
|
|
|
|
this.add_child(windowContainer);
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
this._addWindow(metaWindow);
|
|
|
|
|
|
|
|
this._delegate = this;
|
|
|
|
|
|
|
|
this._stackAbove = null;
|
|
|
|
|
2021-02-01 10:51:27 +00:00
|
|
|
this._cachedBoundingBox = {
|
2021-02-25 15:04:30 +00:00
|
|
|
x: windowContainer.layout_manager.bounding_box.x1,
|
|
|
|
y: windowContainer.layout_manager.bounding_box.y1,
|
|
|
|
width: windowContainer.layout_manager.bounding_box.get_width(),
|
|
|
|
height: windowContainer.layout_manager.bounding_box.get_height(),
|
2021-02-01 10:51:27 +00:00
|
|
|
};
|
|
|
|
|
2021-02-25 15:04:30 +00:00
|
|
|
windowContainer.layout_manager.connect(
|
2020-06-15 21:03:06 +00:00
|
|
|
'notify::bounding-box', layout => {
|
2021-02-01 10:51:27 +00:00
|
|
|
this._cachedBoundingBox = {
|
|
|
|
x: layout.bounding_box.x1,
|
|
|
|
y: layout.bounding_box.y1,
|
|
|
|
width: layout.bounding_box.get_width(),
|
|
|
|
height: layout.bounding_box.get_height(),
|
|
|
|
};
|
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
// A bounding box of 0x0 means all windows were removed
|
|
|
|
if (layout.bounding_box.get_area() > 0)
|
|
|
|
this.emit('size-changed');
|
|
|
|
});
|
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
this._windowActor.connectObject('destroy', () => this.destroy(), this);
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
this._updateAttachedDialogs();
|
|
|
|
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
|
2020-03-29 21:51:13 +00:00
|
|
|
this._draggable = DND.makeDraggable(this, {
|
|
|
|
restoreOnSuccess: true,
|
|
|
|
dragActorMaxSize: WINDOW_DND_SIZE,
|
|
|
|
dragActorOpacity: DRAGGING_WINDOW_OPACITY,
|
|
|
|
});
|
2020-06-15 21:03:06 +00:00
|
|
|
this._draggable.connect('drag-begin', this._onDragBegin.bind(this));
|
|
|
|
this._draggable.connect('drag-cancelled', this._onDragCancelled.bind(this));
|
|
|
|
this._draggable.connect('drag-end', this._onDragEnd.bind(this));
|
|
|
|
this.inDrag = false;
|
|
|
|
|
2023-04-20 10:26:23 +00:00
|
|
|
let clickAction = new Clutter.ClickAction();
|
|
|
|
clickAction.connect('clicked', () => this._activate());
|
|
|
|
clickAction.connect('long-press', (action, actor, state) => {
|
|
|
|
if (state === Clutter.LongPressState.ACTIVATE)
|
|
|
|
this.showOverlay(true);
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
this._draggable.addClickAction(clickAction);
|
|
|
|
|
2020-06-26 15:01:31 +00:00
|
|
|
this._overlayEnabled = true;
|
2021-01-08 17:36:55 +00:00
|
|
|
this._overlayShown = false;
|
2020-06-15 21:03:06 +00:00
|
|
|
this._closeRequested = false;
|
|
|
|
this._idleHideOverlayId = 0;
|
|
|
|
|
2020-10-12 11:27:55 +00:00
|
|
|
const tracker = Shell.WindowTracker.get_default();
|
|
|
|
const app = tracker.get_window_app(this.metaWindow);
|
|
|
|
this._icon = app.create_icon_texture(ICON_SIZE);
|
|
|
|
this._icon.add_style_class_name('icon-dropshadow');
|
|
|
|
this._icon.set({
|
|
|
|
reactive: true,
|
2023-08-06 22:40:20 +00:00
|
|
|
pivot_point: new Graphene.Point({x: 0.5, y: 0.5}),
|
2020-10-12 11:27:55 +00:00
|
|
|
});
|
|
|
|
this._icon.add_constraint(new Clutter.BindConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-10-12 11:27:55 +00:00
|
|
|
coordinate: Clutter.BindCoordinate.POSITION,
|
|
|
|
}));
|
|
|
|
this._icon.add_constraint(new Clutter.AlignConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-10-12 11:27:55 +00:00
|
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
|
|
factor: 0.5,
|
|
|
|
}));
|
|
|
|
this._icon.add_constraint(new Clutter.AlignConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-10-12 11:27:55 +00:00
|
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
2023-08-06 22:40:20 +00:00
|
|
|
pivot_point: new Graphene.Point({x: -1, y: ICON_OVERLAP}),
|
2020-10-12 11:27:55 +00:00
|
|
|
factor: 1,
|
|
|
|
}));
|
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
|
2020-06-15 21:03:06 +00:00
|
|
|
this._title = new St.Label({
|
|
|
|
visible: false,
|
|
|
|
style_class: 'window-caption',
|
|
|
|
text: this._getCaption(),
|
|
|
|
reactive: true,
|
|
|
|
});
|
2021-04-23 18:25:07 +00:00
|
|
|
this._title.clutter_text.single_line_mode = true;
|
2020-06-15 21:03:06 +00:00
|
|
|
this._title.add_constraint(new Clutter.BindConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-10-12 11:27:55 +00:00
|
|
|
coordinate: Clutter.BindCoordinate.X,
|
|
|
|
}));
|
2021-02-09 11:18:29 +00:00
|
|
|
const iconBottomOverlap = ICON_SIZE * (1 - ICON_OVERLAP);
|
2020-10-12 11:27:55 +00:00
|
|
|
this._title.add_constraint(new Clutter.BindConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-10-12 11:27:55 +00:00
|
|
|
coordinate: Clutter.BindCoordinate.Y,
|
2021-02-09 11:18:29 +00:00
|
|
|
offset: scaleFactor * (iconBottomOverlap + ICON_TITLE_SPACING),
|
2020-06-15 21:03:06 +00:00
|
|
|
}));
|
|
|
|
this._title.add_constraint(new Clutter.AlignConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-06-15 21:03:06 +00:00
|
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
|
|
factor: 0.5,
|
|
|
|
}));
|
|
|
|
this._title.add_constraint(new Clutter.AlignConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-06-15 21:03:06 +00:00
|
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
2023-08-06 22:40:20 +00:00
|
|
|
pivot_point: new Graphene.Point({x: -1, y: 0}),
|
2020-06-15 21:03:06 +00:00
|
|
|
factor: 1,
|
|
|
|
}));
|
|
|
|
this._title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
|
|
|
this.label_actor = this._title;
|
2021-08-15 22:36:59 +00:00
|
|
|
this.metaWindow.connectObject(
|
|
|
|
'notify::title', () => (this._title.text = this._getCaption()),
|
|
|
|
this);
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
const layout = Meta.prefs_get_button_layout();
|
|
|
|
this._closeButtonSide =
|
|
|
|
layout.left_buttons.includes(Meta.ButtonFunction.CLOSE)
|
|
|
|
? St.Side.LEFT : St.Side.RIGHT;
|
|
|
|
|
|
|
|
this._closeButton = new St.Button({
|
|
|
|
visible: false,
|
|
|
|
style_class: 'window-close',
|
2022-03-21 15:44:24 +00:00
|
|
|
icon_name: 'preview-close-symbolic',
|
2020-06-15 21:03:06 +00:00
|
|
|
});
|
|
|
|
this._closeButton.add_constraint(new Clutter.BindConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-06-15 21:03:06 +00:00
|
|
|
coordinate: Clutter.BindCoordinate.POSITION,
|
|
|
|
}));
|
|
|
|
this._closeButton.add_constraint(new Clutter.AlignConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-06-15 21:03:06 +00:00
|
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
2023-08-06 22:40:20 +00:00
|
|
|
pivot_point: new Graphene.Point({x: 0.5, y: -1}),
|
2020-06-15 21:03:06 +00:00
|
|
|
factor: this._closeButtonSide === St.Side.LEFT ? 0 : 1,
|
|
|
|
}));
|
|
|
|
this._closeButton.add_constraint(new Clutter.AlignConstraint({
|
2021-02-25 15:04:30 +00:00
|
|
|
source: windowContainer,
|
2020-06-15 21:03:06 +00:00
|
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
2023-08-06 22:40:20 +00:00
|
|
|
pivot_point: new Graphene.Point({x: -1, y: 0.5}),
|
2020-06-15 21:03:06 +00:00
|
|
|
factor: 0,
|
|
|
|
}));
|
|
|
|
this._closeButton.connect('clicked', () => this._deleteAll());
|
|
|
|
|
|
|
|
this.add_child(this._title);
|
2020-10-12 11:27:55 +00:00
|
|
|
this.add_child(this._icon);
|
2020-06-15 21:03:06 +00:00
|
|
|
this.add_child(this._closeButton);
|
2020-07-06 13:44:25 +00:00
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
this._overviewAdjustment.connectObject(
|
|
|
|
'notify::value', () => this._updateIconScale(), this);
|
2021-02-04 20:14:42 +00:00
|
|
|
this._updateIconScale();
|
|
|
|
|
2020-07-06 13:44:25 +00:00
|
|
|
this.connect('notify::realized', () => {
|
|
|
|
if (!this.realized)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._title.ensure_style();
|
2020-10-12 11:27:55 +00:00
|
|
|
this._icon.ensure_style();
|
2020-07-06 13:44:25 +00:00
|
|
|
});
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 20:14:42 +00:00
|
|
|
_updateIconScale() {
|
2023-08-06 22:40:20 +00:00
|
|
|
const {ControlsState} = OverviewControls;
|
|
|
|
const {currentState, initialState, finalState} =
|
2021-02-04 20:14:42 +00:00
|
|
|
this._overviewAdjustment.getStateTransitionParams();
|
|
|
|
const visible =
|
|
|
|
initialState === ControlsState.WINDOW_PICKER ||
|
|
|
|
finalState === ControlsState.WINDOW_PICKER;
|
|
|
|
const scale = visible
|
|
|
|
? 1 - Math.abs(ControlsState.WINDOW_PICKER - currentState) : 0;
|
|
|
|
|
|
|
|
this._icon.set({
|
|
|
|
scale_x: scale,
|
|
|
|
scale_y: scale,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
_windowCanClose() {
|
|
|
|
return this.metaWindow.can_close() &&
|
|
|
|
!this._hasAttachedDialogs();
|
|
|
|
}
|
|
|
|
|
|
|
|
_getCaption() {
|
|
|
|
if (this.metaWindow.title)
|
|
|
|
return this.metaWindow.title;
|
|
|
|
|
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
|
|
let app = tracker.get_window_app(this.metaWindow);
|
|
|
|
return app.get_name();
|
|
|
|
}
|
|
|
|
|
2020-10-12 11:19:07 +00:00
|
|
|
overlapHeights() {
|
|
|
|
const [, titleHeight] = this._title.get_preferred_height(-1);
|
|
|
|
|
|
|
|
const topOverlap = 0;
|
2021-02-09 11:18:29 +00:00
|
|
|
const bottomOverlap = ICON_TITLE_SPACING + titleHeight;
|
2020-10-12 11:19:07 +00:00
|
|
|
|
|
|
|
return [topOverlap, bottomOverlap];
|
|
|
|
}
|
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
chromeHeights() {
|
|
|
|
const [, closeButtonHeight] = this._closeButton.get_preferred_height(-1);
|
2020-10-12 11:27:55 +00:00
|
|
|
const [, iconHeight] = this._icon.get_preferred_height(-1);
|
2023-08-06 22:40:20 +00:00
|
|
|
const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
|
2021-02-08 18:06:37 +00:00
|
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * scaleFactor;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2021-01-08 12:27:10 +00:00
|
|
|
const topOversize = closeButtonHeight / 2;
|
|
|
|
const bottomOversize = (1 - ICON_OVERLAP) * iconHeight;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2021-02-08 18:06:37 +00:00
|
|
|
return [
|
|
|
|
topOversize + activeExtraSize,
|
|
|
|
bottomOversize + activeExtraSize,
|
|
|
|
];
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
chromeWidths() {
|
|
|
|
const [, closeButtonWidth] = this._closeButton.get_preferred_width(-1);
|
2023-08-06 22:40:20 +00:00
|
|
|
const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
|
2021-02-08 18:06:37 +00:00
|
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * scaleFactor;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
const leftOversize = this._closeButtonSide === St.Side.LEFT
|
2021-01-08 12:27:10 +00:00
|
|
|
? closeButtonWidth / 2
|
|
|
|
: 0;
|
2020-06-15 21:03:06 +00:00
|
|
|
const rightOversize = this._closeButtonSide === St.Side.LEFT
|
2021-01-08 12:27:10 +00:00
|
|
|
? 0
|
|
|
|
: closeButtonWidth / 2;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
2021-02-08 18:06:37 +00:00
|
|
|
return [
|
|
|
|
leftOversize + activeExtraSize,
|
|
|
|
rightOversize + activeExtraSize,
|
|
|
|
];
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
showOverlay(animate) {
|
2020-06-26 15:01:31 +00:00
|
|
|
if (!this._overlayEnabled)
|
|
|
|
return;
|
|
|
|
|
2021-01-08 17:36:55 +00:00
|
|
|
if (this._overlayShown)
|
2020-06-15 21:03:06 +00:00
|
|
|
return;
|
|
|
|
|
2021-01-08 17:36:55 +00:00
|
|
|
this._overlayShown = true;
|
2020-10-12 11:19:07 +00:00
|
|
|
this._restack();
|
2021-01-08 17:36:55 +00:00
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
// If we're supposed to animate and an animation in our direction
|
|
|
|
// is already happening, let that one continue
|
2021-01-08 12:27:10 +00:00
|
|
|
const ongoingTransition = this._title.get_transition('opacity');
|
2020-06-15 21:03:06 +00:00
|
|
|
if (animate &&
|
|
|
|
ongoingTransition &&
|
|
|
|
ongoingTransition.get_interval().peek_final_value() === 255)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const toShow = this._windowCanClose()
|
2021-01-08 12:27:10 +00:00
|
|
|
? [this._title, this._closeButton]
|
|
|
|
: [this._title];
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
toShow.forEach(a => {
|
|
|
|
a.opacity = 0;
|
|
|
|
a.show();
|
|
|
|
a.ease({
|
|
|
|
opacity: 255,
|
|
|
|
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-02-25 15:04:30 +00:00
|
|
|
const [width, height] = this.window_container.get_size();
|
2023-08-06 22:40:20 +00:00
|
|
|
const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
|
2021-02-08 18:02:23 +00:00
|
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor;
|
2021-02-21 17:38:05 +00:00
|
|
|
const origSize = Math.max(width, height);
|
|
|
|
const scale = (origSize + activeExtraSize) / origSize;
|
2021-02-08 18:02:23 +00:00
|
|
|
|
2021-02-25 15:04:30 +00:00
|
|
|
this.window_container.ease({
|
2021-02-08 18:02:23 +00:00
|
|
|
scale_x: scale,
|
|
|
|
scale_y: scale,
|
2021-01-08 12:27:10 +00:00
|
|
|
duration: animate ? WINDOW_SCALE_TIME : 0,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
});
|
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
this.emit('show-chrome');
|
|
|
|
}
|
|
|
|
|
|
|
|
hideOverlay(animate) {
|
2021-01-08 17:36:55 +00:00
|
|
|
if (!this._overlayShown)
|
2020-06-15 21:03:06 +00:00
|
|
|
return;
|
|
|
|
|
2021-01-08 17:36:55 +00:00
|
|
|
this._overlayShown = false;
|
2020-10-12 11:19:07 +00:00
|
|
|
this._restack();
|
2021-01-08 17:36:55 +00:00
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
// If we're supposed to animate and an animation in our direction
|
|
|
|
// is already happening, let that one continue
|
2021-01-08 12:27:10 +00:00
|
|
|
const ongoingTransition = this._title.get_transition('opacity');
|
2020-06-15 21:03:06 +00:00
|
|
|
if (animate &&
|
|
|
|
ongoingTransition &&
|
|
|
|
ongoingTransition.get_interval().peek_final_value() === 0)
|
|
|
|
return;
|
|
|
|
|
2021-01-08 12:27:10 +00:00
|
|
|
[this._title, this._closeButton].forEach(a => {
|
2020-06-15 21:03:06 +00:00
|
|
|
a.opacity = 255;
|
|
|
|
a.ease({
|
|
|
|
opacity: 0,
|
|
|
|
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => a.hide(),
|
|
|
|
});
|
|
|
|
});
|
2021-01-08 12:27:10 +00:00
|
|
|
|
2021-02-25 15:04:30 +00:00
|
|
|
this.window_container.ease({
|
2021-01-08 12:27:10 +00:00
|
|
|
scale_x: 1,
|
|
|
|
scale_y: 1,
|
|
|
|
duration: animate ? WINDOW_SCALE_TIME : 0,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_adjustOverlayOffsets() {
|
|
|
|
// Assume that scale-x and scale-y update always set
|
|
|
|
// in lock-step; that allows us to not use separate
|
|
|
|
// handlers for horizontal and vertical offsets
|
2021-02-25 15:04:30 +00:00
|
|
|
const previewScale = this.window_container.scale_x;
|
2021-01-08 12:27:10 +00:00
|
|
|
const [previewWidth, previewHeight] =
|
2021-02-25 15:04:30 +00:00
|
|
|
this.window_container.allocation.get_size();
|
2021-01-08 12:27:10 +00:00
|
|
|
|
|
|
|
const heightIncrease =
|
|
|
|
Math.floor(previewHeight * (previewScale - 1) / 2);
|
|
|
|
const widthIncrease =
|
|
|
|
Math.floor(previewWidth * (previewScale - 1) / 2);
|
|
|
|
|
|
|
|
const closeAlign = this._closeButtonSide === St.Side.LEFT ? -1 : 1;
|
|
|
|
|
|
|
|
this._icon.translation_y = heightIncrease;
|
|
|
|
this._title.translation_y = heightIncrease;
|
|
|
|
this._closeButton.set({
|
|
|
|
translation_x: closeAlign * widthIncrease,
|
|
|
|
translation_y: -heightIncrease,
|
|
|
|
});
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_addWindow(metaWindow) {
|
2021-02-25 15:04:30 +00:00
|
|
|
const clone = this.window_container.layout_manager.add_window(metaWindow);
|
2020-10-24 18:51:56 +00:00
|
|
|
if (!clone)
|
|
|
|
return;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
// We expect this to be used for all interaction rather than
|
|
|
|
// the ClutterClone; as the former is reactive and the latter
|
|
|
|
// is not, this just works for most cases. However, for DND all
|
|
|
|
// actors are picked, so DND operations would operate on the clone.
|
|
|
|
// To avoid this, we hide it from pick.
|
|
|
|
Shell.util_set_hidden_from_pick(clone, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_has_overlaps() {
|
2021-02-16 10:45:50 +00:00
|
|
|
return this._hasAttachedDialogs() ||
|
|
|
|
this._icon.visible ||
|
|
|
|
this._closeButton.visible;
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_deleteAll() {
|
2021-02-25 15:04:30 +00:00
|
|
|
const windows = this.window_container.layout_manager.get_windows();
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
// Delete all windows, starting from the bottom-most (most-modal) one
|
|
|
|
for (const window of windows.reverse())
|
|
|
|
window.delete(global.get_current_time());
|
|
|
|
|
|
|
|
this._closeRequested = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
addDialog(win) {
|
|
|
|
let parent = win.get_transient_for();
|
|
|
|
while (parent.is_attached_dialog())
|
|
|
|
parent = parent.get_transient_for();
|
|
|
|
|
|
|
|
// Display dialog if it is attached to our metaWindow
|
2023-08-07 00:51:19 +00:00
|
|
|
if (win.is_attached_dialog() && parent === this.metaWindow)
|
2020-06-15 21:03:06 +00:00
|
|
|
this._addWindow(win);
|
|
|
|
|
|
|
|
// The dialog popped up after the user tried to close the window,
|
|
|
|
// assume it's a close confirmation and leave the overview
|
|
|
|
if (this._closeRequested)
|
|
|
|
this._activate();
|
|
|
|
}
|
|
|
|
|
|
|
|
_hasAttachedDialogs() {
|
2021-02-25 15:04:30 +00:00
|
|
|
return this.window_container.layout_manager.get_windows().length > 1;
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_updateAttachedDialogs() {
|
|
|
|
let iter = win => {
|
|
|
|
let actor = win.get_compositor_private();
|
|
|
|
|
|
|
|
if (!actor)
|
|
|
|
return false;
|
|
|
|
if (!win.is_attached_dialog())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
this._addWindow(win);
|
|
|
|
win.foreach_transient(iter);
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
this.metaWindow.foreach_transient(iter);
|
|
|
|
}
|
|
|
|
|
|
|
|
get boundingBox() {
|
2023-08-06 22:40:20 +00:00
|
|
|
return {...this._cachedBoundingBox};
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get windowCenter() {
|
2021-02-01 10:51:27 +00:00
|
|
|
return {
|
|
|
|
x: this._cachedBoundingBox.x + this._cachedBoundingBox.width / 2,
|
|
|
|
y: this._cachedBoundingBox.y + this._cachedBoundingBox.height / 2,
|
|
|
|
};
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 01:03:07 +00:00
|
|
|
get overlayEnabled() {
|
2020-06-26 15:01:31 +00:00
|
|
|
return this._overlayEnabled;
|
|
|
|
}
|
|
|
|
|
2021-01-30 01:03:07 +00:00
|
|
|
set overlayEnabled(enabled) {
|
2020-06-26 15:01:31 +00:00
|
|
|
if (this._overlayEnabled === enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._overlayEnabled = enabled;
|
|
|
|
this.notify('overlay-enabled');
|
|
|
|
|
|
|
|
if (!enabled)
|
|
|
|
this.hideOverlay(false);
|
|
|
|
else if (this['has-pointer'] || global.stage.key_focus === this)
|
|
|
|
this.showOverlay(true);
|
|
|
|
}
|
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
// Find the actor just below us, respecting reparenting done by DND code
|
|
|
|
_getActualStackAbove() {
|
|
|
|
if (this._stackAbove == null)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (this.inDrag) {
|
|
|
|
if (this._stackAbove._delegate)
|
|
|
|
return this._stackAbove._delegate._getActualStackAbove();
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return this._stackAbove;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setStackAbove(actor) {
|
|
|
|
this._stackAbove = actor;
|
|
|
|
if (this.inDrag)
|
|
|
|
// We'll fix up the stack after the drag
|
|
|
|
return;
|
|
|
|
|
|
|
|
let parent = this.get_parent();
|
|
|
|
let actualAbove = this._getActualStackAbove();
|
|
|
|
if (actualAbove == null)
|
|
|
|
parent.set_child_below_sibling(this, null);
|
|
|
|
else
|
|
|
|
parent.set_child_above_sibling(this, actualAbove);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDestroy() {
|
|
|
|
this.metaWindow._delegate = null;
|
|
|
|
this._delegate = null;
|
2023-04-17 11:38:23 +00:00
|
|
|
this._destroyed = true;
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
if (this._longPressLater) {
|
2022-09-07 18:23:38 +00:00
|
|
|
const laters = global.compositor.get_laters();
|
|
|
|
laters.remove(this._longPressLater);
|
2020-06-15 21:03:06 +00:00
|
|
|
delete this._longPressLater;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._idleHideOverlayId > 0) {
|
|
|
|
GLib.source_remove(this._idleHideOverlayId);
|
|
|
|
this._idleHideOverlayId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.inDrag) {
|
|
|
|
this.emit('drag-end');
|
|
|
|
this.inDrag = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_activate() {
|
|
|
|
this.emit('selected', global.get_current_time());
|
|
|
|
}
|
|
|
|
|
2023-08-08 16:14:04 +00:00
|
|
|
vfunc_enter_event(event) {
|
2020-06-15 21:03:06 +00:00
|
|
|
this.showOverlay(true);
|
2023-08-08 16:14:04 +00:00
|
|
|
return super.vfunc_enter_event(event);
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 16:14:04 +00:00
|
|
|
vfunc_leave_event(event) {
|
2023-04-17 11:38:23 +00:00
|
|
|
if (this._destroyed)
|
2023-08-08 16:14:04 +00:00
|
|
|
return super.vfunc_leave_event(event);
|
2023-04-17 11:38:23 +00:00
|
|
|
|
2023-08-08 16:14:04 +00:00
|
|
|
if ((event.get_flags() & Clutter.EventFlags.FLAG_GRAB_NOTIFY) !== 0 &&
|
2022-03-07 11:14:52 +00:00
|
|
|
global.stage.get_grab_actor() === this._closeButton)
|
2023-08-08 16:14:04 +00:00
|
|
|
return super.vfunc_leave_event(event);
|
2022-03-07 11:14:52 +00:00
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
if (this._idleHideOverlayId > 0)
|
|
|
|
GLib.source_remove(this._idleHideOverlayId);
|
|
|
|
|
|
|
|
this._idleHideOverlayId = GLib.timeout_add(
|
|
|
|
GLib.PRIORITY_DEFAULT,
|
|
|
|
WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT, () => {
|
|
|
|
if (this._closeButton['has-pointer'] ||
|
|
|
|
this._title['has-pointer'])
|
|
|
|
return GLib.SOURCE_CONTINUE;
|
|
|
|
|
|
|
|
if (!this['has-pointer'])
|
|
|
|
this.hideOverlay(true);
|
|
|
|
|
|
|
|
this._idleHideOverlayId = 0;
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
|
|
|
|
|
|
|
GLib.Source.set_name_by_id(this._idleHideOverlayId, '[gnome-shell] this._idleHideOverlayId');
|
|
|
|
|
2023-08-08 16:14:04 +00:00
|
|
|
return super.vfunc_leave_event(event);
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_key_focus_in() {
|
|
|
|
super.vfunc_key_focus_in();
|
|
|
|
this.showOverlay(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_key_focus_out() {
|
|
|
|
super.vfunc_key_focus_out();
|
2022-03-07 11:14:52 +00:00
|
|
|
|
|
|
|
if (global.stage.get_grab_actor() !== this._closeButton)
|
|
|
|
this.hideOverlay(true);
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 16:14:04 +00:00
|
|
|
vfunc_key_press_event(event) {
|
|
|
|
let symbol = event.get_key_symbol();
|
2023-08-07 00:51:19 +00:00
|
|
|
let isEnter = symbol === Clutter.KEY_Return || symbol === Clutter.KEY_KP_Enter;
|
2020-06-15 21:03:06 +00:00
|
|
|
if (isEnter) {
|
|
|
|
this._activate();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-08 16:14:04 +00:00
|
|
|
return super.vfunc_key_press_event(event);
|
2020-06-15 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
2020-10-12 11:19:07 +00:00
|
|
|
_restack() {
|
|
|
|
// We may not have a parent if DnD completed successfully, in
|
|
|
|
// which case our clone will shortly be destroyed and replaced
|
|
|
|
// with a new one on the target workspace.
|
|
|
|
const parent = this.get_parent();
|
|
|
|
if (parent !== null) {
|
|
|
|
if (this._overlayShown)
|
|
|
|
parent.set_child_above_sibling(this, null);
|
|
|
|
else if (this._stackAbove === null)
|
|
|
|
parent.set_child_below_sibling(this, null);
|
|
|
|
else if (!this._stackAbove._overlayShown)
|
|
|
|
parent.set_child_above_sibling(this, this._stackAbove);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 21:03:06 +00:00
|
|
|
_onDragBegin(_draggable, _time) {
|
|
|
|
this.inDrag = true;
|
|
|
|
this.hideOverlay(false);
|
|
|
|
this.emit('drag-begin');
|
|
|
|
}
|
|
|
|
|
|
|
|
handleDragOver(source, actor, x, y, time) {
|
|
|
|
return this._workspace.handleDragOver(source, actor, x, y, time);
|
|
|
|
}
|
|
|
|
|
|
|
|
acceptDrop(source, actor, x, y, time) {
|
|
|
|
return this._workspace.acceptDrop(source, actor, x, y, time);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDragCancelled(_draggable, _time) {
|
|
|
|
this.emit('drag-cancelled');
|
|
|
|
}
|
|
|
|
|
|
|
|
_onDragEnd(_draggable, _time, _snapback) {
|
|
|
|
this.inDrag = false;
|
|
|
|
|
2020-10-12 11:19:07 +00:00
|
|
|
this._restack();
|
2020-06-15 21:03:06 +00:00
|
|
|
|
|
|
|
if (this['has-pointer'])
|
|
|
|
this.showOverlay(true);
|
|
|
|
|
|
|
|
this.emit('drag-end');
|
|
|
|
}
|
|
|
|
});
|