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 @@ - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - 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 @@ - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - 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: