/* * 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" /* 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); /** * SECTION:gdkpaintable * @Title: GdkPaintable * @Short_description: An interface for a paintable region * @See_also: #ClutterContent, #GtkImage, #GdkTexture, #GtkSnapshot * * #GdkPaintable is a simple interface used by GDK and GDK to represent * objects that can be painted anywhere at any size without requiring any * sort of layout. The interface is inspired by similar concepts elsewhere, * such as #ClutterContent, HTML/CSS Paint * Sources or * SVG Paint Servers. * * A #GdkPaintable can be snapshot at any time and size using * 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 #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 snpashot. Once that happens, it will call * gdk_paintable_invalidate_contents() which will emit the * #GdkPaintable::invalidate-contents signal. * If a paintable is known to never change its contents, it will set the * %GDK_PAINTABLE_STATIC_CONTENT flag. If a consumer cannot deal with changing * contents, it may call gdk_paintable_get_static_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 gdk_paintable_invalidate_size() which will emit the * #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: * gdk_paintable_invalidate_contents(), * gdk_paintable_invalidate_size(), * 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 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; /** * 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 ("invalidate-contents", GDK_TYPE_PAINTABLE, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, 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 gdk_paintable_get_intrinsic_width(), * gdk_paintable_get_intrinsic_height() or 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 ("invalidate-size", GDK_TYPE_PAINTABLE, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, 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 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 #GdkPaintableFlags 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 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 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 ration 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 * this paintable. * * This is a purely informational value and does not in any way limit the values * that may be passed to gdk_paintable_snapshot(). * * Usually when a @paintable returns non-0 values from * gdk_paintable_get_intrinsic_width() and 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.0. * Negative values are never returned. * * Returns: the intrinsic aspect ratio of @paintable or 0.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 to GdkPaintable::snapshot produce the same output. * * This function will emit the GdkPaintable::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 width, height and intrinsic height. * * This function will emit the GdkPaintable::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. * * Applies the sizing algorithm outlined in * 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 contraints, 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 GdkPaintableClass:get_current_image() * virtual function when the paintable is in an incomplete state (like a * #GtkMediaStream 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); }