From e75839bd5dc865b64dab733819857c20a7a5a3d6 Mon Sep 17 00:00:00 2001 From: Alice Mikhaylenko Date: Thu, 23 Mar 2023 02:25:40 +0400 Subject: [PATCH 1/5] checkBox: Reimplement styles in CSS Stop using SVG assets other than the actual check icon. This will help making it recolorable. Part-of: --- data/gnome-shell-icons.gresource.xml | 1 + data/gnome-shell-theme.gresource.xml | 6 - data/icons/scalable/status/check-symbolic.svg | 1 + data/theme/checkbox-focused.svg | 1 - data/theme/checkbox-off-focused-light.svg | 220 ------------------ data/theme/checkbox-off-focused.svg | 1 - data/theme/checkbox-off-light.svg | 211 ----------------- data/theme/checkbox-off.svg | 1 - data/theme/checkbox.svg | 1 - .../gnome-shell-sass/widgets/_check-box.scss | 53 ++++- js/ui/checkBox.js | 5 + 11 files changed, 49 insertions(+), 452 deletions(-) create mode 100644 data/icons/scalable/status/check-symbolic.svg delete mode 100644 data/theme/checkbox-focused.svg delete mode 100644 data/theme/checkbox-off-focused-light.svg delete mode 100644 data/theme/checkbox-off-focused.svg delete mode 100644 data/theme/checkbox-off-light.svg delete mode 100644 data/theme/checkbox-off.svg delete mode 100644 data/theme/checkbox.svg diff --git a/data/gnome-shell-icons.gresource.xml b/data/gnome-shell-icons.gresource.xml index db65673b41..6ace8f0f10 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 4501c3195f..b23b960e93 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 diff --git a/data/icons/scalable/status/check-symbolic.svg b/data/icons/scalable/status/check-symbolic.svg new file mode 100644 index 0000000000..27c620d499 --- /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 01eb733dca..0000000000 --- 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 47673776dd..0000000000 --- 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 d5a042e97f..0000000000 --- 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 5d37c85d60..0000000000 --- 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 50eece1b02..0000000000 --- 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 da385b6281..0000000000 --- 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 1480ade265..f18d6418cf 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/js/ui/checkBox.js b/js/ui/checkBox.js index 4a5dbba7a8..398a882388 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); -- GitLab From 259eaa9bbb3a1cb589b7324e5e928a6d1fa619f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ruiz=20de=20Alegr=C3=ADa?= Date: Tue, 7 May 2019 15:20:59 +0200 Subject: [PATCH 2/5] switch: Style switches with pure CSS Drop hardcoded assets, have a handle actor instead, change its align to move it. Part-of: --- data/gnome-shell-theme.gresource.xml | 4 -- .../gnome-shell-sass/widgets/_switches.scss | 40 ++++++++++++---- data/theme/toggle-off-light.svg | 1 - data/theme/toggle-off.svg | 1 - data/theme/toggle-on-light.svg | 1 - data/theme/toggle-on.svg | 1 - js/ui/popupMenu.js | 48 ++++++++++++------- 7 files changed, 63 insertions(+), 33 deletions(-) delete mode 100644 data/theme/toggle-off-light.svg delete mode 100644 data/theme/toggle-off.svg delete mode 100644 data/theme/toggle-on-light.svg delete mode 100644 data/theme/toggle-on.svg diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml index b23b960e93..db1f4e91d1 100644 --- a/data/gnome-shell-theme.gresource.xml +++ b/data/gnome-shell-theme.gresource.xml @@ -10,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/theme/gnome-shell-sass/widgets/_switches.scss b/data/theme/gnome-shell-sass/widgets/_switches.scss index ef8a7b6368..31e4de05ab 100644 --- a/data/theme/gnome-shell-sass/widgets/_switches.scss +++ b/data/theme/gnome-shell-sass/widgets/_switches.scss @@ -1,18 +1,40 @@ /* 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); + } @else { + background: transparentize(if($variant == 'light', black, white), .85); + } + + StIcon { + icon-size: $base_icon_size; } - & 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; + } + + &:checked { + background: $selected_bg_color; + color: $selected_fg_color; + + .handle { + background: white; + } + } } diff --git a/data/theme/toggle-off-light.svg b/data/theme/toggle-off-light.svg deleted file mode 100644 index aa2385bcfa..0000000000 --- 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 43cb59fc4c..0000000000 --- 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 778f922c8e..0000000000 --- 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 43c221ed76..0000000000 --- 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 84455ddc0c..a37484bbda 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -336,22 +336,25 @@ 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, - }); - super._init({ style_class: 'toggle-switch', - child: box, accessible_role: Atk.Role.CHECK_BOX, - state, }); + 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', x_expand: true, @@ -368,6 +371,20 @@ export const Switch = GObject.registerClass({ }); box.add_child(this._offIcon); + this._handle = new St.Widget({ + style_class: 'handle', + y_align: Clutter.ActorAlign.CENTER, + x_align: Clutter.ActorAlign.START, + x_expand: true, + constraints: new Clutter.BindConstraint({ + source: this, + coordinate: Clutter.BindCoordinate.SIZE, + }), + }); + this.add_child(this._handle); + + this.state = state; + this._a11ySettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.a11y.interface', }); @@ -375,8 +392,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 +399,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() { @@ -398,10 +411,13 @@ export const Switch = GObject.registerClass({ if (this._state === state) return; - if (state) + if (state) { this.add_style_pseudo_class('checked'); - else + this._handle.x_align = Clutter.ActorAlign.END; + } else { this.remove_style_pseudo_class('checked'); + this._handle.x_align = Clutter.ActorAlign.START; + } this._state = state; this.notify('state'); -- GitLab From cca34af31eda6538c657b6d6687aac39d08ea10c Mon Sep 17 00:00:00 2001 From: Alice Mikhaylenko Date: Tue, 18 Jun 2024 21:39:06 +0400 Subject: [PATCH 3/5] switch: Add hover styles Same as checkboxes. Closes https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6455 Part-of: --- data/theme/gnome-shell-sass/widgets/_switches.scss | 13 +++++++++++++ js/ui/popupMenu.js | 1 + 2 files changed, 14 insertions(+) diff --git a/data/theme/gnome-shell-sass/widgets/_switches.scss b/data/theme/gnome-shell-sass/widgets/_switches.scss index 31e4de05ab..28416b5c89 100644 --- a/data/theme/gnome-shell-sass/widgets/_switches.scss +++ b/data/theme/gnome-shell-sass/widgets/_switches.scss @@ -11,8 +11,16 @@ $switch_handle_size: 20px; @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 { @@ -33,6 +41,11 @@ $switch_handle_size: 20px; 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/js/ui/popupMenu.js b/js/ui/popupMenu.js index a37484bbda..bc98be2fc1 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -343,6 +343,7 @@ export const Switch = GObject.registerClass({ super._init({ style_class: 'toggle-switch', accessible_role: Atk.Role.CHECK_BOX, + track_hover: true, }); const box = new St.BoxLayout({ -- GitLab From 71f2355b8adc2d47ed9448a255c2b6ae7432949b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ruiz=20de=20Alegr=C3=ADa?= Date: Tue, 7 May 2019 16:23:11 +0200 Subject: [PATCH 4/5] switch: Animate transitions Use a constraint align instead of :x-align to animate it. Match GTK duration. Part-of: --- js/ui/popupMenu.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index bc98be2fc1..1b52197534 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -375,13 +375,17 @@ export const Switch = GObject.registerClass({ this._handle = new St.Widget({ style_class: 'handle', y_align: Clutter.ActorAlign.CENTER, - x_align: Clutter.ActorAlign.START, - x_expand: true, constraints: new Clutter.BindConstraint({ source: this, - coordinate: Clutter.BindCoordinate.SIZE, + coordinate: Clutter.BindCoordinate.HEIGHT, }), }); + this._handleAlignConstraint = new Clutter.AlignConstraint({ + name: 'align', + align_axis: Clutter.AlignAxis.X_AXIS, + source: this, + }); + this._handle.add_constraint(this._handleAlignConstraint); this.add_child(this._handle); this.state = state; @@ -412,14 +416,24 @@ export const Switch = GObject.registerClass({ 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) { this.add_style_pseudo_class('checked'); - this._handle.x_align = Clutter.ActorAlign.END; + handleAlignFactor = 1.0; } else { this.remove_style_pseudo_class('checked'); - this._handle.x_align = Clutter.ActorAlign.START; + handleAlignFactor = 0.0; } + this._handle.ease_property('@constraints.align.factor', handleAlignFactor, { + duration, + }); + this._state = state; this.notify('state'); } -- GitLab From 946ee936926a0e14fa5f86f62da757bcb581a147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ruiz=20de=20Alegr=C3=ADa?= Date: Tue, 21 Jan 2020 11:30:42 +0100 Subject: [PATCH 5/5] switch: Make handle draggable Listen to state changes for switch menu items, since they can also be changed without activating them now. Part-of: --- js/ui/popupMenu.js | 108 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 1b52197534..d502a7b9e1 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -339,11 +339,13 @@ export const Switch = GObject.registerClass({ }, class Switch extends St.Widget { _init(state) { this._state = false; + this._dragging = false; super._init({ style_class: 'toggle-switch', accessible_role: Atk.Role.CHECK_BOX, track_hover: true, + reactive: true, }); const box = new St.BoxLayout({ @@ -380,12 +382,16 @@ export const Switch = GObject.registerClass({ 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; @@ -413,9 +419,6 @@ 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 @@ -434,13 +437,98 @@ export const Switch = GObject.registerClass({ 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(); + + 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({ @@ -454,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(); @@ -506,8 +597,6 @@ export const PopupSwitchMenuItem = GObject.registerClass({ toggle() { this._switch.toggle(); - this.emit('toggled', this._switch.state); - this.checkAccessibleState(); } get state() { @@ -519,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: -- GitLab