diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index e8b9810359..fc3705cad5 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -279,8 +279,8 @@ - + @@ -344,6 +344,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 94756237b7..bbe287b7d4 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -5885,6 +5885,38 @@ gtk_mount_operation_get_type GtkMountOperationPrivate +
+tranform +3D transformations +GtkTransformType; +GtkTransform; +gtk_transform_ref +gtk_transform_unref + +gtk_transform_print +gtk_transform_to_string +gtk_transform_to_matrix + +gtk_transform_identity +gtk_transform_transform +gtk_transform_matrix +gtk_transform_translate +gtk_transform_translate_3d +gtk_transform_rotate +gtk_transform_rotate_3d +gtk_transform_scale +gtk_transform_scale_3d + +gtk_transform_equal + +gtk_transform_get_transform_type +gtk_transform_get_next + +GTK_TYPE_TRANSFORM +gdk_transform_get_type +gtk_transform_new +
+
gtkorientable Orientable diff --git a/gtk/gtk.h b/gtk/gtk.h index 34fff24a3f..62a3bb3d7f 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -228,6 +228,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtktransform.c b/gtk/gtktransform.c new file mode 100644 index 0000000000..7580a3476b --- /dev/null +++ b/gtk/gtktransform.c @@ -0,0 +1,1113 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * 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 . + * + * Authors: Benjamin Otte + */ + + +/** + * SECTION:GtkTransform + * @Title: GtkTransform + * @Short_description: A description for transform operations + * + * #GtkTransform is an object to describe transform matrices. Unlike + * #graphene_matrix_t, #GtkTransform retains the steps in how a transform was + * constructed, and allows inspecting them. It is modeled after the way + * CSS describes transforms. + * + * #GtkTransform objects are immutable and cannot be changed after creation. + * This means code can safely expose them as properties of objects without + * having to worry about others changing them. + */ + +#include "config.h" + +#include "gtktransformprivate.h" + +typedef struct _GtkTransformClass GtkTransformClass; + +#define GTK_IS_TRANSFORM_TYPE(self,type) ((self) == NULL ? (type) == GTK_TRANSFORM_TYPE_IDENTITY : (self)->transform_class->transform_type == (type)) + +struct _GtkTransform +{ + const GtkTransformClass *transform_class; + + volatile int ref_count; + GtkTransform *next; +}; + +struct _GtkTransformClass +{ + GtkTransformType transform_type; + gsize struct_size; + const char *type_name; + + void (* finalize) (GtkTransform *transform); + GskMatrixCategory (* categorize) (GtkTransform *transform); + void (* to_matrix) (GtkTransform *transform, + graphene_matrix_t *out_matrix); + gboolean (* apply_affine) (GtkTransform *transform, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy); + void (* print) (GtkTransform *transform, + GString *string); + GtkTransform * (* apply) (GtkTransform *transform, + GtkTransform *apply_to); + /* both matrices have the same type */ + gboolean (* equal) (GtkTransform *first_transform, + GtkTransform *second_transform); +}; + +/** + * GtkTransform: (ref-func gtk_transform_ref) (unref-func gtk_transform_unref) + * + * The `GtkTransform` structure contains only private data. + */ + +G_DEFINE_BOXED_TYPE (GtkTransform, gtk_transform, + gtk_transform_ref, + gtk_transform_unref) + +/* + * gtk_transform_is_identity: + * @transform: (allow-none): A transform or %NULL + * + * Checks if the transform is a representation of the identity + * transform. + * + * This is different from a transform like `scale(2) scale(0.5)` + * which just results in an identity transform when simplified. + * + * Returns: %TRUE if this transform is a representation of + * the identity transform + **/ +static gboolean +gtk_transform_is_identity (GtkTransform *self) +{ + return self == NULL || + (GTK_IS_TRANSFORM_TYPE (self, GTK_TRANSFORM_TYPE_IDENTITY) && gtk_transform_is_identity (self->next)); +} + +/*< private > + * gtk_transform_alloc: + * @transform_class: class structure for this self + * @next: (transfer full) Next matrix to multiply with or %NULL if none + * + * Returns: (transfer full): the newly created #GtkTransform + */ +static gpointer +gtk_transform_alloc (const GtkTransformClass *transform_class, + GtkTransform *next) +{ + GtkTransform *self; + + g_return_val_if_fail (transform_class != NULL, NULL); + + self = g_malloc0 (transform_class->struct_size); + + self->transform_class = transform_class; + self->ref_count = 1; + self->next = gtk_transform_is_identity (next) ? NULL : next; + + return self; +} + +/*** IDENTITY ***/ + +static void +gtk_identity_transform_finalize (GtkTransform *transform) +{ +} + +static GskMatrixCategory +gtk_identity_transform_categorize (GtkTransform *transform) +{ + return GSK_MATRIX_CATEGORY_IDENTITY; +} + +static void +gtk_identity_transform_to_matrix (GtkTransform *transform, + graphene_matrix_t *out_matrix) +{ + graphene_matrix_init_identity (out_matrix); +} + +static gboolean +gtk_identity_transform_apply_affine (GtkTransform *transform, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy) +{ + return TRUE; +} + +static void +gtk_identity_transform_print (GtkTransform *transform, + GString *string) +{ + g_string_append (string, "identity"); +} + +static GtkTransform * +gtk_identity_transform_apply (GtkTransform *transform, + GtkTransform *apply_to) +{ + return gtk_transform_identity (apply_to); +} + +static gboolean +gtk_identity_transform_equal (GtkTransform *first_transform, + GtkTransform *second_transform) +{ + return TRUE; +} + +static const GtkTransformClass GTK_IDENTITY_TRANSFORM_CLASS = +{ + GTK_TRANSFORM_TYPE_IDENTITY, + sizeof (GtkTransform), + "GtkIdentityMatrix", + gtk_identity_transform_finalize, + gtk_identity_transform_categorize, + gtk_identity_transform_to_matrix, + gtk_identity_transform_apply_affine, + gtk_identity_transform_print, + gtk_identity_transform_apply, + gtk_identity_transform_equal, +}; + +/** + * gtk_transform_identity: + * @next: (allow-none): the next transform operation or %NULL + * + * Adds an identity multiplication into the list of matrix operations. + * + * This operation is generally useless, but may be useful when interpolating + * matrices, because the identity matrix can be interpolated to and from + * everything, so an identity matrix can be used as a keyframe between two + * different types of matrices. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_identity (GtkTransform *next) +{ + if (gtk_transform_is_identity (next)) + return next; + + return gtk_transform_alloc (>K_IDENTITY_TRANSFORM_CLASS, next); +} + +/*** MATRIX ***/ + +typedef struct _GtkMatrixTransform GtkMatrixTransform; + +struct _GtkMatrixTransform +{ + GtkTransform parent; + + graphene_matrix_t matrix; + GskMatrixCategory category; +}; + +static void +gtk_matrix_transform_finalize (GtkTransform *self) +{ +} + +static GskMatrixCategory +gtk_matrix_transform_categorize (GtkTransform *transform) +{ + GtkMatrixTransform *self = (GtkMatrixTransform *) transform; + + return self->category; +} + +static void +gtk_matrix_transform_to_matrix (GtkTransform *transform, + graphene_matrix_t *out_matrix) +{ + GtkMatrixTransform *self = (GtkMatrixTransform *) transform; + + graphene_matrix_init_from_matrix (out_matrix, &self->matrix); +} + +static gboolean +gtk_matrix_transform_apply_affine (GtkTransform *transform, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy) +{ + GtkMatrixTransform *self = (GtkMatrixTransform *) transform; + + switch (self->category) + { + case GSK_MATRIX_CATEGORY_UNKNOWN: + case GSK_MATRIX_CATEGORY_ANY: + case GSK_MATRIX_CATEGORY_INVERTIBLE: + default: + return FALSE; + + case GSK_MATRIX_CATEGORY_2D_AFFINE: + *out_dx += *out_scale_x * graphene_matrix_get_value (&self->matrix, 3, 0); + *out_dy += *out_scale_y * graphene_matrix_get_value (&self->matrix, 3, 1); + *out_scale_x *= graphene_matrix_get_value (&self->matrix, 0, 0); + *out_scale_y *= graphene_matrix_get_value (&self->matrix, 1, 1); + return TRUE; + + case GSK_MATRIX_CATEGORY_2D_TRANSLATE: + *out_dx += *out_scale_x * graphene_matrix_get_value (&self->matrix, 3, 0); + *out_dy += *out_scale_y * graphene_matrix_get_value (&self->matrix, 3, 1); + return TRUE; + + case GSK_MATRIX_CATEGORY_IDENTITY: + return TRUE; + } +} + +static void +string_append_double (GString *string, + double d) +{ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%g", d); + g_string_append (string, buf); +} + +static void +gtk_matrix_transform_print (GtkTransform *transform, + GString *string) +{ + GtkMatrixTransform *self = (GtkMatrixTransform *) transform; + guint i; + float f[16]; + + g_string_append (string, "matrix3d("); + graphene_matrix_to_float (&self->matrix, f); + for (i = 0; i < 16; i++) + { + if (i > 0) + g_string_append (string, ", "); + string_append_double (string, f[i]); + } + g_string_append (string, ")"); +} + +static GtkTransform * +gtk_matrix_transform_apply (GtkTransform *transform, + GtkTransform *apply_to) +{ + GtkMatrixTransform *self = (GtkMatrixTransform *) transform; + + return gtk_transform_matrix_with_category (apply_to, + &self->matrix, + self->category); +} + +static gboolean +gtk_matrix_transform_equal (GtkTransform *first_transform, + GtkTransform *second_transform) +{ + GtkMatrixTransform *first = (GtkMatrixTransform *) first_transform; + GtkMatrixTransform *second = (GtkMatrixTransform *) second_transform; + + /* Crude, but better than just returning FALSE */ + return memcmp (&first->matrix, &second->matrix, sizeof (graphene_matrix_t)) == 0; +} + +static const GtkTransformClass GTK_TRANSFORM_TRANSFORM_CLASS = +{ + GTK_TRANSFORM_TYPE_TRANSFORM, + sizeof (GtkMatrixTransform), + "GtkMatrixTransform", + gtk_matrix_transform_finalize, + gtk_matrix_transform_categorize, + gtk_matrix_transform_to_matrix, + gtk_matrix_transform_apply_affine, + gtk_matrix_transform_print, + gtk_matrix_transform_apply, + gtk_matrix_transform_equal, +}; + +GtkTransform * +gtk_transform_matrix_with_category (GtkTransform *next, + const graphene_matrix_t *matrix, + GskMatrixCategory category) +{ + GtkMatrixTransform *result = gtk_transform_alloc (>K_TRANSFORM_TRANSFORM_CLASS, next); + + graphene_matrix_init_from_matrix (&result->matrix, matrix); + result->category = category; + + return &result->parent; +} + +/** + * gtk_transform_matrix: + * @next: (allow-none): the next transform + * @matrix: the matrix to multiply @next with + * + * Multiplies @next with the given @matrix. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_matrix (GtkTransform *next, + const graphene_matrix_t *matrix) +{ + return gtk_transform_matrix_with_category (next, matrix, GSK_MATRIX_CATEGORY_UNKNOWN); +} + +/*** TRANSLATE ***/ + +typedef struct _GtkTranslateTransform GtkTranslateTransform; + +struct _GtkTranslateTransform +{ + GtkTransform parent; + + graphene_point3d_t point; +}; + +static void +gtk_translate_transform_finalize (GtkTransform *self) +{ +} + +static GskMatrixCategory +gtk_translate_transform_categorize (GtkTransform *transform) +{ + GtkTranslateTransform *self = (GtkTranslateTransform *) transform; + + if (self->point.z != 0.0) + return GSK_MATRIX_CATEGORY_INVERTIBLE; + + return GSK_MATRIX_CATEGORY_2D_TRANSLATE; +} + +static void +gtk_translate_transform_to_matrix (GtkTransform *transform, + graphene_matrix_t *out_matrix) +{ + GtkTranslateTransform *self = (GtkTranslateTransform *) transform; + + graphene_matrix_init_translate (out_matrix, &self->point); +} + +static gboolean +gtk_translate_transform_apply_affine (GtkTransform *transform, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy) +{ + GtkTranslateTransform *self = (GtkTranslateTransform *) transform; + + if (self->point.z != 0.0) + return FALSE; + + *out_dx += *out_scale_x * self->point.x; + *out_dy += *out_scale_y * self->point.y; + + return TRUE; +} + +static GtkTransform * +gtk_translate_transform_apply (GtkTransform *transform, + GtkTransform *apply_to) +{ + GtkTranslateTransform *self = (GtkTranslateTransform *) transform; + + return gtk_transform_translate_3d (apply_to, &self->point); +} + +static gboolean +gtk_translate_transform_equal (GtkTransform *first_transform, + GtkTransform *second_transform) +{ + GtkTranslateTransform *first = (GtkTranslateTransform *) first_transform; + GtkTranslateTransform *second = (GtkTranslateTransform *) second_transform; + + return graphene_point3d_equal (&first->point, &second->point); +} + +static void +gtk_translate_transform_print (GtkTransform *transform, + GString *string) +{ + GtkTranslateTransform *self = (GtkTranslateTransform *) transform; + + if (self->point.z == 0) + g_string_append (string, "translate("); + else + g_string_append (string, "translate3d("); + + string_append_double (string, self->point.x); + g_string_append (string, ", "); + string_append_double (string, self->point.y); + if (self->point.z != 0) + { + g_string_append (string, ", "); + string_append_double (string, self->point.y); + } + g_string_append (string, ")"); +} + +static const GtkTransformClass GTK_TRANSLATE_TRANSFORM_CLASS = +{ + GTK_TRANSFORM_TYPE_TRANSLATE, + sizeof (GtkTranslateTransform), + "GtkTranslateTransform", + gtk_translate_transform_finalize, + gtk_translate_transform_categorize, + gtk_translate_transform_to_matrix, + gtk_translate_transform_apply_affine, + gtk_translate_transform_print, + gtk_translate_transform_apply, + gtk_translate_transform_equal, +}; + +/** + * gtk_transform_translate: + * @next: (allow-none): the next transform + * @point: the point to translate the matrix by + * + * Translates @next in 2dimensional space by @point. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_translate (GtkTransform *next, + const graphene_point_t *point) +{ + graphene_point3d_t point3d; + + graphene_point3d_init (&point3d, point->x, point->y, 0); + + return gtk_transform_translate_3d (next, &point3d); +} + +/** + * gtk_transform_translate_3d: + * @next: (allow-none): the next transform + * @point: the point to translate the matrix by + * + * Translates @next by @point. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_translate_3d (GtkTransform *next, + const graphene_point3d_t *point) +{ + GtkTranslateTransform *result = gtk_transform_alloc (>K_TRANSLATE_TRANSFORM_CLASS, next); + + graphene_point3d_init_from_point (&result->point, point); + + return &result->parent; +} + +/*** ROTATE ***/ + +typedef struct _GtkRotateTransform GtkRotateTransform; + +struct _GtkRotateTransform +{ + GtkTransform parent; + + float angle; + graphene_vec3_t axis; +}; + +static void +gtk_rotate_transform_finalize (GtkTransform *self) +{ +} + +static GskMatrixCategory +gtk_rotate_transform_categorize (GtkTransform *transform) +{ + return GSK_MATRIX_CATEGORY_INVERTIBLE; +} + +static void +gtk_rotate_transform_to_matrix (GtkTransform *transform, + graphene_matrix_t *out_matrix) +{ + GtkRotateTransform *self = (GtkRotateTransform *) transform; + + graphene_matrix_init_rotate (out_matrix, self->angle, &self->axis); +} + +static gboolean +gtk_rotate_transform_apply_affine (GtkTransform *transform, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy) +{ + return FALSE; +} + +static GtkTransform * +gtk_rotate_transform_apply (GtkTransform *transform, + GtkTransform *apply_to) +{ + GtkRotateTransform *self = (GtkRotateTransform *) transform; + + return gtk_transform_rotate_3d (apply_to, self->angle, &self->axis); +} + +static gboolean +gtk_rotate_transform_equal (GtkTransform *first_transform, + GtkTransform *second_transform) +{ + GtkRotateTransform *first = (GtkRotateTransform *) first_transform; + GtkRotateTransform *second = (GtkRotateTransform *) second_transform; + + return first->angle == second->angle + && graphene_vec3_equal (&first->axis, &second->axis); +} + +static void +gtk_rotate_transform_print (GtkTransform *transform, + GString *string) +{ + GtkRotateTransform *self = (GtkRotateTransform *) transform; + graphene_vec3_t default_axis; + + graphene_vec3_init_from_vec3 (&default_axis, graphene_vec3_z_axis ()); + if (graphene_vec3_equal (&default_axis, &self->axis)) + { + g_string_append (string, "rotate("); + string_append_double (string, self->angle); + g_string_append (string, ")"); + } + else + { + float f[3]; + guint i; + + g_string_append (string, "rotate3d("); + graphene_vec3_to_float (&self->axis, f); + for (i = 0; i < 3; i++) + { + string_append_double (string, f[i]); + g_string_append (string, ", "); + } + string_append_double (string, self->angle); + g_string_append (string, ")"); + } +} + +static const GtkTransformClass GTK_ROTATE_TRANSFORM_CLASS = +{ + GTK_TRANSFORM_TYPE_ROTATE, + sizeof (GtkRotateTransform), + "GtkRotateTransform", + gtk_rotate_transform_finalize, + gtk_rotate_transform_categorize, + gtk_rotate_transform_to_matrix, + gtk_rotate_transform_apply_affine, + gtk_rotate_transform_print, + gtk_rotate_transform_apply, + gtk_rotate_transform_equal, +}; + +/** + * gtk_transform_rotate: + * @next: (allow-none): the next transform + * @angle: the rotation angle, in degrees (clockwise) + * + * Rotates @next @angle degrees in 2D - or in 3Dspeak, around the z axis. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_rotate (GtkTransform *next, + float angle) +{ + return gtk_transform_rotate_3d (next, angle, graphene_vec3_z_axis ()); +} + +/** + * gtk_transform_rotate_3d: + * @next: (allow-none): the next transform + * @angle: the rotation angle, in degrees (clockwise) + * @axis: The rotation axis + * + * Rotates @next @angle degrees around @axis. + * + * For a rotation in 2D space, use gtk_transform_rotate(). + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_rotate_3d (GtkTransform *next, + float angle, + const graphene_vec3_t *axis) +{ + GtkRotateTransform *result = gtk_transform_alloc (>K_ROTATE_TRANSFORM_CLASS, next); + + result->angle = angle; + graphene_vec3_init_from_vec3 (&result->axis, axis); + + return &result->parent; +} + +/*** SCALE ***/ + +typedef struct _GtkScaleTransform GtkScaleTransform; + +struct _GtkScaleTransform +{ + GtkTransform parent; + + float factor_x; + float factor_y; + float factor_z; +}; + +static void +gtk_scale_transform_finalize (GtkTransform *self) +{ +} + +static GskMatrixCategory +gtk_scale_transform_categorize (GtkTransform *transform) +{ + GtkScaleTransform *self = (GtkScaleTransform *) transform; + + if (self->factor_z != 1.0) + return GSK_MATRIX_CATEGORY_INVERTIBLE; + + return GSK_MATRIX_CATEGORY_2D_AFFINE; +} + +static void +gtk_scale_transform_to_matrix (GtkTransform *transform, + graphene_matrix_t *out_matrix) +{ + GtkScaleTransform *self = (GtkScaleTransform *) transform; + + graphene_matrix_init_scale (out_matrix, self->factor_x, self->factor_y, self->factor_z); +} + +static gboolean +gtk_scale_transform_apply_affine (GtkTransform *transform, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy) +{ + GtkScaleTransform *self = (GtkScaleTransform *) transform; + + if (self->factor_z != 1.0) + return FALSE; + + *out_scale_x *= self->factor_x; + *out_scale_y *= self->factor_y; + + return TRUE; +} + +static GtkTransform * +gtk_scale_transform_apply (GtkTransform *transform, + GtkTransform *apply_to) +{ + GtkScaleTransform *self = (GtkScaleTransform *) transform; + + return gtk_transform_scale_3d (apply_to, self->factor_x, self->factor_y, self->factor_z); +} + +static gboolean +gtk_scale_transform_equal (GtkTransform *first_transform, + GtkTransform *second_transform) +{ + GtkScaleTransform *first = (GtkScaleTransform *) first_transform; + GtkScaleTransform *second = (GtkScaleTransform *) second_transform; + + return first->factor_x == second->factor_x + && first->factor_y == second->factor_y + && first->factor_z == second->factor_z; +} + +static void +gtk_scale_transform_print (GtkTransform *transform, + GString *string) +{ + GtkScaleTransform *self = (GtkScaleTransform *) transform; + + if (self->factor_z == 1.0) + { + g_string_append (string, "scale("); + string_append_double (string, self->factor_x); + if (self->factor_x != self->factor_y) + { + g_string_append (string, ", "); + string_append_double (string, self->factor_y); + } + g_string_append (string, ")"); + } + else + { + g_string_append (string, "scale3d("); + string_append_double (string, self->factor_x); + g_string_append (string, ", "); + string_append_double (string, self->factor_y); + g_string_append (string, ", "); + string_append_double (string, self->factor_z); + g_string_append (string, ")"); + } +} + +static const GtkTransformClass GTK_SCALE_TRANSFORM_CLASS = +{ + GTK_TRANSFORM_TYPE_SCALE, + sizeof (GtkScaleTransform), + "GtkScaleTransform", + gtk_scale_transform_finalize, + gtk_scale_transform_categorize, + gtk_scale_transform_to_matrix, + gtk_scale_transform_apply_affine, + gtk_scale_transform_print, + gtk_scale_transform_apply, + gtk_scale_transform_equal, +}; + +/** + * gtk_transform_scale: + * @next: (allow-none): the next transform + * @factor_x: scaling factor on the X axis + * @factor_y: scaling factor on the Y axis + * + * Scales @next in 2-dimensional space by the given factors. + * Use gtk_transform_scale_3d() to scale in all 3 dimensions. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_scale (GtkTransform *next, + float factor_x, + float factor_y) +{ + return gtk_transform_scale_3d (next, factor_x, factor_y, 1.0); +} + +/** + * gtk_transform_scale_3d: + * @next: (allow-none): the next transform + * @factor_x: scaling factor on the X axis + * @factor_y: scaling factor on the Y axis + * @factor_z: scaling factor on the Z axis + * + * Scales @next by the given factors. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_scale_3d (GtkTransform *next, + float factor_x, + float factor_y, + float factor_z) +{ + GtkScaleTransform *result = gtk_transform_alloc (>K_SCALE_TRANSFORM_CLASS, next); + + result->factor_x = factor_x; + result->factor_y = factor_y; + result->factor_z = factor_z; + + return &result->parent; +} + +/*** PUBLIC API ***/ + +static void +gtk_transform_finalize (GtkTransform *self) +{ + self->transform_class->finalize (self); + + gtk_transform_unref (self->next); + + g_free (self); +} + +/** + * gtk_transform_ref: + * @self: (allow-none): a #GtkTransform + * + * Acquires a reference on the given #GtkTransform. + * + * Returns: (transfer none): the #GtkTransform with an additional reference + */ +GtkTransform * +gtk_transform_ref (GtkTransform *self) +{ + if (self == NULL) + return NULL; + + g_atomic_int_inc (&self->ref_count); + + return self; +} + +/** + * gtk_transform_unref: + * @self: (allow-none): a #GtkTransform + * + * Releases a reference on the given #GtkTransform. + * + * If the reference was the last, the resources associated to the @self are + * freed. + */ +void +gtk_transform_unref (GtkTransform *self) +{ + if (self == NULL) + return; + + if (g_atomic_int_dec_and_test (&self->ref_count)) + gtk_transform_finalize (self); +} + +/** + * gtk_transform_print: + * @self: (allow-none): a #GtkTransform + * @string: The string to print into + * + * Converts @self into a string representation suitable for printing that + * can later be parsed with gtk_transform_parse(). + **/ +void +gtk_transform_print (GtkTransform *self, + GString *string) +{ + g_return_if_fail (string != NULL); + + if (self == NULL) + { + g_string_append (string, "none"); + return; + } + + if (self->next != NULL) + { + gtk_transform_print (self->next, string); + g_string_append (string, " "); + } + + self->transform_class->print (self, string); +} + +/** + * gtk_transform_to_string: + * @self: (allow-none): a #GtkTransform + * + * Converts a matrix into a string that is suitable for + * printing and can later be parsed with gtk_transform_parse(). + * + * This is a wrapper around gtk_transform_print(), see that function + * for details. + * + * Returns: A new string for @self + **/ +char * +gtk_transform_to_string (GtkTransform *self) +{ + GString *string; + + string = g_string_new (""); + + gtk_transform_print (self, string); + + return g_string_free (string, FALSE); +} + +/** + * gtk_transform_get_transform_type: + * @self: (allow-none): a #GtkTransform + * + * Returns the type of the @self. + * + * Returns: the type of the #GtkTransform + */ +GtkTransformType +gtk_transform_get_transform_type (GtkTransform *self) +{ + if (self == NULL) + return GTK_TRANSFORM_TYPE_IDENTITY; + + return self->transform_class->transform_type; +} + +/** + * gtk_transform_get_next: + * @self: (allow-none): a #GtkTransform + * + * Gets the rest of the matrix in the chain of operations. + * + * Returns: (transfer none) (nullable): The next transform or + * %NULL if this was the last operation. + **/ +GtkTransform * +gtk_transform_get_next (GtkTransform *self) +{ + if (self == NULL) + return NULL; + + return self->next; +} + +/** + * gtk_transform_to_matrix: + * @self: (allow-none): a #GtkTransform + * @out_matrix: (out) (caller-allocates): The matrix to set + * + * Computes the actual value of @self and stores it in @out_matrix. + * The previous value of @out_matrix will be ignored. + **/ +void +gtk_transform_to_matrix (GtkTransform *self, + graphene_matrix_t *out_matrix) +{ + graphene_matrix_t m; + + if (self == NULL) + { + graphene_matrix_init_identity (out_matrix); + return; + } + + gtk_transform_to_matrix (self->next, out_matrix); + self->transform_class->to_matrix (self, &m); + graphene_matrix_multiply (&m, out_matrix, out_matrix); +} + +gboolean +gtk_transform_to_affine (GtkTransform *self, + float *out_scale_x, + float *out_scale_y, + float *out_dx, + float *out_dy) +{ + if (self == NULL) + { + *out_scale_x = 1.0f; + *out_scale_y = 1.0f; + *out_dx = 0.0f; + *out_dy = 0.0f; + return TRUE; + } + + if (!gtk_transform_to_affine (self->next, + out_scale_x, out_scale_y, + out_dx, out_dy)) + return FALSE; + + return self->transform_class->apply_affine (self, + out_scale_x, out_scale_y, + out_dx, out_dy); +} + +/** + * gtk_transform_transform: + * @next: (allow-none) (transfer full): Transform to apply @other to + * @other: (allow-none): Transform to apply + * + * Applies all the operations from @other to @next. + * + * Returns: The new matrix + **/ +GtkTransform * +gtk_transform_transform (GtkTransform *next, + GtkTransform *other) +{ + if (other == NULL) + return next; + + next = gtk_transform_transform (next, other->next); + return other->transform_class->apply (other, next); +} + +/** + * gtk_transform_equal: + * @first: the first matrix + * @second: the second matrix + * + * Checks two matrices for equality. Note that matrices need to be literally + * identical in their operations, it is not enough that they return the + * same result in gtk_transform_to_matrix(). + * + * Returns: %TRUE if the two matrices can be proven to be equal + **/ +gboolean +gtk_transform_equal (GtkTransform *first, + GtkTransform *second) +{ + if (first == second) + return TRUE; + + if (first == NULL || second == NULL) + return FALSE; + + if (!gtk_transform_equal (first->next, second->next)) + return FALSE; + + if (first->transform_class != second->transform_class) + return FALSE; + + return first->transform_class->equal (first, second); +} + +/* + * gtk_transform_categorize: + * @self: (allow-none): A matrix + * + * + * + * Returns: The category this matrix belongs to + **/ +GskMatrixCategory +gtk_transform_categorize (GtkTransform *self) +{ + if (self == NULL) + return GSK_MATRIX_CATEGORY_IDENTITY; + + return MIN (gtk_transform_categorize (self->next), + self->transform_class->categorize (self)); +} + +/* + * gtk_transform_new: (constructor): + * + * Creates a new identity matrix. This function is meant to be used by language + * bindings. For C code, this equivalent to using %NULL. + * + * See also gtk_transform_identity() for inserting identity matrix operations + * when constructing matrices. + * + * Returns: A new identity matrix + **/ +GtkTransform * +gtk_transform_new (void) +{ + return gtk_transform_alloc (>K_IDENTITY_TRANSFORM_CLASS, NULL); +} diff --git a/gtk/gtktransform.h b/gtk/gtktransform.h new file mode 100644 index 0000000000..b62b044978 --- /dev/null +++ b/gtk/gtktransform.h @@ -0,0 +1,104 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * 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 . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_TRANSFORM_H__ +#define __GTK_TRANSFORM_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_MATRIX (gtk_transform_get_type ()) + +typedef enum +{ + GTK_TRANSFORM_TYPE_IDENTITY, + GTK_TRANSFORM_TYPE_TRANSFORM, + GTK_TRANSFORM_TYPE_TRANSLATE, + GTK_TRANSFORM_TYPE_ROTATE, + GTK_TRANSFORM_TYPE_SCALE +} GtkTransformType; + +GDK_AVAILABLE_IN_ALL +GType gtk_transform_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_ref (GtkTransform *self); +GDK_AVAILABLE_IN_ALL +void gtk_transform_unref (GtkTransform *self); + +GDK_AVAILABLE_IN_ALL +void gtk_transform_print (GtkTransform *self, + GString *string); +GDK_AVAILABLE_IN_ALL +char * gtk_transform_to_string (GtkTransform *self); +GDK_AVAILABLE_IN_ALL +void gtk_transform_to_matrix (GtkTransform *self, + graphene_matrix_t *out_matrix); +GDK_AVAILABLE_IN_ALL +gboolean gtk_transform_equal (GtkTransform *first, + GtkTransform *second) G_GNUC_PURE; + +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_new (void); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_identity (GtkTransform *next); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_transform (GtkTransform *next, + GtkTransform *other); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_matrix (GtkTransform *next, + const graphene_matrix_t *matrix); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_translate (GtkTransform *next, + const graphene_point_t *point); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_translate_3d (GtkTransform *next, + const graphene_point3d_t *point); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_rotate (GtkTransform *next, + float angle); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_rotate_3d (GtkTransform *next, + float angle, + const graphene_vec3_t *axis); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_scale (GtkTransform *next, + float factor_x, + float factor_y); +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_scale_3d (GtkTransform *next, + float factor_x, + float factor_y, + float factor_z); + +GDK_AVAILABLE_IN_ALL +GtkTransformType gtk_transform_get_transform_type (GtkTransform *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_ALL +GtkTransform * gtk_transform_get_next (GtkTransform *self) G_GNUC_PURE; + +G_END_DECLS + +#endif /* __GTK_TRANSFORM_H__ */ diff --git a/gtk/gtktransformprivate.h b/gtk/gtktransformprivate.h new file mode 100644 index 0000000000..5aef0b001d --- /dev/null +++ b/gtk/gtktransformprivate.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2019 Benjamin Otte + * + * 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 . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_TRANSFORM_PRIVATE_H__ +#define __GTK_TRANSFORM_PRIVATE_H__ + +#include "gtktransform.h" + +#include +#include "gsk/gskrendernodeprivate.h" + +G_BEGIN_DECLS + +GskMatrixCategory gtk_transform_categorize (GtkTransform *self); + +gboolean gtk_transform_to_affine (GtkTransform *self, + float *scale_x, + float *scale_y, + float *dx, + float *dy) G_GNUC_WARN_UNUSED_RESULT; + +GtkTransform * gtk_transform_matrix_with_category (GtkTransform *next, + const graphene_matrix_t*matrix, + GskMatrixCategory category); + +G_END_DECLS + +#endif /* __GTK_TRANSFORM_PRIVATE_H__ */ + diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h index 5d1218f189..9876bed3a5 100644 --- a/gtk/gtktypes.h +++ b/gtk/gtktypes.h @@ -45,6 +45,7 @@ typedef struct _GtkSettings GtkSettings; typedef GdkSnapshot GtkSnapshot; typedef struct _GtkStyleContext GtkStyleContext; typedef struct _GtkTooltip GtkTooltip; +typedef struct _GtkTransform GtkTransform; typedef struct _GtkWidget GtkWidget; typedef struct _GtkWidgetPath GtkWidgetPath; typedef struct _GtkWindow GtkWindow; diff --git a/gtk/meson.build b/gtk/meson.build index 1d9a38d923..85f75c8c51 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -376,6 +376,7 @@ gtk_public_sources = files([ 'gtktoolshell.c', 'gtktooltip.c', 'gtktooltipwindow.c', + 'gtktransform.c', 'gtktreednd.c', 'gtktreelistmodel.c', 'gtktreemenu.c', @@ -606,6 +607,7 @@ gtk_public_headers = files([ 'gtktoolitem.h', 'gtktoolshell.h', 'gtktooltip.h', + 'gtktransform.h', 'gtktreednd.h', 'gtktreelistmodel.h', 'gtktreemodel.h',