8423ba44fe
These traditionally got the various ClutterEvent subtype structs as their argument, so it was not allowed to use ClutterEvent generic getter methods in these vfuncs. These methods used direct access to struct fields instead. This got spoiled with the move to make ClutterEvent opaque types, since these are no longer public structs so GNOME Shell most silently failed to fetch the expected values from event fields. But since they are not ClutterEvents either, the getters could not be used on them. Mutter is changing so that these vmethods all contain an alias to the one and only Clutter.Event type, thus lifting those barriers, and making it possible to use the ClutterEvent methods in these vfuncs. Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/2950 Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2872>
662 lines
22 KiB
JavaScript
662 lines
22 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
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';
|
|
|
|
import * as DND from './dnd.js';
|
|
import * as OverviewControls from './overviewControls.js';
|
|
|
|
const WINDOW_DND_SIZE = 256;
|
|
|
|
const WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
|
|
const WINDOW_OVERLAY_FADE_TIME = 200;
|
|
|
|
const WINDOW_SCALE_TIME = 200;
|
|
const WINDOW_ACTIVE_SIZE_INC = 5; // in each direction
|
|
|
|
const DRAGGING_WINDOW_OPACITY = 100;
|
|
|
|
const ICON_SIZE = 64;
|
|
const ICON_OVERLAP = 0.7;
|
|
|
|
const ICON_TITLE_SPACING = 6;
|
|
|
|
export const WindowPreview = GObject.registerClass({
|
|
Properties: {
|
|
'overlay-enabled': GObject.ParamSpec.boolean(
|
|
'overlay-enabled', 'overlay-enabled', 'overlay-enabled',
|
|
GObject.ParamFlags.READWRITE,
|
|
true),
|
|
},
|
|
Signals: {
|
|
'drag-begin': {},
|
|
'drag-cancelled': {},
|
|
'drag-end': {},
|
|
'selected': { param_types: [GObject.TYPE_UINT] },
|
|
'show-chrome': {},
|
|
'size-changed': {},
|
|
},
|
|
}, class WindowPreview extends Shell.WindowPreview {
|
|
_init(metaWindow, workspace, overviewAdjustment) {
|
|
this.metaWindow = metaWindow;
|
|
this.metaWindow._delegate = this;
|
|
this._windowActor = metaWindow.get_compositor_private();
|
|
this._workspace = workspace;
|
|
this._overviewAdjustment = overviewAdjustment;
|
|
|
|
super._init({
|
|
reactive: true,
|
|
can_focus: true,
|
|
accessible_role: Atk.Role.PUSH_BUTTON,
|
|
offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY,
|
|
});
|
|
|
|
const windowContainer = new Clutter.Actor({
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
|
});
|
|
this.window_container = windowContainer;
|
|
|
|
windowContainer.connect('notify::scale-x',
|
|
() => this._adjustOverlayOffsets());
|
|
// 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
|
|
windowContainer.layout_manager = new Shell.WindowPreviewLayout();
|
|
this.add_child(windowContainer);
|
|
|
|
this._addWindow(metaWindow);
|
|
|
|
this._delegate = this;
|
|
|
|
this._stackAbove = null;
|
|
|
|
this._cachedBoundingBox = {
|
|
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(),
|
|
};
|
|
|
|
windowContainer.layout_manager.connect(
|
|
'notify::bounding-box', layout => {
|
|
this._cachedBoundingBox = {
|
|
x: layout.bounding_box.x1,
|
|
y: layout.bounding_box.y1,
|
|
width: layout.bounding_box.get_width(),
|
|
height: layout.bounding_box.get_height(),
|
|
};
|
|
|
|
// A bounding box of 0x0 means all windows were removed
|
|
if (layout.bounding_box.get_area() > 0)
|
|
this.emit('size-changed');
|
|
});
|
|
|
|
this._windowActor.connectObject('destroy', () => this.destroy(), this);
|
|
|
|
this._updateAttachedDialogs();
|
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
this._draggable = DND.makeDraggable(this, {
|
|
restoreOnSuccess: true,
|
|
dragActorMaxSize: WINDOW_DND_SIZE,
|
|
dragActorOpacity: DRAGGING_WINDOW_OPACITY,
|
|
});
|
|
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;
|
|
|
|
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);
|
|
|
|
this._overlayEnabled = true;
|
|
this._overlayShown = false;
|
|
this._closeRequested = false;
|
|
this._idleHideOverlayId = 0;
|
|
|
|
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,
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
|
|
});
|
|
this._icon.add_constraint(new Clutter.BindConstraint({
|
|
source: windowContainer,
|
|
coordinate: Clutter.BindCoordinate.POSITION,
|
|
}));
|
|
this._icon.add_constraint(new Clutter.AlignConstraint({
|
|
source: windowContainer,
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
factor: 0.5,
|
|
}));
|
|
this._icon.add_constraint(new Clutter.AlignConstraint({
|
|
source: windowContainer,
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
|
pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }),
|
|
factor: 1,
|
|
}));
|
|
|
|
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
|
this._title = new St.Label({
|
|
visible: false,
|
|
style_class: 'window-caption',
|
|
text: this._getCaption(),
|
|
reactive: true,
|
|
});
|
|
this._title.clutter_text.single_line_mode = true;
|
|
this._title.add_constraint(new Clutter.BindConstraint({
|
|
source: windowContainer,
|
|
coordinate: Clutter.BindCoordinate.X,
|
|
}));
|
|
const iconBottomOverlap = ICON_SIZE * (1 - ICON_OVERLAP);
|
|
this._title.add_constraint(new Clutter.BindConstraint({
|
|
source: windowContainer,
|
|
coordinate: Clutter.BindCoordinate.Y,
|
|
offset: scaleFactor * (iconBottomOverlap + ICON_TITLE_SPACING),
|
|
}));
|
|
this._title.add_constraint(new Clutter.AlignConstraint({
|
|
source: windowContainer,
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
factor: 0.5,
|
|
}));
|
|
this._title.add_constraint(new Clutter.AlignConstraint({
|
|
source: windowContainer,
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
|
pivot_point: new Graphene.Point({ x: -1, y: 0 }),
|
|
factor: 1,
|
|
}));
|
|
this._title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
|
this.label_actor = this._title;
|
|
this.metaWindow.connectObject(
|
|
'notify::title', () => (this._title.text = this._getCaption()),
|
|
this);
|
|
|
|
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',
|
|
icon_name: 'preview-close-symbolic',
|
|
});
|
|
this._closeButton.add_constraint(new Clutter.BindConstraint({
|
|
source: windowContainer,
|
|
coordinate: Clutter.BindCoordinate.POSITION,
|
|
}));
|
|
this._closeButton.add_constraint(new Clutter.AlignConstraint({
|
|
source: windowContainer,
|
|
align_axis: Clutter.AlignAxis.X_AXIS,
|
|
pivot_point: new Graphene.Point({ x: 0.5, y: -1 }),
|
|
factor: this._closeButtonSide === St.Side.LEFT ? 0 : 1,
|
|
}));
|
|
this._closeButton.add_constraint(new Clutter.AlignConstraint({
|
|
source: windowContainer,
|
|
align_axis: Clutter.AlignAxis.Y_AXIS,
|
|
pivot_point: new Graphene.Point({ x: -1, y: 0.5 }),
|
|
factor: 0,
|
|
}));
|
|
this._closeButton.connect('clicked', () => this._deleteAll());
|
|
|
|
this.add_child(this._title);
|
|
this.add_child(this._icon);
|
|
this.add_child(this._closeButton);
|
|
|
|
this._overviewAdjustment.connectObject(
|
|
'notify::value', () => this._updateIconScale(), this);
|
|
this._updateIconScale();
|
|
|
|
this.connect('notify::realized', () => {
|
|
if (!this.realized)
|
|
return;
|
|
|
|
this._title.ensure_style();
|
|
this._icon.ensure_style();
|
|
});
|
|
}
|
|
|
|
_updateIconScale() {
|
|
const { ControlsState } = OverviewControls;
|
|
const { currentState, initialState, finalState } =
|
|
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,
|
|
});
|
|
}
|
|
|
|
_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();
|
|
}
|
|
|
|
overlapHeights() {
|
|
const [, titleHeight] = this._title.get_preferred_height(-1);
|
|
|
|
const topOverlap = 0;
|
|
const bottomOverlap = ICON_TITLE_SPACING + titleHeight;
|
|
|
|
return [topOverlap, bottomOverlap];
|
|
}
|
|
|
|
chromeHeights() {
|
|
const [, closeButtonHeight] = this._closeButton.get_preferred_height(-1);
|
|
const [, iconHeight] = this._icon.get_preferred_height(-1);
|
|
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * scaleFactor;
|
|
|
|
const topOversize = closeButtonHeight / 2;
|
|
const bottomOversize = (1 - ICON_OVERLAP) * iconHeight;
|
|
|
|
return [
|
|
topOversize + activeExtraSize,
|
|
bottomOversize + activeExtraSize,
|
|
];
|
|
}
|
|
|
|
chromeWidths() {
|
|
const [, closeButtonWidth] = this._closeButton.get_preferred_width(-1);
|
|
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * scaleFactor;
|
|
|
|
const leftOversize = this._closeButtonSide === St.Side.LEFT
|
|
? closeButtonWidth / 2
|
|
: 0;
|
|
const rightOversize = this._closeButtonSide === St.Side.LEFT
|
|
? 0
|
|
: closeButtonWidth / 2;
|
|
|
|
return [
|
|
leftOversize + activeExtraSize,
|
|
rightOversize + activeExtraSize,
|
|
];
|
|
}
|
|
|
|
showOverlay(animate) {
|
|
if (!this._overlayEnabled)
|
|
return;
|
|
|
|
if (this._overlayShown)
|
|
return;
|
|
|
|
this._overlayShown = true;
|
|
this._restack();
|
|
|
|
// If we're supposed to animate and an animation in our direction
|
|
// is already happening, let that one continue
|
|
const ongoingTransition = this._title.get_transition('opacity');
|
|
if (animate &&
|
|
ongoingTransition &&
|
|
ongoingTransition.get_interval().peek_final_value() === 255)
|
|
return;
|
|
|
|
const toShow = this._windowCanClose()
|
|
? [this._title, this._closeButton]
|
|
: [this._title];
|
|
|
|
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,
|
|
});
|
|
});
|
|
|
|
const [width, height] = this.window_container.get_size();
|
|
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
|
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor;
|
|
const origSize = Math.max(width, height);
|
|
const scale = (origSize + activeExtraSize) / origSize;
|
|
|
|
this.window_container.ease({
|
|
scale_x: scale,
|
|
scale_y: scale,
|
|
duration: animate ? WINDOW_SCALE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
});
|
|
|
|
this.emit('show-chrome');
|
|
}
|
|
|
|
hideOverlay(animate) {
|
|
if (!this._overlayShown)
|
|
return;
|
|
|
|
this._overlayShown = false;
|
|
this._restack();
|
|
|
|
// If we're supposed to animate and an animation in our direction
|
|
// is already happening, let that one continue
|
|
const ongoingTransition = this._title.get_transition('opacity');
|
|
if (animate &&
|
|
ongoingTransition &&
|
|
ongoingTransition.get_interval().peek_final_value() === 0)
|
|
return;
|
|
|
|
[this._title, this._closeButton].forEach(a => {
|
|
a.opacity = 255;
|
|
a.ease({
|
|
opacity: 0,
|
|
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
onComplete: () => a.hide(),
|
|
});
|
|
});
|
|
|
|
this.window_container.ease({
|
|
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
|
|
const previewScale = this.window_container.scale_x;
|
|
const [previewWidth, previewHeight] =
|
|
this.window_container.allocation.get_size();
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
_addWindow(metaWindow) {
|
|
const clone = this.window_container.layout_manager.add_window(metaWindow);
|
|
if (!clone)
|
|
return;
|
|
|
|
// 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() {
|
|
return this._hasAttachedDialogs() ||
|
|
this._icon.visible ||
|
|
this._closeButton.visible;
|
|
}
|
|
|
|
_deleteAll() {
|
|
const windows = this.window_container.layout_manager.get_windows();
|
|
|
|
// 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
|
|
if (win.is_attached_dialog() && parent == this.metaWindow)
|
|
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() {
|
|
return this.window_container.layout_manager.get_windows().length > 1;
|
|
}
|
|
|
|
_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() {
|
|
return { ...this._cachedBoundingBox };
|
|
}
|
|
|
|
get windowCenter() {
|
|
return {
|
|
x: this._cachedBoundingBox.x + this._cachedBoundingBox.width / 2,
|
|
y: this._cachedBoundingBox.y + this._cachedBoundingBox.height / 2,
|
|
};
|
|
}
|
|
|
|
get overlayEnabled() {
|
|
return this._overlayEnabled;
|
|
}
|
|
|
|
set overlayEnabled(enabled) {
|
|
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);
|
|
}
|
|
|
|
// 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;
|
|
this._destroyed = true;
|
|
|
|
if (this._longPressLater) {
|
|
const laters = global.compositor.get_laters();
|
|
laters.remove(this._longPressLater);
|
|
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());
|
|
}
|
|
|
|
vfunc_enter_event(event) {
|
|
this.showOverlay(true);
|
|
return super.vfunc_enter_event(event);
|
|
}
|
|
|
|
vfunc_leave_event(event) {
|
|
if (this._destroyed)
|
|
return super.vfunc_leave_event(event);
|
|
|
|
if ((event.get_flags() & Clutter.EventFlags.FLAG_GRAB_NOTIFY) !== 0 &&
|
|
global.stage.get_grab_actor() === this._closeButton)
|
|
return super.vfunc_leave_event(event);
|
|
|
|
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');
|
|
|
|
return super.vfunc_leave_event(event);
|
|
}
|
|
|
|
vfunc_key_focus_in() {
|
|
super.vfunc_key_focus_in();
|
|
this.showOverlay(true);
|
|
}
|
|
|
|
vfunc_key_focus_out() {
|
|
super.vfunc_key_focus_out();
|
|
|
|
if (global.stage.get_grab_actor() !== this._closeButton)
|
|
this.hideOverlay(true);
|
|
}
|
|
|
|
vfunc_key_press_event(event) {
|
|
let symbol = event.get_key_symbol();
|
|
let isEnter = symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter;
|
|
if (isEnter) {
|
|
this._activate();
|
|
return true;
|
|
}
|
|
|
|
return super.vfunc_key_press_event(event);
|
|
}
|
|
|
|
_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);
|
|
}
|
|
}
|
|
|
|
_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;
|
|
|
|
this._restack();
|
|
|
|
if (this['has-pointer'])
|
|
this.showOverlay(true);
|
|
|
|
this.emit('drag-end');
|
|
}
|
|
});
|