/* bz-group-tile-css-watcher.c
*
* Copyright 2025 Adam Masciola
*
* 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 3 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, see .
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include "bz-group-tile-css-watcher.h"
#define LUMINANCE_THRESHOLD 130.0
struct _BzGroupTileCssWatcher
{
GObject parent_instance;
GWeakRef widget;
BzEntryGroup *group;
GtkCssProvider *css;
char *light_class;
char *dark_class;
char *light_text_class;
char *dark_text_class;
};
G_DEFINE_FINAL_TYPE (BzGroupTileCssWatcher, bz_group_tile_css_watcher, G_TYPE_OBJECT);
enum
{
PROP_0,
PROP_WIDGET,
PROP_GROUP,
LAST_PROP
};
static GParamSpec *props[LAST_PROP] = { 0 };
static void
refresh (BzGroupTileCssWatcher *self);
static void
clear (BzGroupTileCssWatcher *self);
static void
bz_group_tile_css_watcher_dispose (GObject *object)
{
BzGroupTileCssWatcher *self = BZ_GROUP_TILE_CSS_WATCHER (object);
clear (self);
g_weak_ref_clear (&self->widget);
g_clear_pointer (&self->group, g_object_unref);
G_OBJECT_CLASS (bz_group_tile_css_watcher_parent_class)->dispose (object);
}
static void
bz_group_tile_css_watcher_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
BzGroupTileCssWatcher *self = BZ_GROUP_TILE_CSS_WATCHER (object);
switch (prop_id)
{
case PROP_WIDGET:
g_value_take_object (value, bz_group_tile_css_watcher_dup_widget (self));
break;
case PROP_GROUP:
g_value_set_object (value, bz_group_tile_css_watcher_get_group (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
bz_group_tile_css_watcher_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
BzGroupTileCssWatcher *self = BZ_GROUP_TILE_CSS_WATCHER (object);
switch (prop_id)
{
case PROP_WIDGET:
bz_group_tile_css_watcher_set_widget (self, g_value_get_object (value));
break;
case PROP_GROUP:
bz_group_tile_css_watcher_set_group (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
bz_group_tile_css_watcher_class_init (BzGroupTileCssWatcherClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = bz_group_tile_css_watcher_set_property;
object_class->get_property = bz_group_tile_css_watcher_get_property;
object_class->dispose = bz_group_tile_css_watcher_dispose;
props[PROP_WIDGET] =
g_param_spec_object (
"widget",
NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_GROUP] =
g_param_spec_object (
"group",
NULL, NULL,
BZ_TYPE_ENTRY_GROUP,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, props);
}
static void
dark_changed (BzGroupTileCssWatcher *self,
GParamSpec *pspec,
AdwStyleManager *mgr)
{
g_autoptr (GtkWidget) widget = NULL;
gboolean is_dark;
if (self->css == NULL)
return;
widget = g_weak_ref_get (&self->widget);
if (widget == NULL)
return;
is_dark = adw_style_manager_get_dark (adw_style_manager_get_default ());
gtk_widget_remove_css_class (widget, self->light_class);
gtk_widget_remove_css_class (widget, self->dark_class);
gtk_widget_remove_css_class (widget, self->light_text_class);
gtk_widget_remove_css_class (widget, self->dark_text_class);
gtk_widget_add_css_class (widget, is_dark ? self->dark_class : self->light_class);
gtk_widget_add_css_class (widget, is_dark ? self->dark_text_class : self->light_text_class);
}
static void
bz_group_tile_css_watcher_init (BzGroupTileCssWatcher *self)
{
g_weak_ref_init (&self->widget, NULL);
g_signal_connect_object (
adw_style_manager_get_default (),
"notify::dark",
G_CALLBACK (dark_changed),
self,
G_CONNECT_SWAPPED);
}
BzGroupTileCssWatcher *
bz_group_tile_css_watcher_new (void)
{
return g_object_new (BZ_TYPE_GROUP_TILE_CSS_WATCHER, NULL);
}
GtkWidget *
bz_group_tile_css_watcher_dup_widget (BzGroupTileCssWatcher *self)
{
g_return_val_if_fail (BZ_IS_GROUP_TILE_CSS_WATCHER (self), NULL);
return g_weak_ref_get (&self->widget);
}
BzEntryGroup *
bz_group_tile_css_watcher_get_group (BzGroupTileCssWatcher *self)
{
g_return_val_if_fail (BZ_IS_GROUP_TILE_CSS_WATCHER (self), NULL);
return self->group;
}
void
bz_group_tile_css_watcher_set_widget (BzGroupTileCssWatcher *self,
GtkWidget *widget)
{
g_return_if_fail (BZ_IS_GROUP_TILE_CSS_WATCHER (self));
g_weak_ref_clear (&self->widget);
if (widget != NULL)
g_weak_ref_init (&self->widget, widget);
refresh (self);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_WIDGET]);
}
void
bz_group_tile_css_watcher_set_group (BzGroupTileCssWatcher *self,
BzEntryGroup *group)
{
g_return_if_fail (BZ_IS_GROUP_TILE_CSS_WATCHER (self));
g_clear_pointer (&self->group, g_object_unref);
if (group != NULL)
self->group = g_object_ref (group);
refresh (self);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_GROUP]);
}
static gdouble
get_luminance (GdkRGBA *rgba)
{
return (0.299 * rgba->red * 255.0) +
(0.587 * rgba->green * 255.0) +
(0.114 * rgba->blue * 255.0);
}
static gboolean
color_is_light (const char *hex_color)
{
GdkRGBA rgba;
gdouble luminance;
if (hex_color == NULL || !gdk_rgba_parse (&rgba, hex_color))
return FALSE;
luminance = get_luminance (&rgba);
return luminance > LUMINANCE_THRESHOLD;
}
static void
refresh (BzGroupTileCssWatcher *self)
{
g_autoptr (GtkWidget) widget = NULL;
const char *id = NULL;
const char *light_accent_color = NULL;
const char *dark_accent_color = NULL;
clear (self);
widget = g_weak_ref_get (&self->widget);
if (self->group == NULL ||
widget == NULL)
return;
id = bz_entry_group_get_id (self->group);
light_accent_color = bz_entry_group_get_light_accent_color (self->group);
dark_accent_color = bz_entry_group_get_dark_accent_color (self->group);
if (light_accent_color != NULL ||
dark_accent_color != NULL)
{
g_autoptr (GString) fixed_id = NULL;
g_autofree char *css_string = NULL;
gboolean is_dark;
fixed_id = g_string_new (id);
g_string_replace (fixed_id, ".", "--", 0);
self->light_class = g_strdup_printf ("%s-light", fixed_id->str);
self->dark_class = g_strdup_printf ("%s-dark", fixed_id->str);
self->light_text_class = g_strdup (
color_is_light (light_accent_color != NULL ? light_accent_color : dark_accent_color)
? "flathub-gunmetal"
: "flathub-lotion");
self->dark_text_class = g_strdup (
color_is_light (dark_accent_color != NULL ? dark_accent_color : light_accent_color)
? "flathub-gunmetal"
: "flathub-lotion");
css_string = g_strdup_printf (
".%s{background-color:%s;}\n"
".%s{background-color:%s;}",
self->light_class,
light_accent_color != NULL ? light_accent_color : dark_accent_color,
self->dark_class,
dark_accent_color != NULL ? dark_accent_color : light_accent_color);
self->css = gtk_css_provider_new ();
gtk_css_provider_load_from_string (
self->css, css_string);
gtk_style_context_add_provider_for_display (
gdk_display_get_default (),
GTK_STYLE_PROVIDER (self->css),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
is_dark = adw_style_manager_get_dark (adw_style_manager_get_default ());
gtk_widget_add_css_class (widget, is_dark ? self->dark_class : self->light_class);
gtk_widget_add_css_class (widget, is_dark ? self->dark_text_class : self->light_text_class);
}
}
static void
clear (BzGroupTileCssWatcher *self)
{
g_autoptr (GtkWidget) widget = NULL;
widget = g_weak_ref_get (&self->widget);
if (widget != NULL)
{
if (self->light_class != NULL)
gtk_widget_remove_css_class (widget, self->light_class);
if (self->dark_class != NULL)
gtk_widget_remove_css_class (widget, self->dark_class);
if (self->light_text_class != NULL)
gtk_widget_remove_css_class (widget, self->light_text_class);
if (self->dark_text_class != NULL)
gtk_widget_remove_css_class (widget, self->dark_text_class);
}
g_clear_pointer (&self->light_class, g_free);
g_clear_pointer (&self->dark_class, g_free);
g_clear_pointer (&self->light_text_class, g_free);
g_clear_pointer (&self->dark_text_class, g_free);
if (self->css != NULL)
gtk_style_context_remove_provider_for_display (
gdk_display_get_default (),
GTK_STYLE_PROVIDER (self->css));
g_clear_pointer (&self->css, g_object_unref);
}
/* End of bz-group-tile-css-watcher.c */