diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index 038c87cf07d71fe58f0c472eda7997f7803a2d72..a297df19d900acc7a4feee7c4454afa9809d1993 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -28,11 +28,5 @@
keyboard-layout-filled-symbolic.svg
keyboard-shift-filled-symbolic.svg
process-working.svg
- toggle-off.svg
- toggle-off-dark.svg
- toggle-off-hc.svg
- toggle-on.svg
- toggle-on-dark.svg
- toggle-on-hc.svg
diff --git a/data/theme/gnome-shell-high-contrast.scss b/data/theme/gnome-shell-high-contrast.scss
index a6a5314cf202056e586ad6932a93c7c1803d4b20..01f895b6e2da19875d3b73679c18b6f1cc6c034f 100644
--- a/data/theme/gnome-shell-high-contrast.scss
+++ b/data/theme/gnome-shell-high-contrast.scss
@@ -10,10 +10,9 @@ stage {
-st-icon-style: symbolic;
}
-.toggle-switch { width: 48px; }
-.toggle-switch-us, .toggle-switch-intl {
- background-image: url("resource:///org/gnome/shell/theme/toggle-off-hc.svg");
- &:checked { background-image: url("resource:///org/gnome/shell/theme/toggle-on-hc.svg"); }
+.toggle-switch {
+ background: lighten($bg_color, 30%);
+ .handle { background-color: #FFF; }
}
//force opaque panel
diff --git a/data/theme/gnome-shell-sass/widgets/_switches.scss b/data/theme/gnome-shell-sass/widgets/_switches.scss
index fd7472ed3c2660de0e0c7b9f8a3084bcc46bc7de..b8ca5b05dd33c293e7e3d533b866807d3406347d 100644
--- a/data/theme/gnome-shell-sass/widgets/_switches.scss
+++ b/data/theme/gnome-shell-sass/widgets/_switches.scss
@@ -1,16 +1,33 @@
/* Switches */
-// these are equal to the size of the SVG assets
-$switch_height: 22px;
-$switch_width: 46px;
+$switch_height: 1.5em;
+$switch_width: 2.86em;
+$switch_handle_border: 0.07em;
.toggle-switch {
- color: $fg_color;
+ $_handle_size: $switch_height - 2 * $switch_handle_border;
+ $_transition-duration: 250ms;
height: $switch_height;
width: $switch_width;
- background-size: contain;
- background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-off.svg"),url("resource:///org/gnome/shell/theme/toggle-off-dark.svg"));
- &:checked {
- background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-on.svg"),url("resource:///org/gnome/shell/theme/toggle-on-dark.svg"));
+ border-radius: $switch_height;
+ background: mix($borders_color, $bg_color, 50%);
+ box-shadow: inset 0 0 0 1px $borders_color;
+ transition-duration: $_transition-duration;
+
+ .handle {
+ width: $_handle_size;
+ height: $_handle_size;
+ border-radius: $_handle_size;
+ background: lighten($bg_color, 3%);
+ border: $switch_handle_border solid darken($bg_color, 14%);
+ transition-duration: $_transition-duration;
+ }
+
+ &:checked {
+ background: $selected_bg_color;
+ box-shadow: inset 0 0 0 1px darken($selected_bg_color, 25%);
+ .handle {
+ @if $variant == 'light' { border-color: darken($selected_bg_color, 15%); }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/data/theme/toggle-off-dark.svg b/data/theme/toggle-off-dark.svg
deleted file mode 100644
index 163a135dca2d27086d47e88b445f89c7904f3aa3..0000000000000000000000000000000000000000
--- a/data/theme/toggle-off-dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/toggle-off-hc.svg b/data/theme/toggle-off-hc.svg
deleted file mode 100644
index 809cd3f7b70b0ed6d4ccc2143ce5f8bab8fe81da..0000000000000000000000000000000000000000
--- a/data/theme/toggle-off-hc.svg
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
-
diff --git a/data/theme/toggle-off.svg b/data/theme/toggle-off.svg
deleted file mode 100644
index ee51782943a7d6bd21fbfcdc38274361379208c7..0000000000000000000000000000000000000000
--- a/data/theme/toggle-off.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/toggle-on-dark.svg b/data/theme/toggle-on-dark.svg
deleted file mode 100644
index b71931ac84f72d32551cedcd6ee4343680a8e7b4..0000000000000000000000000000000000000000
--- a/data/theme/toggle-on-dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/toggle-on-hc.svg b/data/theme/toggle-on-hc.svg
deleted file mode 100644
index 9952f95407c99162a413d519767054fdb6082462..0000000000000000000000000000000000000000
--- a/data/theme/toggle-on-hc.svg
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
diff --git a/data/theme/toggle-on.svg b/data/theme/toggle-on.svg
deleted file mode 100644
index 28be67e2896f26463f04e1b425e8d5aed45effd5..0000000000000000000000000000000000000000
--- a/data/theme/toggle-on.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 11528560d41e443a0ded781301fd06a36cabc513..3a1299ed89ef59e5df65dfb96f184130c2b57818 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -317,16 +317,31 @@ var Switch = GObject.registerClass({
GObject.ParamFlags.READWRITE,
false),
},
-}, class Switch extends St.Bin {
- _init(state) {
+}, class Switch extends St.BoxLayout {
+ _init(state, reactive = true) {
this._state = false;
+ this._dragging = false;
super._init({
style_class: 'toggle-switch',
accessible_role: Atk.Role.CHECK_BOX,
can_focus: true,
- state,
});
+
+ this._handle = new St.Widget({
+ style_class: 'handle',
+ reactive,
+ });
+ this._handleAlignConstraint = new Clutter.AlignConstraint({
+ align_axis: Clutter.AlignAxis.X_AXIS,
+ source: this,
+ });
+ this._handle.add_constraint_with_name('align', 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.state = state;
}
get state() {
@@ -334,21 +349,118 @@ var Switch = GObject.registerClass({
}
set state(state) {
- if (this._state === state)
- return;
+ let handleAlignFactor;
+ const duration = this._handle.mapped
+ ? this._handle.get_theme_node().get_transition_duration()
+ : 0;
- if (state)
+ if (state) {
this.add_style_pseudo_class('checked');
- else
+ handleAlignFactor = 1.0;
+ } else {
this.remove_style_pseudo_class('checked');
+ handleAlignFactor = 0.0;
+ }
+
+ this._handle.ease_property('@constraints.align.factor', handleAlignFactor, {
+ duration,
+ });
- this._state = state;
- this.notify('state');
+ if (this._state !== state) {
+ this._state = state;
+ this.notify('state');
+ }
}
toggle() {
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();
+
+ if (sequence != null)
+ device.sequence_grab(sequence, this);
+ else
+ device.grab(this);
+
+ this._grabbedDevice = device;
+ this._grabbedSequence = sequence;
+
+ if (sequence == null) {
+ this._releaseId = this.connect('button-release-event', this._endDragging.bind(this));
+ this._motionId = this.connect('motion-event', this._motionEvent.bind(this));
+ }
+
+ return Clutter.EVENT_STOP;
+ }
+
+ _touchDragging(actor, event) {
+ let device = event.get_device();
+ let sequence = event.get_event_sequence();
+
+ if (!this._dragging && event.type() == Clutter.EventType.TOUCH_BEGIN) {
+ return this._startDragging(event);
+ } else if (device.sequence_get_grabbed_actor(sequence) == actor) {
+ if (event.type() == Clutter.EventType.TOUCH_UPDATE)
+ return this._motionEvent(actor, event);
+ else if (event.type() == Clutter.EventType.TOUCH_END ||
+ event.type() == Clutter.EventType.TOUCH_CANCEL)
+ return this._endDragging();
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ _endDragging() {
+ if (this._dragging) {
+ if (this._releaseId) {
+ this.disconnect(this._releaseId);
+ this._releaseId = null;
+ }
+ if (this._motionId) {
+ this.disconnect(this._motionId);
+ this._motionId = null;
+ }
+
+ if (this._grabbedSequence != null)
+ this._grabbedDevice.sequence_ungrab(this._grabbedSequence);
+ else
+ this._grabbedDevice.ungrab();
+
+ if (this._dragged == null)
+ this.toggle();
+ else
+ this.state = this._handleAlignConstraint.get_factor() > 0.5;
+
+ this._dragged = null;
+ 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);
+
+ factor = Math.max(factor, 0.0);
+ factor = Math.min(factor, 1.0);
+
+ this._handleAlignConstraint.set_factor(factor);
+
+ return Clutter.EVENT_STOP;
+ }
});
var PopupSwitchMenuItem = GObject.registerClass({
@@ -358,7 +470,10 @@ var PopupSwitchMenuItem = GObject.registerClass({
super._init(params);
this.label = new St.Label({ text });
- this._switch = new Switch(active);
+
+ let switchReactive = params && params.reactive;
+ this._switch = new Switch(active, switchReactive);
+ this._switch.connect('notify::state', this._onToggled.bind(this));
this.accessible_role = Atk.Role.CHECK_MENU_ITEM;
this.checkAccessibleState();
@@ -408,8 +523,6 @@ var PopupSwitchMenuItem = GObject.registerClass({
toggle() {
this._switch.toggle();
- this.emit('toggled', this._switch.state);
- this.checkAccessibleState();
}
get state() {
@@ -421,6 +534,11 @@ var PopupSwitchMenuItem = GObject.registerClass({
this.checkAccessibleState();
}
+ _onToggled(sw, state) {
+ this.emit('toggled', state);
+ this.checkAccessibleState();
+ }
+
checkAccessibleState() {
switch (this.accessible_role) {
case Atk.Role.CHECK_MENU_ITEM: