/* * Copyright (C) 2017 Richard Hughes * * Licensed under the GNU General Public License Version 2 * * 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. */ #include "config.h" #include #include #include "cc-night-light-widget.h" #include "cc-display-resources.h" struct _CcNightLightWidget { GtkDrawingArea parent; gdouble to; gdouble from; gdouble now; cairo_surface_t *surface_sunrise; cairo_surface_t *surface_sunset; CcNightLightWidgetMode mode; }; G_DEFINE_TYPE (CcNightLightWidget, cc_night_light_widget, GTK_TYPE_DRAWING_AREA); static gboolean cc_night_light_widget_draw (GtkWidget *widget, cairo_t *cr); void cc_night_light_widget_set_to (CcNightLightWidget *self, gdouble to) { self->to = to; gtk_widget_queue_draw (GTK_WIDGET (self)); } void cc_night_light_widget_set_from (CcNightLightWidget *self, gdouble from) { self->from = from; gtk_widget_queue_draw (GTK_WIDGET (self)); } void cc_night_light_widget_set_now (CcNightLightWidget *self, gdouble now) { self->now = now; gtk_widget_queue_draw (GTK_WIDGET (self)); } void cc_night_light_widget_set_mode (CcNightLightWidget *self, CcNightLightWidgetMode mode) { self->mode = mode; gtk_widget_queue_draw (GTK_WIDGET (self)); } static cairo_status_t _png_read_func (void *closure, unsigned char *data, unsigned int length) { GInputStream *stream = G_INPUT_STREAM (closure); gssize read; g_autoptr(GError) error = NULL; read = g_input_stream_read (stream, data, (gsize) length, NULL, &error); if (read < 0) { g_warning ("failed to read form stream: %s", error->message); return CAIRO_STATUS_READ_ERROR; } return CAIRO_STATUS_SUCCESS; } static const char *scaled_sizes[] = { NULL, "16x16", }; static cairo_surface_t * read_surface_from_resource (const gchar *dirname, int scale_factor, const gchar *basename) { /* Always ensure that we have a resource for the scale factor */ scale_factor = CLAMP (scale_factor, 1, G_N_ELEMENTS (scaled_sizes) - 1); g_autoptr(GError) error = NULL; g_autoptr(GInputStream) stream = NULL; g_autofree char *path = g_build_filename (dirname, scaled_sizes[scale_factor], basename, NULL); stream = g_resource_open_stream (cc_display_get_resource (), path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error); if (stream == NULL) { g_error ("failed to load PNG data: %s", error->message); return NULL; } return cairo_image_surface_create_from_png_stream (_png_read_func, stream); } static void cc_night_light_widget_style_updated (GtkWidget *widget) { GTK_WIDGET_CLASS (cc_night_light_widget_parent_class)->style_updated (widget); CcNightLightWidget *self = CC_NIGHT_LIGHT_WIDGET (widget); GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); g_clear_pointer (&self->surface_sunrise, (GDestroyNotify) cairo_surface_destroy); g_clear_pointer (&self->surface_sunset, (GDestroyNotify) cairo_surface_destroy); self->surface_sunrise = gtk_icon_theme_load_surface (icon_theme, "daytime-sunrise-symbolic", 16, gtk_widget_get_scale_factor (widget), gtk_widget_get_window (widget), 0, NULL); if (self->surface_sunrise == NULL) self->surface_sunrise = read_surface_from_resource ("/org/gnome/control-center/display", gtk_widget_get_scale_factor (widget), "sunrise.png"); self->surface_sunset = gtk_icon_theme_load_surface (icon_theme, "daytime-sunset-symbolic", 16, gtk_widget_get_scale_factor (widget), gtk_widget_get_window (widget), 0, NULL); if (self->surface_sunset == NULL) self->surface_sunset = read_surface_from_resource ("/org/gnome/control-center/display", gtk_widget_get_scale_factor (widget), "sunset.png"); } static void cc_night_light_widget_finalize (GObject *object) { CcNightLightWidget *self = CC_NIGHT_LIGHT_WIDGET (object); g_clear_pointer (&self->surface_sunrise, (GDestroyNotify) cairo_surface_destroy); g_clear_pointer (&self->surface_sunset, (GDestroyNotify) cairo_surface_destroy); G_OBJECT_CLASS (cc_night_light_widget_parent_class)->finalize (object); } static void cc_night_light_widget_class_init (CcNightLightWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = cc_night_light_widget_finalize; widget_class->draw = cc_night_light_widget_draw; widget_class->style_updated = cc_night_light_widget_style_updated; } static void cc_night_light_widget_init (CcNightLightWidget *self) { self->to = 8; self->from = 16; self->now = 11; } static gboolean is_frac_day_between (gdouble value, gdouble to, gdouble from) { /* wraparound to the next day */ if (from < to) from += 24; /* wraparound to the previous day */ if (value < from && value < to) value += 24; /* test limits */ return value > to && value <= from; } static void rounded_rectangle (cairo_t *cr, gdouble x, gdouble y, gdouble radius, gdouble width, gdouble height) { gdouble degrees = G_PI / 180.0; cairo_new_sub_path (cr); cairo_arc (cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees); cairo_arc (cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees); cairo_arc (cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees); cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees); cairo_close_path (cr); } static gboolean cc_night_light_widget_draw (GtkWidget *widget, cairo_t *cr) { CdColorRGB color; CdColorRGB color_temperature; CdColorRGB color_unity; GtkAllocation rect; const guint arrow_sz = 5; /* px */ const guint icon_sz = 16; /* px */ const guint pad_upper_sz = 6; /* px */ const guint pad_lower_sz = 4; /* px */ guint line_x = 0; /* px */ guint bar_sz; CcNightLightWidget *self = (CcNightLightWidget*) widget; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (CC_IS_NIGHT_LIGHT_WIDGET (self), FALSE); cd_color_rgb_set (&color_temperature, 0.992, 0.796, 0.612); cd_color_rgb_set (&color_unity, 0.773, 0.862, 0.953); /* * / * | icon_sz * \ * <- pad_upper_sz * / * | bar_sz (calculated) * \ * <- pad_lower_sz * / * | arrow_sz * \ */ gtk_widget_get_allocation (widget, &rect); bar_sz = rect.height - (icon_sz + pad_upper_sz + pad_lower_sz + arrow_sz); /* clip to a rounded rectangle */ cairo_save (cr); cairo_set_line_width (cr, 1); rounded_rectangle (cr, 0, icon_sz + pad_upper_sz, 6, rect.width, bar_sz); cairo_clip (cr); /* draw each color line */ gdouble subsect = 24.f / (gdouble) rect.width; if (gtk_widget_is_sensitive (widget)) { cairo_set_line_width (cr, 1); for (guint x = 0; x < rect.width; x += 1) { gdouble frac_hour = subsect * x; if (is_frac_day_between (frac_hour, self->to - 1, self->to)) { gdouble frac = 1.f - (self->to - frac_hour); cd_color_rgb_interpolate (&color_temperature, &color_unity, frac, &color); } else if (is_frac_day_between (frac_hour, self->from - 1, self->from)) { gdouble frac = self->from - frac_hour; cd_color_rgb_interpolate (&color_temperature, &color_unity, frac, &color); } else if (is_frac_day_between (frac_hour, self->to, self->from)) { cd_color_rgb_copy (&color_unity, &color); } else { cd_color_rgb_copy (&color_temperature, &color); } cairo_set_source_rgb (cr, color.R, color.G, color.B); cairo_move_to (cr, x + 0.5, icon_sz + pad_upper_sz); cairo_line_to (cr, x + 0.5, icon_sz + pad_upper_sz + bar_sz); cairo_stroke (cr); } } else { rounded_rectangle (cr, 0, icon_sz + pad_upper_sz, 6, rect.width, bar_sz); cairo_set_source_rgb (cr, 0.95f, 0.95f, 0.95f); cairo_fill (cr); } /* apply border */ rounded_rectangle (cr, 0, icon_sz + pad_upper_sz, 6, rect.width, bar_sz); cairo_set_source_rgb (cr, 0.65, 0.65, 0.65); cairo_set_line_width (cr, 1); cairo_stroke (cr); cairo_restore (cr); /* apply arrow */ if (gtk_widget_is_sensitive (widget)) { line_x = self->now / subsect; cairo_move_to (cr, line_x - arrow_sz + 0.5, icon_sz + pad_upper_sz + bar_sz + pad_lower_sz + arrow_sz); cairo_line_to (cr, line_x + arrow_sz + 0.5, icon_sz + pad_upper_sz + bar_sz + pad_lower_sz + arrow_sz); cairo_line_to (cr, line_x + 0.5, icon_sz + pad_upper_sz + bar_sz + pad_lower_sz); cairo_close_path (cr); cairo_set_source_rgb (cr, 0.333, 0.333, 0.333); cairo_fill (cr); } /* draw icons */ if (gtk_widget_is_sensitive (widget) && self->mode == CC_NIGHT_LIGHT_WIDGET_MODE_AUTOMATIC) { if (self->to <= 0) line_x = rect.width - icon_sz; else line_x = MIN (MAX ((self->to / subsect) - (icon_sz / 2), 0), rect.width - icon_sz); cairo_set_source_surface (cr, self->surface_sunrise, line_x, 0); cairo_paint (cr); if (self->from <= 0) line_x = rect.width - icon_sz; else line_x = MIN (MAX ((self->from / subsect) - (icon_sz / 2), 0), rect.width - icon_sz); cairo_set_source_surface (cr, self->surface_sunset, line_x, 0); cairo_paint (cr); } return FALSE; } GtkWidget * cc_night_light_widget_new (void) { return g_object_new (CC_TYPE_NIGHT_LIGHT_WIDGET, NULL); }