[gtk+] css: Add GtkCssTransformValue



commit 965cd4a9c04719ad0bfe1ae6fd9b179bbba8d595
Author: Benjamin Otte <otte redhat com>
Date:   Mon May 5 14:48:27 2014 +0200

    css: Add GtkCssTransformValue
    
    The value implements the 2D parts of CSS transforms. See
      http://www.w3.org/TR/css3-transforms/
    For the specification.
    
    All it does is give us an expressive way to define Cairo matrices (and
    their transforms)

 gtk/Makefile.am                   |    2 +
 gtk/gtkcsstransformvalue.c        | 1049 +++++++++++++++++++++++++++++++++++++
 gtk/gtkcsstransformvalueprivate.h |   36 ++
 3 files changed, 1087 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 76c1c4f..2ad225f 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -483,6 +483,7 @@ gtk_private_h_sources =             \
        gtkcssstringvalueprivate.h      \
        gtkcssstylefuncsprivate.h \
        gtkcssstylepropertyprivate.h \
+       gtkcsstransformvalueprivate.h   \
        gtkcsstransitionprivate.h       \
        gtkcsstypedvalueprivate.h       \
        gtkcssunsetvalueprivate.h       \
@@ -734,6 +735,7 @@ gtk_base_c_sources =                \
        gtkcssstylefuncs.c      \
        gtkcssstyleproperty.c   \
        gtkcssstylepropertyimpl.c \
+       gtkcsstransformvalue.c  \
        gtkcsstransition.c      \
        gtkcsstypedvalue.c      \
        gtkcssunsetvalue.c      \
diff --git a/gtk/gtkcsstransformvalue.c b/gtk/gtkcsstransformvalue.c
new file mode 100644
index 0000000..9fcbfb9
--- /dev/null
+++ b/gtk/gtkcsstransformvalue.c
@@ -0,0 +1,1049 @@
+/* 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_get_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;
+
+  /* Don’t 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_get_matrix (start, &start_matrix);
+          cairo_matrix_init_identity (&end_matrix);
+          gtk_css_transform_value_get_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_apply (const GtkCssValue *transform,
+                                cairo_t           *cr)
+{
+  cairo_matrix_t matrix, invert;
+
+  g_return_val_if_fail (transform->class == &GTK_CSS_VALUE_TRANSFORM, FALSE);
+  g_return_val_if_fail (cr != NULL, FALSE);
+  
+  gtk_css_transform_value_get_matrix (transform, &matrix);
+
+  invert = matrix;
+  if (cairo_matrix_invert (&invert) != CAIRO_STATUS_SUCCESS)
+    return FALSE;
+
+  cairo_transform (cr, &matrix);
+
+  return TRUE;
+}
+
diff --git a/gtk/gtkcsstransformvalueprivate.h b/gtk/gtkcsstransformvalueprivate.h
new file mode 100644
index 0000000..2c57c3f
--- /dev/null
+++ b/gtk/gtkcsstransformvalueprivate.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2014 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.1 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/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_CSS_TRANSFORM_VALUE_PRIVATE_H__
+#define __GTK_CSS_TRANSFORM_VALUE_PRIVATE_H__
+
+#include "gtkcssparserprivate.h"
+#include "gtkcssvalueprivate.h"
+
+G_BEGIN_DECLS
+
+GtkCssValue *   _gtk_css_transform_value_new_none       (void);
+GtkCssValue *   _gtk_css_transform_value_parse          (GtkCssParser           *parser);
+
+gboolean        _gtk_css_transform_value_apply          (const GtkCssValue      *transform,
+                                                         cairo_t                *cr);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_TRANSFORM_VALUE_PRIVATE_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]