overview: Add a facility for swipe-scrolling
The workspaces view allows to drag the active workspace to swipe-scroll to the next or previous workspace. While this behavior can come in handy in general, there are good reasons to move the functionality to the overview: - Finding a spot on a workspace to start a drag can be hard, especially when the workspace contains a single window - With the new layout, workspaces have no visible border, making it hard to predict where a drag can be initiated - The same behavior is equally useful for other elements So add setScrollAdjustment() to the overview, which allows setting an adjustment controlled with swipe-scrolling (either horizontally or vertically); only a single adjustment can be controlled at a time. A swipe-scroll can be initiated on any part of the background that is not occupied by a reactive actor. For cases where further control is needed, 'swipe-scroll-start' and 'swipe-scroll-end' signals are emitted. https://bugzilla.gnome.org/show_bug.cgi?id=635034
This commit is contained in:
parent
56cf7a6628
commit
a6da22fa70
@ -1,6 +1,7 @@
|
|||||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
const Meta = imports.gi.Meta;
|
const Meta = imports.gi.Meta;
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
@ -32,6 +33,18 @@ const DASH_SPLIT_FRACTION = 0.1;
|
|||||||
|
|
||||||
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
|
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
|
||||||
|
|
||||||
|
const SwipeScrollDirection = {
|
||||||
|
NONE: 0,
|
||||||
|
HORIZONTAL: 1,
|
||||||
|
VERTICAL: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
const SwipeScrollResult = {
|
||||||
|
CANCEL: 0,
|
||||||
|
SWIPE: 1,
|
||||||
|
CLICK: 2
|
||||||
|
};
|
||||||
|
|
||||||
function ShellInfo() {
|
function ShellInfo() {
|
||||||
this._init();
|
this._init();
|
||||||
}
|
}
|
||||||
@ -98,7 +111,8 @@ Overview.prototype = {
|
|||||||
|
|
||||||
this._spacing = 0;
|
this._spacing = 0;
|
||||||
|
|
||||||
this._group = new St.Group({ name: 'overview' });
|
this._group = new St.Group({ name: 'overview',
|
||||||
|
reactive: true });
|
||||||
this._group._delegate = this;
|
this._group._delegate = this;
|
||||||
this._group.connect('style-changed',
|
this._group.connect('style-changed',
|
||||||
Lang.bind(this, function() {
|
Lang.bind(this, function() {
|
||||||
@ -110,6 +124,11 @@ Overview.prototype = {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this._scrollDirection = SwipeScrollDirection.NONE;
|
||||||
|
this._scrollAdjustment = null;
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
this._buttonPressId = 0;
|
||||||
|
|
||||||
this.shellInfo = new ShellInfo();
|
this.shellInfo = new ShellInfo();
|
||||||
|
|
||||||
this._workspacesDisplay = null;
|
this._workspacesDisplay = null;
|
||||||
@ -154,7 +173,7 @@ Overview.prototype = {
|
|||||||
this._dash.actor.add_constraint(this.viewSelector.constrainY);
|
this._dash.actor.add_constraint(this.viewSelector.constrainY);
|
||||||
this._dash.actor.add_constraint(this.viewSelector.constrainHeight);
|
this._dash.actor.add_constraint(this.viewSelector.constrainHeight);
|
||||||
|
|
||||||
this._coverPane.lower_bottom();
|
this._coverPane.hide();
|
||||||
|
|
||||||
// XDND
|
// XDND
|
||||||
this._dragMonitor = {
|
this._dragMonitor = {
|
||||||
@ -233,6 +252,149 @@ Overview.prototype = {
|
|||||||
return DND.DragMotionResult.CONTINUE;
|
return DND.DragMotionResult.CONTINUE;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setScrollAdjustment: function(adjustment, direction) {
|
||||||
|
this._scrollAdjustment = adjustment;
|
||||||
|
if (this._scrollAdjustment == null)
|
||||||
|
this._scrollDirection = SwipeScrollDirection.NONE;
|
||||||
|
else
|
||||||
|
this._scrollDirection = direction;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onButtonPress: function(actor, event) {
|
||||||
|
if (this._scrollDirection == SwipeScrollDirection.NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let [stageX, stageY] = event.get_coords();
|
||||||
|
this._dragStartX = this._dragX = stageX;
|
||||||
|
this._dragStartY = this._dragY = stageY;
|
||||||
|
this._dragStartValue = this._scrollAdjustment.value;
|
||||||
|
this._lastMotionTime = -1; // used to track "stopping" while swipe-scrolling
|
||||||
|
this._capturedEventId = global.stage.connect('captured-event',
|
||||||
|
Lang.bind(this, this._onCapturedEvent));
|
||||||
|
this.emit('swipe-scroll-begin');
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCapturedEvent: function(actor, event) {
|
||||||
|
let stageX, stageY;
|
||||||
|
switch(event.type()) {
|
||||||
|
case Clutter.EventType.BUTTON_RELEASE:
|
||||||
|
[stageX, stageY] = event.get_coords();
|
||||||
|
|
||||||
|
// default to snapping back to the original value
|
||||||
|
let newValue = this._dragStartValue;
|
||||||
|
|
||||||
|
let minValue = this._scrollAdjustment.lower;
|
||||||
|
let maxValue = this._scrollAdjustment.upper - this._scrollAdjustment.page_size;
|
||||||
|
|
||||||
|
let direction;
|
||||||
|
if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) {
|
||||||
|
direction = stageX > this._dragStartX ? -1 : 1;
|
||||||
|
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||||
|
direction *= -1;
|
||||||
|
} else {
|
||||||
|
direction = stageY > this._dragStartY ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We default to scroll a full page size; both the first
|
||||||
|
// and the last page may be smaller though, so we need to
|
||||||
|
// adjust difference in those cases.
|
||||||
|
let difference = direction * this._scrollAdjustment.page_size;
|
||||||
|
if (this._dragStartValue + difference > maxValue)
|
||||||
|
difference = maxValue - this._dragStartValue;
|
||||||
|
else if (this._dragStartValue + difference < minValue)
|
||||||
|
difference = minValue - this._dragStartValue;
|
||||||
|
|
||||||
|
// If the user has moved more than half the scroll
|
||||||
|
// difference, we want to "settle" to the new value
|
||||||
|
// even if the user stops dragging rather "throws" by
|
||||||
|
// releasing during the drag.
|
||||||
|
let distance = this._dragStartValue - this._scrollAdjustment.value;
|
||||||
|
let noStop = Math.abs(distance / difference) > 0.5;
|
||||||
|
|
||||||
|
// We detect if the user is stopped by comparing the
|
||||||
|
// timestamp of the button release with the timestamp of
|
||||||
|
// the last motion. Experimentally, a difference of 0 or 1
|
||||||
|
// millisecond indicates that the mouse is in motion, a
|
||||||
|
// larger difference indicates that the mouse is stopped.
|
||||||
|
if ((this._lastMotionTime > 0 &&
|
||||||
|
this._lastMotionTime > event.get_time() - 2) ||
|
||||||
|
noStop) {
|
||||||
|
if (this._dragStartValue + difference >= minValue &&
|
||||||
|
this._dragStartValue + difference <= maxValue)
|
||||||
|
newValue += difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if the user has moved the mouse enough to trigger
|
||||||
|
// a drag
|
||||||
|
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
||||||
|
if (Math.abs(stageX - this._dragStartX) < threshold &&
|
||||||
|
Math.abs(stageY - this._dragStartY) < threshold) {
|
||||||
|
// no motion? It's a click!
|
||||||
|
this.emit('swipe-scroll-end', SwipeScrollResult.CLICK);
|
||||||
|
} else {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
if (newValue == this._dragStartValue)
|
||||||
|
result = SwipeScrollResult.CANCEL;
|
||||||
|
else
|
||||||
|
result = SwipeScrollResult.SWIPE;
|
||||||
|
|
||||||
|
// The event capture handler is disconnected
|
||||||
|
// while scrolling to the final position, so
|
||||||
|
// to avoid undesired prelights we raise
|
||||||
|
// the cover pane.
|
||||||
|
this._coverPane.raise_top();
|
||||||
|
this._coverPane.show();
|
||||||
|
|
||||||
|
Tweener.addTween(this._scrollAdjustment,
|
||||||
|
{ value: newValue,
|
||||||
|
time: ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onCompleteScope: this,
|
||||||
|
onComplete: function() {
|
||||||
|
this._coverPane.hide();
|
||||||
|
this.emit('swipe-scroll-end',
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
global.stage.disconnect(this._capturedEventId);
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Clutter.EventType.MOTION:
|
||||||
|
[stageX, stageY] = event.get_coords();
|
||||||
|
let dx = this._dragX - stageX;
|
||||||
|
let dy = this._dragY - stageY;
|
||||||
|
let primary = global.get_primary_monitor();
|
||||||
|
|
||||||
|
if (this._scrollDirection == SwipeScrollDirection.HORIZONTAL) {
|
||||||
|
if (St.Widget.get_default_direction() == St.TextDirection.RTL)
|
||||||
|
this._scrollAdjustment.value -= (dx / primary.width) * this._scrollAdjustment.page_size;
|
||||||
|
else
|
||||||
|
this._scrollAdjustment.value += (dx / primary.width) * this._scrollAdjustment.page_size;
|
||||||
|
} else {
|
||||||
|
this._scrollAdjustment.value += (dy / primary.height) * this._scrollAdjustment.page_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dragX = stageX;
|
||||||
|
this._dragY = stageY;
|
||||||
|
this._lastMotionTime = event.get_time();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Block enter/leave events to avoid prelights
|
||||||
|
// during swipe-scroll
|
||||||
|
case Clutter.EventType.ENTER:
|
||||||
|
case Clutter.EventType.LEAVE:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
_getDesktopClone: function() {
|
_getDesktopClone: function() {
|
||||||
let windows = global.get_window_actors().filter(function(w) {
|
let windows = global.get_window_actors().filter(function(w) {
|
||||||
return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP;
|
return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP;
|
||||||
@ -335,6 +497,9 @@ Overview.prototype = {
|
|||||||
this._modal = true;
|
this._modal = true;
|
||||||
this._animateVisible();
|
this._animateVisible();
|
||||||
this._shown = true;
|
this._shown = true;
|
||||||
|
|
||||||
|
this._buttonPressId = this._group.connect('button-press-event',
|
||||||
|
Lang.bind(this, this._onButtonPress));
|
||||||
},
|
},
|
||||||
|
|
||||||
_animateVisible: function() {
|
_animateVisible: function() {
|
||||||
@ -402,6 +567,7 @@ Overview.prototype = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._coverPane.raise_top();
|
this._coverPane.raise_top();
|
||||||
|
this._coverPane.show();
|
||||||
this.emit('showing');
|
this.emit('showing');
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -432,6 +598,10 @@ Overview.prototype = {
|
|||||||
|
|
||||||
this._shown = false;
|
this._shown = false;
|
||||||
this._syncInputMode();
|
this._syncInputMode();
|
||||||
|
|
||||||
|
if (this._buttonPressId > 0)
|
||||||
|
this._group.disconnect(this._buttonPressId);
|
||||||
|
this._buttonPressId = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
// hideTemporarily:
|
// hideTemporarily:
|
||||||
@ -541,13 +711,14 @@ Overview.prototype = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._coverPane.raise_top();
|
this._coverPane.raise_top();
|
||||||
|
this._coverPane.show();
|
||||||
this.emit('hiding');
|
this.emit('hiding');
|
||||||
},
|
},
|
||||||
|
|
||||||
_showDone: function() {
|
_showDone: function() {
|
||||||
this.animationInProgress = false;
|
this.animationInProgress = false;
|
||||||
this._desktopFade.hide();
|
this._desktopFade.hide();
|
||||||
this._coverPane.lower_bottom();
|
this._coverPane.hide();
|
||||||
|
|
||||||
this.emit('shown');
|
this.emit('shown');
|
||||||
// Handle any calls to hide* while we were showing
|
// Handle any calls to hide* while we were showing
|
||||||
@ -575,7 +746,7 @@ Overview.prototype = {
|
|||||||
this.animationInProgress = false;
|
this.animationInProgress = false;
|
||||||
this._hideInProgress = false;
|
this._hideInProgress = false;
|
||||||
|
|
||||||
this._coverPane.lower_bottom();
|
this._coverPane.hide();
|
||||||
|
|
||||||
this.emit('hidden');
|
this.emit('hidden');
|
||||||
// Handle any calls to show* while we were hiding
|
// Handle any calls to show* while we were hiding
|
||||||
|
Loading…
Reference in New Issue
Block a user