/* * Copyright © 2018 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 */ #include "config.h" #include "gdkpaintable.h" #include "gdksnapshotprivate.h" #include "gdk-private.h" /* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */ void gtk_snapshot_push_debug (GdkSnapshot *snapshot, const char *message, ...) G_GNUC_PRINTF (2, 3); void gtk_snapshot_pop (GdkSnapshot *snapshot); /** * GdkPaintable: * * `GdkPaintable` is a simple interface used by GTK to represent content that * can be painted. * * The content of a `GdkPaintable` can be painted anywhere at any size * without requiring any sort of layout. The interface is inspired by * similar concepts elsewhere, such as * [ClutterContent](https://developer.gnome.org/clutter/stable/ClutterContent.html), * [HTML/CSS Paint Sources](https://www.w3.org/TR/css-images-4/#paint-source), * or [SVG Paint Servers](https://www.w3.org/TR/SVG2/pservers.html). * * A `GdkPaintable` can be snapshot at any time and size using * [method@Gdk.Paintable.snapshot]. How the paintable interprets that size and * if it scales or centers itself into the given rectangle is implementation * defined, though if you are implementing a `GdkPaintable` and don't know what * to do, it is suggested that you scale your paintable ignoring any potential * aspect ratio. * * The contents that a `GdkPaintable` produces may depend on the [class@GdkSnapshot] * passed to it. For example, paintables may decide to use more detailed images * on higher resolution screens or when OpenGL is available. A `GdkPaintable` * will however always produce the same output for the same snapshot. * * A `GdkPaintable` may change its contents, meaning that it will now produce * a different output with the same snapshot. Once that happens, it will call * [method@Gdk.Paintable.invalidate_contents] which will emit the * [signal@GdkPaintable::invalidate-contents] signal. If a paintable is known * to never change its contents, it will set the %GDK_PAINTABLE_STATIC_CONTENTS * flag. If a consumer cannot deal with changing contents, it may call * [method@Gdk.Paintable.get_current_image] which will return a static * paintable and use that. * * A paintable can report an intrinsic (or preferred) size or aspect ratio it * wishes to be rendered at, though it doesn't have to. Consumers of the interface * can use this information to layout thepaintable appropriately. Just like the * contents, the size of a paintable can change. A paintable will indicate this * by calling [method@Gdk.Paintable.invalidate_size] which will emit the * [signal@GdkPaintable::invalidate-size] signal. And just like for contents, * if a paintable is known to never change its size, it will set the * %GDK_PAINTABLE_STATIC_SIZE flag. * * Besides API for applications, there are some functions that are only * useful for implementing subclasses and should not be used by applications: * [method@Gdk.Paintable.invalidate_contents], * [method@Gdk.Paintable.invalidate_size], * [func@Gdk.Paintable.new_empty]. */ G_DEFINE_INTERFACE (GdkPaintable, gdk_paintable, G_TYPE_OBJECT) enum { INVALIDATE_CONTENTS, INVALIDATE_SIZE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void gdk_paintable_default_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, double width, double height) { g_critical ("Paintable of type '%s' does not implement GdkPaintable::snapshot", G_OBJECT_TYPE_NAME (paintable)); } static GdkPaintable * gdk_paintable_default_get_current_image (GdkPaintable *paintable) { g_warning ("FIXME: implement by snapshotting at default size and returning a GskRendererNodePaintable"); return paintable; } static GdkPaintableFlags gdk_paintable_default_get_flags (GdkPaintable *paintable) { return 0; } static int gdk_paintable_default_get_intrinsic_width (GdkPaintable *paintable) { return 0; } static int gdk_paintable_default_get_intrinsic_height (GdkPaintable *paintable) { return 0; } static double gdk_paintable_default_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { int width, height; width = gdk_paintable_get_intrinsic_width (paintable); height = gdk_paintable_get_intrinsic_height (paintable); if (width <= 0 || height <= 0) return 0.0; return (double) width / height; }; static void g_value_object_transform_value (const GValue *src_value, GValue *dest_value) { if (src_value->data[0].v_pointer && g_type_is_a (G_OBJECT_TYPE (src_value->data[0].v_pointer), G_VALUE_TYPE (dest_value))) dest_value->data[0].v_pointer = g_object_ref (src_value->data[0].v_pointer); else dest_value->data[0].v_pointer = NULL; } static void gdk_paintable_default_init (GdkPaintableInterface *iface) { iface->snapshot = gdk_paintable_default_snapshot; iface->get_current_image = gdk_paintable_default_get_current_image; iface->get_flags = gdk_paintable_default_get_flags; iface->get_intrinsic_width = gdk_paintable_default_get_intrinsic_width; iface->get_intrinsic_height = gdk_paintable_default_get_intrinsic_height; iface->get_intrinsic_aspect_ratio = gdk_paintable_default_get_intrinsic_aspect_ratio; g_value_register_transform_func (G_TYPE_OBJECT, GDK_TYPE_PAINTABLE, g_value_object_transform_value); g_value_register_transform_func (GDK_TYPE_PAINTABLE, G_TYPE_OBJECT, g_value_object_transform_value); /** * GdkPaintable::invalidate-contents * @paintable: a `GdkPaintable` * * Emitted when the contents of the @paintable change. * * Examples for such an event would be videos changing to the next frame or * the icon theme for an icon changing. */ signals[INVALIDATE_CONTENTS] = g_signal_new (I_("invalidate-contents"), GDK_TYPE_PAINTABLE, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * GdkPaintable::invalidate-size * @paintable: a `GdkPaintable` * * Emitted when the intrinsic size of the @paintable changes. * * This means the values reported by at least one of * [method@Gdk.Paintable.get_intrinsic_width], * [method@Gdk.Paintable.get_intrinsic_height] or * [method@Gdk.Paintable.get_intrinsic_aspect_ratio] * has changed. * * Examples for such an event would be a paintable displaying * the contents of a toplevel surface being resized. */ signals[INVALIDATE_SIZE] = g_signal_new (I_("invalidate-size"), GDK_TYPE_PAINTABLE, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } /** * gdk_paintable_snapshot: * @paintable: a `GdkPaintable` * @snapshot: a `GdkSnapshot` to snapshot to * @width: width to snapshot in * @height: height to snapshot in * * Snapshots the given paintable with the given @width and @height. * * The paintable is drawn at the current (0,0) offset of the @snapshot. * If @width and @height are not larger than zero, this function will * do nothing. */ void gdk_paintable_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, double width, double height) { GdkPaintableInterface *iface; g_return_if_fail (GDK_IS_PAINTABLE (paintable)); g_return_if_fail (snapshot != NULL); if (width <= 0.0 || height <= 0.0) return; gtk_snapshot_push_debug (snapshot, "%s %p @ %gx%g", G_OBJECT_TYPE_NAME (paintable), paintable, width, height); iface = GDK_PAINTABLE_GET_IFACE (paintable); iface->snapshot (paintable, snapshot, width, height); gtk_snapshot_pop (snapshot); } #define GDK_PAINTABLE_IMMUTABLE (GDK_PAINTABLE_STATIC_SIZE | GDK_PAINTABLE_STATIC_CONTENTS) static inline gboolean gdk_paintable_is_immutable (GdkPaintable *paintable) { return (gdk_paintable_get_flags (paintable) & GDK_PAINTABLE_IMMUTABLE) == GDK_PAINTABLE_IMMUTABLE; } /** * gdk_paintable_get_current_image: * @paintable: a `GdkPaintable` * * Gets an immutable paintable for the current contents displayed by @paintable. * * This is useful when you want to retain the current state of an animation, * for example to take a screenshot of a running animation. * * If the @paintable is already immutable, it will return itself. * * Returns: (transfer full): An immutable paintable for the current * contents of @paintable */ GdkPaintable * gdk_paintable_get_current_image (GdkPaintable *paintable) { GdkPaintableInterface *iface; g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), NULL); if (gdk_paintable_is_immutable (paintable)) return g_object_ref (paintable); iface = GDK_PAINTABLE_GET_IFACE (paintable); return iface->get_current_image (paintable); } /** * gdk_paintable_get_flags: * @paintable: a `GdkPaintable` * * Get flags for the paintable. * * This is oftentimes useful for optimizations. * * See [flags@Gdk.PaintableFlags] for the flags and what they mean. * * Returns: The `GdkPaintableFlags` for this paintable */ GdkPaintableFlags gdk_paintable_get_flags (GdkPaintable *paintable) { GdkPaintableInterface *iface; g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), 0); iface = GDK_PAINTABLE_GET_IFACE (paintable); return iface->get_flags (paintable); } /** * gdk_paintable_get_intrinsic_width: * @paintable: a `GdkPaintable` * * Gets the preferred width the @paintable would like to be displayed at. * * Consumers of this interface can use this to reserve enough space to draw * the paintable. * * This is a purely informational value and does not in any way limit the * values that may be passed to [method@Gdk.Paintable.snapshot]. * * If the @paintable does not have a preferred width, it returns 0. * Negative values are never returned. * * Returns: the intrinsic width of @paintable or 0 if none. */ int gdk_paintable_get_intrinsic_width (GdkPaintable *paintable) { GdkPaintableInterface *iface; g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), 0); iface = GDK_PAINTABLE_GET_IFACE (paintable); return iface->get_intrinsic_width (paintable); } /** * gdk_paintable_get_intrinsic_height: * @paintable: a `GdkPaintable` * * Gets the preferred height the @paintable would like to be displayed at. * * Consumers of this interface can use this to reserve enough space to draw * the paintable. * * This is a purely informational value and does not in any way limit the * values that may be passed to [method@Gdk.Paintable.snapshot]. * * If the @paintable does not have a preferred height, it returns 0. * Negative values are never returned. * * Returns: the intrinsic height of @paintable or 0 if none. */ int gdk_paintable_get_intrinsic_height (GdkPaintable *paintable) { GdkPaintableInterface *iface; g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), 0); iface = GDK_PAINTABLE_GET_IFACE (paintable); return iface->get_intrinsic_height (paintable); } /** * gdk_paintable_get_intrinsic_aspect_ratio: * @paintable: a `GdkPaintable` * * Gets the preferred aspect ratio the @paintable would like to be displayed at. * * The aspect ratio is the width divided by the height, so a value of 0.5 * means that the @paintable prefers to be displayed twice as high as it * is wide. Consumers of this interface can use this to preserve aspect * ratio when displaying the paintable. * * This is a purely informational value and does not in any way limit the * values that may be passed to [method@Gdk.Paintable.snapshot]. * * Usually when a @paintable returns nonzero values from * [method@Gdk.Paintable.get_intrinsic_width] and * [method@Gdk.Paintable.get_intrinsic_height] the aspect ratio * should conform to those values, though that is not required. * * If the @paintable does not have a preferred aspect ratio, * it returns 0. Negative values are never returned. * * Returns: the intrinsic aspect ratio of @paintable or 0 if none. */ double gdk_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { GdkPaintableInterface *iface; g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), 0); iface = GDK_PAINTABLE_GET_IFACE (paintable); return iface->get_intrinsic_aspect_ratio (paintable); } /** * gdk_paintable_invalidate_contents: * @paintable: a `GdkPaintable` * * Called by implementations of `GdkPaintable` to invalidate their contents. * * Unless the contents are invalidated, implementations must guarantee that * multiple calls of [method@Gdk.Paintable.snapshot] produce the same output. * * This function will emit the [signal@Gdk.Paintable::invalidate-contents] * signal. * * If a @paintable reports the %GDK_PAINTABLE_STATIC_CONTENTS flag, * it must not call this function. */ void gdk_paintable_invalidate_contents (GdkPaintable *paintable) { g_return_if_fail (GDK_IS_PAINTABLE (paintable)); g_return_if_fail (!(gdk_paintable_get_flags (paintable) & GDK_PAINTABLE_STATIC_CONTENTS)); g_signal_emit (paintable, signals[INVALIDATE_CONTENTS], 0); } /** * gdk_paintable_invalidate_size: * @paintable: a `GdkPaintable` * * Called by implementations of `GdkPaintable` to invalidate their size. * * As long as the size is not invalidated, @paintable must return the same * values for its intrinsic width, height and aspect ratio. * * This function will emit the [signal@Gdk.Paintable::invalidate-size] * signal. * * If a @paintable reports the %GDK_PAINTABLE_STATIC_SIZE flag, * it must not call this function. */ void gdk_paintable_invalidate_size (GdkPaintable *paintable) { g_return_if_fail (GDK_IS_PAINTABLE (paintable)); g_return_if_fail (!(gdk_paintable_get_flags (paintable) & GDK_PAINTABLE_STATIC_SIZE)); g_signal_emit (paintable, signals[INVALIDATE_SIZE], 0); } /** * gdk_paintable_compute_concrete_size: * @paintable: a `GdkPaintable` * @specified_width: the width @paintable could be drawn into or * 0.0 if unknown * @specified_height: the height @paintable could be drawn into or * 0.0 if unknown * @default_width: the width @paintable would be drawn into if * no other constraints were given * @default_height: the height @paintable would be drawn into if * no other constraints were given * @concrete_width: (out): will be set to the concrete width computed * @concrete_height: (out): will be set to the concrete height computed * * Compute a concrete size for the `GdkPaintable`. * * Applies the sizing algorithm outlined in the * [CSS Image spec](https://drafts.csswg.org/css-images-3/#default-sizing) * to the given @paintable. See that link for more details. * * It is not necessary to call this function when both @specified_width * and @specified_height are known, but it is useful to call this * function in GtkWidget:measure implementations to compute the * other dimension when only one dimension is given. */ void gdk_paintable_compute_concrete_size (GdkPaintable *paintable, double specified_width, double specified_height, double default_width, double default_height, double *concrete_width, double *concrete_height) { double image_width, image_height, image_aspect; g_return_if_fail (GDK_IS_PAINTABLE (paintable)); g_return_if_fail (specified_width >= 0); g_return_if_fail (specified_height >= 0); g_return_if_fail (default_width > 0); g_return_if_fail (default_height > 0); g_return_if_fail (concrete_width != NULL); g_return_if_fail (concrete_height != NULL); /* If the specified size is a definite width and height, * the concrete object size is given that width and height. */ if (specified_width && specified_height) { *concrete_width = specified_width; *concrete_height = specified_height; return; } image_width = gdk_paintable_get_intrinsic_width (paintable); image_height = gdk_paintable_get_intrinsic_height (paintable); image_aspect = gdk_paintable_get_intrinsic_aspect_ratio (paintable); /* If the specified size has neither a definite width nor height, * and has no additional constraints, the dimensions of the concrete * object size are calculated as follows: */ if (specified_width == 0.0 && specified_height == 0.0) { /* If the object has only an intrinsic aspect ratio, * the concrete object size must have that aspect ratio, * and additionally be as large as possible without either * its height or width exceeding the height or width of the * default object size. */ if (image_aspect > 0 && image_width == 0 && image_height == 0) { if (image_aspect * default_height > default_width) { *concrete_width = default_width; *concrete_height = default_width / image_aspect; } else { *concrete_width = default_height * image_aspect; *concrete_height = default_height; } } else { /* Otherwise, the width and height of the concrete object * size is the same as the object's intrinsic width and * intrinsic height, if they exist. * If the concrete object size is still missing a width or * height, and the object has an intrinsic aspect ratio, * the missing dimension is calculated from the present * dimension and the intrinsic aspect ratio. * Otherwise, the missing dimension is taken from the default * object size. */ if (image_width) *concrete_width = image_width; else if (image_aspect) *concrete_width = image_height * image_aspect; else *concrete_width = default_width; if (image_height) *concrete_height = image_height; else if (image_aspect) *concrete_height = image_width / image_aspect; else *concrete_height = default_height; } return; } /* If the specified size has only a width or height, but not both, * then the concrete object size is given that specified width or height. * The other dimension is calculated as follows: * If the object has an intrinsic aspect ratio, the missing dimension of * the concrete object size is calculated using the intrinsic aspect-ratio * and the present dimension. * Otherwise, if the missing dimension is present in the object's intrinsic * dimensions, the missing dimension is taken from the object's intrinsic * dimensions. * Otherwise, the missing dimension of the concrete object size is taken * from the default object size. */ if (specified_width) { *concrete_width = specified_width; if (image_aspect) *concrete_height = specified_width / image_aspect; else if (image_height) *concrete_height = image_height; else *concrete_height = default_height; } else { *concrete_height = specified_height; if (image_aspect) *concrete_width = specified_height * image_aspect; else if (image_width) *concrete_width = image_width; else *concrete_width = default_width; } } #define GDK_TYPE_EMPTY_PAINTABLE (gdk_empty_paintable_get_type()) static G_DECLARE_FINAL_TYPE(GdkEmptyPaintable, gdk_empty_paintable, GDK, EMPTY_PAINTABLE, GObject) struct _GdkEmptyPaintable { GObject parent_instance; int width; int height; }; struct _GdkEmptyPaintableClass { GObjectClass parent_class; }; static void gdk_empty_paintable_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, double width, double height) { } static GdkPaintableFlags gdk_empty_paintable_get_flags (GdkPaintable *paintable) { return GDK_PAINTABLE_STATIC_SIZE | GDK_PAINTABLE_STATIC_CONTENTS; } static int gdk_empty_paintable_get_intrinsic_width (GdkPaintable *paintable) { GdkEmptyPaintable *self = GDK_EMPTY_PAINTABLE (paintable); return self->width; } static int gdk_empty_paintable_get_intrinsic_height (GdkPaintable *paintable) { GdkEmptyPaintable *self = GDK_EMPTY_PAINTABLE (paintable); return self->height; } static void gdk_empty_paintable_paintable_init (GdkPaintableInterface *iface) { iface->snapshot = gdk_empty_paintable_snapshot; iface->get_flags = gdk_empty_paintable_get_flags; iface->get_intrinsic_width = gdk_empty_paintable_get_intrinsic_width; iface->get_intrinsic_height = gdk_empty_paintable_get_intrinsic_height; } G_DEFINE_TYPE_WITH_CODE (GdkEmptyPaintable, gdk_empty_paintable, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, gdk_empty_paintable_paintable_init)) static void gdk_empty_paintable_class_init (GdkEmptyPaintableClass *klass) { } static void gdk_empty_paintable_init (GdkEmptyPaintable *self) { } /** * gdk_paintable_new_empty: * @intrinsic_width: The intrinsic width to report. Can be 0 for no width. * @intrinsic_height: The intrinsic height to report. Can be 0 for no height. * * Returns a paintable that has the given intrinsic size and draws nothing. * * This is often useful for implementing the * [vfunc@Gdk.Paintable.get_current_image] virtual function * when the paintable is in an incomplete state (like a * [class@Gtk.MediaStream] before receiving the first frame). * * Returns: (transfer full): a `GdkPaintable` */ GdkPaintable * gdk_paintable_new_empty (int intrinsic_width, int intrinsic_height) { GdkEmptyPaintable *result; g_return_val_if_fail (intrinsic_width >= 0, NULL); g_return_val_if_fail (intrinsic_height >= 0, NULL); result = g_object_new (GDK_TYPE_EMPTY_PAINTABLE, NULL); result->width = intrinsic_width; result->height = intrinsic_height; return GDK_PAINTABLE (result); }