gtk/gtk/gtkcsstransformvalue.c
Benjamin Otte cb0b4b5410 css: Change CssTransformValue API
Provide API to query the matrix instead of API that applies the matrix
directly. This makes the API more flexible.

See the commits implementing shadows.
2014-05-14 04:28:35 +02:00

1049 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GTK - The GIMP Toolkit
* Copyright (C) 2011 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkcssbgsizevalueprivate.h"
#include <math.h>
#include <string.h>
#include "gtkcssnumbervalueprivate.h"
typedef union _GtkCssTransform GtkCssTransform;
typedef enum {
GTK_CSS_TRANSFORM_NONE,
GTK_CSS_TRANSFORM_MATRIX,
GTK_CSS_TRANSFORM_TRANSLATE,
GTK_CSS_TRANSFORM_ROTATE,
GTK_CSS_TRANSFORM_SCALE,
GTK_CSS_TRANSFORM_SKEW,
GTK_CSS_TRANSFORM_SKEW_X,
GTK_CSS_TRANSFORM_SKEW_Y
} GtkCssTransformType;
union _GtkCssTransform {
GtkCssTransformType type;
struct {
GtkCssTransformType type;
cairo_matrix_t matrix;
} matrix;
struct {
GtkCssTransformType type;
GtkCssValue *x;
GtkCssValue *y;
} translate, scale, skew;
struct {
GtkCssTransformType type;
GtkCssValue *rotate;
} rotate;
struct {
GtkCssTransformType type;
GtkCssValue *skew;
} skew_x, skew_y;
};
struct _GtkCssValue {
GTK_CSS_VALUE_BASE
guint n_transforms;
GtkCssTransform transforms[1];
};
static GtkCssValue * gtk_css_transform_value_alloc (guint n_values);
static gboolean gtk_css_transform_value_is_none (const GtkCssValue *value);
static void
gtk_css_transform_clear (GtkCssTransform *transform)
{
switch (transform->type)
{
case GTK_CSS_TRANSFORM_MATRIX:
break;
case GTK_CSS_TRANSFORM_TRANSLATE:
_gtk_css_value_unref (transform->translate.x);
_gtk_css_value_unref (transform->translate.y);
break;
case GTK_CSS_TRANSFORM_ROTATE:
_gtk_css_value_unref (transform->rotate.rotate);
break;
case GTK_CSS_TRANSFORM_SCALE:
_gtk_css_value_unref (transform->scale.x);
_gtk_css_value_unref (transform->scale.y);
break;
case GTK_CSS_TRANSFORM_SKEW:
_gtk_css_value_unref (transform->skew.x);
_gtk_css_value_unref (transform->skew.y);
break;
case GTK_CSS_TRANSFORM_SKEW_X:
_gtk_css_value_unref (transform->skew_x.skew);
break;
case GTK_CSS_TRANSFORM_SKEW_Y:
_gtk_css_value_unref (transform->skew_y.skew);
break;
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
break;
}
}
static void
gtk_css_transform_init_identity (GtkCssTransform *transform,
GtkCssTransformType type)
{
switch (type)
{
case GTK_CSS_TRANSFORM_MATRIX:
cairo_matrix_init_identity (&transform->matrix.matrix);
break;
case GTK_CSS_TRANSFORM_TRANSLATE:
transform->translate.x = _gtk_css_number_value_new (0, GTK_CSS_PX);
transform->translate.y = _gtk_css_number_value_new (0, GTK_CSS_PX);
break;
case GTK_CSS_TRANSFORM_ROTATE:
transform->rotate.rotate = _gtk_css_number_value_new (0, GTK_CSS_DEG);
break;
case GTK_CSS_TRANSFORM_SCALE:
transform->scale.x = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
transform->scale.y = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
break;
case GTK_CSS_TRANSFORM_SKEW:
transform->skew.x = _gtk_css_number_value_new (0, GTK_CSS_DEG);
transform->skew.y = _gtk_css_number_value_new (0, GTK_CSS_DEG);
break;
case GTK_CSS_TRANSFORM_SKEW_X:
transform->skew_x.skew = _gtk_css_number_value_new (0, GTK_CSS_DEG);
break;
case GTK_CSS_TRANSFORM_SKEW_Y:
transform->skew_y.skew = _gtk_css_number_value_new (0, GTK_CSS_DEG);
break;
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
break;
}
transform->type = type;
}
static void
gtk_cairo_matrix_skew (cairo_matrix_t *matrix,
double skew_x,
double skew_y)
{
cairo_matrix_t skew = { 1, tan (skew_x), tan (skew_y), 1, 0, 0 };
cairo_matrix_multiply (matrix, &skew, matrix);
}
static void
gtk_css_transform_apply (const GtkCssTransform *transform,
cairo_matrix_t *matrix)
{
switch (transform->type)
{
case GTK_CSS_TRANSFORM_MATRIX:
cairo_matrix_multiply (matrix, &transform->matrix.matrix, matrix);
break;
case GTK_CSS_TRANSFORM_TRANSLATE:
cairo_matrix_translate (matrix,
_gtk_css_number_value_get (transform->translate.x, 100),
_gtk_css_number_value_get (transform->translate.y, 100));
break;
case GTK_CSS_TRANSFORM_ROTATE:
cairo_matrix_rotate (matrix,
_gtk_css_number_value_get (transform->rotate.rotate, 100) * (2 * G_PI) / 360);
break;
case GTK_CSS_TRANSFORM_SCALE:
cairo_matrix_scale (matrix,
_gtk_css_number_value_get (transform->scale.x, 1),
_gtk_css_number_value_get (transform->scale.y, 1));
break;
case GTK_CSS_TRANSFORM_SKEW:
gtk_cairo_matrix_skew (matrix,
_gtk_css_number_value_get (transform->skew.x, 100),
_gtk_css_number_value_get (transform->skew.y, 100));
break;
case GTK_CSS_TRANSFORM_SKEW_X:
gtk_cairo_matrix_skew (matrix,
_gtk_css_number_value_get (transform->skew_x.skew, 100),
0);
break;
case GTK_CSS_TRANSFORM_SKEW_Y:
gtk_cairo_matrix_skew (matrix,
0,
_gtk_css_number_value_get (transform->skew_y.skew, 100));
break;
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
break;
}
}
/* NB: The returned matrix may be invalid */
static void
gtk_css_transform_value_compute_matrix (const GtkCssValue *value,
cairo_matrix_t *matrix)
{
guint i;
cairo_matrix_init_identity (matrix);
for (i = 0; i < value->n_transforms; i++)
{
gtk_css_transform_apply (&value->transforms[i], matrix);
}
}
static void
gtk_css_value_transform_free (GtkCssValue *value)
{
guint i;
for (i = 0; i < value->n_transforms; i++)
{
gtk_css_transform_clear (&value->transforms[i]);
}
g_slice_free1 (sizeof (GtkCssValue) + sizeof (GtkCssTransform) * (value->n_transforms - 1), value);
}
/* returns TRUE if dest == src */
static gboolean
gtk_css_transform_compute (GtkCssTransform *dest,
GtkCssTransform *src,
guint property_id,
GtkStyleProviderPrivate *provider,
int scale,
GtkCssComputedValues *values,
GtkCssComputedValues *parent_values,
GtkCssDependencies *dependencies)
{
GtkCssDependencies x_deps, y_deps;
dest->type = src->type;
switch (src->type)
{
case GTK_CSS_TRANSFORM_MATRIX:
return TRUE;
case GTK_CSS_TRANSFORM_TRANSLATE:
x_deps = y_deps = 0;
dest->translate.x = _gtk_css_value_compute (src->translate.x, property_id, provider, scale, values, parent_values, &x_deps);
dest->translate.y = _gtk_css_value_compute (src->translate.y, property_id, provider, scale, values, parent_values, &y_deps);
*dependencies = _gtk_css_dependencies_union (x_deps, y_deps);
return dest->translate.x == src->translate.x
&& dest->translate.y == src->translate.y;
case GTK_CSS_TRANSFORM_ROTATE:
dest->rotate.rotate = _gtk_css_value_compute (src->rotate.rotate, property_id, provider, scale, values, parent_values, dependencies);
return dest->rotate.rotate == src->rotate.rotate;
case GTK_CSS_TRANSFORM_SCALE:
x_deps = y_deps = 0;
dest->scale.x = _gtk_css_value_compute (src->scale.x, property_id, provider, scale, values, parent_values, &x_deps);
dest->scale.y = _gtk_css_value_compute (src->scale.y, property_id, provider, scale, values, parent_values, &y_deps);
*dependencies = _gtk_css_dependencies_union (x_deps, y_deps);
return dest->scale.x == src->scale.x
&& dest->scale.y == src->scale.y;
case GTK_CSS_TRANSFORM_SKEW:
x_deps = y_deps = 0;
dest->skew.x = _gtk_css_value_compute (src->skew.x, property_id, provider, scale, values, parent_values, &x_deps);
dest->skew.y = _gtk_css_value_compute (src->skew.y, property_id, provider, scale, values, parent_values, &y_deps);
*dependencies = _gtk_css_dependencies_union (x_deps, y_deps);
return dest->skew.x == src->skew.x
&& dest->skew.y == src->skew.y;
case GTK_CSS_TRANSFORM_SKEW_X:
dest->skew_x.skew = _gtk_css_value_compute (src->skew_x.skew, property_id, provider, scale, values, parent_values, dependencies);
return dest->skew_x.skew == src->skew_x.skew;
case GTK_CSS_TRANSFORM_SKEW_Y:
dest->skew_y.skew = _gtk_css_value_compute (src->skew_y.skew, property_id, provider, scale, values, parent_values, dependencies);
return dest->skew_y.skew == src->skew_y.skew;
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
return FALSE;
}
}
static GtkCssValue *
gtk_css_value_transform_compute (GtkCssValue *value,
guint property_id,
GtkStyleProviderPrivate *provider,
int scale,
GtkCssComputedValues *values,
GtkCssComputedValues *parent_values,
GtkCssDependencies *dependencies)
{
GtkCssDependencies transform_deps;
GtkCssValue *result;
gboolean changes;
guint i;
/* Special case the 99% case of "none" */
if (gtk_css_transform_value_is_none (value))
return _gtk_css_value_ref (value);
changes = FALSE;
result = gtk_css_transform_value_alloc (value->n_transforms);
for (i = 0; i < value->n_transforms; i++)
{
changes |= !gtk_css_transform_compute (&result->transforms[i],
&value->transforms[i],
property_id,
provider,
scale,
values,
parent_values,
&transform_deps);
*dependencies = _gtk_css_dependencies_union (*dependencies, transform_deps);
}
if (!changes)
{
_gtk_css_value_unref (result);
result = _gtk_css_value_ref (value);
}
return result;
}
static gboolean
gtk_css_transform_equal (const GtkCssTransform *transform1,
const GtkCssTransform *transform2)
{
switch (transform1->type)
{
case GTK_CSS_TRANSFORM_MATRIX:
return transform1->matrix.matrix.xx == transform2->matrix.matrix.xx
&& transform1->matrix.matrix.xy == transform2->matrix.matrix.xy
&& transform1->matrix.matrix.yx == transform2->matrix.matrix.yx
&& transform1->matrix.matrix.yy == transform2->matrix.matrix.yy
&& transform1->matrix.matrix.x0 == transform2->matrix.matrix.x0
&& transform1->matrix.matrix.y0 == transform2->matrix.matrix.y0;
case GTK_CSS_TRANSFORM_TRANSLATE:
return _gtk_css_value_equal (transform1->translate.x, transform2->translate.x)
&& _gtk_css_value_equal (transform1->translate.y, transform2->translate.y);
case GTK_CSS_TRANSFORM_ROTATE:
return _gtk_css_value_equal (transform1->rotate.rotate, transform2->rotate.rotate);
case GTK_CSS_TRANSFORM_SCALE:
return _gtk_css_value_equal (transform1->scale.x, transform2->scale.x)
&& _gtk_css_value_equal (transform1->scale.y, transform2->scale.y);
case GTK_CSS_TRANSFORM_SKEW:
return _gtk_css_value_equal (transform1->skew.x, transform2->skew.x)
&& _gtk_css_value_equal (transform1->skew.y, transform2->skew.y);
case GTK_CSS_TRANSFORM_SKEW_X:
return _gtk_css_value_equal (transform1->skew_x.skew, transform2->skew_x.skew);
case GTK_CSS_TRANSFORM_SKEW_Y:
return _gtk_css_value_equal (transform1->skew_y.skew, transform2->skew_y.skew);
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
return FALSE;
}
}
static gboolean
gtk_css_value_transform_equal (const GtkCssValue *value1,
const GtkCssValue *value2)
{
const GtkCssValue *larger;
guint i, n;
n = MIN (value1->n_transforms, value2->n_transforms);
for (i = 0; i < n; i++)
{
if (!gtk_css_transform_equal (&value1->transforms[i], &value2->transforms[i]))
return FALSE;
}
larger = value1->n_transforms > value2->n_transforms ? value1 : value2;
for (; i < larger->n_transforms; i++)
{
GtkCssTransform transform;
gtk_css_transform_init_identity (&transform, larger->transforms[i].type);
if (!gtk_css_transform_equal (&larger->transforms[i], &transform))
{
gtk_css_transform_clear (&transform);
return FALSE;
}
gtk_css_transform_clear (&transform);
}
return TRUE;
}
typedef struct _DecomposedMatrix DecomposedMatrix;
struct _DecomposedMatrix {
double translate[2];
double scale[2];
double angle;
double m11;
double m12;
double m21;
double m22;
};
static void
decomposed_init (DecomposedMatrix *decomposed,
const cairo_matrix_t *matrix)
{
double row0x = matrix->xx;
double row0y = matrix->xy;
double row1x = matrix->yx;
double row1y = matrix->yy;
double determinant;
decomposed->translate[0] = matrix->x0;
decomposed->translate[1] = matrix->y0;
decomposed->scale[0] = sqrt (row0x * row0x + row0y * row0y);
decomposed->scale[1] = sqrt (row1x * row1x + row1y * row1y);
/* If determinant is negative, one axis was flipped. */
determinant = row0x * row1y - row0y * row1x;
if (determinant < 0)
{
/* Flip axis with minimum unit vector dot product. */
if (row0x < row1y)
decomposed->scale[0] = - decomposed->scale[0];
else
decomposed->scale[1] = - decomposed->scale[1];
}
/* Renormalize matrix to remove scale. */
if (decomposed->scale[0])
{
row0x /= decomposed->scale[0];
row0y /= decomposed->scale[0];
}
if (decomposed->scale[1])
{
row1x /= decomposed->scale[1];
row1y /= decomposed->scale[1];
}
/* Compute rotation and renormalize matrix. */
decomposed->angle = atan2(row0y, row0x);
if (decomposed->angle)
{
/* Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)]
* = [row0x, -row0y, row0y, row0x]
* Thanks to the normalization above.
*/
decomposed->m11 = row0x;
decomposed->m12 = row0y;
decomposed->m21 = row1x;
decomposed->m22 = row1y;
}
else
{
decomposed->m11 = row0x * row0x - row0y * row1x;
decomposed->m12 = row0x * row0y - row0y * row1y;
decomposed->m21 = row0y * row0x + row0x * row1x;
decomposed->m22 = row0y * row0y + row0x * row1y;
}
/* Convert into degrees because our rotation functions expect it. */
decomposed->angle = decomposed->angle * 360 / (2 * G_PI);
}
static void
decomposed_interpolate (DecomposedMatrix *result,
const DecomposedMatrix *start,
const DecomposedMatrix *end,
double progress)
{
double start_angle, end_angle;
result->translate[0] = start->translate[0] + (end->translate[0] - start->translate[0]) * progress;
result->translate[1] = start->translate[1] + (end->translate[1] - start->translate[1]) * progress;
result->m11 = start->m11 + (end->m11 - start->m11) * progress;
result->m12 = start->m12 + (end->m12 - start->m12) * progress;
result->m21 = start->m21 + (end->m21 - start->m21) * progress;
result->m22 = start->m22 + (end->m22 - start->m22) * progress;
/* If x-axis of one is flipped, and y-axis of the other,
* convert to an unflipped rotation.
*/
if ((start->scale[0] < 0 && end->scale[1] < 0) || (start->scale[1] < 0 && end->scale[0] < 0))
{
result->scale[0] = - start->scale[0];
result->scale[1] = - start->scale[1];
start_angle = start->angle < 0 ? start->angle + 180 : start->angle - 180;
end_angle = end->angle;
}
else
{
result->scale[0] = start->scale[0];
result->scale[1] = start->scale[1];
start_angle = start->angle;
end_angle = end->angle;
}
result->scale[0] = result->scale[0] + (end->scale[0] - result->scale[0]) * progress;
result->scale[1] = result->scale[1] + (end->scale[1] - result->scale[1]) * progress;
/* Dont rotate the long way around. */
if (start_angle == 0)
start_angle = 360;
if (end_angle == 0)
end_angle = 360;
if (ABS (start_angle - end_angle) > 180)
{
if (start_angle > end_angle)
start_angle -= 360;
else
end_angle -= 360;
}
result->angle = start_angle + (end_angle - start_angle) * progress;
}
static void
decomposed_apply (const DecomposedMatrix *decomposed,
cairo_matrix_t *matrix)
{
matrix->xx = decomposed->m11;
matrix->xy = decomposed->m12;
matrix->yx = decomposed->m21;
matrix->yy = decomposed->m22;
matrix->x0 = 0;
matrix->y0 = 0;
/* Translate matrix. */
cairo_matrix_translate (matrix, decomposed->translate[0], decomposed->translate[1]);
/* Rotate matrix. */
cairo_matrix_rotate (matrix, decomposed->angle * (2 * G_PI) / 360);
/* Scale matrix. */
cairo_matrix_scale (matrix, decomposed->scale[0], decomposed->scale[1]);
}
static void
gtk_css_transform_matrix_transition (cairo_matrix_t *result,
const cairo_matrix_t *start,
const cairo_matrix_t *end,
double progress)
{
DecomposedMatrix dresult, dstart, dend;
decomposed_init (&dstart, start);
decomposed_init (&dend, end);
decomposed_interpolate (&dresult, &dstart, &dend, progress);
decomposed_apply (&dresult, result);
}
static void
gtk_css_transform_transition (GtkCssTransform *result,
const GtkCssTransform *start,
const GtkCssTransform *end,
guint property_id,
double progress)
{
result->type = start->type;
switch (start->type)
{
case GTK_CSS_TRANSFORM_MATRIX:
gtk_css_transform_matrix_transition (&result->matrix.matrix,
&start->matrix.matrix,
&end->matrix.matrix,
progress);
break;
case GTK_CSS_TRANSFORM_TRANSLATE:
result->translate.x = _gtk_css_value_transition (start->translate.x, end->translate.x, property_id, progress);
result->translate.y = _gtk_css_value_transition (start->translate.y, end->translate.y, property_id, progress);
break;
case GTK_CSS_TRANSFORM_ROTATE:
result->rotate.rotate = _gtk_css_value_transition (start->rotate.rotate, end->rotate.rotate, property_id, progress);
break;
case GTK_CSS_TRANSFORM_SCALE:
result->scale.x = _gtk_css_value_transition (start->scale.x, end->scale.x, property_id, progress);
result->scale.y = _gtk_css_value_transition (start->scale.y, end->scale.y, property_id, progress);
break;
case GTK_CSS_TRANSFORM_SKEW:
result->skew.x = _gtk_css_value_transition (start->skew.x, end->skew.x, property_id, progress);
result->skew.y = _gtk_css_value_transition (start->skew.y, end->skew.y, property_id, progress);
break;
case GTK_CSS_TRANSFORM_SKEW_X:
result->skew_x.skew = _gtk_css_value_transition (start->skew_x.skew, end->skew_x.skew, property_id, progress);
break;
case GTK_CSS_TRANSFORM_SKEW_Y:
result->skew_y.skew = _gtk_css_value_transition (start->skew_y.skew, end->skew_y.skew, property_id, progress);
break;
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
break;
}
}
static GtkCssValue *
gtk_css_value_transform_transition (GtkCssValue *start,
GtkCssValue *end,
guint property_id,
double progress)
{
GtkCssValue *result;
guint i, n;
if (gtk_css_transform_value_is_none (start))
{
if (gtk_css_transform_value_is_none (end))
return _gtk_css_value_ref (start);
n = 0;
}
else if (gtk_css_transform_value_is_none (end))
{
n = 0;
}
else
{
n = MIN (start->n_transforms, end->n_transforms);
}
/* Check transforms are compatible. If not, transition between
* their result matrices.
*/
for (i = 0; i < n; i++)
{
if (start->transforms[i].type != end->transforms[i].type)
{
cairo_matrix_t start_matrix, end_matrix;
cairo_matrix_init_identity (&start_matrix);
gtk_css_transform_value_compute_matrix (start, &start_matrix);
cairo_matrix_init_identity (&end_matrix);
gtk_css_transform_value_compute_matrix (end, &end_matrix);
result = gtk_css_transform_value_alloc (1);
result->transforms[0].type = GTK_CSS_TRANSFORM_MATRIX;
gtk_css_transform_matrix_transition (&result->transforms[0].matrix.matrix, &start_matrix, &end_matrix, progress);
return result;
}
}
result = gtk_css_transform_value_alloc (MAX (start->n_transforms, end->n_transforms));
for (i = 0; i < n; i++)
{
gtk_css_transform_transition (&result->transforms[i],
&start->transforms[i],
&end->transforms[i],
property_id,
progress);
}
for (; i < start->n_transforms; i++)
{
GtkCssTransform transform;
gtk_css_transform_init_identity (&transform, start->transforms[i].type);
gtk_css_transform_transition (&result->transforms[i],
&start->transforms[i],
&transform,
property_id,
progress);
gtk_css_transform_clear (&transform);
}
for (; i < end->n_transforms; i++)
{
GtkCssTransform transform;
gtk_css_transform_init_identity (&transform, end->transforms[i].type);
gtk_css_transform_transition (&result->transforms[i],
&transform,
&end->transforms[i],
property_id,
progress);
gtk_css_transform_clear (&transform);
}
g_assert (i == MAX (start->n_transforms, end->n_transforms));
return result;
}
static void
gtk_css_transform_print (const GtkCssTransform *transform,
GString *string)
{
char buf[G_ASCII_DTOSTR_BUF_SIZE];
switch (transform->type)
{
case GTK_CSS_TRANSFORM_MATRIX:
g_string_append (string, "matrix(");
g_ascii_dtostr (buf, sizeof (buf), transform->matrix.matrix.xx);
g_string_append (string, buf);
g_string_append (string, ", ");
g_ascii_dtostr (buf, sizeof (buf), transform->matrix.matrix.xy);
g_string_append (string, buf);
g_string_append (string, ", ");
g_ascii_dtostr (buf, sizeof (buf), transform->matrix.matrix.x0);
g_string_append (string, buf);
g_string_append (string, ", ");
g_ascii_dtostr (buf, sizeof (buf), transform->matrix.matrix.yx);
g_string_append (string, buf);
g_string_append (string, ", ");
g_ascii_dtostr (buf, sizeof (buf), transform->matrix.matrix.yy);
g_string_append (string, buf);
g_string_append (string, ", ");
g_ascii_dtostr (buf, sizeof (buf), transform->matrix.matrix.y0);
g_string_append (string, buf);
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_TRANSLATE:
g_string_append (string, "translate(");
_gtk_css_value_print (transform->translate.x, string);
g_string_append (string, ", ");
_gtk_css_value_print (transform->translate.y, string);
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_ROTATE:
g_string_append (string, "rotate(");
_gtk_css_value_print (transform->rotate.rotate, string);
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_SCALE:
g_string_append (string, "scale(");
_gtk_css_value_print (transform->scale.x, string);
if (!_gtk_css_value_equal (transform->scale.x, transform->scale.y))
{
g_string_append (string, ", ");
_gtk_css_value_print (transform->scale.y, string);
}
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_SKEW:
g_string_append (string, "skew(");
_gtk_css_value_print (transform->skew.x, string);
g_string_append (string, ", ");
_gtk_css_value_print (transform->skew.y, string);
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_SKEW_X:
g_string_append (string, "skewX(");
_gtk_css_value_print (transform->skew_x.skew, string);
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_SKEW_Y:
g_string_append (string, "skewY(");
_gtk_css_value_print (transform->skew_y.skew, string);
g_string_append (string, ")");
break;
case GTK_CSS_TRANSFORM_NONE:
default:
g_assert_not_reached ();
break;
}
}
static void
gtk_css_value_transform_print (const GtkCssValue *value,
GString *string)
{
guint i;
if (gtk_css_transform_value_is_none (value))
{
g_string_append (string, "none");
return;
}
for (i = 0; i < value->n_transforms; i++)
{
if (i > 0)
g_string_append_c (string, ' ');
gtk_css_transform_print (&value->transforms[i], string);
}
}
static const GtkCssValueClass GTK_CSS_VALUE_TRANSFORM = {
gtk_css_value_transform_free,
gtk_css_value_transform_compute,
gtk_css_value_transform_equal,
gtk_css_value_transform_transition,
gtk_css_value_transform_print
};
static GtkCssValue none_singleton = { &GTK_CSS_VALUE_TRANSFORM, 1, 0, { { GTK_CSS_TRANSFORM_NONE } } };
static GtkCssValue *
gtk_css_transform_value_alloc (guint n_transforms)
{
GtkCssValue *result;
g_return_val_if_fail (n_transforms > 0, NULL);
result = _gtk_css_value_alloc (&GTK_CSS_VALUE_TRANSFORM, sizeof (GtkCssValue) + sizeof (GtkCssTransform) * (n_transforms - 1));
result->n_transforms = n_transforms;
return result;
}
GtkCssValue *
_gtk_css_transform_value_new_none (void)
{
return _gtk_css_value_ref (&none_singleton);
}
static gboolean
gtk_css_transform_value_is_none (const GtkCssValue *value)
{
return value->n_transforms == 0;
}
static gboolean
gtk_css_transform_parse (GtkCssTransform *transform,
GtkCssParser *parser)
{
if (_gtk_css_parser_try (parser, "matrix(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_MATRIX;
/* FIXME: Improve error handling here */
if (!_gtk_css_parser_try_double (parser, &transform->matrix.matrix.xx)
|| !_gtk_css_parser_try (parser, ",", TRUE)
|| !_gtk_css_parser_try_double (parser, &transform->matrix.matrix.xy)
|| !_gtk_css_parser_try (parser, ",", TRUE)
|| !_gtk_css_parser_try_double (parser, &transform->matrix.matrix.x0)
|| !_gtk_css_parser_try (parser, ",", TRUE)
|| !_gtk_css_parser_try_double (parser, &transform->matrix.matrix.yx)
|| !_gtk_css_parser_try (parser, ",", TRUE)
|| !_gtk_css_parser_try_double (parser, &transform->matrix.matrix.yy)
|| !_gtk_css_parser_try (parser, ",", TRUE)
|| !_gtk_css_parser_try_double (parser, &transform->matrix.matrix.y0))
{
_gtk_css_parser_error (parser, "invalid syntax for matrix()");
return FALSE;
}
}
else if (_gtk_css_parser_try (parser, "translate(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_TRANSLATE;
transform->translate.x = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH);
if (transform->translate.x == NULL)
return FALSE;
if (_gtk_css_parser_try (parser, ",", TRUE))
{
transform->translate.y = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH);
if (transform->translate.y == NULL)
{
_gtk_css_value_unref (transform->translate.x);
return FALSE;
}
}
else
{
transform->translate.y = _gtk_css_number_value_new (0, GTK_CSS_PX);
}
}
else if (_gtk_css_parser_try (parser, "translateX(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_TRANSLATE;
transform->translate.x = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH);
if (transform->translate.x == NULL)
return FALSE;
transform->translate.y = _gtk_css_number_value_new (0, GTK_CSS_PX);
}
else if (_gtk_css_parser_try (parser, "translateY(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_TRANSLATE;
transform->translate.y = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH);
if (transform->translate.y == NULL)
return FALSE;
transform->translate.x = _gtk_css_number_value_new (0, GTK_CSS_PX);
}
else if (_gtk_css_parser_try (parser, "scale(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_SCALE;
transform->scale.x = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER);
if (transform->scale.x == NULL)
return FALSE;
if (_gtk_css_parser_try (parser, ",", TRUE))
{
transform->scale.y = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER);
if (transform->scale.y == NULL)
{
_gtk_css_value_unref (transform->scale.x);
return FALSE;
}
}
else
{
transform->scale.y = _gtk_css_value_ref (transform->scale.x);
}
}
else if (_gtk_css_parser_try (parser, "scaleX(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_SCALE;
transform->scale.x = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER);
if (transform->scale.x == NULL)
return FALSE;
transform->scale.y = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
}
else if (_gtk_css_parser_try (parser, "scaleY(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_SCALE;
transform->scale.y = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER);
if (transform->scale.y == NULL)
return FALSE;
transform->scale.x = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
}
else if (_gtk_css_parser_try (parser, "rotate(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_ROTATE;
transform->rotate.rotate = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
if (transform->rotate.rotate == NULL)
return FALSE;
}
else if (_gtk_css_parser_try (parser, "skew(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_SKEW;
transform->skew.x = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
if (transform->skew.x == NULL)
return FALSE;
if (_gtk_css_parser_try (parser, ",", TRUE))
{
transform->skew.y = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
if (transform->skew.y == NULL)
{
_gtk_css_value_unref (transform->skew.x);
return FALSE;
}
}
else
{
transform->skew.y = _gtk_css_number_value_new (0, GTK_CSS_DEG);
}
}
else if (_gtk_css_parser_try (parser, "skewX(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_SKEW_X;
transform->skew_x.skew = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
if (transform->skew_x.skew == NULL)
return FALSE;
}
else if (_gtk_css_parser_try (parser, "skewY(", TRUE))
{
transform->type = GTK_CSS_TRANSFORM_SKEW_Y;
transform->skew_y.skew = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
if (transform->skew_y.skew == NULL)
return FALSE;
}
else
{
_gtk_css_parser_error (parser, "unknown syntax for transform");
return FALSE;
}
if (!_gtk_css_parser_try (parser, ")", TRUE))
{
gtk_css_transform_clear (transform);
_gtk_css_parser_error (parser, "Expected closing ')'");
return FALSE;
}
return TRUE;
}
GtkCssValue *
_gtk_css_transform_value_parse (GtkCssParser *parser)
{
GtkCssValue *value;
GArray *array;
guint i;
if (_gtk_css_parser_try (parser, "none", TRUE))
return _gtk_css_transform_value_new_none ();
array = g_array_new (FALSE, FALSE, sizeof (GtkCssTransform));
do {
GtkCssTransform transform;
if (!gtk_css_transform_parse (&transform, parser))
{
for (i = 0; i < array->len; i++)
{
gtk_css_transform_clear (&g_array_index (array, GtkCssTransform, i));
}
g_array_free (array, TRUE);
return NULL;
}
g_array_append_val (array, transform);
} while (!_gtk_css_parser_begins_with (parser, ';'));
value = gtk_css_transform_value_alloc (array->len);
memcpy (value->transforms, array->data, sizeof (GtkCssTransform) * array->len);
g_array_free (array, TRUE);
return value;
}
gboolean
_gtk_css_transform_value_get_matrix (const GtkCssValue *transform,
cairo_matrix_t *matrix)
{
cairo_matrix_t invert;
g_return_val_if_fail (transform->class == &GTK_CSS_VALUE_TRANSFORM, FALSE);
g_return_val_if_fail (matrix != NULL, FALSE);
gtk_css_transform_value_compute_matrix (transform, &invert);
*matrix = invert;
if (cairo_matrix_invert (&invert) != CAIRO_STATUS_SUCCESS)
return FALSE;
return TRUE;
}