diff --git a/data/gnome-shell-icons.gresource.xml b/data/gnome-shell-icons.gresource.xml
index db65673b414c1d03ff9e268c69c958bb4978ad2a..6ace8f0f10fdc6facac645475e5b98739454b512 100644
--- a/data/gnome-shell-icons.gresource.xml
+++ b/data/gnome-shell-icons.gresource.xml
@@ -37,6 +37,7 @@
scalable/actions/shell-focus-top-bar-symbolic-rtl.svg
scalable/actions/shell-focus-windows-symbolic.svg
scalable/status/background-app-ghost-symbolic.svg
+ scalable/status/check-symbolic.svg
scalable/status/keyboard-brightness-high-symbolic.svg
scalable/status/keyboard-brightness-medium-symbolic.svg
scalable/status/keyboard-brightness-off-symbolic.svg
diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index 4501c3195fcbdd652097dc7dbb15cd2a36312d96..db1f4e91d11f74cd428c69c71413f7462a2697da 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -3,12 +3,6 @@
calendar-today.svg
calendar-today-light.svg
- checkbox.svg
- checkbox-focused.svg
- checkbox-off-focused-light.svg
- checkbox-off-focused.svg
- checkbox-off-light.svg
- checkbox-off.svg
gnome-shell-dark.css
gnome-shell-light.css
gnome-shell-high-contrast.css
@@ -16,10 +10,6 @@
pad-osd.css
process-working-light.svg
process-working-dark.svg
- toggle-off.svg
- toggle-off-light.svg
- toggle-on.svg
- toggle-on-light.svg
workspace-placeholder.svg
diff --git a/data/icons/scalable/status/check-symbolic.svg b/data/icons/scalable/status/check-symbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..27c620d499d2ce382337d6781ec2ed8f289af0a9
--- /dev/null
+++ b/data/icons/scalable/status/check-symbolic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/data/theme/checkbox-focused.svg b/data/theme/checkbox-focused.svg
deleted file mode 100644
index 01eb733dca7472d501b4ea79fef57072270be4b3..0000000000000000000000000000000000000000
--- a/data/theme/checkbox-focused.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/checkbox-off-focused-light.svg b/data/theme/checkbox-off-focused-light.svg
deleted file mode 100644
index 47673776dd11062ab4771a051ce287efd5ab6eaa..0000000000000000000000000000000000000000
--- a/data/theme/checkbox-off-focused-light.svg
+++ /dev/null
@@ -1,220 +0,0 @@
-
-
-
-
diff --git a/data/theme/checkbox-off-focused.svg b/data/theme/checkbox-off-focused.svg
deleted file mode 100644
index d5a042e97fe2665d5fc7b76e60b95963a71e1ecb..0000000000000000000000000000000000000000
--- a/data/theme/checkbox-off-focused.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/checkbox-off-light.svg b/data/theme/checkbox-off-light.svg
deleted file mode 100644
index 5d37c85d6066b4c4962da2eef66fcaa2abbbed5a..0000000000000000000000000000000000000000
--- a/data/theme/checkbox-off-light.svg
+++ /dev/null
@@ -1,211 +0,0 @@
-
-
-
-
diff --git a/data/theme/checkbox-off.svg b/data/theme/checkbox-off.svg
deleted file mode 100644
index 50eece1b028ef2bcecadc8cba6a8bf3828701d23..0000000000000000000000000000000000000000
--- a/data/theme/checkbox-off.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/checkbox.svg b/data/theme/checkbox.svg
deleted file mode 100644
index da385b6281d8acd16913b8095d82c5b1a032ded9..0000000000000000000000000000000000000000
--- a/data/theme/checkbox.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/gnome-shell-sass/widgets/_check-box.scss b/data/theme/gnome-shell-sass/widgets/_check-box.scss
index 1480ade26562816b33abe03bdc003baaf403cd19..f18d6418cf23da3adf766cd24269bcd5628b1439 100644
--- a/data/theme/gnome-shell-sass/widgets/_check-box.scss
+++ b/data/theme/gnome-shell-sass/widgets/_check-box.scss
@@ -1,18 +1,49 @@
/* Check Boxes */
-// these are equal to the size of the SVG assets
-$check_height: 24px;
-$check_width: 24px;
-
-
.check-box {
StBoxLayout { spacing: .8em; }
+
StBin {
- width: $check_width;
- height: $check_height;
- background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off.svg"));
+ border-radius: 7px;
+ padding: 2px;
+ }
+
+ &:focus StBin {
+ // Trick due to St limitations. It needs a background to draw a box-shadow
+ background-color: rgba(0, 0, 0, 0.01);
+ box-shadow: inset 0 0 0 2px transparentize($selected_bg_color, .65);
+ }
+
+ StIcon {
+ icon-size: 14px;
+ padding: 1px;
+
+ color: transparent;
+ border-radius: 6px;
+ border: 2px solid transparentize(if($variant == 'light', black, white), .85);
+ }
+
+ &:hover StIcon {
+ border-color: transparentize(if($variant == 'light', black, white), .8);
+ }
+
+ &:active StIcon {
+ border-color: transparentize(if($variant == 'light', black, white), .7);
+ }
+
+ &:checked StIcon {
+ background-color: $selected_bg_color;
+ color: $selected_fg_color;
+ border-color: transparent;
+ }
+
+ &:checked:hover StIcon {
+ background-color: lighten($selected_bg_color, 5%);
+ color: lighten($selected_fg_color, 5%);
+ }
+
+ &:checked:active StIcon {
+ background-color: darken($selected_bg_color, 7%);
+ color: darken($selected_fg_color, 7%);
}
- &:focus StBin { background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-focused-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off-focused.svg"));; }
- &:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox.svg"); }
- &:focus:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox-focused.svg"); }
}
diff --git a/data/theme/gnome-shell-sass/widgets/_switches.scss b/data/theme/gnome-shell-sass/widgets/_switches.scss
index ef8a7b636841329f7968f678a6b7c254f448ec75..28416b5c89e94f8c31927d6eb6ae6bfa0cc1a623 100644
--- a/data/theme/gnome-shell-sass/widgets/_switches.scss
+++ b/data/theme/gnome-shell-sass/widgets/_switches.scss
@@ -1,18 +1,53 @@
/* Switches */
-// these are equal to the size of the SVG assets
-$switch_height: 26px;
$switch_width: 46px;
+$switch_handle_size: 20px;
.toggle-switch {
- color: $fg_color;
- height: $switch_height;
width: $switch_width;
- background-size: contain;
- background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-off-light.svg"),url("resource:///org/gnome/shell/theme/toggle-off.svg"));
- &:checked {
- background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-on-light.svg"),url("resource:///org/gnome/shell/theme/toggle-on.svg"));
+ border-radius: $forced_circular_radius;
+ transition-duration: 100ms;
+ color: $fg_color;
+
+ @if $contrast == 'high' {
+ background: transparentize(if($variant == 'light', black, white), .7);
+
+ &:hover {
+ background: transparentize(if($variant == 'light', black, white), .6);
+ }
+ } @else {
+ background: transparentize(if($variant == 'light', black, white), .85);
+
+ &:hover {
+ background: transparentize(if($variant == 'light', black, white), .8);
+ }
+ }
+
+ StIcon {
+ icon-size: $base_icon_size;
+ }
+
+ .handle {
+ margin: 3px;
+ width: $switch_handle_size;
+ height: $switch_handle_size;
+ border-radius: $forced_circular_radius;
+ background: if($variant == 'light', white, mix(white, $bg_color, 80%));
+ box-shadow: 0 2px 4px transparentize(black, .8);
+ transition-duration: 100ms;
}
- & StIcon {icon-size: $base_icon_size;}
+ &:checked {
+ background: $selected_bg_color;
+ color: $selected_fg_color;
+
+ &:hover {
+ background-color: lighten($selected_bg_color, 5%);
+ color: lighten($selected_fg_color, 5%);
+ }
+
+ .handle {
+ background: white;
+ }
+ }
}
diff --git a/data/theme/toggle-off-light.svg b/data/theme/toggle-off-light.svg
deleted file mode 100644
index aa2385bcfa8da3d8dce9f01b2916676178284d13..0000000000000000000000000000000000000000
--- a/data/theme/toggle-off-light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/toggle-off.svg b/data/theme/toggle-off.svg
deleted file mode 100644
index 43cb59fc4c7ec10827392884b324da3cb00a92b7..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-light.svg b/data/theme/toggle-on-light.svg
deleted file mode 100644
index 778f922c8eeb92e0c0dedb6dbd2a9db20b2aad96..0000000000000000000000000000000000000000
--- a/data/theme/toggle-on-light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/data/theme/toggle-on.svg b/data/theme/toggle-on.svg
deleted file mode 100644
index 43c221ed7693840b36ace0d372dd337528e4f9cf..0000000000000000000000000000000000000000
--- a/data/theme/toggle-on.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/js/ui/checkBox.js b/js/ui/checkBox.js
index 4a5dbba7a89f85a43e3f5f7622a33c776e8a5880..398a88238852e81fba905d3c54c38dfbef66d92c 100644
--- a/js/ui/checkBox.js
+++ b/js/ui/checkBox.js
@@ -23,6 +23,11 @@ class CheckBox extends St.Button {
this._box = new St.Bin({y_align: Clutter.ActorAlign.START});
container.add_child(this._box);
+ this._check = new St.Icon({
+ icon_name: 'check-symbolic',
+ });
+ this._box.set_child(this._check);
+
this._label = new St.Label({y_align: Clutter.ActorAlign.CENTER});
this._label.clutter_text.set_line_wrap(true);
this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 84455ddc0c3d94d3624a217db7831d0fe11809d6..d502a7b9e13a556c5c1c5072f493699442a71e32 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -336,21 +336,27 @@ export const Switch = GObject.registerClass({
GObject.ParamFlags.READWRITE,
false),
},
-}, class Switch extends St.Bin {
+}, class Switch extends St.Widget {
_init(state) {
this._state = false;
-
- const box = new St.BoxLayout({
- x_expand: true,
- y_expand: true,
- });
+ this._dragging = false;
super._init({
style_class: 'toggle-switch',
- child: box,
accessible_role: Atk.Role.CHECK_BOX,
- state,
+ track_hover: true,
+ reactive: true,
+ });
+
+ const box = new St.BoxLayout({
+ x_expand: true,
+ y_expand: true,
+ constraints: new Clutter.BindConstraint({
+ source: this,
+ coordinate: Clutter.BindCoordinate.SIZE,
+ }),
});
+ this.add_child(box);
this._onIcon = new St.Icon({
icon_name: 'switch-on-symbolic',
@@ -368,6 +374,28 @@ export const Switch = GObject.registerClass({
});
box.add_child(this._offIcon);
+ this._handle = new St.Widget({
+ style_class: 'handle',
+ y_align: Clutter.ActorAlign.CENTER,
+ constraints: new Clutter.BindConstraint({
+ source: this,
+ coordinate: Clutter.BindCoordinate.HEIGHT,
+ }),
+ });
+ this.bind_property('reactive', this._handle, 'reactive', GObject.BindingFlags.SYNC_CREATE);
+
+ this._handleAlignConstraint = new Clutter.AlignConstraint({
+ name: 'align',
+ align_axis: Clutter.AlignAxis.X_AXIS,
+ source: this,
+ });
+ 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.state = state;
+
this._a11ySettings = new Gio.Settings({
schema_id: 'org.gnome.desktop.a11y.interface',
});
@@ -375,8 +403,6 @@ export const Switch = GObject.registerClass({
this._a11ySettings.connectObject('changed::show-status-shapes',
() => this._updateIconOpacity(),
this);
- this.connect('notify::state',
- () => this._updateIconOpacity());
this._updateIconOpacity();
}
@@ -384,10 +410,8 @@ export const Switch = GObject.registerClass({
const activeOpacity = this._a11ySettings.get_boolean('show-status-shapes')
? 255. : 0.;
- this._onIcon.opacity = this.state
- ? activeOpacity : 0.;
- this._offIcon.opacity = this.state
- ? 0. : activeOpacity;
+ this._onIcon.opacity = activeOpacity;
+ this._offIcon.opacity = activeOpacity;
}
get state() {
@@ -395,21 +419,116 @@ export const Switch = GObject.registerClass({
}
set state(state) {
- if (this._state === state)
- return;
+ let handleAlignFactor;
+ // Calling get_theme_node() while unmapped is an error, avoid that
+ 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._state = state;
- this.notify('state');
+ this._handle.ease_property('@constraints.align.factor', handleAlignFactor, {
+ duration,
+ });
+
+ 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();
+
+ 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({
@@ -423,7 +542,10 @@ export const PopupSwitchMenuItem = GObject.registerClass({
y_expand: true,
y_align: Clutter.ActorAlign.CENTER,
});
+
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.checkAccessibleState();
@@ -475,8 +597,6 @@ export const PopupSwitchMenuItem = GObject.registerClass({
toggle() {
this._switch.toggle();
- this.emit('toggled', this._switch.state);
- this.checkAccessibleState();
}
get state() {
@@ -488,6 +608,11 @@ export const 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: