popover: Bring back the beak

Bring back the code that draws an arrow.
This commit is contained in:
Matthias Clasen 2019-05-05 23:53:02 +00:00
parent fa9cbf6c7d
commit fd0a290d77
4 changed files with 480 additions and 22 deletions

View File

@ -124,6 +124,21 @@
#include "gdk/gdkeventsprivate.h"
#include "gtkpointerfocusprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtksnapshot.h"
#include "gtkrender.h"
#include "gtkstylecontextprivate.h"
#include "gtkroundedboxprivate.h"
#include "gsk/gskroundedrectprivate.h"
#ifdef GDK_WINDOWING_WAYLAND
#include "wayland/gdkwayland.h"
#endif
#define TAIL_GAP_WIDTH 24
#define TAIL_HEIGHT 12
#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
static GListStore *popover_list = NULL;
@ -141,6 +156,10 @@ typedef struct {
gboolean modal;
GtkWidget *contents_widget;
GtkCssNode *arrow_node;
GdkRectangle final_rect;
GtkPositionType final_position;
} GtkPopoverPrivate;
enum {
@ -364,6 +383,42 @@ surface_event (GdkSurface *surface,
return TRUE;
}
static void
surface_moved_to_rect (GdkSurface *surface,
GdkRectangle *flipped_rect,
GdkRectangle *final_rect,
gboolean flipped_x,
gboolean flipped_y,
GtkWidget *widget)
{
GtkPopover *popover = GTK_POPOVER (widget);
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
priv->final_rect = *final_rect;
switch (priv->position)
{
case GTK_POS_LEFT:
priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT;
break;
case GTK_POS_RIGHT:
priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT;
break;
case GTK_POS_TOP:
priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP;
break;
case GTK_POS_BOTTOM:
priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM;
break;
default:
g_assert_not_reached ();
break;
}
if (priv->final_position != priv->position)
gtk_widget_queue_allocate (widget);
}
static void
measure_contents (GtkGizmo *gizmo,
GtkOrientation orientation,
@ -435,16 +490,29 @@ add_actions (GtkPopover *popover)
g_object_unref (actions);
}
static void
node_style_changed_cb (GtkCssNode *node,
GtkCssStyleChange *change,
GtkWidget *widget)
{
if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_SIZE))
gtk_widget_queue_resize (widget);
else
gtk_widget_queue_draw (widget);
}
static void
gtk_popover_init (GtkPopover *popover)
{
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
GtkWidget *widget = GTK_WIDGET (popover);
GtkEventController *controller;
GtkStyleContext *context;
gtk_widget_set_has_surface (GTK_WIDGET (popover), TRUE);
priv->position = GTK_POS_TOP;
priv->final_position = GTK_POS_TOP;
priv->modal = TRUE;
controller = gtk_event_controller_key_new ();
@ -453,6 +521,15 @@ gtk_popover_init (GtkPopover *popover)
g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gtk_popover_key_pressed), popover);
gtk_widget_add_controller (GTK_WIDGET (popover), controller);
priv->arrow_node = gtk_css_node_new ();
gtk_css_node_set_name (priv->arrow_node, I_("arrow"));
gtk_css_node_set_parent (priv->arrow_node, gtk_widget_get_css_node (widget));
gtk_css_node_set_state (priv->arrow_node,
gtk_css_node_get_state (gtk_widget_get_css_node (widget)));
g_signal_connect_object (priv->arrow_node, "style-changed",
G_CALLBACK (node_style_changed_cb), popover, 0);
g_object_unref (priv->arrow_node);
priv->contents_widget = gtk_gizmo_new ("contents",
measure_contents,
allocate_contents,
@ -487,6 +564,7 @@ gtk_popover_realize (GtkWidget *widget)
g_signal_connect_swapped (priv->surface, "size-changed", G_CALLBACK (surface_size_changed), widget);
g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget);
g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget);
g_signal_connect (priv->surface, "moved-to-rect", G_CALLBACK (surface_moved_to_rect), widget);
GTK_WIDGET_CLASS (gtk_popover_parent_class)->realize (widget);
@ -508,6 +586,7 @@ gtk_popover_unrealize (GtkWidget *widget)
g_signal_handlers_disconnect_by_func (priv->surface, surface_size_changed, widget);
g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget);
g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget);
g_signal_handlers_disconnect_by_func (priv->surface, surface_moved_to_rect, widget);
gdk_surface_set_widget (priv->surface, NULL);
g_clear_object (&priv->surface);
@ -660,6 +739,291 @@ gtk_popover_constructed (GObject *object)
g_object_unref (object);
}
static void
gtk_popover_get_gap_coords (GtkPopover *popover,
gint *initial_x_out,
gint *initial_y_out,
gint *tip_x_out,
gint *tip_y_out,
gint *final_x_out,
gint *final_y_out)
{
GtkWidget *widget = GTK_WIDGET (popover);
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
GdkRectangle rect = { 0 };
gint base, tip, tip_pos;
gint initial_x, initial_y;
gint tip_x, tip_y;
gint final_x, final_y;
GtkPositionType pos;
gint border_radius;
GtkStyleContext *context;
GtkBorder border;
int popover_width, popover_height;
popover_width = gtk_widget_get_allocated_width (widget);
popover_height = gtk_widget_get_allocated_height (widget);
gtk_widget_get_surface_allocation (priv->relative_to, &rect);
if (priv->has_pointing_to)
{
rect.x += priv->pointing_to.x;
rect.y += priv->pointing_to.y;
rect.width = priv->pointing_to.width;
rect.height = priv->pointing_to.height;
}
rect.x -= priv->final_rect.x;
rect.y -= priv->final_rect.y;
context = gtk_widget_get_style_context (priv->contents_widget);
pos = priv->final_position;
gtk_style_context_get_border (context, &border);
gtk_style_context_get (context,
GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
NULL);
if (pos == GTK_POS_BOTTOM || pos == GTK_POS_RIGHT)
{
tip = 0;
base = TAIL_HEIGHT + border.top;
}
else if (pos == GTK_POS_TOP)
{
base = popover_height - border.bottom - TAIL_HEIGHT;
tip = popover_height;
}
else if (pos == GTK_POS_LEFT)
{
base = popover_width - border.right - TAIL_HEIGHT;
tip = popover_width;
}
else
g_assert_not_reached ();
if (POS_IS_VERTICAL (pos))
{
tip_pos = rect.x + (rect.width / 2);
initial_x = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
border_radius,
popover_width - TAIL_GAP_WIDTH - border_radius);
initial_y = base;
tip_x = CLAMP (tip_pos, 0, popover_width);
tip_y = tip;
final_x = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
border_radius + TAIL_GAP_WIDTH,
popover_width - border_radius);
final_y = base;
}
else
{
tip_pos = rect.y + (rect.height / 2);
initial_x = base;
initial_y = CLAMP (tip_pos - TAIL_GAP_WIDTH / 2,
border_radius,
popover_height - TAIL_GAP_WIDTH - border_radius);
tip_x = tip;
tip_y = CLAMP (tip_pos, 0, popover_height);
final_x = base;
final_y = CLAMP (tip_pos + TAIL_GAP_WIDTH / 2,
border_radius + TAIL_GAP_WIDTH,
popover_height - border_radius);
}
*initial_x_out = initial_x;
*initial_y_out = initial_y;
*tip_x_out = tip_x;
*tip_y_out = tip_y;
*final_x_out = final_x;
*final_y_out = final_y;
}
static void
get_margin (GtkWidget *widget,
GtkBorder *border)
{
GtkStyleContext *context;
context = gtk_widget_get_style_context (widget);
gtk_style_context_get_margin (context, border);
}
static void
gtk_popover_get_rect_for_size (GtkPopover *popover,
int popover_width,
int popover_height,
GdkRectangle *rect)
{
GtkWidget *widget = GTK_WIDGET (popover);
int x, y, w, h;
GtkBorder margin;
get_margin (widget, &margin);
x = 0;
y = 0;
w = popover_width;
h = popover_height;
x += MAX (TAIL_HEIGHT, margin.left);
y += MAX (TAIL_HEIGHT, margin.top);
w -= x + MAX (TAIL_HEIGHT, margin.right);
h -= y + MAX (TAIL_HEIGHT, margin.bottom);
rect->x = x;
rect->y = y;
rect->width = w;
rect->height = h;
}
static void
gtk_popover_get_rect_coords (GtkPopover *popover,
int *x_out,
int *y_out,
int *w_out,
int *h_out)
{
GtkWidget *widget = GTK_WIDGET (popover);
GdkRectangle rect;
GtkAllocation allocation;
gtk_widget_get_allocation (widget, &allocation);
gtk_popover_get_rect_for_size (popover, allocation.width, allocation.height, &rect);
*x_out = rect.x;
*y_out = rect.y;
*w_out = rect.width;
*h_out = rect.height;
}
static void
gtk_popover_apply_tail_path (GtkPopover *popover,
cairo_t *cr)
{
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
gint initial_x, initial_y;
gint tip_x, tip_y;
gint final_x, final_y;
GtkStyleContext *context;
GtkBorder border;
if (!priv->relative_to)
return;
context = gtk_widget_get_style_context (priv->contents_widget);
gtk_style_context_get_border (context, &border);
cairo_set_line_width (cr, 1);
gtk_popover_get_gap_coords (popover,
&initial_x, &initial_y,
&tip_x, &tip_y,
&final_x, &final_y);
cairo_move_to (cr, initial_x, initial_y);
cairo_line_to (cr, tip_x, tip_y);
cairo_line_to (cr, final_x, final_y);
}
static void
gtk_popover_fill_border_path (GtkPopover *popover,
cairo_t *cr)
{
GtkWidget *widget = GTK_WIDGET (popover);
GtkAllocation allocation;
GtkStyleContext *context;
int x, y, w, h;
GskRoundedRect box;
context = gtk_widget_get_style_context (widget);
gtk_widget_get_allocation (widget, &allocation);
cairo_set_source_rgba (cr, 0, 0, 0, 1);
gtk_popover_apply_tail_path (popover, cr);
cairo_close_path (cr);
cairo_fill (cr);
gtk_popover_get_rect_coords (popover, &x, &y, &w, &h);
gtk_rounded_boxes_init_for_style (&box,
NULL, NULL,
gtk_style_context_lookup_style (context),
x, y, w, h);
gsk_rounded_rect_path (&box, cr);
cairo_fill (cr);
}
static void
gtk_popover_update_shape (GtkPopover *popover)
{
GtkWidget *widget = GTK_WIDGET (popover);
cairo_surface_t *cairo_surface;
cairo_region_t *region;
GdkSurface *surface;
cairo_t *cr;
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget)))
return;
#endif
surface = gtk_widget_get_surface (widget);
cairo_surface =
gdk_surface_create_similar_surface (surface,
CAIRO_CONTENT_COLOR_ALPHA,
gdk_surface_get_width (surface),
gdk_surface_get_height (surface));
cr = cairo_create (cairo_surface);
gtk_popover_fill_border_path (popover, cr);
cairo_destroy (cr);
region = gdk_cairo_region_create_from_surface (cairo_surface);
cairo_surface_destroy (cairo_surface);
gtk_widget_input_shape_combine_region (widget, region);
cairo_region_destroy (region);
}
static gint
get_border_radius (GtkWidget *widget)
{
GtkStyleContext *context;
gint border_radius;
context = gtk_widget_get_style_context (widget);
gtk_style_context_get (context,
GTK_STYLE_PROPERTY_BORDER_RADIUS, &border_radius,
NULL);
return border_radius;
}
static gint
get_minimal_size (GtkPopover *popover,
GtkOrientation orientation)
{
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
GtkPositionType pos;
gint minimal_size;
minimal_size = 2 * get_border_radius (GTK_WIDGET (popover));
pos = priv->position;
if ((orientation == GTK_ORIENTATION_HORIZONTAL && POS_IS_VERTICAL (pos)) ||
(orientation == GTK_ORIENTATION_VERTICAL && !POS_IS_VERTICAL (pos)))
minimal_size += TAIL_GAP_WIDTH;
return minimal_size;
}
static void
gtk_popover_measure (GtkWidget *widget,
GtkOrientation orientation,
@ -671,11 +1035,22 @@ gtk_popover_measure (GtkWidget *widget,
{
GtkPopover *popover = GTK_POPOVER (widget);
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
int minimal_size;
if (for_size >= 0)
for_size -= TAIL_HEIGHT;
gtk_widget_measure (priv->contents_widget,
orientation, for_size,
minimum, natural,
minimum_baseline, natural_baseline);
minimal_size = get_minimal_size (popover, orientation);
*minimum = MAX (*minimum, minimal_size);
*natural = MAX (*natural, minimal_size);
*minimum += TAIL_HEIGHT;
*natural += TAIL_HEIGHT;
}
static void
@ -686,13 +1061,40 @@ gtk_popover_size_allocate (GtkWidget *widget,
{
GtkPopover *popover = GTK_POPOVER (widget);
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
GtkAllocation child_alloc;
if (priv->surface)
gtk_popover_move_resize (popover);
gtk_widget_size_allocate (priv->contents_widget,
&(GtkAllocation) { 0, 0, width, height },
baseline);
switch (priv->final_position)
{
case GTK_POS_TOP:
child_alloc.x = TAIL_HEIGHT / 2;
child_alloc.y = 0;
break;
case GTK_POS_BOTTOM:
child_alloc.x = TAIL_HEIGHT / 2;
child_alloc.y = TAIL_HEIGHT;
break;
case GTK_POS_LEFT:
child_alloc.x = 0;
child_alloc.y = TAIL_HEIGHT / 2;
break;
case GTK_POS_RIGHT:
child_alloc.x = TAIL_HEIGHT;
child_alloc.y = TAIL_HEIGHT / 2;
break;
default:
break;
}
child_alloc.width = width - TAIL_HEIGHT;
child_alloc.height = height - TAIL_HEIGHT;
gtk_widget_size_allocate (priv->contents_widget, &child_alloc, baseline);
if (priv->surface)
gtk_popover_update_shape (popover);
}
static void
@ -701,8 +1103,53 @@ gtk_popover_snapshot (GtkWidget *widget,
{
GtkPopover *popover = GTK_POPOVER (widget);
GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
GtkStyleContext *context;
GtkBorder border;
cairo_t *cr;
gtk_widget_snapshot_child (widget, priv->contents_widget, snapshot);
cr = gtk_snapshot_append_cairo (snapshot,
&GRAPHENE_RECT_INIT (
0, 0,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget)
));
/* Clip to the arrow shape */
cairo_save (cr);
gtk_popover_apply_tail_path (popover, cr);
cairo_clip (cr);
context = gtk_widget_get_style_context (widget);
gtk_style_context_save_to_node (context, priv->arrow_node);
gtk_style_context_get_border (context, &border);
/* Render the arrow background */
gtk_render_background (context, cr,
0, 0,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget));
/* Render the border of the arrow tip */
if (border.bottom > 0)
{
GdkRGBA *border_color;
gtk_style_context_get (context, "border-color", &border_color, NULL);
gtk_popover_apply_tail_path (popover, cr);
gdk_cairo_set_source_rgba (cr, border_color);
cairo_set_line_width (cr, border.bottom + 1);
cairo_stroke (cr);
gdk_rgba_free (border_color);
}
cairo_restore (cr);
cairo_destroy (cr);
gtk_style_context_restore (context);
}
static void
@ -1061,6 +1508,18 @@ gtk_popover_get_pointing_to (GtkPopover *popover,
if (priv->has_pointing_to)
*rect = priv->pointing_to;
else if (priv->relative_to)
{
graphene_rect_t r;
if (!gtk_widget_compute_bounds (priv->relative_to, priv->relative_to, &r))
return FALSE;
rect->x = floorf (r.origin.x);
rect->y = floorf (r.origin.y);
rect->width = ceilf (r.size.width);
rect->height = ceilf (r.size.height);
}
return priv->has_pointing_to;
}
@ -1089,6 +1548,7 @@ gtk_popover_set_position (GtkPopover *popover,
return;
priv->position = position;
priv->final_position = position;
g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_POSITION]);
}

