diff --git a/README.md b/README.md index 7a1d93aa..cabc3997 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ You'll need the following dependencies: gobject-introspection libglib2.0-dev + libgtk-4-dev >=4.22 libgranite-7-dev >= 7.0.0 libwingpanel-9-dev meson >= 0.58.0 diff --git a/data/gresource.xml b/data/gresource.xml index a6a0c06a..4433612c 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -1,35 +1,6 @@ - indicator.css - - - icons/active.svg - icons/disabled.svg - icons/paired.svg - - icons/disabled-10.svg - icons/disabled-20.svg - icons/disabled-30.svg - icons/disabled-40.svg - icons/disabled-50.svg - icons/disabled-60.svg - icons/disabled-70.svg - icons/disabled-80.svg - icons/disabled-90.svg - - icons/active.svg - icons/disabled.svg - icons/paired.svg - - icons/disabled-10.svg - icons/disabled-20.svg - icons/disabled-30.svg - icons/disabled-40.svg - icons/disabled-50.svg - icons/disabled-60.svg - icons/disabled-70.svg - icons/disabled-80.svg - icons/disabled-90.svg + icons/bluetooth.svg diff --git a/data/icons/active.svg b/data/icons/active.svg deleted file mode 100644 index c6a05fbb..00000000 --- a/data/icons/active.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - diff --git a/data/icons/bluetooth.svg b/data/icons/bluetooth.svg new file mode 100644 index 00000000..e9932cdf --- /dev/null +++ b/data/icons/bluetooth.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + diff --git a/data/icons/disabled-10.svg b/data/icons/disabled-10.svg deleted file mode 100644 index e2b63b02..00000000 --- a/data/icons/disabled-10.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-20.svg b/data/icons/disabled-20.svg deleted file mode 100644 index 245d1343..00000000 --- a/data/icons/disabled-20.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-30.svg b/data/icons/disabled-30.svg deleted file mode 100644 index 07e96485..00000000 --- a/data/icons/disabled-30.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-40.svg b/data/icons/disabled-40.svg deleted file mode 100644 index f33f474e..00000000 --- a/data/icons/disabled-40.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-50.svg b/data/icons/disabled-50.svg deleted file mode 100644 index 2a85122f..00000000 --- a/data/icons/disabled-50.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-60.svg b/data/icons/disabled-60.svg deleted file mode 100644 index a43ed4e5..00000000 --- a/data/icons/disabled-60.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-70.svg b/data/icons/disabled-70.svg deleted file mode 100644 index b57f479f..00000000 --- a/data/icons/disabled-70.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/disabled-80.svg b/data/icons/disabled-80.svg deleted file mode 100644 index 2bb7b89a..00000000 --- a/data/icons/disabled-80.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/data/icons/disabled-90.svg b/data/icons/disabled-90.svg deleted file mode 100644 index e2601b86..00000000 --- a/data/icons/disabled-90.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/data/icons/disabled.svg b/data/icons/disabled.svg deleted file mode 100644 index 76e74907..00000000 --- a/data/icons/disabled.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/data/icons/paired.svg b/data/icons/paired.svg deleted file mode 100644 index ac6b6a3c..00000000 --- a/data/icons/paired.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/data/indicator.css b/data/indicator.css deleted file mode 100644 index 0bdc8cc6..00000000 --- a/data/indicator.css +++ /dev/null @@ -1,50 +0,0 @@ -/* -* Copyright 2017 elementary, Inc. (https://elementary.io) -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public -* License as published by the Free Software Foundation; either -* version 2 of the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA. -*/ - -.bluetooth-icon { - animation: none; - min-width: 24px; - opacity: 1; - transition: none; - -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-active-symbolic"); -} - -.bluetooth-icon.paired { - animation: none; - -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-paired-symbolic"); -} - -.bluetooth-icon.disabled { - animation: bluetooth-disabled 160ms cubic-bezier(0.4, 0.0, 0.2, 1); - -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-symbolic"); -} - -@keyframes bluetooth-disabled { - 0% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-active-symbolic"); } - 10% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-10-symbolic"); } - 20% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-20-symbolic"); } - 30% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-30-symbolic"); } - 40% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-40-symbolic"); } - 50% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-50-symbolic"); } - 60% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-60-symbolic"); } - 70% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-70-symbolic"); } - 80% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-80-symbolic"); } - 90% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-90-symbolic"); } - 100% { -gtk-icon-source: -gtk-icontheme("indicator-bluetooth-disabled-symbolic"); } -} diff --git a/meson.build b/meson.build index 837146f9..18b1da9a 100644 --- a/meson.build +++ b/meson.build @@ -47,13 +47,14 @@ shared_module( 'src/Widgets/Device.vala', 'src/Widgets/DisplayWidget.vala', 'src/Widgets/PopoverWidget.vala', + 'src' / 'Widgets' / 'Symbol.vala', config_file, gresource, dependencies: [ dependency('glib-2.0'), dependency('gobject-2.0'), dependency('granite-7', version: '>=7.0.0'), - dependency('gtk4'), + dependency('gtk4', version: '>=4.22'), wingpanel_dep ], install: true, diff --git a/src/Widgets/DisplayWidget.vala b/src/Widgets/DisplayWidget.vala index 21ad3156..a61f585a 100644 --- a/src/Widgets/DisplayWidget.vala +++ b/src/Widgets/DisplayWidget.vala @@ -6,28 +6,18 @@ public class BluetoothIndicator.Widgets.DisplayWidget : Granite.Bin { public BluetoothIndicator.Services.ObjectManager object_manager { get; construct; } - private Gtk.Spinner spinner; + private Granite.Symbol symbol; public DisplayWidget (BluetoothIndicator.Services.ObjectManager object_manager) { Object (object_manager: object_manager); } construct { - spinner = new Gtk.Spinner (); - - child = spinner; - - // Prevent a race that skips automatic resource loading - // https://github.com/elementary/panel-bluetooth/issues/203 - Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()).add_resource_path ("/org/elementary/wingpanel/icons"); - - var provider = new Gtk.CssProvider (); - provider.load_from_resource ("io/elementary/wingpanel/bluetooth/indicator.css"); - - Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + symbol = new Granite.Symbol ("/io/elementary/wingpanel/bluetooth/icons/bluetooth.svg") { + pixel_size = 24 + }; - spinner.add_css_class ("bluetooth-icon"); - spinner.add_css_class ("disabled"); + child = symbol; object_manager.global_state_changed.connect ((state, connected) => { set_icon (); @@ -68,18 +58,16 @@ public class BluetoothIndicator.Widgets.DisplayWidget : Granite.Bin { string context; if (state) { - spinner.remove_css_class ("disabled"); context = _("Middle-click to turn Bluetooth off"); if (connected) { - spinner.add_css_class ("paired"); + symbol.state = Granite.SymbolState.ACTIVE; description = _("Bluetooth connected"); } else { - spinner.remove_css_class ("paired"); + symbol.state = Granite.SymbolState.NORMAL; description = _("Bluetooth is on"); } } else { - spinner.remove_css_class ("paired"); - spinner.add_css_class ("disabled"); + symbol.state = Granite.SymbolState.DISABLED; description = _("Bluetooth is off"); context = _("Middle-click to turn Bluetooth on"); } diff --git a/src/Widgets/Symbol.vala b/src/Widgets/Symbol.vala new file mode 100644 index 00000000..dbc77b5e --- /dev/null +++ b/src/Widgets/Symbol.vala @@ -0,0 +1,78 @@ +/* +* SPDX-License-Identifier: LGPL-2.1-or-later +* SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) +*/ + +namespace Granite.SymbolState { + // The default state + public const string NORMAL = "normal"; + // Disabled state represented by a slash + public const string DISABLED = "disabled"; + // e.g. paired, connected, needs attention + public const string ACTIVE = "active"; +} + +public class Granite.Symbol : Granite.Bin { + public string resource_path { get; construct; } + + public int pixel_size { + get { return image.pixel_size; } + set { image.pixel_size = value; } + } + + public uint state_index { + get { return svg.state; } + set { + uint length = -1; + svg.get_state_names (out length); + + if (value > length - 1) { + warning ("Granite.Symbol set to undefined state. Ignoring."); + return; + } + + svg.state = value; + notify_property ("state"); + } + } + + public string state { + get { + uint length = -1; + return svg.get_state_names (out length)[state_index]; + } + set { + uint length = -1; + var names = svg.get_state_names (out length); + + for (int i = 0; i < length; i++) { + if (names[i] == value) { + state_index = i; + return; + } + } + + warning ("Granite.Symbol set to undefined state. Ignoring."); + } + } + + private Gtk.Image image; + private Gtk.Svg svg; + + public Symbol (string resource_path) { + Object (resource_path: resource_path); + } + + construct { + svg = new Gtk.Svg.from_resource (resource_path); + + image = new Gtk.Image.from_paintable (svg); + + child = image; + + child.realize.connect (() => { + svg.set_frame_clock (get_frame_clock ()); + svg.play (); + }); + } +}