Compare commits

...

2 Commits

Author SHA1 Message Date
Matthias Clasen
f2e45bbc87 Quick visualization of rounded rects 2023-05-14 23:14:03 -04:00
Matthias Clasen
9965cfbc3b Sketch rounded-rect intersection
Still to do:
 - Handle the cases that need solving for ellipse intersections
 - Write tests
2023-05-14 21:59:30 -04:00
4 changed files with 548 additions and 1 deletions

View File

@@ -689,6 +689,230 @@ gsk_rounded_rect_intersect_with_rect (const GskRoundedRect *self,
return GSK_INTERSECTION_NONEMPTY;
}
static inline void
rect_corner (const graphene_rect_t *r,
unsigned int i,
graphene_point_t *p)
{
switch (i)
{
case GSK_CORNER_TOP_LEFT:
graphene_rect_get_top_left (r, p);
break;
case GSK_CORNER_TOP_RIGHT:
graphene_rect_get_top_right (r, p);
break;
case GSK_CORNER_BOTTOM_RIGHT:
graphene_rect_get_bottom_right (r, p);
break;
case GSK_CORNER_BOTTOM_LEFT:
graphene_rect_get_bottom_left (r, p);
break;
default:
g_assert_not_reached ();
}
}
static inline void
corner_rect (const GskRoundedRect *s,
unsigned int i,
graphene_rect_t *r)
{
switch (i)
{
case GSK_CORNER_TOP_LEFT:
graphene_rect_init (r,
s->bounds.origin.x,
s->bounds.origin.y,
s->corner[i].width,
s->corner[i].height);
break;
case GSK_CORNER_TOP_RIGHT:
graphene_rect_init (r,
s->bounds.origin.x + s->bounds.size.width - s->corner[i].width,
s->bounds.origin.y,
s->corner[i].width,
s->corner[i].height);
break;
case GSK_CORNER_BOTTOM_RIGHT:
graphene_rect_init (r,
s->bounds.origin.x + s->bounds.size.width - s->corner[i].width,
s->bounds.origin.y + s->bounds.size.height - s->corner[i].height,
s->corner[i].width,
s->corner[i].height);
break;
case GSK_CORNER_BOTTOM_LEFT:
graphene_rect_init (r,
s->bounds.origin.x,
s->bounds.origin.y + s->bounds.size.height - s->corner[i].height,
s->corner[i].width,
s->corner[i].height);
break;
default:
g_assert_not_reached ();
}
}
static inline gboolean
point_in_interior (const graphene_point_t *p,
const graphene_rect_t *r)
{
if (graphene_rect_contains_point (r, p))
{
if (p->x > r->origin.x && p->x < r->origin.x + r->size.width)
return TRUE;
if (p->y > r->origin.y && p->y < r->origin.y + r->size.height)
return TRUE;
}
return FALSE;
}
GskRoundedRectIntersection
gsk_rounded_rect_intersect (const GskRoundedRect *self,
const GskRoundedRect *other,
GskRoundedRect *result)
{
if (!graphene_rect_intersection (&self->bounds, &other->bounds, &result->bounds))
return GSK_INTERSECTION_EMPTY;
for (unsigned int i = 0; i < 4; i++)
{
graphene_point_t p, p1, p2;
rect_corner (&self->bounds, i, &p1);
rect_corner (&other->bounds, i, &p2);
rect_corner (&result->bounds, i, &p);
if (graphene_point_equal (&p, &p1))
{
if (graphene_point_equal (&p, &p2))
{
graphene_rect_t c;
graphene_rect_t d;
corner_rect (self, i, &c);
corner_rect (other, i, &d);
/* corners coincide */
if (graphene_rect_contains_rect (&c, &d))
{
graphene_point_t q1, q2;
rect_corner (&c, (i + 1) % 4, &q1);
rect_corner (&c, (i + 3) % 4, &q2);
if (gsk_rounded_rect_contains_point (other, &q1) &&
gsk_rounded_rect_contains_point (other, &q2))
result->corner[i] = self->corner[i];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else if (graphene_rect_contains_rect (&d, &c))
{
graphene_point_t q1, q2;
rect_corner (&d, (i + 1) % 4, &q1);
rect_corner (&d, (i + 3) % 4, &q2);
if (gsk_rounded_rect_contains_point (self, &q1) &&
gsk_rounded_rect_contains_point (self, &q2))
result->corner[i] = other->corner[i];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
{
graphene_rect_t c;
graphene_point_t q1, q2;
corner_rect (self, i, &c);
rect_corner (&c, (i + 1) % 4, &q1);
rect_corner (&c, (i + 3) % 4, &q2);
if (gsk_rounded_rect_contains_point (other, &q1) &&
gsk_rounded_rect_contains_point (other, &q2))
{
if (gsk_rounded_rect_contains_point (other, &p))
result->corner[i] = self->corner[i];
else
#if 1
return GSK_INTERSECTION_NEEDS_QUARTIC;
#else
if (/* no intersection for i */)
result->corner[i] = self->corner[i];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
#endif
}
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
}
else if (graphene_point_equal (&p, &p2))
{
graphene_rect_t d;
graphene_point_t q1, q2;
corner_rect (other, i, &d);
rect_corner (&d, (i + 1) % 4, &q1);
rect_corner (&d, (i + 3) % 4, &q2);
if (gsk_rounded_rect_contains_point (self, &q1) &&
gsk_rounded_rect_contains_point (self, &q2))
{
if (gsk_rounded_rect_contains_point (self, &p))
result->corner[i] = other->corner[i];
else
#if 1
return GSK_INTERSECTION_NEEDS_QUARTIC;
#else
if (/* no intersection for i */
result->corner[i] = other->corner[i];
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
#endif
}
else
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
else
{
graphene_rect_t c, d;
corner_rect (self, (i + 2) % 4, &c);
if (graphene_rect_contains_point (&c, &p) &&
!gsk_rounded_rect_contains_point (self, &p))
return GSK_INTERSECTION_EMPTY;
corner_rect (other, (i + 2) % 4, &d);
if (graphene_rect_contains_point (&d, &p) &&
!gsk_rounded_rect_contains_point (other, &p))
return GSK_INTERSECTION_EMPTY;
for (unsigned int j = 0; j < 4; j++)
{
corner_rect (self, j, &c);
corner_rect (other, j, &d);
if (point_in_interior (&p, &c) ||
point_in_interior (&p, &d))
return GSK_INTERSECTION_NOT_REPRESENTABLE;
}
result->corner[i] = (graphene_size_t) { 0, 0 };
}
}
return GSK_INTERSECTION_NONEMPTY;
}
static void
append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
{

View File

@@ -37,13 +37,17 @@ char * gsk_rounded_rect_to_string (const GskRounde
typedef enum {
GSK_INTERSECTION_EMPTY,
GSK_INTERSECTION_NONEMPTY,
GSK_INTERSECTION_NOT_REPRESENTABLE
GSK_INTERSECTION_NOT_REPRESENTABLE,
GSK_INTERSECTION_NEEDS_QUARTIC
} GskRoundedRectIntersection;
GskRoundedRectIntersection gsk_rounded_rect_intersect_with_rect (const GskRoundedRect *self,
const graphene_rect_t *rect,
GskRoundedRect *result) G_GNUC_PURE;
GskRoundedRectIntersection gsk_rounded_rect_intersect (const GskRoundedRect *self,
const GskRoundedRect *other,
GskRoundedRect *result) G_GNUC_PURE;
G_END_DECLS

View File

@@ -146,6 +146,13 @@ foreach t: gtk_tests
)
endforeach
executable('testroundedrect',
sources: [ 'testroundedrect.c' ],
include_directories: [confinc, gdkinc],
c_args: test_args + common_cflags,
dependencies: [libgtk_static_dep, libm],
)
if libsysprof_dep.found()
executable('testperf',
sources: 'testperf.c',

312
tests/testroundedrect.c Normal file
View File

@@ -0,0 +1,312 @@
#include <gtk/gtk.h>
#include <gtk/css/gtkcssparserprivate.h>
#include <gsk/gskroundedrectprivate.h>
#define TEST_TYPE_WIDGET (test_widget_get_type ())
G_DECLARE_FINAL_TYPE (TestWidget, test_widget, TEST, WIDGET, GtkWidget)
struct _TestWidget
{
GtkWidget parent_instance;
GskRoundedRect rect1;
GskRoundedRect rect2;
GskRoundedRect rect3;
GskRoundedRectIntersection result;
};
struct _TestWidgetClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (TestWidget, test_widget, GTK_TYPE_WIDGET)
static void
test_widget_init (TestWidget *self)
{
}
static void
test_widget_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
TestWidget *self = TEST_WIDGET (widget);
if (orientation == GTK_ORIENTATION_VERTICAL)
*minimum = *natural = MAX (self->rect1.bounds.origin.x + self->rect1.bounds.size.width,
self->rect2.bounds.origin.x + self->rect2.bounds.size.width);
else
*minimum = *natural = MAX (self->rect1.bounds.origin.y + self->rect1.bounds.size.height,
self->rect2.bounds.origin.y + self->rect2.bounds.size.height);
}
static void
test_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
TestWidget *self = TEST_WIDGET (widget);
float widths[4] = { 1, 1, 1, 1 };
GdkRGBA colors1[4];
GdkRGBA colors2[4];
GdkRGBA colors3[4];
GskRoundedRect rect3;
gdk_rgba_parse (&colors1[0], "red");
colors1[1] = colors1[2] = colors1[3] = colors1[0];
gdk_rgba_parse (&colors2[0], "blue");
colors2[1] = colors2[2] = colors2[3] = colors2[0];
gdk_rgba_parse (&colors3[0], "magenta");
colors3[1] = colors3[2] = colors3[3] = colors3[0];
gtk_snapshot_append_border (snapshot, &self->rect1, widths, colors1);
gtk_snapshot_append_border (snapshot, &self->rect2, widths, colors2);
switch (gsk_rounded_rect_intersect (&self->rect1, &self->rect2, &rect3))
{
case GSK_INTERSECTION_NONEMPTY:
gtk_snapshot_append_border (snapshot, &rect3, widths, colors3);
break;
default:
;
}
}
static void
test_widget_class_init (TestWidgetClass *class)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
widget_class->snapshot = test_widget_snapshot;
widget_class->measure = test_widget_measure;
}
static GtkWidget *
test_widget_new (void)
{
return g_object_new (TEST_TYPE_WIDGET, NULL);
}
static void
update_intersection (TestWidget *self)
{
self->result = gsk_rounded_rect_intersect (&self->rect1, &self->rect2, &self->rect3);
}
static void
test_widget_set_rect1 (TestWidget *self,
GskRoundedRect *rect)
{
self->rect1 = *rect;
update_intersection (self);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
static void
test_widget_set_rect2 (TestWidget *self,
GskRoundedRect *rect)
{
self->rect2 = *rect;
update_intersection (self);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
static gboolean
parse_rect (GtkCssParser *parser,
graphene_rect_t *out_rect)
{
double numbers[4];
if (!gtk_css_parser_consume_number (parser, &numbers[0]) ||
!gtk_css_parser_consume_number (parser, &numbers[1]) ||
!gtk_css_parser_consume_number (parser, &numbers[2]) ||
!gtk_css_parser_consume_number (parser, &numbers[3]))
return FALSE;
graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
return TRUE;
}
static gboolean
parse_rounded_rect (GtkCssParser *parser,
GskRoundedRect *out_rect)
{
graphene_rect_t r;
graphene_size_t corners[4];
double d;
guint i;
if (!parse_rect (parser, &r))
return FALSE;
if (!gtk_css_parser_try_delim (parser, '/'))
{
gsk_rounded_rect_init_from_rect (out_rect, &r, 0);
return TRUE;
}
for (i = 0; i < 4; i++)
{
if (!gtk_css_parser_has_number (parser))
break;
if (!gtk_css_parser_consume_number (parser, &d))
return FALSE;
corners[i].width = d;
}
if (i == 0)
{
gtk_css_parser_error_syntax (parser, "Expected a number");
return FALSE;
}
/* The magic (i - 1) >> 1 below makes it take the correct value
* according to spec. Feel free to check the 4 cases
*/
for (; i < 4; i++)
corners[i].width = corners[(i - 1) >> 1].width;
if (gtk_css_parser_try_delim (parser, '/'))
{
gtk_css_parser_consume_token (parser);
for (i = 0; i < 4; i++)
{
if (!gtk_css_parser_has_number (parser))
break;
if (!gtk_css_parser_consume_number (parser, &d))
return FALSE;
corners[i].height = d;
}
if (i == 0)
{
gtk_css_parser_error_syntax (parser, "Expected a number");
return FALSE;
}
for (; i < 4; i++)
corners[i].height = corners[(i - 1) >> 1].height;
}
else
{
for (i = 0; i < 4; i++)
corners[i].height = corners[i].width;
}
gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
return TRUE;
}
static GtkWidget *label;
static void
update_label (GtkLabel *label,
GskRoundedRectIntersection result)
{
const char *labels[] = {
"Empty", "Not empty", "Not representable", "Who knows"
};
gtk_label_set_label (label, labels[result]);
}
static void
activate1_cb (GtkEntry *entry, TestWidget *test)
{
GtkCssParser *parser;
const char *text;
GBytes *bytes;
GskRoundedRect rect;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
bytes = g_bytes_new_static (text, strlen (text) + 1);
parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, NULL, NULL);
if (parse_rounded_rect (parser, &rect))
{
test_widget_set_rect1 (test, &rect);
update_label (GTK_LABEL (label), test->result);
}
gtk_css_parser_unref (parser);
g_bytes_unref (bytes);
}
static void
activate2_cb (GtkEntry *entry, TestWidget *test)
{
GtkCssParser *parser;
const char *text;
GBytes *bytes;
GskRoundedRect rect;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
bytes = g_bytes_new_static (text, strlen (text) + 1);
parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, NULL, NULL);
if (parse_rounded_rect (parser, &rect))
{
test_widget_set_rect2 (test, &rect);
update_label (GTK_LABEL (label), test->result);
}
gtk_css_parser_unref (parser);
g_bytes_unref (bytes);
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *box;
GtkWidget *grid;
GtkWidget *entry1;
GtkWidget *entry2;
GtkWidget *test;
gtk_init ();
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_window_set_child (GTK_WINDOW (window), box);
grid = gtk_grid_new ();
gtk_box_append (GTK_BOX (box), grid);
test = test_widget_new ();
gtk_widget_set_hexpand (test, TRUE);
gtk_widget_set_vexpand (test, TRUE);
gtk_widget_set_halign (test, GTK_ALIGN_CENTER);
gtk_widget_set_valign (test, GTK_ALIGN_CENTER);
gtk_box_append (GTK_BOX (box), test);
entry1 = gtk_entry_new ();
g_signal_connect (entry1, "activate", G_CALLBACK (activate1_cb), test);
gtk_grid_attach (GTK_GRID (grid), entry1, 0, 0, 1, 1);
entry2 = gtk_entry_new ();
g_signal_connect (entry2, "activate", G_CALLBACK (activate2_cb), test);
gtk_grid_attach (GTK_GRID (grid), entry2, 0, 1, 1, 1);
label = gtk_label_new ("");
gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1);
gtk_window_present (GTK_WINDOW (window));
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, FALSE);
return 0;
}