1 /*
2  * Copyright (C) 2001 Havoc Pennington
3  * Copyright (C) 2016 Alberts Muktupāvels
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include <glib/gi18n-lib.h>
22 
23 #include "meta-color-spec-private.h"
24 #include "meta-gradient-spec-private.h"
25 #include "meta-theme.h"
26 
27 struct _MetaGradientSpec
28 {
29   MetaGradientType  type;
30   GSList           *color_specs;
31 };
32 
33 struct _MetaAlphaGradientSpec
34 {
35   MetaGradientType  type;
36   guchar           *alphas;
37   gint              n_alphas;
38 };
39 
40 
41 static cairo_pattern_t *
create_cairo_pattern_from_gradient_spec(const MetaGradientSpec * spec,const MetaAlphaGradientSpec * alpha_spec,GtkStyleContext * context)42 create_cairo_pattern_from_gradient_spec (const MetaGradientSpec      *spec,
43                                          const MetaAlphaGradientSpec *alpha_spec,
44                                          GtkStyleContext             *context)
45 {
46   gint n_colors;
47   cairo_pattern_t *pattern;
48   GSList *tmp;
49   gint i;
50 
51   n_colors = g_slist_length (spec->color_specs);
52   if (n_colors == 0)
53     return NULL;
54 
55   if (alpha_spec != NULL && alpha_spec->n_alphas != 1)
56     g_assert (n_colors == alpha_spec->n_alphas);
57 
58   if (spec->type == META_GRADIENT_HORIZONTAL)
59     pattern = cairo_pattern_create_linear (0, 0, 1, 0);
60   else if (spec->type == META_GRADIENT_VERTICAL)
61     pattern = cairo_pattern_create_linear (0, 0, 0, 1);
62   else if (spec->type == META_GRADIENT_DIAGONAL)
63     pattern = cairo_pattern_create_linear (0, 0, 1, 1);
64   else
65     g_assert_not_reached ();
66 
67   i = 0;
68   tmp = spec->color_specs;
69   while (tmp != NULL)
70     {
71       GdkRGBA color;
72 
73       meta_color_spec_render (tmp->data, context, &color);
74 
75       if (alpha_spec != NULL)
76         {
77           gdouble alpha;
78 
79           if (alpha_spec->n_alphas == 1)
80             alpha = alpha_spec->alphas[0] / 255.0;
81           else
82             alpha = alpha_spec->alphas[i] / 255.0;
83 
84           cairo_pattern_add_color_stop_rgba (pattern, i / (gfloat) (n_colors - 1),
85                                              color.red, color.green, color.blue,
86                                              alpha);
87         }
88       else
89         cairo_pattern_add_color_stop_rgb (pattern, i / (gfloat) (n_colors - 1),
90                                           color.red, color.green, color.blue);
91 
92       tmp = tmp->next;
93       ++i;
94     }
95 
96   if (cairo_pattern_status (pattern) != CAIRO_STATUS_SUCCESS)
97     {
98       cairo_pattern_destroy (pattern);
99       return NULL;
100     }
101 
102   return pattern;
103 }
104 
105 static void
free_color_spec(gpointer spec,gpointer user_data)106 free_color_spec (gpointer spec,
107                  gpointer user_data)
108 {
109   meta_color_spec_free (spec);
110 }
111 
112 MetaGradientSpec *
meta_gradient_spec_new(MetaGradientType type)113 meta_gradient_spec_new (MetaGradientType type)
114 {
115   MetaGradientSpec *spec;
116 
117   spec = g_new (MetaGradientSpec, 1);
118 
119   spec->type = type;
120   spec->color_specs = NULL;
121 
122   return spec;
123 }
124 
125 void
meta_gradient_spec_free(MetaGradientSpec * spec)126 meta_gradient_spec_free (MetaGradientSpec *spec)
127 {
128   g_return_if_fail (spec != NULL);
129 
130   g_slist_foreach (spec->color_specs, free_color_spec, NULL);
131   g_slist_free (spec->color_specs);
132 
133   g_free (spec);
134 }
135 
136 void
meta_gradient_spec_add_color_spec(MetaGradientSpec * spec,MetaColorSpec * color_spec)137 meta_gradient_spec_add_color_spec (MetaGradientSpec *spec,
138                                    MetaColorSpec    *color_spec)
139 {
140   spec->color_specs = g_slist_append (spec->color_specs, color_spec);
141 }
142 
143 void
meta_gradient_spec_render(const MetaGradientSpec * spec,const MetaAlphaGradientSpec * alpha_spec,cairo_t * cr,GtkStyleContext * context,gdouble x,gdouble y,gdouble width,gdouble height)144 meta_gradient_spec_render (const MetaGradientSpec      *spec,
145                            const MetaAlphaGradientSpec *alpha_spec,
146                            cairo_t                     *cr,
147                            GtkStyleContext             *context,
148                            gdouble                      x,
149                            gdouble                      y,
150                            gdouble                      width,
151                            gdouble                      height)
152 {
153   cairo_pattern_t *pattern;
154 
155   pattern = create_cairo_pattern_from_gradient_spec (spec, alpha_spec, context);
156   if (pattern == NULL)
157     return;
158 
159   cairo_save (cr);
160 
161   cairo_rectangle (cr, x, y, width, height);
162 
163   cairo_translate (cr, x, y);
164   cairo_scale (cr, width, height);
165 
166   cairo_set_source (cr, pattern);
167   cairo_fill (cr);
168   cairo_pattern_destroy (pattern);
169 
170   cairo_restore (cr);
171 }
172 
173 gboolean
meta_gradient_spec_validate(MetaGradientSpec * spec,GError ** error)174 meta_gradient_spec_validate (MetaGradientSpec  *spec,
175                              GError           **error)
176 {
177   g_return_val_if_fail (spec != NULL, FALSE);
178 
179   if (g_slist_length (spec->color_specs) < 2)
180     {
181       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
182                    _("Gradients should have at least two colors"));
183 
184       return FALSE;
185     }
186 
187   return TRUE;
188 }
189 
190 MetaAlphaGradientSpec *
meta_alpha_gradient_spec_new(MetaGradientType type,gint n_alphas)191 meta_alpha_gradient_spec_new (MetaGradientType type,
192                               gint             n_alphas)
193 {
194   MetaAlphaGradientSpec *spec;
195 
196   g_return_val_if_fail (n_alphas > 0, NULL);
197 
198   spec = g_new0 (MetaAlphaGradientSpec, 1);
199 
200   spec->type = type;
201   spec->alphas = g_new0 (guchar, n_alphas);
202   spec->n_alphas = n_alphas;
203 
204   return spec;
205 }
206 
207 void
meta_alpha_gradient_spec_free(MetaAlphaGradientSpec * spec)208 meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec)
209 {
210   g_return_if_fail (spec != NULL);
211 
212   g_free (spec->alphas);
213   g_free (spec);
214 }
215 
216 void
meta_alpha_gradient_spec_add_alpha(MetaAlphaGradientSpec * spec,gint n_alpha,gdouble alpha)217 meta_alpha_gradient_spec_add_alpha (MetaAlphaGradientSpec *spec,
218                                     gint                   n_alpha,
219                                     gdouble                alpha)
220 {
221   spec->alphas[n_alpha] = (guchar) (alpha * 255);
222 }
223 
224 guchar
meta_alpha_gradient_spec_get_alpha(MetaAlphaGradientSpec * spec,gint n_alpha)225 meta_alpha_gradient_spec_get_alpha (MetaAlphaGradientSpec *spec,
226                                     gint                   n_alpha)
227 {
228   return spec->alphas[n_alpha];
229 }
230 
231 void
meta_alpha_gradient_spec_render(MetaAlphaGradientSpec * spec,GdkRGBA color,cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height)232 meta_alpha_gradient_spec_render (MetaAlphaGradientSpec *spec,
233                                  GdkRGBA                color,
234                                  cairo_t               *cr,
235                                  gdouble                x,
236                                  gdouble                y,
237                                  gdouble                width,
238                                  gdouble                height)
239 {
240   if (!spec || spec->n_alphas == 1)
241     {
242       if (spec)
243         color.alpha = spec->alphas[0] / 255.0;
244 
245       gdk_cairo_set_source_rgba (cr, &color);
246       cairo_rectangle (cr, x, y, width, height);
247       cairo_fill (cr);
248     }
249   else
250     {
251       cairo_pattern_t *pattern;
252       gint n_alphas;
253       gint i;
254 
255       /* Hardcoded in meta-theme-metacity.c */
256       g_assert (spec->type == META_GRADIENT_HORIZONTAL);
257 
258       pattern = cairo_pattern_create_linear (0, 0, 1, 0);
259       n_alphas = spec->n_alphas;
260 
261       for (i = 0; i < n_alphas; i++)
262         cairo_pattern_add_color_stop_rgba (pattern, i / (gfloat) (n_alphas - 1),
263                                            color.red, color.green, color.blue,
264                                            spec->alphas[i] / 255.0);
265 
266       if (cairo_pattern_status (pattern) != CAIRO_STATUS_SUCCESS)
267         {
268           cairo_pattern_destroy (pattern);
269           return;
270         }
271 
272       cairo_save (cr);
273       cairo_rectangle (cr, x, y, width, height);
274 
275       cairo_translate (cr, x, y);
276       cairo_scale (cr, width, height);
277 
278       cairo_set_source (cr, pattern);
279       cairo_fill (cr);
280 
281       cairo_pattern_destroy (pattern);
282       cairo_restore (cr);
283     }
284 }
285 
286 cairo_pattern_t *
meta_alpha_gradient_spec_get_mask(const MetaAlphaGradientSpec * spec)287 meta_alpha_gradient_spec_get_mask (const MetaAlphaGradientSpec *spec)
288 {
289   gint n_alphas;
290   cairo_pattern_t *pattern;
291   gint i;
292 
293   /* Hardcoded in meta-theme-metacity.c */
294   g_assert (spec->type == META_GRADIENT_HORIZONTAL);
295 
296   n_alphas = spec->n_alphas;
297   if (n_alphas == 0)
298     return NULL;
299 
300   if (n_alphas == 1)
301     return cairo_pattern_create_rgba (0, 0, 0, spec->alphas[0] / 255.0);
302 
303   pattern = cairo_pattern_create_linear (0, 0, 1, 0);
304 
305   for (i = 0; i < n_alphas; i++)
306     cairo_pattern_add_color_stop_rgba (pattern, i / (gfloat) (n_alphas - 1),
307                                        0, 0, 0, spec->alphas[i] / 255.0);
308 
309   if (cairo_pattern_status (pattern) != CAIRO_STATUS_SUCCESS)
310     {
311       cairo_pattern_destroy (pattern);
312       return NULL;
313     }
314 
315   return pattern;
316 }
317