View File

@ -2170,19 +2170,21 @@ menuitem {
* Popovers *
***************/
popover.background {
background-color: transparent;
padding: 0px;
}
popover.menu>arrow,
popover>arrow {
background-color: $popover_bg_color;
border: 1px solid $borders_color;
}
popover>contents.menu {
padding: 8px;
background-color: white;
margin: 0px;
}
popover>contents {
padding: 2px;
padding: 8px;
background-color: $popover_bg_color;
border: 1px solid $borders_color;
margin: 0px;
}
@ -4702,7 +4704,3 @@ popover.emoji-completion contents row box {
popover.emoji-completion .emoji:hover {
background-color: $popover_hover_color;
}
popover.menu {
background-color: lightgray;
}

View File

@ -869,11 +869,11 @@ menuitem check:dir(ltr), menuitem radio:dir(ltr) { margin-right: 7px; }
menuitem check:dir(rtl), menuitem radio:dir(rtl) { margin-left: 7px; }
/*************** Popovers * */
popover > arrow { background-color: #353535; border: 1px solid #1b1b1b; }
popover.background { background-color: transparent; padding: 0px; }
popover > contents.menu { padding: 8px; background-color: white; margin: 0px; }
popover.menu > arrow, popover > arrow { background-color: #353535; border: 1px solid #1b1b1b; }
popover > contents { padding: 2px; margin: 0px; }
popover > contents { padding: 8px; background-color: #353535; border: 1px solid #1b1b1b; margin: 0px; }
popover > contents.background { border-radius: 9px; background-color: #353535; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
@ -1888,7 +1888,7 @@ colorswatch overlay:hover { box-shadow: inset 0 1px rgba(255, 255, 255, 0.4), in
colorswatch overlay:backdrop, colorswatch overlay:backdrop:hover { border-color: #1b1b1b; box-shadow: none; }
colorswatch#add-color-button { border-radius: 5px 5px 0 0; }
colorswatch#add-color-button { border-radius: 5px 0 0 5px; }
colorswatch#add-color-button:only-child { border-radius: 5px; }

View File

@ -877,11 +877,11 @@ menuitem check:dir(ltr), menuitem radio:dir(ltr) { margin-right: 7px; }
menuitem check:dir(rtl), menuitem radio:dir(rtl) { margin-left: 7px; }
/*************** Popovers * */
popover > arrow { background-color: #f6f5f4; border: 1px solid #cdc7c2; }
popover.background { background-color: transparent; padding: 0px; }
popover > contents.menu { padding: 8px; background-color: white; margin: 0px; }
popover.menu > arrow, popover > arrow { background-color: #f6f5f4; border: 1px solid #cdc7c2; }
popover > contents { padding: 2px; margin: 0px; }
popover > contents { padding: 8px; background-color: #f6f5f4; border: 1px solid #cdc7c2; margin: 0px; }
popover > contents.background { border-radius: 9px; background-color: #f6f5f4; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
@ -1904,7 +1904,7 @@ colorswatch overlay:hover { box-shadow: inset 0 1px rgba(255, 255, 255, 0.4), in
colorswatch overlay:backdrop, colorswatch overlay:backdrop:hover { border-color: rgba(0, 0, 0, 0.3); box-shadow: none; }
colorswatch#add-color-button { border-radius: 5px 5px 0 0; }
colorswatch#add-color-button { border-radius: 5px 0 0 5px; }
colorswatch#add-color-button:only-child { border-radius: 5px; }