diff --git a/demo/GraniteDemo.vala b/demo/GraniteDemo.vala index 9e4d8d305..ae1476b1e 100644 --- a/demo/GraniteDemo.vala +++ b/demo/GraniteDemo.vala @@ -26,6 +26,7 @@ public class Granite.Demo : Gtk.Application { var overlaybar_view = new OverlayBarView (); var toast_view = new ToastView (); var settings_uris_view = new SettingsUrisView (); + var style_manager_view = new StyleManagerView (); var utils_view = new UtilsView (); var placeholder = new WelcomeView (); var dialogs_view = new DialogsView (window); @@ -33,6 +34,7 @@ public class Granite.Demo : Gtk.Application { var main_stack = new Gtk.Stack (); main_stack.add_titled (placeholder, "placeholder", "Placeholder"); + main_stack.add_titled (style_manager_view, "style_manager", "StyleManager"); main_stack.add_titled (accel_label_view, "accel_label", "AccelLabel"); main_stack.add_titled (css_view, "css", "Style Classes"); main_stack.add_titled (date_time_picker_view, "pickers", "Date & Time"); @@ -46,24 +48,11 @@ public class Granite.Demo : Gtk.Application { main_stack.add_titled (dialogs_view, "dialogs", "Dialogs"); main_stack.add_titled (application_view, "application", "Application"); - var gtk_settings = Gtk.Settings.get_default (); - - var mode_switch = new Granite.ModeSwitch.from_icon_name ( - "display-brightness-symbolic", - "weather-clear-night-symbolic" - ) { - primary_icon_tooltip_text = ("Light background"), - secondary_icon_tooltip_text = ("Dark background"), - valign = CENTER - }; - mode_switch.bind_property ("active", gtk_settings, "gtk-application-prefer-dark-theme", BIDIRECTIONAL); - var end_header = new Gtk.HeaderBar () { show_title_buttons = false }; end_header.add_css_class (Granite.STYLE_CLASS_FLAT); end_header.pack_end (new Gtk.WindowControls (END)); - end_header.pack_end (mode_switch); var end_box = new Gtk.Box (VERTICAL, 0); end_box.append (end_header); @@ -94,9 +83,6 @@ public class Granite.Demo : Gtk.Application { shrink_start_child = false }; - var granite_settings = Granite.Settings.get_default (); - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - window.child = paned; window.set_default_size (900, 600); window.set_size_request (750, 500); @@ -105,10 +91,6 @@ public class Granite.Demo : Gtk.Application { add_window (window); window.show (); - - granite_settings.notify["prefers-color-scheme"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - }); } public static int main (string[] args) { diff --git a/demo/Views/StyleManagerView.vala b/demo/Views/StyleManagerView.vala new file mode 100644 index 000000000..e00720044 --- /dev/null +++ b/demo/Views/StyleManagerView.vala @@ -0,0 +1,51 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +public class StyleManagerView : Gtk.Box { + construct { + var label = new Granite.HeaderLabel ("Visual Style"); + + var dont_button = new Gtk.CheckButton.with_label ("Follow system setting") { + active = true + }; + + var force_light = new Gtk.CheckButton.with_label ("Light") { + group = dont_button + }; + + var force_dark = new Gtk.CheckButton.with_label ("Dark") { + group = force_light + }; + + halign = CENTER; + valign = CENTER; + orientation = VERTICAL; + spacing = 6; + append (label); + append (dont_button); + append (force_light); + append (force_dark); + + var style_manager = Granite.StyleManager.get_default (); + + dont_button.toggled.connect (() => { + if (dont_button.active) { + style_manager.color_scheme = NO_PREFERENCE; + } + }); + + force_light.toggled.connect (() => { + if (force_light.active) { + style_manager.color_scheme = LIGHT; + } + }); + + force_dark.toggled.connect (() => { + if (force_dark.active) { + style_manager.color_scheme = DARK; + } + }); + } +} diff --git a/demo/meson.build b/demo/meson.build index 744f89193..bc27e08f1 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -13,6 +13,7 @@ executable( 'Views/ModeButtonView.vala', 'Views/OverlayBarView.vala', 'Views/SettingsUrisView.vala', + 'Views/StyleManagerView.vala', 'Views/ToastView.vala', 'Views/UtilsView.vala', 'Views/WelcomeView.vala', diff --git a/lib/Init.vala b/lib/Init.vala index 98472c582..ffc13197e 100644 --- a/lib/Init.vala +++ b/lib/Init.vala @@ -5,9 +5,6 @@ namespace Granite { private static bool initialized = false; - private static Gtk.CssProvider? base_provider = null; - private static Gtk.CssProvider? dark_provider = null; - private static Gtk.CssProvider? app_provider = null; /** * Initializes Granite. @@ -24,77 +21,14 @@ namespace Granite { typeof (Granite.Settings).ensure (); unowned var display_manager = Gdk.DisplayManager.@get (); - display_manager.display_opened.connect (register_display); + display_manager.display_opened.connect (StyleManager.init_for_display); foreach (unowned var display in display_manager.list_displays ()) { - register_display (display); + StyleManager.init_for_display (display); } GLib.Intl.bindtextdomain (Granite.GETTEXT_PACKAGE, Granite.LOCALEDIR); GLib.Intl.bind_textdomain_codeset (Granite.GETTEXT_PACKAGE, "UTF-8"); initialized = true; } - - private static void register_display (Gdk.Display display) { - var gtk_settings = Gtk.Settings.get_for_display (display); - gtk_settings.notify["gtk-application-prefer-dark-theme"].connect (() => { - set_provider_for_display (display, gtk_settings.gtk_application_prefer_dark_theme); - }); - - set_provider_for_display (display, gtk_settings.gtk_application_prefer_dark_theme); - - var icon_theme = Gtk.IconTheme.get_for_display (display); - icon_theme.add_resource_path ("/io/elementary/granite"); - } - - private static void set_provider_for_display (Gdk.Display display, bool prefer_dark_style) { - if (app_provider == null) { - var base_path = Application.get_default ().resource_base_path; - if (base_path != null) { - var base_uri = "resource://" + base_path; - var base_file = File.new_for_uri (base_uri); - - app_provider = init_provider_from_file (base_file.get_child ("Application.css")); - } - - if (app_provider != null) { - Gtk.StyleContext.add_provider_for_display (display, app_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - } - } - - if (prefer_dark_style) { - if (base_provider != null) { - Gtk.StyleContext.remove_provider_for_display (display, base_provider); - } - - if (dark_provider == null) { - dark_provider = new Gtk.CssProvider (); - dark_provider.load_from_resource ("/io/elementary/granite/Granite-dark.css"); - } - - Gtk.StyleContext.add_provider_for_display (display, dark_provider, Gtk.STYLE_PROVIDER_PRIORITY_THEME); - } else { - if (dark_provider != null) { - Gtk.StyleContext.remove_provider_for_display (display, dark_provider); - } - - if (base_provider == null) { - base_provider = new Gtk.CssProvider (); - base_provider.load_from_resource ("/io/elementary/granite/Granite.css"); - } - - Gtk.StyleContext.add_provider_for_display (display, base_provider, Gtk.STYLE_PROVIDER_PRIORITY_THEME); - } - } - - private static Gtk.CssProvider? init_provider_from_file (File file) { - if (file.query_exists ()) { - var provider = new Gtk.CssProvider (); - provider.load_from_file (file); - - return provider; - } - - return null; - } } diff --git a/lib/StyleManager.vala b/lib/StyleManager.vala new file mode 100644 index 000000000..1b0cc92c3 --- /dev/null +++ b/lib/StyleManager.vala @@ -0,0 +1,130 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/** + * A class for managing the style of the application. This handles switching light and dark mode based + * based on system preference or application preference (see {@link color_scheme}), etc. + */ +public class Granite.StyleManager : Object { + private static Gtk.CssProvider? base_provider = null; + private static Gtk.CssProvider? dark_provider = null; + private static Gtk.CssProvider? app_provider = null; + + private static HashTable? style_managers_by_displays; + + /** + * Returns the {@link Granite.StyleManager} that handles the default display + * as gotten by {@link Gdk.Display.get_default ()}. + */ + public static unowned StyleManager get_default () { + return style_managers_by_displays[Gdk.Display.get_default ()]; + } + + /** + * Returns the {@link Granite.StyleManager} that handles the given {@link Gdk.Display}. + */ + public static unowned StyleManager get_for_display (Gdk.Display display) { + return style_managers_by_displays[display]; + } + + internal static void init_for_display (Gdk.Display display) { + if (style_managers_by_displays == null) { + style_managers_by_displays = new HashTable (null, null); + } + + style_managers_by_displays[display] = new StyleManager (display); + } + + /** + * The {@link Granite.Settings.ColorScheme} requested by the application + * Uses value from {@link Granite.Settings.prefers_color_scheme} when set to {@link Granite.Settings.ColorScheme.NO_PREFERENCE }. + * Default value is {@link Granite.Settings.ColorScheme.NO_PREFERENCE } + */ + public Settings.ColorScheme color_scheme { get; set; default = NO_PREFERENCE; } + + /** + * The {@link Gdk.Display} handled by #this. + */ + public Gdk.Display display { get; construct; } + + private StyleManager (Gdk.Display display) { + Object (display: display); + } + + construct { + var gtk_settings = Gtk.Settings.get_for_display (display); + gtk_settings.notify["gtk-application-prefer-dark-theme"].connect (set_provider_for_display); + set_provider_for_display (); + + var granite_settings = Granite.Settings.get_default (); + granite_settings.notify["prefers-color-scheme"].connect (update_color_scheme); + notify["color-scheme"].connect (update_color_scheme); + update_color_scheme (); + + var icon_theme = Gtk.IconTheme.get_for_display (display); + icon_theme.add_resource_path ("/io/elementary/granite"); + } + + private void set_provider_for_display () { + if (app_provider == null) { + var base_path = Application.get_default ().resource_base_path; + if (base_path != null) { + var base_uri = "resource://" + base_path; + var base_file = File.new_for_uri (base_uri); + + app_provider = init_provider_from_file (base_file.get_child ("Application.css")); + } + + if (app_provider != null) { + Gtk.StyleContext.add_provider_for_display (display, app_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + } + + if (Gtk.Settings.get_for_display (display).gtk_application_prefer_dark_theme) { + if (base_provider != null) { + Gtk.StyleContext.remove_provider_for_display (display, base_provider); + } + + if (dark_provider == null) { + dark_provider = new Gtk.CssProvider (); + dark_provider.load_from_resource ("/io/elementary/granite/Granite-dark.css"); + } + + Gtk.StyleContext.add_provider_for_display (display, dark_provider, Gtk.STYLE_PROVIDER_PRIORITY_THEME); + } else { + if (dark_provider != null) { + Gtk.StyleContext.remove_provider_for_display (display, dark_provider); + } + + if (base_provider == null) { + base_provider = new Gtk.CssProvider (); + base_provider.load_from_resource ("/io/elementary/granite/Granite.css"); + } + + Gtk.StyleContext.add_provider_for_display (display, base_provider, Gtk.STYLE_PROVIDER_PRIORITY_THEME); + } + } + + private Gtk.CssProvider? init_provider_from_file (File file) { + if (file.query_exists ()) { + var provider = new Gtk.CssProvider (); + provider.load_from_file (file); + + return provider; + } + + return null; + } + + private void update_color_scheme () { + var gtk_settings = Gtk.Settings.get_for_display (display); + if (color_scheme == NO_PREFERENCE) { + var granite_settings = Granite.Settings.get_default (); + gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == DARK; + } else { + gtk_settings.gtk_application_prefer_dark_theme = color_scheme == DARK; + } + } +} diff --git a/lib/meson.build b/lib/meson.build index e92fd7881..eacc07550 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -5,6 +5,7 @@ libgranite_sources = files( 'DateTime.vala', 'Constants.vala', 'Init.vala', + 'StyleManager.vala', 'Services/Application.vala', 'Services/AsyncMutex.vala',