diff --git a/gtk/gtkroundedbox.c b/gtk/gtkroundedbox.c index 0519bfed51..4e7c19d0cd 100644 --- a/gtk/gtkroundedbox.c +++ b/gtk/gtkroundedbox.c @@ -246,6 +246,102 @@ _gtk_rounded_box_path (const GtkRoundedBox *box, G_PI / 2, G_PI); } +double +_gtk_rounded_box_guess_length (const GtkRoundedBox *box, + GtkCssSide side) +{ + double length; + GtkCssCorner before, after; + + before = side; + after = (side + 1) % 4; + + if (side & 1) + length = box->box.height + - box->corner[before].vertical + - box->corner[after].vertical; + else + length = box->box.width + - box->corner[before].horizontal + - box->corner[after].horizontal; + + length += G_PI * 0.125 * (box->corner[before].horizontal + + box->corner[before].vertical + + box->corner[after].horizontal + + box->corner[after].vertical); + + return length; +} + +void +_gtk_rounded_box_path_side (const GtkRoundedBox *box, + cairo_t *cr, + GtkCssSide side) +{ + switch (side) + { + case GTK_CSS_TOP: + _cairo_ellipsis (cr, + box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal, + box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical, + box->corner[GTK_CSS_TOP_LEFT].horizontal, + box->corner[GTK_CSS_TOP_LEFT].vertical, + 5 * G_PI / 4, 3 * G_PI / 2); + _cairo_ellipsis (cr, + box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal, + box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical, + box->corner[GTK_CSS_TOP_RIGHT].horizontal, + box->corner[GTK_CSS_TOP_RIGHT].vertical, + - G_PI / 2, -G_PI / 4); + break; + case GTK_CSS_RIGHT: + _cairo_ellipsis (cr, + box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal, + box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical, + box->corner[GTK_CSS_TOP_RIGHT].horizontal, + box->corner[GTK_CSS_TOP_RIGHT].vertical, + - G_PI / 4, 0); + _cairo_ellipsis (cr, + box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal, + box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical, + box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal, + box->corner[GTK_CSS_BOTTOM_RIGHT].vertical, + 0, G_PI / 4); + break; + case GTK_CSS_BOTTOM: + _cairo_ellipsis (cr, + box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal, + box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical, + box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal, + box->corner[GTK_CSS_BOTTOM_RIGHT].vertical, + G_PI / 4, G_PI / 2); + _cairo_ellipsis (cr, + box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal, + box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical, + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal, + box->corner[GTK_CSS_BOTTOM_LEFT].vertical, + G_PI / 2, 3 * G_PI / 4); + break; + case GTK_CSS_LEFT: + _cairo_ellipsis (cr, + box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal, + box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical, + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal, + box->corner[GTK_CSS_BOTTOM_LEFT].vertical, + 3 * G_PI / 4, G_PI); + _cairo_ellipsis (cr, + box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal, + box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical, + box->corner[GTK_CSS_TOP_LEFT].horizontal, + box->corner[GTK_CSS_TOP_LEFT].vertical, + G_PI, 5 * G_PI / 4); + break; + default: + g_assert_not_reached (); + break; + } +} + void _gtk_rounded_box_path_top (const GtkRoundedBox *outer, const GtkRoundedBox *inner, diff --git a/gtk/gtkroundedboxprivate.h b/gtk/gtkroundedboxprivate.h index a832b9b577..98309e7f36 100644 --- a/gtk/gtkroundedboxprivate.h +++ b/gtk/gtkroundedboxprivate.h @@ -56,8 +56,14 @@ void _gtk_rounded_box_move (GtkRoundedBox double dx, double dy); +double _gtk_rounded_box_guess_length (const GtkRoundedBox *box, + GtkCssSide side); + void _gtk_rounded_box_path (const GtkRoundedBox *box, cairo_t *cr); +void _gtk_rounded_box_path_side (const GtkRoundedBox *box, + cairo_t *cr, + GtkCssSide side); void _gtk_rounded_box_path_top (const GtkRoundedBox *outer, const GtkRoundedBox *inner, cairo_t *cr); diff --git a/gtk/gtkthemingengine.c b/gtk/gtkthemingengine.c index b9e2e38db9..149c5b3534 100644 --- a/gtk/gtkthemingengine.c +++ b/gtk/gtkthemingengine.c @@ -1434,6 +1434,158 @@ render_frame_fill (cairo_t *cr, } } +static void +set_stroke_style (cairo_t *cr, + double line_width, + GtkBorderStyle style, + double length) +{ + double segments[2]; + double n; + + cairo_set_line_width (cr, line_width); + + if (style == GTK_BORDER_STYLE_DOTTED) + { + n = round (0.5 * length / line_width); + + segments[0] = 0; + segments[1] = n ? length / n : 2; + cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + } + else + { + n = length / line_width; + /* Optimize the common case of an integer-sized rectangle + * Again, we care about focus rectangles. + */ + if (n == nearbyint (n)) + { + segments[0] = 1; + segments[1] = 2; + } + else + { + n = round ((1. / 3) * n); + + segments[0] = n ? (1. / 3) * length / n : 1; + segments[1] = 2 * segments[1]; + } + cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0); + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER); + } +} + +static int +get_border_side (GtkBorder *border, + GtkCssSide side) +{ + switch (side) + { + case GTK_CSS_TOP: + return border->top; + case GTK_CSS_RIGHT: + return border->right; + case GTK_CSS_BOTTOM: + return border->bottom; + case GTK_CSS_LEFT: + return border->left; + default: + g_assert_not_reached (); + return 0; + } +} + +static void +render_frame_stroke (cairo_t *cr, + GtkRoundedBox *border_box, + GtkBorder *border, + GdkRGBA colors[4], + guint hidden_side, + GtkBorderStyle stroke_style) +{ + gboolean different_colors, different_borders; + GtkRoundedBox stroke_box; + guint i; + + different_colors = !gdk_rgba_equal (&colors[0], &colors[1]) || + !gdk_rgba_equal (&colors[0], &colors[2]) || + !gdk_rgba_equal (&colors[0], &colors[3]); + different_borders = border->top != border->right || + border->top != border->bottom || + border->top != border->left; + + stroke_box = *border_box; + _gtk_rounded_box_shrink (&stroke_box, + border->top / 2.0, + border->right / 2.0, + border->bottom / 2.0, + border->left / 2.0); + + if (!different_colors && !different_borders && hidden_side == 0) + { + double length = 0; + + /* FAST PATH: + * Mostly expected to trigger for focus rectangles */ + for (i = 0; i < 4; i++) + { + length += _gtk_rounded_box_guess_length (&stroke_box, i); + _gtk_rounded_box_path_side (&stroke_box, cr, i); + } + + gdk_cairo_set_source_rgba (cr, &colors[0]); + set_stroke_style (cr, border->top, stroke_style, length); + cairo_stroke (cr); + } + else + { + GtkRoundedBox padding_box; + + padding_box = *border_box; + _gtk_rounded_box_path (&padding_box, cr); + _gtk_rounded_box_shrink (&padding_box, + border->top, + border->right, + border->bottom, + border->left); + + for (i = 0; i < 4; i++) + { + if (hidden_side & (1 << i)) + continue; + + cairo_save (cr); + + if (i == 0) + _gtk_rounded_box_path_top (border_box, &padding_box, cr); + else if (i == 1) + _gtk_rounded_box_path_right (border_box, &padding_box, cr); + else if (i == 2) + _gtk_rounded_box_path_bottom (border_box, &padding_box, cr); + else if (i == 3) + _gtk_rounded_box_path_left (border_box, &padding_box, cr); + cairo_clip (cr); + + _gtk_rounded_box_path_side (&stroke_box, cr, i); + + gdk_cairo_set_source_rgba (cr, &colors[i]); + set_stroke_style (cr, + get_border_side (border, i), + stroke_style, + _gtk_rounded_box_guess_length (&stroke_box, i)); + cairo_stroke (cr); + + cairo_restore (cr); + } + } +} + static void render_frame_internal (GtkThemingEngine *engine, cairo_t *cr, @@ -1539,6 +1691,19 @@ render_frame_internal (GtkThemingEngine *engine, 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); + } + + render_frame_stroke (cr, &border_box, &border, colors, dont_draw, border_style[i]); + } break; case GTK_BORDER_STYLE_DOUBLE: {