Merge branch 'mask-nodes-rebased' into 'main'
gsk: Introduce mask nodes See merge request GNOME/gtk!5497
This commit is contained in:
@@ -215,6 +215,12 @@
|
||||
<file>demo3widget.h</file>
|
||||
<file>demo3widget.ui</file>
|
||||
</gresource>
|
||||
<gresource prefix="/mask">
|
||||
<file>demo4widget.c</file>
|
||||
<file>demo4widget.h</file>
|
||||
<file>hsla.h</file>
|
||||
<file>hsla.c</file>
|
||||
</gresource>
|
||||
<gresource prefix="/paintable_svg">
|
||||
<file>svgpaintable.h</file>
|
||||
<file>svgpaintable.c</file>
|
||||
@@ -311,6 +317,7 @@
|
||||
<file>list_store.c</file>
|
||||
<file>main.c</file>
|
||||
<file>markup.c</file>
|
||||
<file>mask.c</file>
|
||||
<file>menu.c</file>
|
||||
<file>overlay.c</file>
|
||||
<file>overlay_decorative.c</file>
|
||||
|
||||
123
demos/gtk-demo/demo4widget.c
Normal file
123
demos/gtk-demo/demo4widget.c
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <math.h>
|
||||
#include "demo4widget.h"
|
||||
#include "hsla.h"
|
||||
|
||||
struct _Demo4Widget
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
PangoLayout *layout;
|
||||
GskColorStop stops[8];
|
||||
gsize n_stops;
|
||||
|
||||
guint tick;
|
||||
};
|
||||
|
||||
struct _Demo4WidgetClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (Demo4Widget, demo4_widget, GTK_TYPE_WIDGET)
|
||||
|
||||
static void
|
||||
rotate_color (GdkRGBA *rgba)
|
||||
{
|
||||
GdkHSLA hsla;
|
||||
|
||||
_gdk_hsla_init_from_rgba (&hsla, rgba);
|
||||
hsla.hue -= 1;
|
||||
_gdk_rgba_init_from_hsla (rgba, &hsla);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
rotate_colors (GtkWidget *widget,
|
||||
GdkFrameClock *clock,
|
||||
gpointer user_data)
|
||||
{
|
||||
Demo4Widget *self = DEMO4_WIDGET (widget);
|
||||
|
||||
for (unsigned int i = 0; i < self->n_stops; i++)
|
||||
rotate_color (&self->stops[i].color);
|
||||
|
||||
gtk_widget_queue_draw (widget);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
demo4_widget_init (Demo4Widget *self)
|
||||
{
|
||||
PangoFontDescription *desc;
|
||||
|
||||
self->n_stops = 8;
|
||||
self->stops[0].offset = 0;
|
||||
self->stops[0].color = (GdkRGBA) { 1, 0, 0, 1 };
|
||||
|
||||
for (unsigned int i = 1; i < self->n_stops; i++)
|
||||
{
|
||||
GdkHSLA hsla;
|
||||
|
||||
self->stops[i].offset = i / (double)(self->n_stops - 1);
|
||||
_gdk_hsla_init_from_rgba (&hsla, &self->stops[i - 1].color);
|
||||
hsla.hue += 360.0 / (double)(self->n_stops - 1);
|
||||
_gdk_rgba_init_from_hsla (&self->stops[i].color, &hsla);
|
||||
}
|
||||
|
||||
self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), "123");
|
||||
desc = pango_font_description_from_string ("Cantarell Bold 210");
|
||||
pango_layout_set_font_description (self->layout, desc);
|
||||
pango_font_description_free (desc);
|
||||
|
||||
self->tick = gtk_widget_add_tick_callback (GTK_WIDGET (self), rotate_colors, NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
demo4_widget_dispose (GObject *object)
|
||||
{
|
||||
Demo4Widget *self = DEMO4_WIDGET (object);
|
||||
|
||||
g_clear_object (&self->layout);
|
||||
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick);
|
||||
|
||||
G_OBJECT_CLASS (demo4_widget_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
demo4_widget_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
Demo4Widget *self = DEMO4_WIDGET (widget);
|
||||
int width, height;
|
||||
|
||||
width = gtk_widget_get_width (widget);
|
||||
height = gtk_widget_get_height (widget);
|
||||
|
||||
gtk_snapshot_push_mask (snapshot);
|
||||
gtk_snapshot_append_layout (snapshot, self->layout, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
gtk_snapshot_pop (snapshot);
|
||||
|
||||
gtk_snapshot_append_linear_gradient (snapshot,
|
||||
&GRAPHENE_RECT_INIT (0, 0, width, height),
|
||||
&GRAPHENE_POINT_INIT (0, 0),
|
||||
&GRAPHENE_POINT_INIT (width, height),
|
||||
self->stops,
|
||||
self->n_stops);
|
||||
gtk_snapshot_pop (snapshot);
|
||||
}
|
||||
|
||||
static void
|
||||
demo4_widget_class_init (Demo4WidgetClass *class)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||||
|
||||
object_class->dispose = demo4_widget_dispose;
|
||||
|
||||
widget_class->snapshot = demo4_widget_snapshot;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
demo4_widget_new (void)
|
||||
{
|
||||
return g_object_new (DEMO4_TYPE_WIDGET, NULL);
|
||||
}
|
||||
8
demos/gtk-demo/demo4widget.h
Normal file
8
demos/gtk-demo/demo4widget.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define DEMO4_TYPE_WIDGET (demo4_widget_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (Demo4Widget, demo4_widget, DEMO4, WIDGET, GtkWidget)
|
||||
|
||||
GtkWidget * demo4_widget_new (void);
|
||||
146
demos/gtk-demo/hsla.c
Normal file
146
demos/gtk-demo/hsla.c
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <gdk/gdk.h>
|
||||
#include "hsla.h"
|
||||
|
||||
void
|
||||
_gdk_hsla_init_from_rgba (GdkHSLA *hsla,
|
||||
const GdkRGBA *rgba)
|
||||
{
|
||||
float min;
|
||||
float max;
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
float delta;
|
||||
|
||||
g_return_if_fail (hsla != NULL);
|
||||
g_return_if_fail (rgba != NULL);
|
||||
|
||||
red = rgba->red;
|
||||
green = rgba->green;
|
||||
blue = rgba->blue;
|
||||
|
||||
if (red > green)
|
||||
{
|
||||
if (red > blue)
|
||||
max = red;
|
||||
else
|
||||
max = blue;
|
||||
|
||||
if (green < blue)
|
||||
min = green;
|
||||
else
|
||||
min = blue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (green > blue)
|
||||
max = green;
|
||||
else
|
||||
max = blue;
|
||||
|
||||
if (red < blue)
|
||||
min = red;
|
||||
else
|
||||
min = blue;
|
||||
}
|
||||
|
||||
hsla->lightness = (max + min) / 2;
|
||||
hsla->saturation = 0;
|
||||
hsla->hue = 0;
|
||||
hsla->alpha = rgba->alpha;
|
||||
|
||||
if (max != min)
|
||||
{
|
||||
if (hsla->lightness <= 0.5)
|
||||
hsla->saturation = (max - min) / (max + min);
|
||||
else
|
||||
hsla->saturation = (max - min) / (2 - max - min);
|
||||
|
||||
delta = max -min;
|
||||
if (red == max)
|
||||
hsla->hue = (green - blue) / delta;
|
||||
else if (green == max)
|
||||
hsla->hue = 2 + (blue - red) / delta;
|
||||
else if (blue == max)
|
||||
hsla->hue = 4 + (red - green) / delta;
|
||||
|
||||
hsla->hue *= 60;
|
||||
if (hsla->hue < 0.0)
|
||||
hsla->hue += 360;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_gdk_rgba_init_from_hsla (GdkRGBA *rgba,
|
||||
const GdkHSLA *hsla)
|
||||
{
|
||||
float hue;
|
||||
float lightness;
|
||||
float saturation;
|
||||
float m1, m2;
|
||||
|
||||
lightness = hsla->lightness;
|
||||
saturation = hsla->saturation;
|
||||
|
||||
if (lightness <= 0.5)
|
||||
m2 = lightness * (1 + saturation);
|
||||
else
|
||||
m2 = lightness + saturation - lightness * saturation;
|
||||
m1 = 2 * lightness - m2;
|
||||
|
||||
rgba->alpha = hsla->alpha;
|
||||
|
||||
if (saturation == 0)
|
||||
{
|
||||
rgba->red = lightness;
|
||||
rgba->green = lightness;
|
||||
rgba->blue = lightness;
|
||||
}
|
||||
else
|
||||
{
|
||||
hue = hsla->hue + 120;
|
||||
while (hue > 360)
|
||||
hue -= 360;
|
||||
while (hue < 0)
|
||||
hue += 360;
|
||||
|
||||
if (hue < 60)
|
||||
rgba->red = m1 + (m2 - m1) * hue / 60;
|
||||
else if (hue < 180)
|
||||
rgba->red = m2;
|
||||
else if (hue < 240)
|
||||
rgba->red = m1 + (m2 - m1) * (240 - hue) / 60;
|
||||
else
|
||||
rgba->red = m1;
|
||||
|
||||
hue = hsla->hue;
|
||||
while (hue > 360)
|
||||
hue -= 360;
|
||||
while (hue < 0)
|
||||
hue += 360;
|
||||
|
||||
if (hue < 60)
|
||||
rgba->green = m1 + (m2 - m1) * hue / 60;
|
||||
else if (hue < 180)
|
||||
rgba->green = m2;
|
||||
else if (hue < 240)
|
||||
rgba->green = m1 + (m2 - m1) * (240 - hue) / 60;
|
||||
else
|
||||
rgba->green = m1;
|
||||
|
||||
hue = hsla->hue - 120;
|
||||
while (hue > 360)
|
||||
hue -= 360;
|
||||
while (hue < 0)
|
||||
hue += 360;
|
||||
|
||||
if (hue < 60)
|
||||
rgba->blue = m1 + (m2 - m1) * hue / 60;
|
||||
else if (hue < 180)
|
||||
rgba->blue = m2;
|
||||
else if (hue < 240)
|
||||
rgba->blue = m1 + (m2 - m1) * (240 - hue) / 60;
|
||||
else
|
||||
rgba->blue = m1;
|
||||
}
|
||||
}
|
||||
15
demos/gtk-demo/hsla.h
Normal file
15
demos/gtk-demo/hsla.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct _GdkHSLA GdkHSLA;
|
||||
|
||||
struct _GdkHSLA {
|
||||
float hue;
|
||||
float saturation;
|
||||
float lightness;
|
||||
float alpha;
|
||||
};
|
||||
|
||||
void _gdk_hsla_init_from_rgba (GdkHSLA *hsla,
|
||||
const GdkRGBA *rgba);
|
||||
void _gdk_rgba_init_from_hsla (GdkRGBA *rgba,
|
||||
const GdkHSLA *hsla);
|
||||
46
demos/gtk-demo/mask.c
Normal file
46
demos/gtk-demo/mask.c
Normal file
@@ -0,0 +1,46 @@
|
||||
/* Masking
|
||||
*
|
||||
* Demonstrates mask nodes.
|
||||
*
|
||||
* This demo uses a text node as mask for
|
||||
* an animated linear gradient.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "demo4widget.h"
|
||||
|
||||
|
||||
GtkWidget *
|
||||
do_mask (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkWidget *box;
|
||||
GtkWidget *widget;
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Mask Nodes");
|
||||
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
|
||||
gtk_window_set_display (GTK_WINDOW (window),
|
||||
gtk_widget_get_display (do_widget));
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_window_set_child (GTK_WINDOW (window), box);
|
||||
|
||||
widget = demo4_widget_new ();
|
||||
gtk_widget_set_hexpand (widget, TRUE);
|
||||
gtk_widget_set_vexpand (widget, TRUE);
|
||||
|
||||
gtk_box_append (GTK_BOX (box), widget);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_widget_set_visible (window, TRUE);
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -46,6 +46,7 @@ demos = files([
|
||||
'links.c',
|
||||
'listbox.c',
|
||||
'listbox_controls.c',
|
||||
'mask.c',
|
||||
'menu.c',
|
||||
'flowbox.c',
|
||||
'list_store.c',
|
||||
@@ -114,6 +115,7 @@ extra_demo_sources = files([
|
||||
'gtkshadertoy.c',
|
||||
'gtkshaderstack.c',
|
||||
'gskshaderpaintable.c',
|
||||
'hsla.c',
|
||||
'puzzlepiece.c',
|
||||
'bluroverlay.c',
|
||||
'demoimage.c',
|
||||
@@ -126,6 +128,7 @@ extra_demo_sources = files([
|
||||
'four_point_transform.c',
|
||||
'demo2widget.c',
|
||||
'demo3widget.c',
|
||||
'demo4widget.c',
|
||||
'pixbufpaintable.c',
|
||||
'script-names.c',
|
||||
'unicode-names.c',
|
||||
|
||||
@@ -270,6 +270,7 @@ collect_reused_child_nodes (GskRenderer *renderer,
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
|
||||
default:
|
||||
|
||||
@@ -846,6 +847,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
|
||||
}
|
||||
break; /* Fallback */
|
||||
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_TEXTURE_SCALE_NODE:
|
||||
case GSK_TEXT_NODE:
|
||||
case GSK_RADIAL_GRADIENT_NODE:
|
||||
|
||||
@@ -60,6 +60,10 @@ GSK_GL_DEFINE_PROGRAM (linear_gradient,
|
||||
GSK_GL_ADD_UNIFORM (3, LINEAR_GRADIENT_POINTS, u_points)
|
||||
GSK_GL_ADD_UNIFORM (4, LINEAR_GRADIENT_REPEAT, u_repeat))
|
||||
|
||||
GSK_GL_DEFINE_PROGRAM (mask,
|
||||
GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("mask.glsl")),
|
||||
GSK_GL_ADD_UNIFORM (1, MASK_SOURCE, u_mask))
|
||||
|
||||
GSK_GL_DEFINE_PROGRAM (outset_shadow,
|
||||
GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("outset_shadow.glsl")),
|
||||
GSK_GL_ADD_UNIFORM (1, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
|
||||
|
||||
@@ -3277,6 +3277,56 @@ gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job,
|
||||
gsk_gl_render_job_end_draw (job);
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_gl_render_job_visit_mask_node (GskGLRenderJob *job,
|
||||
const GskRenderNode *node)
|
||||
{
|
||||
const GskRenderNode *source = gsk_mask_node_get_source (node);
|
||||
const GskRenderNode *mask = gsk_mask_node_get_mask (node);
|
||||
GskGLRenderOffscreen source_offscreen = {0};
|
||||
GskGLRenderOffscreen mask_offscreen = {0};
|
||||
|
||||
source_offscreen.bounds = &node->bounds;
|
||||
source_offscreen.force_offscreen = TRUE;
|
||||
source_offscreen.reset_clip = TRUE;
|
||||
|
||||
mask_offscreen.bounds = &node->bounds;
|
||||
mask_offscreen.force_offscreen = TRUE;
|
||||
mask_offscreen.reset_clip = TRUE;
|
||||
|
||||
/* TODO: We create 2 textures here as big as the mask node, but both
|
||||
* nodes might be a lot smaller than that.
|
||||
*/
|
||||
if (!gsk_gl_render_job_visit_node_with_offscreen (job, source, &source_offscreen))
|
||||
{
|
||||
gsk_gl_render_job_visit_node (job, source);
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert (source_offscreen.was_offscreen);
|
||||
|
||||
if (!gsk_gl_render_job_visit_node_with_offscreen (job, mask, &mask_offscreen))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert (mask_offscreen.was_offscreen);
|
||||
|
||||
gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, mask));
|
||||
gsk_gl_program_set_uniform_texture (job->current_program,
|
||||
UNIFORM_SHARED_SOURCE, 0,
|
||||
GL_TEXTURE_2D,
|
||||
GL_TEXTURE0,
|
||||
source_offscreen.texture_id);
|
||||
gsk_gl_program_set_uniform_texture (job->current_program,
|
||||
UNIFORM_MASK_SOURCE, 0,
|
||||
GL_TEXTURE_2D,
|
||||
GL_TEXTURE1,
|
||||
mask_offscreen.texture_id);
|
||||
gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds);
|
||||
gsk_gl_render_job_end_draw (job);
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_gl_render_job_visit_color_matrix_node (GskGLRenderJob *job,
|
||||
const GskRenderNode *node)
|
||||
@@ -3869,6 +3919,10 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job,
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_MASK_NODE:
|
||||
gsk_gl_render_job_visit_mask_node (job, node);
|
||||
break;
|
||||
|
||||
case GSK_OPACITY_NODE:
|
||||
gsk_gl_render_job_visit_opacity_node (job, node);
|
||||
break;
|
||||
|
||||
19
gsk/gl/resources/mask.glsl
Normal file
19
gsk/gl/resources/mask.glsl
Normal file
@@ -0,0 +1,19 @@
|
||||
// VERTEX_SHADER:
|
||||
// mask.glsl
|
||||
|
||||
void main() {
|
||||
gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
|
||||
|
||||
vUv = vec2(aUv.x, aUv.y);
|
||||
}
|
||||
|
||||
// FRAGMENT_SHADER:
|
||||
// mask.glsl
|
||||
|
||||
uniform sampler2D u_mask;
|
||||
|
||||
void main() {
|
||||
vec4 source = GskTexture(u_source, vUv);
|
||||
vec4 mask = GskTexture(u_mask, vUv);
|
||||
gskSetOutputColor(vec4 (source * mask.a));
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
* @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle
|
||||
* @GSK_SHADOW_NODE: A node that draws a shadow below its child
|
||||
* @GSK_BLEND_NODE: A node that blends two children together
|
||||
* @GSK_MASK_NODE: A node that masks one child with another
|
||||
* @GSK_CROSS_FADE_NODE: A node that cross-fades between two children
|
||||
* @GSK_TEXT_NODE: A node containing a glyph string
|
||||
* @GSK_BLUR_NODE: A node that applies a blur
|
||||
@@ -77,6 +78,7 @@ typedef enum {
|
||||
GSK_ROUNDED_CLIP_NODE,
|
||||
GSK_SHADOW_NODE,
|
||||
GSK_BLEND_NODE,
|
||||
GSK_MASK_NODE,
|
||||
GSK_CROSS_FADE_NODE,
|
||||
GSK_TEXT_NODE,
|
||||
GSK_BLUR_NODE,
|
||||
|
||||
@@ -164,6 +164,7 @@ GskRenderNode * gsk_render_node_deserialize (GBytes
|
||||
#define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type())
|
||||
#define GSK_TYPE_TEXT_NODE (gsk_text_node_get_type())
|
||||
#define GSK_TYPE_BLUR_NODE (gsk_blur_node_get_type())
|
||||
#define GSK_TYPE_MASK_NODE (gsk_mask_node_get_type())
|
||||
#define GSK_TYPE_GL_SHADER_NODE (gsk_gl_shader_node_get_type())
|
||||
|
||||
typedef struct _GskDebugNode GskDebugNode;
|
||||
@@ -191,6 +192,7 @@ typedef struct _GskBlendNode GskBlendNode;
|
||||
typedef struct _GskCrossFadeNode GskCrossFadeNode;
|
||||
typedef struct _GskTextNode GskTextNode;
|
||||
typedef struct _GskBlurNode GskBlurNode;
|
||||
typedef struct _GskMaskNode GskMaskNode;
|
||||
typedef struct _GskGLShaderNode GskGLShaderNode;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
@@ -529,8 +531,18 @@ GskRenderNode * gsk_blur_node_get_child (const GskRender
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_blur_node_get_radius (const GskRenderNode *node) G_GNUC_PURE;
|
||||
|
||||
GDK_AVAILABLE_IN_4_10
|
||||
GType gsk_mask_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_4_10
|
||||
GskRenderNode * gsk_mask_node_new (GskRenderNode *source,
|
||||
GskRenderNode *mask);
|
||||
GDK_AVAILABLE_IN_4_10
|
||||
GskRenderNode * gsk_mask_node_get_source (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_4_10
|
||||
GskRenderNode * gsk_mask_node_get_mask (const GskRenderNode *node);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_gl_shader_node_get_type (void) G_GNUC_CONST;
|
||||
GType gsk_gl_shader_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskRenderNode * gsk_gl_shader_node_new (GskGLShader *shader,
|
||||
const graphene_rect_t *bounds,
|
||||
|
||||
@@ -5184,6 +5184,135 @@ gsk_blur_node_get_radius (const GskRenderNode *node)
|
||||
return self->radius;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_MASK_NODE */
|
||||
|
||||
/**
|
||||
* GskMaskNode:
|
||||
*
|
||||
* A render node masking one child node with another.
|
||||
*
|
||||
* Since: 4.10
|
||||
*/
|
||||
typedef struct _GskMaskNode GskMaskNode;
|
||||
|
||||
struct _GskMaskNode
|
||||
{
|
||||
GskRenderNode render_node;
|
||||
|
||||
GskRenderNode *mask;
|
||||
GskRenderNode *source;
|
||||
};
|
||||
|
||||
static void
|
||||
gsk_mask_node_finalize (GskRenderNode *node)
|
||||
{
|
||||
GskMaskNode *self = (GskMaskNode *) node;
|
||||
|
||||
gsk_render_node_unref (self->source);
|
||||
gsk_render_node_unref (self->mask);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_mask_node_draw (GskRenderNode *node,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GskMaskNode *self = (GskMaskNode *) node;
|
||||
cairo_pattern_t *mask_pattern;
|
||||
|
||||
cairo_push_group (cr);
|
||||
gsk_render_node_draw (self->source, cr);
|
||||
cairo_pop_group_to_source (cr);
|
||||
|
||||
cairo_push_group (cr);
|
||||
gsk_render_node_draw (self->mask, cr);
|
||||
mask_pattern = cairo_pop_group (cr);
|
||||
|
||||
cairo_mask (cr, mask_pattern);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_mask_node_diff (GskRenderNode *node1,
|
||||
GskRenderNode *node2,
|
||||
cairo_region_t *region)
|
||||
{
|
||||
GskMaskNode *self1 = (GskMaskNode *) node1;
|
||||
GskMaskNode *self2 = (GskMaskNode *) node2;
|
||||
|
||||
gsk_render_node_diff (self1->source, self2->source, region);
|
||||
gsk_render_node_diff (self1->mask, self2->mask, region);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_mask_node_new:
|
||||
* @source: The bottom node to be drawn
|
||||
* @mask: The node to be blended onto the @bottom node
|
||||
*
|
||||
* Creates a `GskRenderNode` that will use @blend_mode to blend the @top
|
||||
* node onto the @bottom node.
|
||||
*
|
||||
* Returns: (transfer full) (type GskMaskNode): A new `GskRenderNode`
|
||||
*
|
||||
* Since: 4.10
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_mask_node_new (GskRenderNode *source,
|
||||
GskRenderNode *mask)
|
||||
{
|
||||
GskMaskNode *self;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (source), NULL);
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (mask), NULL);
|
||||
|
||||
self = gsk_render_node_alloc (GSK_MASK_NODE);
|
||||
self->source = gsk_render_node_ref (source);
|
||||
self->mask = gsk_render_node_ref (mask);
|
||||
|
||||
graphene_rect_union (&source->bounds, &mask->bounds, &self->render_node.bounds);
|
||||
|
||||
return &self->render_node;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_mask_node_get_source:
|
||||
* @node: (type GskBlendNode): a mask `GskRenderNode`
|
||||
*
|
||||
* Retrieves the source `GskRenderNode` child of the @node.
|
||||
*
|
||||
* Returns: (transfer none): the source child node
|
||||
*
|
||||
* Since: 4.10
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_mask_node_get_source (const GskRenderNode *node)
|
||||
{
|
||||
const GskMaskNode *self = (const GskMaskNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_MASK_NODE), NULL);
|
||||
|
||||
return self->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_mask_node_get_mask:
|
||||
* @node: (type GskBlendNode): a mask `GskRenderNode`
|
||||
*
|
||||
* Retrieves the mask `GskRenderNode` child of the @node.
|
||||
*
|
||||
* Returns: (transfer none): the mask child node
|
||||
*
|
||||
* Since: 4.10
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_mask_node_get_mask (const GskRenderNode *node)
|
||||
{
|
||||
const GskMaskNode *self = (const GskMaskNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_MASK_NODE), NULL);
|
||||
|
||||
return self->mask;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_DEBUG_NODE */
|
||||
|
||||
@@ -5556,6 +5685,7 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_text_node, GSK_TEXT_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blur_node, GSK_BLUR_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_mask_node, GSK_MASK_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_gl_shader_node, GSK_GL_SHADER_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_debug_node, GSK_DEBUG_NODE)
|
||||
|
||||
@@ -5946,6 +6076,22 @@ gsk_render_node_init_types_once (void)
|
||||
gsk_render_node_types[GSK_BLUR_NODE] = node_type;
|
||||
}
|
||||
|
||||
{
|
||||
const GskRenderNodeTypeInfo node_info =
|
||||
{
|
||||
GSK_MASK_NODE,
|
||||
sizeof (GskMaskNode),
|
||||
NULL,
|
||||
gsk_mask_node_finalize,
|
||||
gsk_mask_node_draw,
|
||||
NULL,
|
||||
gsk_mask_node_diff,
|
||||
};
|
||||
|
||||
GType node_type = gsk_render_node_type_register_static (I_("GskMaskNode"), &node_info);
|
||||
gsk_render_node_types[GSK_MASK_NODE] = node_type;
|
||||
}
|
||||
|
||||
{
|
||||
const GskRenderNodeTypeInfo node_info =
|
||||
{
|
||||
|
||||
@@ -1376,6 +1376,31 @@ parse_glshader_node (GtkCssParser *parser)
|
||||
return node;
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_mask_node (GtkCssParser *parser)
|
||||
{
|
||||
GskRenderNode *source = NULL;
|
||||
GskRenderNode *mask = NULL;
|
||||
const Declaration declarations[] = {
|
||||
{ "source", parse_node, clear_node, &source },
|
||||
{ "mask", parse_node, clear_node, &mask },
|
||||
};
|
||||
GskRenderNode *result;
|
||||
|
||||
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
||||
if (source == NULL)
|
||||
source = create_default_render_node ();
|
||||
if (mask == NULL)
|
||||
mask = gsk_color_node_new (&GDK_RGBA("AAFF00"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
||||
|
||||
result = gsk_mask_node_new (source, mask);
|
||||
|
||||
gsk_render_node_unref (source);
|
||||
gsk_render_node_unref (mask);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_border_node (GtkCssParser *parser)
|
||||
{
|
||||
@@ -1914,6 +1939,7 @@ parse_node (GtkCssParser *parser,
|
||||
{ "texture-scale", parse_texture_scale_node },
|
||||
{ "transform", parse_transform_node },
|
||||
{ "glshader", parse_glshader_node },
|
||||
{ "mask", parse_mask_node },
|
||||
};
|
||||
GskRenderNode **node_p = out_node;
|
||||
guint i;
|
||||
@@ -3100,6 +3126,17 @@ render_node_print (Printer *p,
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_MASK_NODE:
|
||||
{
|
||||
start_node (p, "mask");
|
||||
|
||||
append_node_param (p, "source", gsk_mask_node_get_source (node));
|
||||
append_node_param (p, "mask", gsk_mask_node_get_mask (node));
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_NOT_A_RENDER_NODE:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
|
||||
@@ -19,6 +19,7 @@ gsk_private_gl_shaders = [
|
||||
'gl/resources/repeat.glsl',
|
||||
'gl/resources/custom.glsl',
|
||||
'gl/resources/filled_border.glsl',
|
||||
'gl/resources/mask.glsl',
|
||||
]
|
||||
|
||||
gsk_public_sources = files([
|
||||
|
||||
@@ -504,6 +504,9 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
|
||||
g_array_append_val (self->render_ops, op);
|
||||
return;
|
||||
|
||||
case GSK_MASK_NODE:
|
||||
goto fallback;
|
||||
|
||||
case GSK_COLOR_MATRIX_NODE:
|
||||
if (gsk_vulkan_clip_contains_rect (&constants->clip, &node->bounds))
|
||||
pipeline_type = GSK_VULKAN_PIPELINE_COLOR_MATRIX;
|
||||
|
||||
@@ -121,6 +121,9 @@ struct _GtkSnapshotState {
|
||||
struct {
|
||||
char *message;
|
||||
} debug;
|
||||
struct {
|
||||
GskRenderNode *mask_node;
|
||||
} mask;
|
||||
} data;
|
||||
};
|
||||
|
||||
@@ -1241,6 +1244,79 @@ gtk_snapshot_push_blend (GtkSnapshot *snapshot,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_mask_source (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
GskRenderNode **nodes,
|
||||
guint n_nodes)
|
||||
{
|
||||
GskRenderNode *source_child, *mask_child, *mask_node;
|
||||
|
||||
mask_child = gsk_render_node_ref (state->data.mask.mask_node);
|
||||
source_child = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
||||
|
||||
if (source_child == NULL || mask_child == NULL)
|
||||
return NULL;
|
||||
|
||||
mask_node = gsk_mask_node_new (source_child, mask_child);
|
||||
|
||||
gsk_render_node_unref (source_child);
|
||||
gsk_render_node_unref (mask_child);
|
||||
|
||||
return mask_node;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_snapshot_clear_mask_source (GtkSnapshotState *state)
|
||||
{
|
||||
g_clear_pointer (&(state->data.mask.mask_node), gsk_render_node_unref);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_mask_mask (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
GskRenderNode **nodes,
|
||||
guint n_nodes)
|
||||
{
|
||||
GtkSnapshotState *prev_state = gtk_snapshot_get_previous_state (snapshot);
|
||||
|
||||
g_assert (prev_state->collect_func == gtk_snapshot_collect_mask_source);
|
||||
|
||||
prev_state->data.mask.mask_node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_push_mask:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
*
|
||||
* Until the first call to [method@Gtk.Snapshot.pop], the
|
||||
* mask image for the mask operation will be recorded.
|
||||
* After that call, the source image will be recorded until
|
||||
* the second call to [method@Gtk.Snapshot.pop].
|
||||
*
|
||||
* Calling this function requires 2 subsequent calls to gtk_snapshot_pop().
|
||||
*
|
||||
* Since: 4.10
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_push_mask (GtkSnapshot *snapshot)
|
||||
{
|
||||
GtkSnapshotState *current_state = gtk_snapshot_get_current_state (snapshot);
|
||||
GtkSnapshotState *source_state;
|
||||
|
||||
source_state = gtk_snapshot_push_state (snapshot,
|
||||
current_state->transform,
|
||||
gtk_snapshot_collect_mask_source,
|
||||
gtk_snapshot_clear_mask_source);
|
||||
|
||||
gtk_snapshot_push_state (snapshot,
|
||||
source_state->transform,
|
||||
gtk_snapshot_collect_mask_mask,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_cross_fade_end (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
|
||||
@@ -95,6 +95,9 @@ void gtk_snapshot_push_shadow (GtkSnapshot
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_blend (GtkSnapshot *snapshot,
|
||||
GskBlendMode blend_mode);
|
||||
GDK_AVAILABLE_IN_4_10
|
||||
void gtk_snapshot_push_mask (GtkSnapshot *snapshot);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_cross_fade (GtkSnapshot *snapshot,
|
||||
double progress);
|
||||
|
||||
@@ -305,6 +305,10 @@ create_list_model_for_render_node (GskRenderNode *node)
|
||||
return create_render_node_list_model ((GskRenderNode *[2]) { gsk_blend_node_get_bottom_child (node),
|
||||
gsk_blend_node_get_top_child (node) }, 2);
|
||||
|
||||
case GSK_MASK_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[2]) { gsk_mask_node_get_source (node),
|
||||
gsk_mask_node_get_mask (node) }, 2);
|
||||
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[2]) { gsk_cross_fade_node_get_start_child (node),
|
||||
gsk_cross_fade_node_get_end_child (node) }, 2);
|
||||
@@ -425,6 +429,8 @@ node_type_name (GskRenderNodeType type)
|
||||
return "Shadow";
|
||||
case GSK_BLEND_NODE:
|
||||
return "Blend";
|
||||
case GSK_MASK_NODE:
|
||||
return "Mask";
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
return "CrossFade";
|
||||
case GSK_TEXT_NODE:
|
||||
@@ -462,6 +468,7 @@ node_name (GskRenderNode *node)
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
case GSK_SHADOW_NODE:
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
case GSK_TEXT_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
@@ -1132,6 +1139,9 @@ populate_render_node_properties (GListStore *store,
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_MASK_NODE:
|
||||
break;
|
||||
|
||||
case GSK_BLUR_NODE:
|
||||
add_float_row (store, "Radius", gsk_blur_node_get_radius (node));
|
||||
break;
|
||||
|
||||
20
testsuite/gsk/compare/mask.node
Normal file
20
testsuite/gsk/compare/mask.node
Normal file
@@ -0,0 +1,20 @@
|
||||
mask {
|
||||
source: container {
|
||||
color {
|
||||
color: red;
|
||||
bounds: 0 0 50 50;
|
||||
}
|
||||
color {
|
||||
color: yellow;
|
||||
bounds: 50 0 50 50;
|
||||
}
|
||||
color {
|
||||
color: magenta;
|
||||
bounds: 0 50 100 50;
|
||||
}
|
||||
}
|
||||
mask: color {
|
||||
color: black;
|
||||
bounds: 20 20 50 50;
|
||||
}
|
||||
}
|
||||
BIN
testsuite/gsk/compare/mask.png
Normal file
BIN
testsuite/gsk/compare/mask.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 B |
@@ -54,6 +54,7 @@ compare_render_tests = [
|
||||
'inset-shadow-multiple',
|
||||
'invalid-transform',
|
||||
'issue-3615',
|
||||
'mask',
|
||||
'nested-rounded-clips',
|
||||
'opacity_clip',
|
||||
'opacity-overdraw',
|
||||
|
||||
Reference in New Issue
Block a user