dnd: create zones and actors on the fly

Precreated zones doesn't work when the user can alter the space during
grab. (eg. resize a window or insert new ones)

TODO: handle empty spaces
This commit is contained in:
Ole Jørgen Brønner
2020-02-16 01:56:27 +01:00
committed by Tor Hedin Brønner
parent ffb51ad2a5
commit d594dc81a2

270
grab.js
View File

@@ -32,136 +32,6 @@ function monitorAtPoint(gx, gy) {
return null;
}
function createDndZonesForMonitors() {
let monitorToZones = new Map();
for (let [monitor, space] of Tiling.spaces.monitors) {
monitorToZones.set(monitor, createDnDZones(space));
}
return monitorToZones;
}
function createDnDZones(space) {
// In pixels protuding into the window. Ie. exclusive window_gap
let columnZoneMargin = 100;
let tileZoneMargin = 250;
function mkZoneActor(zone, color="red") {
let actor = new St.Widget({style_class: "tile-preview"})
actor.visible = false;
actor.x = zone.rect.x;
actor.y = zone.rect.y;
actor.width = zone.rect.width;
actor.height = zone.rect.height;
space.cloneContainer.add_actor(actor);
actor.raise_top();
return actor
}
function mkColumnZone(x, position) {
// Represent zones as center, margin instead of "regular" rects?
let margin = prefs.window_gap / 2 + columnZoneMargin
let detection = {
x: x - prefs.window_gap - columnZoneMargin,
y: 0,
width: margin * 2,
height: space.height
};
let zone = {
// Detection:
rect: detection,
position: position,
space: space,
// Visualization:
center: detection.x + detection.width / 2,
marginA: margin, // left
marginB: margin, // right
// Animation props:
originProp: "x",
sizeProp: "width",
};
zone.actor = mkZoneActor(zone, "red");
zone.actor.y = Tiling.panelBox.height;
zone.actor.height = space.height - Tiling.panelBox.height;
return zone;
}
function mkTileZone(x, y, width, position) {
let detection = {
x: x + columnZoneMargin,
y: y - prefs.window_gap - tileZoneMargin,
width: width - columnZoneMargin * 2,
height: prefs.window_gap + tileZoneMargin * 2,
};
let margin = prefs.window_gap + tileZoneMargin;
let cy = detection.y + detection.height / 2;
let marginTop = Math.min(cy - Tiling.panelBox.height, margin);
let marginBottom = margin;
let zone = {
rect: detection,
position: position,
space: space,
// Visualization:
center: cy,
marginA: marginTop,
marginB: marginBottom,
// Animation props:
originProp: "y",
sizeProp: "height",
};
zone.actor = mkZoneActor(zone, "blue");
zone.actor.x = x - prefs.window_gap / 2;
zone.actor.width = width + prefs.window_gap;
return zone;
}
let zones = [];
for (let i = 0; i < space.length; i++) {
let col = space[i];
zones.push(mkColumnZone(col[0].clone.targetX, [i]));
for (let j = 0; j < col.length; j++) {
let metaWindow = col[j];
let x = metaWindow.clone.targetX;
let y = metaWindow.clone.targetY;
let width = metaWindow._targetWidth || metaWindow.clone.width;
zones.push(mkTileZone(x, y, width, [i, j]));
}
let lastRow = col[col.length-1];
let x = lastRow.clone.targetX;
let y = lastRow.clone.targetY;
let width = lastRow._targetWidth || lastRow.clone.width;
let height = lastRow._targetHeight || lastRow.clone.height;
zones.push(mkTileZone(x, y + height + prefs.window_gap, width, [i, col.length]));
}
zones.push(mkColumnZone(space.cloneContainer.width + prefs.window_gap, [space.length]));
if (space.length === 0) {
space.targetX = Math.round(space.width/2);
space.cloneContainer.x = space.targetX;
let width = Math.round(space.width/2);
let zone = zones[0];
zone.rect.width = width;
zone.rect.x = -Math.round(width/2);
zone.rect.center = 0;
}
return zones;
}
var MoveGrab = class MoveGrab {
constructor(metaWindow, type, space) {
this.window = metaWindow;
@@ -170,6 +40,7 @@ var MoveGrab = class MoveGrab {
this.grabbed = false;
this.initialSpace = space || Tiling.spaces.spaceOfWindow(metaWindow);
this.zoneActors = new Set();
}
begin({center} = {}) {
@@ -181,6 +52,7 @@ var MoveGrab = class MoveGrab {
global.display.end_grab_op(global.get_current_time());
global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW);
for (let [monitor, $] of Tiling.spaces.monitors) {
monitor.clickOverlay.deactivate();
}
@@ -263,9 +135,6 @@ var MoveGrab = class MoveGrab {
this.signals.connect(global.stage, "button-press-event", this.end.bind(this));
this.spaceToDndZones = new Map();
this.monitorToZones = createDndZonesForMonitors();
let monitor = monitorAtPoint(gx, gy);
let onSame = monitor === space.monitor;
@@ -285,7 +154,6 @@ var MoveGrab = class MoveGrab {
for (let [workspace, space] of Tiling.spaces) {
this.signals.connect(space.background, "motion-event", this.spaceMotion.bind(this, space));
this.spaceToDndZones.set(space, createDnDZones(space));
}
this.selectDndZone(space, sx, sy, single && onSame);
}
@@ -298,25 +166,126 @@ var MoveGrab = class MoveGrab {
/** x,y in scroll cooridinates */
selectDndZone(space, x, y, initial=false) {
let dndZones = this.spaceToDndZones.get(space);
const gap = prefs.window_gap;
const halfGap = gap / 2;
const columnZoneMargin = 100 + halfGap;
const rowZoneMargin = 250 + halfGap;
let newDndTarget = null;
for (let zone of dndZones) {
if (isInRect(x, y, zone.rect)) {
if (newDndTarget) {
// Treat ambiguous zones as non-match (this way we don't have to ensure zones are non-overlapping :P)
newDndTarget = null;
let target = null;
const tilingHeight = space.height - Tiling.panelBox.height;
// TODO: move actor creation to activateDndTarget?
function mkZoneActor(props) {
let actor = new St.Widget({style_class: "tile-preview"});
actor.x = props.x;
actor.y = props.y;
actor.width = props.width;
actor.height = props.height;
return actor;
}
const lastClone = space[space.length - 1][0].clone;
const fakeClone = {
clone: {
targetX: lastClone.targetX + lastClone.width + gap,
targetY: lastClone.targetY,
width: columnZoneMargin,
height: tilingHeight
}
};
const columns = [...space, [fakeClone]];
for (let j = 0; j < columns.length; j++) {
const column = columns[j];
const metaWindow = column[0];
const clone = metaWindow.clone;
// FIXME: Non-uniform column width
const colX = clone.targetX;
const colW = clone.width;
// Fast forward if pointer is not inside column
if (x < colX - gap - columnZoneMargin) {
continue;
}
if (colX + colW < x) {
continue;
}
const cx = colX - halfGap;
const l = cx - columnZoneMargin;
const r = cx + columnZoneMargin;
if (l <= x && x <= r) {
target = {
position: [j],
center: cx,
originProp: "x",
sizeProp: "width",
marginA: columnZoneMargin,
marginB: columnZoneMargin,
space: space,
actor: mkZoneActor({
x: l,
y: Tiling.panelBox.height,
width: columnZoneMargin*2,
height: tilingHeight
})
};
break;
}
for (let i = 0; i < column.length + 1; i++) {
let clone;
if (i < column.length) {
clone = column[i].clone;
} else {
let lastClone = column[i-1].clone;
clone = {
targetX: lastClone.targetX,
targetY: lastClone.targetY + lastClone.height + gap,
width: lastClone.width,
height: 0
};
}
const isFirst = i === 0;
const isLast = i === column.length;
const cy = clone.targetY - halfGap;
const t = cy - rowZoneMargin;
const b = cy + rowZoneMargin;
if (t <= y && y <= b) {
target = {
position: [j, i],
center: cy,
originProp: "y",
sizeProp: "height",
marginA: isFirst ? 0 : rowZoneMargin,
marginB: isLast ? 0 : rowZoneMargin,
space: space,
actor: mkZoneActor({
x: clone.targetX,
y: cy,
width: clone.width,
height: (isFirst ? 0 : rowZoneMargin) + (isLast ? 0 : rowZoneMargin),
})
};
break;
}
newDndTarget = zone;
}
}
function sameTarget(a, b) {
if (a === b)
return true;
if (!a || !b)
return false;
return a.position[0] === b.position[0] && a.position[1] === b.position[1];
}
// TODO: rename dndTarget to selectedZone ?
if (newDndTarget !== this.dndTarget) {
if (!sameTarget(target, this.dndTarget)) {
this.dndTarget && this.deactivateDndTarget(this.dndTarget);
if (newDndTarget)
this.activateDndTarget(newDndTarget, initial);
if (target)
this.activateDndTarget(target, initial);
}
}
@@ -376,13 +345,11 @@ var MoveGrab = class MoveGrab {
let destSpace;
let [gx, gy, $] = global.get_pointer();
this.zoneActors.forEach(actor => actor.destroy());
if (this.dnd) {
let dndTarget = this.dndTarget;
for (let [space, zones] of this.spaceToDndZones) {
zones.forEach(zone => zone.actor.destroy());
}
if (dndTarget) {
let space = dndTarget.space;
destSpace = space;
@@ -485,8 +452,11 @@ var MoveGrab = class MoveGrab {
}
activateDndTarget(zone, first) {
zone.space.cloneContainer.add_child(zone.actor);
zone.actor.raise_top();
zone.space.selection.hide();
this.dndTarget = zone;
this.zoneActors.add(zone.actor);
let params = {
time: prefs.animation_time,
@@ -520,7 +490,7 @@ var MoveGrab = class MoveGrab {
time: prefs.animation_time,
[zone.originProp]: zone.center,
[zone.sizeProp]: 0,
onComplete: () => zone.actor.hide()
onComplete: () => { zone.actor.destroy(); this.zoneActors.delete(zone.actor); }
});
}