1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2011 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "gtkcsseasevalueprivate.h" |
21 | |
22 | #include <math.h> |
23 | |
24 | typedef enum { |
25 | GTK_CSS_EASE_CUBIC_BEZIER, |
26 | GTK_CSS_EASE_STEPS |
27 | } GtkCssEaseType; |
28 | |
29 | struct _GtkCssValue { |
30 | GTK_CSS_VALUE_BASE |
31 | GtkCssEaseType type; |
32 | union { |
33 | struct { |
34 | double x1; |
35 | double y1; |
36 | double x2; |
37 | double y2; |
38 | } cubic; |
39 | struct { |
40 | guint steps; |
41 | gboolean start; |
42 | } steps; |
43 | } u; |
44 | }; |
45 | |
46 | static void |
47 | gtk_css_value_ease_free (GtkCssValue *value) |
48 | { |
49 | g_slice_free (GtkCssValue, value); |
50 | } |
51 | |
52 | static GtkCssValue * |
53 | gtk_css_value_ease_compute (GtkCssValue *value, |
54 | guint property_id, |
55 | GtkStyleProvider *provider, |
56 | GtkCssStyle *style, |
57 | GtkCssStyle *parent_style) |
58 | { |
59 | return _gtk_css_value_ref (value); |
60 | } |
61 | |
62 | static gboolean |
63 | gtk_css_value_ease_equal (const GtkCssValue *ease1, |
64 | const GtkCssValue *ease2) |
65 | { |
66 | if (ease1->type != ease2->type) |
67 | return FALSE; |
68 | |
69 | switch (ease1->type) |
70 | { |
71 | case GTK_CSS_EASE_CUBIC_BEZIER: |
72 | return ease1->u.cubic.x1 == ease2->u.cubic.x1 && |
73 | ease1->u.cubic.y1 == ease2->u.cubic.y1 && |
74 | ease1->u.cubic.x2 == ease2->u.cubic.x2 && |
75 | ease1->u.cubic.y2 == ease2->u.cubic.y2; |
76 | case GTK_CSS_EASE_STEPS: |
77 | return ease1->u.steps.steps == ease2->u.steps.steps && |
78 | ease1->u.steps.start == ease2->u.steps.start; |
79 | default: |
80 | g_assert_not_reached (); |
81 | return FALSE; |
82 | } |
83 | } |
84 | |
85 | static GtkCssValue * |
86 | gtk_css_value_ease_transition (GtkCssValue *start, |
87 | GtkCssValue *end, |
88 | guint property_id, |
89 | double progress) |
90 | { |
91 | return NULL; |
92 | } |
93 | |
94 | static void |
95 | gtk_css_value_ease_print (const GtkCssValue *ease, |
96 | GString *string) |
97 | { |
98 | switch (ease->type) |
99 | { |
100 | case GTK_CSS_EASE_CUBIC_BEZIER: |
101 | if (ease->u.cubic.x1 == 0.25 && ease->u.cubic.y1 == 0.1 && |
102 | ease->u.cubic.x2 == 0.25 && ease->u.cubic.y2 == 1.0) |
103 | g_string_append (string, val: "ease" ); |
104 | else if (ease->u.cubic.x1 == 0.0 && ease->u.cubic.y1 == 0.0 && |
105 | ease->u.cubic.x2 == 1.0 && ease->u.cubic.y2 == 1.0) |
106 | g_string_append (string, val: "linear" ); |
107 | else if (ease->u.cubic.x1 == 0.42 && ease->u.cubic.y1 == 0.0 && |
108 | ease->u.cubic.x2 == 1.0 && ease->u.cubic.y2 == 1.0) |
109 | g_string_append (string, val: "ease-in" ); |
110 | else if (ease->u.cubic.x1 == 0.0 && ease->u.cubic.y1 == 0.0 && |
111 | ease->u.cubic.x2 == 0.58 && ease->u.cubic.y2 == 1.0) |
112 | g_string_append (string, val: "ease-out" ); |
113 | else if (ease->u.cubic.x1 == 0.42 && ease->u.cubic.y1 == 0.0 && |
114 | ease->u.cubic.x2 == 0.58 && ease->u.cubic.y2 == 1.0) |
115 | g_string_append (string, val: "ease-in-out" ); |
116 | else |
117 | g_string_append_printf (string, format: "cubic-bezier(%g,%g,%g,%g)" , |
118 | ease->u.cubic.x1, ease->u.cubic.y1, |
119 | ease->u.cubic.x2, ease->u.cubic.y2); |
120 | break; |
121 | case GTK_CSS_EASE_STEPS: |
122 | if (ease->u.steps.steps == 1) |
123 | { |
124 | g_string_append (string, val: ease->u.steps.start ? "step-start" : "step-end" ); |
125 | } |
126 | else |
127 | { |
128 | g_string_append_printf (string, format: "steps(%u%s)" , ease->u.steps.steps, ease->u.steps.start ? ",start" : "" ); |
129 | } |
130 | break; |
131 | default: |
132 | g_assert_not_reached (); |
133 | break; |
134 | } |
135 | } |
136 | |
137 | static const GtkCssValueClass GTK_CSS_VALUE_EASE = { |
138 | "GtkCssEaseValue" , |
139 | gtk_css_value_ease_free, |
140 | gtk_css_value_ease_compute, |
141 | gtk_css_value_ease_equal, |
142 | gtk_css_value_ease_transition, |
143 | NULL, |
144 | NULL, |
145 | gtk_css_value_ease_print |
146 | }; |
147 | |
148 | GtkCssValue * |
149 | _gtk_css_ease_value_new_cubic_bezier (double x1, |
150 | double y1, |
151 | double x2, |
152 | double y2) |
153 | { |
154 | GtkCssValue *value; |
155 | |
156 | g_return_val_if_fail (x1 >= 0.0, NULL); |
157 | g_return_val_if_fail (x1 <= 1.0, NULL); |
158 | g_return_val_if_fail (x2 >= 0.0, NULL); |
159 | g_return_val_if_fail (x2 <= 1.0, NULL); |
160 | |
161 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_EASE); |
162 | |
163 | value->type = GTK_CSS_EASE_CUBIC_BEZIER; |
164 | value->u.cubic.x1 = x1; |
165 | value->u.cubic.y1 = y1; |
166 | value->u.cubic.x2 = x2; |
167 | value->u.cubic.y2 = y2; |
168 | value->is_computed = TRUE; |
169 | |
170 | return value; |
171 | } |
172 | |
173 | static GtkCssValue * |
174 | _gtk_css_ease_value_new_steps (guint n_steps, |
175 | gboolean start) |
176 | { |
177 | GtkCssValue *value; |
178 | |
179 | g_return_val_if_fail (n_steps > 0, NULL); |
180 | |
181 | value = _gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_EASE); |
182 | |
183 | value->type = GTK_CSS_EASE_STEPS; |
184 | value->u.steps.steps = n_steps; |
185 | value->u.steps.start = start; |
186 | value->is_computed = TRUE; |
187 | |
188 | return value; |
189 | } |
190 | |
191 | static const struct { |
192 | const char *name; |
193 | guint is_bezier :1; |
194 | guint is_function :1; |
195 | double values[4]; |
196 | } parser_values[] = { |
197 | { "linear" , TRUE, FALSE, { 0.0, 0.0, 1.0, 1.0 } }, |
198 | { "ease-in-out" , TRUE, FALSE, { 0.42, 0.0, 0.58, 1.0 } }, |
199 | { "ease-in" , TRUE, FALSE, { 0.42, 0.0, 1.0, 1.0 } }, |
200 | { "ease-out" , TRUE, FALSE, { 0.0, 0.0, 0.58, 1.0 } }, |
201 | { "ease" , TRUE, FALSE, { 0.25, 0.1, 0.25, 1.0 } }, |
202 | { "step-start" , FALSE, FALSE, { 1.0, 1.0, 0.0, 0.0 } }, |
203 | { "step-end" , FALSE, FALSE, { 1.0, 0.0, 0.0, 0.0 } }, |
204 | { "steps" , FALSE, TRUE, { 0.0, 0.0, 0.0, 0.0 } }, |
205 | { "cubic-bezier" , TRUE, TRUE, { 0.0, 0.0, 0.0, 0.0 } } |
206 | }; |
207 | |
208 | gboolean |
209 | _gtk_css_ease_value_can_parse (GtkCssParser *parser) |
210 | { |
211 | guint i; |
212 | |
213 | for (i = 0; i < G_N_ELEMENTS (parser_values); i++) |
214 | { |
215 | if (parser_values[i].is_function) |
216 | { |
217 | if (gtk_css_parser_has_function (self: parser, name: parser_values[i].name)) |
218 | return TRUE; |
219 | } |
220 | else |
221 | { |
222 | if (gtk_css_parser_has_ident (self: parser, ident: parser_values[i].name)) |
223 | return TRUE; |
224 | } |
225 | } |
226 | |
227 | return FALSE; |
228 | } |
229 | |
230 | static guint |
231 | gtk_css_ease_value_parse_cubic_bezier_arg (GtkCssParser *parser, |
232 | guint arg, |
233 | gpointer data) |
234 | { |
235 | double *values = data; |
236 | |
237 | if (!gtk_css_parser_consume_number (self: parser, number: &values[arg])) |
238 | return 0; |
239 | |
240 | if (arg % 2 == 0) |
241 | { |
242 | if (values[arg] < 0 || values[arg] > 1.0) |
243 | { |
244 | gtk_css_parser_error_value (self: parser, format: "value %g out of range. Must be from 0.0 to 1.0" , values[arg]); |
245 | return 0; |
246 | } |
247 | } |
248 | |
249 | return 1; |
250 | } |
251 | |
252 | static GtkCssValue * |
253 | gtk_css_ease_value_parse_cubic_bezier (GtkCssParser *parser) |
254 | { |
255 | double values[4]; |
256 | |
257 | if (!gtk_css_parser_consume_function (self: parser, min_args: 4, max_args: 4, parse_func: gtk_css_ease_value_parse_cubic_bezier_arg, data: values)) |
258 | return NULL; |
259 | |
260 | return _gtk_css_ease_value_new_cubic_bezier (x1: values[0], y1: values[1], x2: values[2], y2: values[3]); |
261 | } |
262 | |
263 | typedef struct |
264 | { |
265 | int n_steps; |
266 | gboolean start; |
267 | } ParseStepsData; |
268 | |
269 | static guint |
270 | gtk_css_ease_value_parse_steps_arg (GtkCssParser *parser, |
271 | guint arg, |
272 | gpointer data_) |
273 | { |
274 | ParseStepsData *data = data_; |
275 | |
276 | switch (arg) |
277 | { |
278 | case 0: |
279 | if (!gtk_css_parser_consume_integer (self: parser, number: &data->n_steps)) |
280 | { |
281 | return 0; |
282 | } |
283 | else if (data->n_steps < 1) |
284 | { |
285 | gtk_css_parser_error_value (self: parser, format: "Number of steps must be > 0" ); |
286 | return 0; |
287 | } |
288 | return 1; |
289 | |
290 | case 1: |
291 | if (gtk_css_parser_try_ident (self: parser, ident: "start" )) |
292 | data->start = TRUE; |
293 | else if (gtk_css_parser_try_ident (self: parser, ident: "end" )) |
294 | data->start = FALSE; |
295 | else |
296 | { |
297 | gtk_css_parser_error_syntax (self: parser, format: "Only allowed values are 'start' and 'end'" ); |
298 | return 0; |
299 | } |
300 | return 1; |
301 | |
302 | default: |
303 | g_return_val_if_reached (0); |
304 | } |
305 | } |
306 | |
307 | static GtkCssValue * |
308 | gtk_css_ease_value_parse_steps (GtkCssParser *parser) |
309 | { |
310 | ParseStepsData data = { 0, FALSE }; |
311 | |
312 | if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 2, parse_func: gtk_css_ease_value_parse_steps_arg, data: &data)) |
313 | return NULL; |
314 | |
315 | return _gtk_css_ease_value_new_steps (n_steps: data.n_steps, start: data.start); |
316 | } |
317 | |
318 | GtkCssValue * |
319 | _gtk_css_ease_value_parse (GtkCssParser *parser) |
320 | { |
321 | guint i; |
322 | |
323 | g_return_val_if_fail (parser != NULL, NULL); |
324 | |
325 | for (i = 0; i < G_N_ELEMENTS (parser_values); i++) |
326 | { |
327 | if (parser_values[i].is_function) |
328 | { |
329 | if (gtk_css_parser_has_function (self: parser, name: parser_values[i].name)) |
330 | { |
331 | if (parser_values[i].is_bezier) |
332 | return gtk_css_ease_value_parse_cubic_bezier (parser); |
333 | else |
334 | return gtk_css_ease_value_parse_steps (parser); |
335 | } |
336 | } |
337 | else |
338 | { |
339 | if (gtk_css_parser_try_ident (self: parser, ident: parser_values[i].name)) |
340 | { |
341 | if (parser_values[i].is_bezier) |
342 | return _gtk_css_ease_value_new_cubic_bezier (x1: parser_values[i].values[0], |
343 | y1: parser_values[i].values[1], |
344 | x2: parser_values[i].values[2], |
345 | y2: parser_values[i].values[3]); |
346 | else |
347 | return _gtk_css_ease_value_new_steps (n_steps: parser_values[i].values[0], |
348 | start: parser_values[i].values[1] != 0.0); |
349 | } |
350 | } |
351 | } |
352 | |
353 | gtk_css_parser_error_syntax (self: parser, format: "Expected a valid ease value" ); |
354 | return NULL; |
355 | } |
356 | |
357 | double |
358 | _gtk_css_ease_value_transform (const GtkCssValue *ease, |
359 | double progress) |
360 | { |
361 | g_return_val_if_fail (ease->class == >K_CSS_VALUE_EASE, 1.0); |
362 | |
363 | if (progress <= 0) |
364 | return 0; |
365 | if (progress >= 1) |
366 | return 1; |
367 | |
368 | switch (ease->type) |
369 | { |
370 | case GTK_CSS_EASE_CUBIC_BEZIER: |
371 | { |
372 | static const double epsilon = 0.00001; |
373 | double tmin, t, tmax; |
374 | |
375 | tmin = 0.0; |
376 | tmax = 1.0; |
377 | t = progress; |
378 | |
379 | while (tmin < tmax) |
380 | { |
381 | double sample; |
382 | sample = (((1.0 + 3 * ease->u.cubic.x1 - 3 * ease->u.cubic.x2) * t |
383 | + -6 * ease->u.cubic.x1 + 3 * ease->u.cubic.x2) * t |
384 | + 3 * ease->u.cubic.x1 ) * t; |
385 | if (fabs(x: sample - progress) < epsilon) |
386 | break; |
387 | |
388 | if (progress > sample) |
389 | tmin = t; |
390 | else |
391 | tmax = t; |
392 | t = (tmax + tmin) * .5; |
393 | } |
394 | |
395 | return (((1.0 + 3 * ease->u.cubic.y1 - 3 * ease->u.cubic.y2) * t |
396 | + -6 * ease->u.cubic.y1 + 3 * ease->u.cubic.y2) * t |
397 | + 3 * ease->u.cubic.y1 ) * t; |
398 | } |
399 | case GTK_CSS_EASE_STEPS: |
400 | progress *= ease->u.steps.steps; |
401 | progress = floor (x: progress) + (ease->u.steps.start ? 0 : 1); |
402 | return progress / ease->u.steps.steps; |
403 | default: |
404 | g_assert_not_reached (); |
405 | return 1.0; |
406 | } |
407 | } |
408 | |
409 | |
410 | |