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