summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2016-12-18 18:14:53 +0100
committerBenjamin Otte <otte@redhat.com>2016-12-20 18:01:11 +0100
commit75b76af2214277856efe9629939859deebcf914d (patch)
treefe695e958ebb0f35374758e21e0789889e8f2036
parent9af468cdec1c8b4dc603ff36c286288b2c85eb9e (diff)
downloadgtk+-75b76af2214277856efe9629939859deebcf914d.tar.gz
gsk: Add GskBorderNode
The node draws a solid CSS border, which can be used to cover everything but dashed and dotted borders (double, groove, inset, ...). For different border styles, we overlay multiple nodes and set their colors to transparent for sides with non-matching styles.
-rw-r--r--docs/reference/gsk/gsk4-sections.txt1
-rw-r--r--gsk/gskenums.h2
-rw-r--r--gsk/gskrendernode.h5
-rw-r--r--gsk/gskrendernodeimpl.c186
-rw-r--r--gsk/gskrendernodeprivate.h4
-rw-r--r--gtk/gtkrenderborder.c209
-rw-r--r--gtk/gtkrenderborderprivate.h4
-rw-r--r--gtk/inspector/gtktreemodelrendernode.c1
8 files changed, 389 insertions, 23 deletions
diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt
index 3b47dab517..023abe5d56 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -35,6 +35,7 @@ gsk_render_node_set_name
gsk_color_node_new
gsk_linear_gradient_node_new
gsk_repeating_linear_gradient_node_new
+gsk_border_node_new
gsk_texture_node_new
gsk_cairo_node_new
gsk_cairo_node_get_draw_context
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index 1d46c9230e..7bad39d1c2 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -31,6 +31,7 @@
* @GSK_LINEAR_GRADIENT_NODE: A node drawing a linear gradient
* @GSK_REPEATING_LINEAR_GRADIENT_NODE: A node drawing a repeating
* linear gradient
+ * @GSK_BORDER_NODE: A node stroking a border around an area
* @GSK_TEXTURE_NODE: A node drawing a #GskTexture
* @GSK_TRANSFORM_NODE: A node that renders its child after applying a
* matrix transform
@@ -51,6 +52,7 @@ typedef enum {
GSK_COLOR_NODE,
GSK_LINEAR_GRADIENT_NODE,
GSK_REPEATING_LINEAR_GRADIENT_NODE,
+ GSK_BORDER_NODE,
GSK_TEXTURE_NODE,
GSK_TRANSFORM_NODE,
GSK_OPACITY_NODE,
diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h
index 14fa797db9..a349b8b302 100644
--- a/gsk/gskrendernode.h
+++ b/gsk/gskrendernode.h
@@ -74,6 +74,11 @@ GskRenderNode * gsk_repeating_linear_gradient_node_new (const graphene_
gsize n_color_stops);
GDK_AVAILABLE_IN_3_90
+GskRenderNode * gsk_border_node_new (const GskRoundedRect *outline,
+ const float border_width[4],
+ const GdkRGBA border_color[4]);
+
+GDK_AVAILABLE_IN_3_90
GskRenderNode * gsk_cairo_node_new (const graphene_rect_t *bounds);
GDK_AVAILABLE_IN_3_90
cairo_t * gsk_cairo_node_get_draw_context (GskRenderNode *node,
diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c
index ee3cb46015..c5d8d615d3 100644
--- a/gsk/gskrendernodeimpl.c
+++ b/gsk/gskrendernodeimpl.c
@@ -271,6 +271,192 @@ gsk_repeating_linear_gradient_node_new (const graphene_rect_t *bounds,
return &self->render_node;
}
+/*** GSK_BORDER_NODE ***/
+
+typedef struct _GskBorderNode GskBorderNode;
+
+struct _GskBorderNode
+{
+ GskRenderNode render_node;
+
+ GskRoundedRect outline;
+ float border_width[4];
+ GdkRGBA border_color[4];
+};
+
+static void
+gsk_border_node_finalize (GskRenderNode *node)
+{
+}
+
+static void
+gsk_border_node_make_immutable (GskRenderNode *node)
+{
+}
+
+static void
+gsk_border_node_draw (GskRenderNode *node,
+ cairo_t *cr)
+{
+ GskBorderNode *self = (GskBorderNode *) node;
+ GskRoundedRect inside;
+
+ cairo_save (cr);
+
+ gsk_rounded_rect_init_copy (&inside, &self->outline);
+ gsk_rounded_rect_shrink (&inside,
+ self->border_width[0], self->border_width[1],
+ self->border_width[2], self->border_width[3]);
+
+ cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+ gsk_rounded_rect_path (&self->outline, cr);
+ gsk_rounded_rect_path (&inside, cr);
+
+ if (gdk_rgba_equal (&self->border_color[0], &self->border_color[1]) &&
+ gdk_rgba_equal (&self->border_color[0], &self->border_color[2]) &&
+ gdk_rgba_equal (&self->border_color[0], &self->border_color[3]))
+ {
+ gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
+ cairo_fill (cr);
+ }
+ else
+ {
+ const graphene_rect_t *bounds = &self->outline.bounds;
+ /* distance to center "line":
+ * +-------------------------+
+ * | |
+ * | |
+ * | ---this-line--- |
+ * | |
+ * | |
+ * +-------------------------+
+ * That line is equidistant from all sides. It's either horiontal
+ * or vertical, depending on if the rect is wider or taller.
+ * We use the 4 sides spanned up by connecting the line to the corner
+ * points to color the regions of the rectangle differently.
+ * Note that the call to cairo_fill() will add the potential final
+ * segment by closing the path, so we don't have to care.
+ */
+ float dst = MIN (bounds->size.width, bounds->size.height) / 2.0;
+
+ cairo_clip (cr);
+
+ /* top */
+ cairo_move_to (cr, bounds->origin.x + dst, bounds->origin.y + dst);
+ cairo_rel_line_to (cr, - dst, - dst);
+ cairo_rel_line_to (cr, bounds->size.width, 0);
+ cairo_rel_line_to (cr, - dst, dst);
+ gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
+ cairo_fill (cr);
+
+ /* right */
+ cairo_move_to (cr, bounds->origin.x + bounds->size.width - dst, bounds->origin.y + dst);
+ cairo_rel_line_to (cr, dst, - dst);
+ cairo_rel_line_to (cr, 0, bounds->size.height);
+ cairo_rel_line_to (cr, - dst, - dst);
+ gdk_cairo_set_source_rgba (cr, &self->border_color[1]);
+ cairo_fill (cr);
+
+ /* bottom */
+ cairo_move_to (cr, bounds->origin.x + bounds->size.width - dst, bounds->origin.y + bounds->size.height - dst);
+ cairo_rel_line_to (cr, dst, dst);
+ cairo_rel_line_to (cr, - bounds->size.width, 0);
+ cairo_rel_line_to (cr, dst, - dst);
+ gdk_cairo_set_source_rgba (cr, &self->border_color[2]);
+ cairo_fill (cr);
+
+ /* left */
+ cairo_move_to (cr, bounds->origin.x + dst, bounds->origin.y + bounds->size.height - dst);
+ cairo_rel_line_to (cr, - dst, dst);
+ cairo_rel_line_to (cr, 0, - bounds->size.height);
+ cairo_rel_line_to (cr, dst, dst);
+ gdk_cairo_set_source_rgba (cr, &self->border_color[3]);
+ cairo_fill (cr);
+ }
+
+ cairo_restore (cr);
+}
+
+static void
+gsk_border_node_get_bounds (GskRenderNode *node,
+ graphene_rect_t *bounds)
+{
+ GskBorderNode *self = (GskBorderNode *) node;
+
+ graphene_rect_init_from_rect (bounds, &self->outline.bounds);
+}
+
+static const GskRenderNodeClass GSK_BORDER_NODE_CLASS = {
+ GSK_BORDER_NODE,
+ sizeof (GskBorderNode),
+ "GskBorderNode",
+ gsk_border_node_finalize,
+ gsk_border_node_make_immutable,
+ gsk_border_node_draw,
+ gsk_border_node_get_bounds
+};
+
+const GskRoundedRect *
+gsk_border_node_peek_outline (GskRenderNode *node)
+{
+ GskBorderNode *self = (GskBorderNode *) node;
+
+ return &self->outline;
+}
+
+float
+gsk_border_node_get_width (GskRenderNode *node,
+ guint i)
+{
+ GskBorderNode *self = (GskBorderNode *) node;
+
+ return self->border_width[i];
+}
+
+const GdkRGBA *
+gsk_border_node_peek_color (GskRenderNode *node,
+ guint i)
+{
+ GskBorderNode *self = (GskBorderNode *) node;
+
+ return &self->border_color[i];
+}
+
+/**
+ * gsk_border_node_new:
+ * @outline: a #GskRoundedRect describing the outline of the border
+ * @border_width: the stroke width of the border on the top, right, bottom and
+ * left side respectively.
+ * @border_color: the color used on the top, right, bottom and left side.
+ *
+ * Creates a #GskRenderNode that will stroke a border rectangle inside the
+ * given @outline. The 4 sides of the border can have different widths and
+ * colors.
+ *
+ * Returns: A new #GskRenderNode
+ *
+ * Since: 3.90
+ */
+GskRenderNode *
+gsk_border_node_new (const GskRoundedRect *outline,
+ const float border_width[4],
+ const GdkRGBA border_color[4])
+{
+ GskBorderNode *self;
+
+ g_return_val_if_fail (outline != NULL, NULL);
+ g_return_val_if_fail (border_width != NULL, NULL);
+ g_return_val_if_fail (border_color != NULL, NULL);
+
+ self = (GskBorderNode *) gsk_render_node_new (&GSK_BORDER_NODE_CLASS);
+
+ gsk_rounded_rect_init_copy (&self->outline, outline);
+ memcpy (self->border_width, border_width, sizeof (self->border_width));
+ memcpy (self->border_color, border_color, sizeof (self->border_color));
+
+ return &self->render_node;
+}
+
/*** GSK_TEXTURE_NODE ***/
typedef struct _GskTextureNode GskTextureNode;
diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h
index e945ee7b2c..24e87e8880 100644
--- a/gsk/gskrendernodeprivate.h
+++ b/gsk/gskrendernodeprivate.h
@@ -48,6 +48,10 @@ void gsk_render_node_get_bounds (GskRenderNode *node,
graphene_rect_t *frame);
double gsk_opacity_node_get_opacity (GskRenderNode *node);
+const GskRoundedRect * gsk_border_node_peek_outline (GskRenderNode *node);
+float gsk_border_node_get_width (GskRenderNode *node, guint i);
+const GdkRGBA * gsk_border_node_peek_color (GskRenderNode *node, guint i);
+
cairo_surface_t *gsk_cairo_node_get_surface (GskRenderNode *node);
GskTexture *gsk_texture_node_get_texture (GskRenderNode *node);
diff --git a/gtk/gtkrenderborder.c b/gtk/gtkrenderborder.c
index 5a17516499..7f739c3d64 100644
--- a/gtk/gtkrenderborder.c
+++ b/gtk/gtkrenderborder.c
@@ -406,6 +406,44 @@ render_frame_fill (cairo_t *cr,
}
static void
+snapshot_frame_fill (GtkSnapshot *snapshot,
+ const GskRoundedRect *outline,
+ const float border_width[4],
+ const GdkRGBA colors[4],
+ guint hidden_side)
+{
+ GskRoundedRect offset_outline;
+ GskRenderNode *node;
+ double off_x, off_y;
+
+ if (hidden_side)
+ {
+ GdkRGBA real_colors[4];
+ guint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (hidden_side & (1 << i))
+ real_colors[i] = (GdkRGBA) { 0, 0, 0, 0 };
+ else
+ real_colors[i] = colors[i];
+ }
+
+ snapshot_frame_fill (snapshot, outline, border_width, real_colors, 0);
+ return;
+ }
+
+ gtk_snapshot_get_offset (snapshot, &off_x, &off_y);
+ gsk_rounded_rect_init_copy (&offset_outline, outline);
+ gsk_rounded_rect_offset (&offset_outline, off_x, off_y);
+
+ node = gsk_border_node_new (&offset_outline, border_width, colors);
+ gsk_render_node_set_name (node, "Border");
+ gtk_snapshot_append_node (snapshot, node);
+ gsk_render_node_unref (node);
+}
+
+static void
set_stroke_style (cairo_t *cr,
double line_width,
GtkBorderStyle style,
@@ -540,6 +578,24 @@ render_frame_stroke (cairo_t *cr,
}
static void
+snapshot_frame_stroke (GtkSnapshot *snapshot,
+ GskRoundedRect *outline,
+ const float border_width[4],
+ GdkRGBA colors[4],
+ guint hidden_side,
+ GtkBorderStyle stroke_style)
+{
+ double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
+ cairo_t *cr;
+
+ cr = gtk_snapshot_append_cairo_node (snapshot,
+ &outline->bounds,
+ "BorderStroke");
+ render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
+ cairo_destroy (cr);
+}
+
+static void
color_shade (const GdkRGBA *color,
gdouble factor,
GdkRGBA *color_return)
@@ -679,6 +735,128 @@ render_border (cairo_t *cr,
cairo_restore (cr);
}
+static void
+snapshot_border (GtkSnapshot *snapshot,
+ GskRoundedRect *border_box,
+ const float border_width[4],
+ GdkRGBA colors[4],
+ GtkBorderStyle border_style[4])
+{
+ guint hidden_side = 0;
+ guint i, j;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (hidden_side & (1 << i))
+ continue;
+
+ /* NB: code below divides by this value */
+ /* a border smaller than this will not noticably modify
+ * pixels on screen, and since we don't compare with 0,
+ * we'll use this value */
+ if (border_width[i] < 1.0 / 1024)
+ continue;
+
+ switch (border_style[i])
+ {
+ case GTK_BORDER_STYLE_NONE:
+ case GTK_BORDER_STYLE_HIDDEN:
+ case GTK_BORDER_STYLE_SOLID:
+ break;
+ case GTK_BORDER_STYLE_INSET:
+ if (i == 1 || i == 2)
+ color_shade (&colors[i], 1.8, &colors[i]);
+ break;
+ case GTK_BORDER_STYLE_OUTSET:
+ if (i == 0 || i == 3)
+ color_shade (&colors[i], 1.8, &colors[i]);
+ break;
+ case GTK_BORDER_STYLE_DOTTED:
+ case GTK_BORDER_STYLE_DASHED:
+ {
+ guint dont_draw = hidden_side;
+
+ for (j = 0; j < 4; j++)
+ {
+ if (border_style[j] == border_style[i])
+ hidden_side |= (1 << j);
+ else
+ dont_draw |= (1 << j);
+ }
+
+ snapshot_frame_stroke (snapshot, border_box, border_width, colors, dont_draw, border_style[i]);
+ }
+ break;
+ case GTK_BORDER_STYLE_DOUBLE:
+ {
+ GskRoundedRect other_box;
+ float other_border[4];
+ guint dont_draw = hidden_side;
+
+ for (j = 0; j < 4; j++)
+ {
+ if (border_style[j] == GTK_BORDER_STYLE_DOUBLE)
+ hidden_side |= (1 << j);
+ else
+ dont_draw |= (1 << j);
+
+ other_border[j] = border_width[j] / 3;
+ }
+
+ snapshot_frame_fill (snapshot, border_box, other_border, colors, dont_draw);
+
+ other_box = *border_box;
+ gsk_rounded_rect_shrink (&other_box,
+ 2 * other_border[GTK_CSS_TOP],
+ 2 * other_border[GTK_CSS_RIGHT],
+ 2 * other_border[GTK_CSS_BOTTOM],
+ 2 * other_border[GTK_CSS_LEFT]);
+ snapshot_frame_fill (snapshot, &other_box, other_border, colors, dont_draw);
+ }
+ break;
+ case GTK_BORDER_STYLE_GROOVE:
+ case GTK_BORDER_STYLE_RIDGE:
+ {
+ GskRoundedRect other_box;
+ GdkRGBA other_colors[4];
+ guint dont_draw = hidden_side;
+ float other_border[4];
+
+ for (j = 0; j < 4; j++)
+ {
+ other_colors[j] = colors[j];
+ if ((j == 0 || j == 3) ^ (border_style[j] == GTK_BORDER_STYLE_RIDGE))
+ color_shade (&other_colors[j], 1.8, &other_colors[j]);
+ else
+ color_shade (&colors[j], 1.8, &colors[j]);
+ if (border_style[j] == GTK_BORDER_STYLE_GROOVE ||
+ border_style[j] == GTK_BORDER_STYLE_RIDGE)
+ hidden_side |= (1 << j);
+ else
+ dont_draw |= (1 << j);
+ other_border[j] = border_width[j] / 2;
+ }
+
+ snapshot_frame_fill (snapshot, border_box, other_border, colors, dont_draw);
+
+ other_box = *border_box;
+ gsk_rounded_rect_shrink (&other_box,
+ other_border[GTK_CSS_TOP],
+ other_border[GTK_CSS_RIGHT],
+ other_border[GTK_CSS_BOTTOM],
+ other_border[GTK_CSS_LEFT]);
+ snapshot_frame_fill (snapshot, &other_box, other_border, other_colors, dont_draw);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ snapshot_frame_fill (snapshot, border_box, border_width, colors, hidden_side);
+}
+
gboolean
gtk_css_style_render_has_border (GtkCssStyle *style)
{
@@ -744,13 +922,13 @@ gtk_css_style_render_border (GtkCssStyle *style,
void
gtk_css_style_snapshot_border (GtkCssStyle *style,
- GtkSnapshot *state,
+ GtkSnapshot *snapshot,
gdouble width,
gdouble height,
GtkJunctionSides junction)
{
GtkBorderImage border_image;
- double border_width[4];
+ float border_width[4];
graphene_rect_t bounds;
cairo_t *cr;
@@ -763,10 +941,12 @@ gtk_css_style_snapshot_border (GtkCssStyle *style,
if (gtk_border_image_init (&border_image, style))
{
- cr = gtk_snapshot_append_cairo_node (state,
+ double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
+
+ cr = gtk_snapshot_append_cairo_node (snapshot,
&bounds,
"Border Image");
- gtk_border_image_render (&border_image, border_width, cr, 0, 0, width, height);
+ gtk_border_image_render (&border_image, double_width, cr, 0, 0, width, height);
cairo_destroy (cr);
}
else
@@ -782,10 +962,6 @@ gtk_css_style_snapshot_border (GtkCssStyle *style,
border_width[3] == 0)
return;
- cr = gtk_snapshot_append_cairo_node (state,
- &bounds,
- "Border");
-
border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_STYLE));
border_style[1] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_RIGHT_STYLE));
border_style[2] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_STYLE));
@@ -799,9 +975,7 @@ gtk_css_style_snapshot_border (GtkCssStyle *style,
_gtk_rounded_box_init_rect (&border_box, 0, 0, width, height);
_gtk_rounded_box_apply_border_radius_for_style (&border_box, style, junction);
- render_border (cr, &border_box, border_width, colors, border_style);
-
- cairo_destroy (cr);
+ snapshot_border (snapshot, &border_box, border_width, colors, border_style);
}
}
@@ -904,20 +1078,19 @@ gtk_css_style_render_outline (GtkCssStyle *style,
void
gtk_css_style_snapshot_outline (GtkCssStyle *style,
- GtkSnapshot *state,
+ GtkSnapshot *snapshot,
gdouble width,
gdouble height)
{
GtkBorderStyle border_style[4];
GskRoundedRect border_box;
- double border_width[4];
+ float border_width[4];
GdkRGBA colors[4];
border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_STYLE));
if (border_style[0] != GTK_BORDER_STYLE_NONE)
{
cairo_rectangle_t rect;
- cairo_t *cr;
compute_outline_rect (style, 0, 0, width, height, &rect);
@@ -930,13 +1103,7 @@ gtk_css_style_snapshot_outline (GtkCssStyle *style,
_gtk_rounded_box_init_rect (&border_box, rect.x, rect.y, rect.width, rect.height);
_gtk_rounded_box_apply_outline_radius_for_style (&border_box, style, GTK_JUNCTION_NONE);
- cr = gtk_snapshot_append_cairo_node (state,
- &GRAPHENE_RECT_INIT (rect.x, rect.y, rect.width, rect.height),
- "Outline");
-
- render_border (cr, &border_box, border_width, colors, border_style);
-
- cairo_destroy (cr);
+ snapshot_border (snapshot, &border_box, border_width, colors, border_style);
}
}
diff --git a/gtk/gtkrenderborderprivate.h b/gtk/gtkrenderborderprivate.h
index f89cdcb600..4de9d6da35 100644
--- a/gtk/gtkrenderborderprivate.h
+++ b/gtk/gtkrenderborderprivate.h
@@ -44,7 +44,7 @@ gboolean gtk_css_style_render_border_get_clip (GtkCssStyle
gdouble height,
GdkRectangle *out_clip) G_GNUC_WARN_UNUSED_RESULT;
void gtk_css_style_snapshot_border (GtkCssStyle *style,
- GtkSnapshot *state,
+ GtkSnapshot *snapshot,
gdouble width,
gdouble height,
GtkJunctionSides junction);
@@ -57,7 +57,7 @@ void gtk_css_style_render_outline (GtkCssStyle
gdouble width,
gdouble height);
void gtk_css_style_snapshot_outline (GtkCssStyle *style,
- GtkSnapshot *state,
+ GtkSnapshot *snapshot,
gdouble width,
gdouble height);
gboolean gtk_css_style_render_outline_get_clip (GtkCssStyle *style,
diff --git a/gtk/inspector/gtktreemodelrendernode.c b/gtk/inspector/gtktreemodelrendernode.c
index fff0a06f41..96b0d944e9 100644
--- a/gtk/inspector/gtktreemodelrendernode.c
+++ b/gtk/inspector/gtktreemodelrendernode.c
@@ -527,6 +527,7 @@ append_node (GtkTreeModelRenderNode *nodemodel,
case GSK_COLOR_NODE:
case GSK_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+ case GSK_BORDER_NODE:
/* no children */
break;