switch: Make handle draggable

Listen to state changes for switch menu items, since they can also be
changed without activating them now.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2717>
This commit is contained in:
Daniel Ruiz de Alegría 2020-01-21 11:30:42 +01:00 committed by Florian Müllner
parent 71f2355b8a
commit 946ee93692

View File

@ -339,11 +339,13 @@ export const Switch = GObject.registerClass({
}, class Switch extends St.Widget { }, class Switch extends St.Widget {
_init(state) { _init(state) {
this._state = false; this._state = false;
this._dragging = false;
super._init({ super._init({
style_class: 'toggle-switch', style_class: 'toggle-switch',
accessible_role: Atk.Role.CHECK_BOX, accessible_role: Atk.Role.CHECK_BOX,
track_hover: true, track_hover: true,
reactive: true,
}); });
const box = new St.BoxLayout({ const box = new St.BoxLayout({
@ -380,12 +382,16 @@ export const Switch = GObject.registerClass({
coordinate: Clutter.BindCoordinate.HEIGHT, coordinate: Clutter.BindCoordinate.HEIGHT,
}), }),
}); });
this.bind_property('reactive', this._handle, 'reactive', GObject.BindingFlags.SYNC_CREATE);
this._handleAlignConstraint = new Clutter.AlignConstraint({ this._handleAlignConstraint = new Clutter.AlignConstraint({
name: 'align', name: 'align',
align_axis: Clutter.AlignAxis.X_AXIS, align_axis: Clutter.AlignAxis.X_AXIS,
source: this, source: this,
}); });
this._handle.add_constraint(this._handleAlignConstraint); this._handle.add_constraint(this._handleAlignConstraint);
this._handle.connect('button-press-event', (actor, event) => this._startDragging(event));
this._handle.connect('touch-event', this._touchDragging.bind(this));
this.add_child(this._handle); this.add_child(this._handle);
this.state = state; this.state = state;
@ -413,9 +419,6 @@ export const Switch = GObject.registerClass({
} }
set state(state) { set state(state) {
if (this._state === state)
return;
let handleAlignFactor; let handleAlignFactor;
// Calling get_theme_node() while unmapped is an error, avoid that // Calling get_theme_node() while unmapped is an error, avoid that
const duration = this._handle.mapped const duration = this._handle.mapped
@ -434,13 +437,98 @@ export const Switch = GObject.registerClass({
duration, duration,
}); });
this._state = state; if (this._state !== state) {
this.notify('state'); this._state = state;
this.notify('state');
}
} }
toggle() { toggle() {
this.state = !this.state; this.state = !this.state;
} }
_startDragging(event) {
if (this._dragging)
return Clutter.EVENT_PROPAGATE;
this._dragging = true;
[this._initialGrabX] = event.get_coords();
let device = event.get_device();
let sequence = event.get_event_sequence();
this._grab = global.stage.grab(this);
this._grabbedDevice = device;
this._grabbedSequence = sequence;
return Clutter.EVENT_STOP;
}
vfunc_motion_event() {
if (this._dragging && !this._grabbedSequence)
return this._motionEvent(this, Clutter.get_current_event());
return Clutter.EVENT_PROPAGATE;
}
vfunc_button_release_event() {
if (this._dragging && !this._grabbedSequence)
return this._endDragging();
return Clutter.EVENT_PROPAGATE;
}
_touchDragging(actor, event) {
let sequence = event.get_event_sequence();
if (!this._dragging &&
event.type() === Clutter.EventType.TOUCH_BEGIN) {
this.startDragging(event);
return Clutter.EVENT_STOP;
} else if (this._grabbedSequence &&
sequence.get_slot() === this._grabbedSequence.get_slot()) {
if (event.type() === Clutter.EventType.TOUCH_UPDATE)
return this._motionEvent(this, event);
else if (event.type() === Clutter.EventType.TOUCH_END)
return this._endDragging();
}
return Clutter.EVENT_PROPAGATE;
}
_endDragging() {
if (!this._dragging)
return Clutter.EVENT_PROPAGATE;
if (this._grab) {
this._grab.dismiss();
this._grab = null;
}
if (this._dragged)
this.state = this._handleAlignConstraint.get_factor() > 0.5;
else
this.toggle();
this._dragged = false;
this._grabbedSequence = null;
this._grabbedDevice = null;
this._dragging = false;
return Clutter.EVENT_STOP;
}
_motionEvent(actor, event) {
this._dragged = true;
let [absX] = event.get_coords();
let factorDiff = (absX - this._initialGrabX) / (this.get_width() - this._handle.get_width());
let factor = factorDiff + (this.state ? 1.0 : 0.0);
this._handleAlignConstraint.set_factor(Math.clamp(factor, 0, 1));
return Clutter.EVENT_STOP;
}
}); });
export const PopupSwitchMenuItem = GObject.registerClass({ export const PopupSwitchMenuItem = GObject.registerClass({
@ -454,7 +542,10 @@ export const PopupSwitchMenuItem = GObject.registerClass({
y_expand: true, y_expand: true,
y_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER,
}); });
this._switch = new Switch(active); this._switch = new Switch(active);
this._switch.connect('notify::state', this._onToggled.bind(this));
this.bind_property('reactive', this._switch, 'reactive', GObject.BindingFlags.SYNC_CREATE);
this.accessible_role = Atk.Role.CHECK_MENU_ITEM; this.accessible_role = Atk.Role.CHECK_MENU_ITEM;
this.checkAccessibleState(); this.checkAccessibleState();
@ -506,8 +597,6 @@ export const PopupSwitchMenuItem = GObject.registerClass({
toggle() { toggle() {
this._switch.toggle(); this._switch.toggle();
this.emit('toggled', this._switch.state);
this.checkAccessibleState();
} }
get state() { get state() {
@ -519,6 +608,11 @@ export const PopupSwitchMenuItem = GObject.registerClass({
this.checkAccessibleState(); this.checkAccessibleState();
} }
_onToggled(sw, state) {
this.emit('toggled', state);
this.checkAccessibleState();
}
checkAccessibleState() { checkAccessibleState() {
switch (this.accessible_role) { switch (this.accessible_role) {
case Atk.Role.CHECK_MENU_ITEM: case Atk.Role.CHECK_MENU_ITEM: