egg: bring multi-paned ported to GTK 4

This is out of libpanel, which already has it but with the dock stuff
removed (more like DzlMultiPaned). We probably still need some work to
handle natural allocation from children.
This commit is contained in:
Christian Hergert
2021-10-01 14:49:04 -07:00
parent 124754ee51
commit 5e67104823
7 changed files with 1353 additions and 0 deletions

View File

@ -0,0 +1,35 @@
/* egg-handle.h
*
* Copyright 2021 Christian Hergert <chergert@redhat.com>
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define EGG_TYPE_HANDLE (egg_handle_get_type())
G_DECLARE_FINAL_TYPE (EggHandle, egg_handle, EGG, HANDLE, GtkWidget)
GtkWidget *egg_handle_new (GtkPositionType position);
void egg_handle_set_position (EggHandle *self,
GtkPositionType position);
G_END_DECLS

View File

@ -0,0 +1,145 @@
/* egg-handle.c
*
* Copyright 2021 Christian Hergert <chergert@redhat.com>
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include "config.h"
#include "egg-handle-private.h"
#define EXTRA_SIZE 8
struct _EggHandle
{
GtkWidget parent_instance;
GtkWidget *separator;
GtkPositionType position : 3;
};
G_DEFINE_TYPE (EggHandle, egg_handle, GTK_TYPE_WIDGET)
static gboolean
egg_handle_contains (GtkWidget *widget,
double x,
double y)
{
EggHandle *self = (EggHandle *)widget;
graphene_rect_t area;
g_assert (EGG_IS_HANDLE (self));
if (!gtk_widget_compute_bounds (GTK_WIDGET (self->separator),
GTK_WIDGET (self),
&area))
return FALSE;
switch (self->position)
{
case GTK_POS_LEFT:
area.origin.x -= EXTRA_SIZE;
area.size.width = EXTRA_SIZE;
break;
case GTK_POS_RIGHT:
area.size.width = EXTRA_SIZE;
break;
case GTK_POS_TOP:
area.origin.y -= EXTRA_SIZE;
area.size.height = EXTRA_SIZE;
break;
case GTK_POS_BOTTOM:
area.size.height = EXTRA_SIZE;
break;
default:
g_assert_not_reached ();
break;
}
return graphene_rect_contains_point (&area, &GRAPHENE_POINT_INIT (x, y));
}
static void
egg_handle_dispose (GObject *object)
{
EggHandle *self = (EggHandle *)object;
g_clear_pointer (&self->separator, gtk_widget_unparent);
G_OBJECT_CLASS (egg_handle_parent_class)->dispose (object);
}
static void
egg_handle_class_init (EggHandleClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = egg_handle_dispose;
widget_class->contains = egg_handle_contains;
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
}
static void
egg_handle_init (EggHandle *self)
{
self->separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_widget_set_parent (GTK_WIDGET (self->separator), GTK_WIDGET (self));
}
void
egg_handle_set_position (EggHandle *self,
GtkPositionType position)
{
g_return_if_fail (EGG_IS_HANDLE (self));
self->position = position;
switch (position)
{
case GTK_POS_LEFT:
case GTK_POS_RIGHT:
gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "col-resize");
gtk_orientable_set_orientation (GTK_ORIENTABLE (self->separator), GTK_ORIENTATION_VERTICAL);
break;
case GTK_POS_TOP:
case GTK_POS_BOTTOM:
gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "row-resize");
gtk_orientable_set_orientation (GTK_ORIENTABLE (self->separator), GTK_ORIENTATION_HORIZONTAL);
break;
default:
g_assert_not_reached ();
}
}
GtkWidget *
egg_handle_new (GtkPositionType position)
{
EggHandle *self;
self = g_object_new (EGG_TYPE_HANDLE, NULL);
egg_handle_set_position (self, position);
return GTK_WIDGET (self);
}

View File

@ -0,0 +1,48 @@
/* egg-paned.h
*
* Copyright 2021 Christian Hergert <chergert@redhat.com>
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define EGG_TYPE_PANED (egg_paned_get_type())
G_DECLARE_FINAL_TYPE (EggPaned, egg_paned, EGG, PANED, GtkWidget)
GtkWidget *egg_paned_new (void);
void egg_paned_append (EggPaned *self,
GtkWidget *child);
void egg_paned_prepend (EggPaned *self,
GtkWidget *child);
void egg_paned_insert (EggPaned *self,
int position,
GtkWidget *child);
void egg_paned_insert_after (EggPaned *self,
GtkWidget *child,
GtkWidget *sibling);
void egg_paned_remove (EggPaned *self,
GtkWidget *child);
guint egg_paned_get_n_children (EggPaned *self);
GtkWidget *egg_paned_get_nth_child (EggPaned *self,
guint nth);
G_END_DECLS

View File

@ -0,0 +1,593 @@
/* egg-paned.c
*
* Copyright 2021 Christian Hergert <chergert@redhat.com>
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include "config.h"
#include <string.h>
#include "egg-paned-private.h"
#include "egg-resizer-private.h"
struct _EggPaned
{
GtkWidget parent_instance;
GtkOrientation orientation;
};
static void buildable_iface_init (GtkBuildableIface *iface);
G_DEFINE_TYPE_WITH_CODE (EggPaned, egg_paned, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
enum {
PROP_0,
N_PROPS,
PROP_ORIENTATION,
};
static void
update_orientation (GtkWidget *widget,
GtkOrientation orientation)
{
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
gtk_widget_remove_css_class (widget, "vertical");
gtk_widget_add_css_class (widget, "horizontal");
}
else
{
gtk_widget_remove_css_class (widget, "horizontal");
gtk_widget_add_css_class (widget, "vertical");
}
gtk_accessible_update_property (GTK_ACCESSIBLE (widget),
GTK_ACCESSIBLE_PROPERTY_ORIENTATION, orientation,
-1);
}
/**
* egg_paned_new:
*
* Create a new #EggPaned.
*
* Returns: (transfer full): a newly created #EggPaned
*/
GtkWidget *
egg_paned_new (void)
{
return g_object_new (EGG_TYPE_PANED, NULL);
}
static void
egg_paned_set_orientation (EggPaned *self,
GtkOrientation orientation)
{
GtkPositionType pos;
g_assert (EGG_IS_PANED (self));
g_assert (orientation == GTK_ORIENTATION_HORIZONTAL ||
orientation == GTK_ORIENTATION_VERTICAL);
if (self->orientation == orientation)
return;
self->orientation = orientation;
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
pos = GTK_POS_LEFT;
else
pos = GTK_POS_TOP;
for (GtkWidget *child = gtk_widget_get_last_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_prev_sibling (child))
{
g_assert (EGG_IS_RESIZER (child));
egg_resizer_set_position (EGG_RESIZER (child), pos);
}
update_orientation (GTK_WIDGET (self), self->orientation);
gtk_widget_queue_resize (GTK_WIDGET (self));
g_object_notify (G_OBJECT (self), "orientation");
}
static void
egg_paned_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
EggPaned *self = (EggPaned *)widget;
g_assert (EGG_IS_PANED (self));
*minimum = 0;
*natural = 0;
*minimum_baseline = -1;
*natural_baseline = -1;
for (GtkWidget *child = gtk_widget_get_first_child (widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
int child_min, child_nat;
gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL);
if (orientation == self->orientation)
{
*minimum += child_min;
*natural += child_nat;
}
else
{
*minimum = MAX (*minimum, child_min);
*natural = MAX (*natural, child_nat);
}
}
}
typedef struct
{
GtkWidget *widget;
GtkRequisition min_request;
GtkRequisition nat_request;
GtkAllocation alloc;
} ChildAllocation;
static void
egg_paned_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
EggPaned *self = (EggPaned *)widget;
ChildAllocation *allocs;
ChildAllocation *last_alloc = NULL;
GtkOrientation orientation;
guint n_children = 0;
guint n_expand = 0;
guint i;
int extra_width = width;
int extra_height = height;
int expand_width;
int expand_height;
int x, y;
g_assert (EGG_IS_PANED (self));
GTK_WIDGET_CLASS (egg_paned_parent_class)->size_allocate (widget, width, height, baseline);
n_children = egg_paned_get_n_children (self);
if (n_children == 1)
{
GtkWidget *child = gtk_widget_get_first_child (widget);
GtkAllocation alloc = { 0, 0, width, height };
if (gtk_widget_get_visible (child))
{
gtk_widget_size_allocate (child, &alloc, -1);
return;
}
}
orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self));
allocs = g_newa (ChildAllocation, n_children);
memset (allocs, 0, sizeof *allocs * n_children);
/* Give min size to each of the children */
i = 0;
for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_next_sibling (child), i++)
{
ChildAllocation *child_alloc = &allocs[i];
if (!gtk_widget_get_visible (child))
continue;
gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, height,
&child_alloc->min_request.width,
&child_alloc->nat_request.width,
NULL, NULL);
gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, width,
&child_alloc->min_request.height,
&child_alloc->nat_request.height,
NULL, NULL);
child_alloc->alloc.width = child_alloc->min_request.width;
child_alloc->alloc.height = child_alloc->min_request.height;
n_expand += gtk_widget_compute_expand (child, orientation);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
extra_width -= child_alloc->alloc.width;
child_alloc->alloc.height = height;
}
else
{
extra_height -= child_alloc->alloc.height;
child_alloc->alloc.width = width;
}
}
/* Now try to distribute extra space for natural size */
i = 0;
for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_next_sibling (child), i++)
{
ChildAllocation *child_alloc = &allocs[i];
if (!gtk_widget_get_visible (child))
continue;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
int taken = MIN (extra_width, child_alloc->nat_request.width - child_alloc->alloc.width);
if (taken > 0)
{
child_alloc->alloc.width += taken;
extra_width -= taken;
}
}
else
{
int taken = MIN (extra_height, child_alloc->nat_request.height - child_alloc->alloc.height);
if (taken > 0)
{
child_alloc->alloc.height += taken;
extra_height -= taken;
}
}
last_alloc = child_alloc;
}
/* Now give extra space for those that expand */
expand_width = n_expand ? extra_width / n_expand : 0;
expand_height = n_expand ? extra_height / n_expand : 0;
i = n_children;
for (GtkWidget *child = gtk_widget_get_last_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_prev_sibling (child), i--)
{
ChildAllocation *child_alloc = &allocs[i-1];
if (!gtk_widget_get_visible (child))
continue;
if (!gtk_widget_compute_expand (child, orientation))
continue;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_alloc->alloc.width += expand_width;
extra_width -= expand_width;
}
else
{
child_alloc->alloc.height += expand_height;
extra_height -= expand_height;
}
}
/* Give any leftover to the last visible child */
if (last_alloc)
{
if (orientation == GTK_ORIENTATION_HORIZONTAL)
last_alloc->alloc.width += extra_width;
else
last_alloc->alloc.height += extra_height;
}
i = 0;
x = 0;
y = 0;
for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_next_sibling (child), i++)
{
ChildAllocation *child_alloc = &allocs[i];
child_alloc->alloc.x = x;
child_alloc->alloc.y = y;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
x += child_alloc->alloc.width;
else
y += child_alloc->alloc.height;
gtk_widget_size_allocate (child, &child_alloc->alloc, -1);
}
}
static void
egg_paned_dispose (GObject *object)
{
EggPaned *self = (EggPaned *)object;
GtkWidget *child;
child = gtk_widget_get_first_child (GTK_WIDGET (self));
while (child)
{
GtkWidget *next = gtk_widget_get_next_sibling (child);
gtk_widget_unparent (child);
child = next;
}
G_OBJECT_CLASS (egg_paned_parent_class)->dispose (object);
}
static void
egg_paned_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EggPaned *self = EGG_PANED (object);
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_enum (value, self->orientation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
egg_paned_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EggPaned *self = EGG_PANED (object);
switch (prop_id)
{
case PROP_ORIENTATION:
egg_paned_set_orientation (self, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
egg_paned_class_init (EggPanedClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = egg_paned_dispose;
object_class->get_property = egg_paned_get_property;
object_class->set_property = egg_paned_set_property;
widget_class->measure = egg_paned_measure;
widget_class->size_allocate = egg_paned_size_allocate;
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
gtk_widget_class_set_css_name (widget_class, "eggpaned");
}
static void
egg_paned_init (EggPaned *self)
{
self->orientation = GTK_ORIENTATION_HORIZONTAL;
update_orientation (GTK_WIDGET (self), self->orientation);
}
static void
egg_paned_update_handles (EggPaned *self)
{
GtkWidget *child;
g_assert (EGG_IS_PANED (self));
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkWidget *handle;
g_assert (EGG_IS_RESIZER (child));
if ((handle = egg_resizer_get_handle (EGG_RESIZER (child))))
gtk_widget_show (handle);
}
if ((child = gtk_widget_get_last_child (GTK_WIDGET (self))))
{
GtkWidget *handle = egg_resizer_get_handle (EGG_RESIZER (child));
gtk_widget_hide (handle);
}
}
void
egg_paned_remove (EggPaned *self,
GtkWidget *child)
{
GtkWidget *resizer;
g_return_if_fail (EGG_IS_PANED (self));
g_return_if_fail (GTK_IS_WIDGET (child));
resizer = gtk_widget_get_ancestor (child, EGG_TYPE_RESIZER);
g_return_if_fail (resizer != NULL &&
gtk_widget_get_parent (resizer) == GTK_WIDGET (self));
gtk_widget_unparent (resizer);
egg_paned_update_handles (self);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
void
egg_paned_insert (EggPaned *self,
int position,
GtkWidget *child)
{
GtkPositionType pos;
GtkWidget *resizer;
g_return_if_fail (EGG_IS_PANED (self));
g_return_if_fail (GTK_IS_WIDGET (child));
g_return_if_fail (gtk_widget_get_parent (child) == NULL);
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
pos = GTK_POS_LEFT;
else
pos = GTK_POS_TOP;
resizer = egg_resizer_new (pos);
egg_resizer_set_child (EGG_RESIZER (resizer), child);
if (position < 0)
gtk_widget_insert_before (GTK_WIDGET (resizer), GTK_WIDGET (self), NULL);
else if (position == 0)
gtk_widget_insert_after (GTK_WIDGET (resizer), GTK_WIDGET (self), NULL);
else
{
GtkWidget *sibling = gtk_widget_get_first_child (GTK_WIDGET (self));
for (int i = position; i > 0 && sibling != NULL; i--)
sibling = gtk_widget_get_next_sibling (sibling);
gtk_widget_insert_before (GTK_WIDGET (resizer), GTK_WIDGET (self), sibling);
}
egg_paned_update_handles (self);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
void
egg_paned_append (EggPaned *self,
GtkWidget *child)
{
egg_paned_insert (self, -1, child);
}
void
egg_paned_prepend (EggPaned *self,
GtkWidget *child)
{
egg_paned_insert (self, 0, child);
}
void
egg_paned_insert_after (EggPaned *self,
GtkWidget *child,
GtkWidget *sibling)
{
int position = 0;
g_return_if_fail (EGG_IS_PANED (self));
g_return_if_fail (GTK_IS_WIDGET (child));
g_return_if_fail (!sibling || GTK_IS_WIDGET (sibling));
if (sibling == NULL)
{
egg_paned_prepend (self, child);
return;
}
/* TODO: We should reverse insert() to call this */
for (GtkWidget *ancestor = gtk_widget_get_first_child (GTK_WIDGET (self));
ancestor != NULL;
ancestor = gtk_widget_get_next_sibling (ancestor))
{
position++;
if (sibling == ancestor || gtk_widget_is_ancestor (sibling, ancestor))
break;
}
egg_paned_insert (self, position, child);
}
guint
egg_paned_get_n_children (EggPaned *self)
{
guint count = 0;
for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_next_sibling (child))
count++;
return count;
}
GtkWidget *
egg_paned_get_nth_child (EggPaned *self,
guint nth)
{
g_return_val_if_fail (EGG_IS_PANED (self), NULL);
for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self));
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
g_assert (EGG_IS_RESIZER (child));
if (nth == 0)
return egg_resizer_get_child (EGG_RESIZER (child));
nth--;
}
return NULL;
}
static void
egg_paned_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *type)
{
if (GTK_IS_WIDGET (child))
egg_paned_append (EGG_PANED (buildable), GTK_WIDGET (child));
else
g_warning ("Cannot add child of type %s to %s",
G_OBJECT_TYPE_NAME (child),
G_OBJECT_TYPE_NAME (buildable));
}
static void
buildable_iface_init (GtkBuildableIface *iface)
{
iface->add_child = egg_paned_add_child;
}

View File

@ -0,0 +1,40 @@
/* egg-resizer.h
*
* Copyright 2021 Christian Hergert <chergert@redhat.com>
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define EGG_TYPE_RESIZER (egg_resizer_get_type())
G_DECLARE_FINAL_TYPE (EggResizer, egg_resizer, EGG, RESIZER, GtkWidget)
GtkWidget *egg_resizer_new (GtkPositionType position);
GtkPositionType egg_resizer_get_position (EggResizer *self);
void egg_resizer_set_position (EggResizer *self,
GtkPositionType position);
GtkWidget *egg_resizer_get_child (EggResizer *self);
void egg_resizer_set_child (EggResizer *self,
GtkWidget *child);
GtkWidget *egg_resizer_get_handle (EggResizer *self);
G_END_DECLS

View File

@ -0,0 +1,488 @@
/* egg-resizer.c
*
* Copyright 2021 Christian Hergert <chergert@redhat.com>
*
* This file is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include "config.h"
#include "egg-handle-private.h"
#include "egg-resizer-private.h"
#define HANDLE_SIZE 8
struct _EggResizer
{
GtkWidget parent_instance;
EggHandle *handle;
GtkWidget *child;
double drag_orig_size;
double drag_position;
GtkPositionType position : 3;
};
G_DEFINE_TYPE (EggResizer, egg_resizer, GTK_TYPE_WIDGET)
enum {
PROP_0,
PROP_CHILD,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
egg_resizer_drag_begin_cb (EggResizer *self,
double start_x,
double start_y,
GtkGestureDrag *drag)
{
GtkAllocation child_alloc;
GtkAllocation handle_alloc;
g_assert (EGG_IS_RESIZER (self));
g_assert (GTK_IS_GESTURE_DRAG (drag));
if (self->child == NULL)
return;
switch (self->position)
{
case GTK_POS_LEFT:
if (start_x > gtk_widget_get_width (GTK_WIDGET (self)) - HANDLE_SIZE)
goto start_drag;
break;
case GTK_POS_RIGHT:
if (start_x <= HANDLE_SIZE)
goto start_drag;
break;
case GTK_POS_TOP:
if (start_y > gtk_widget_get_height (GTK_WIDGET (self)) - HANDLE_SIZE)
goto start_drag;
break;
case GTK_POS_BOTTOM:
if (start_y <= HANDLE_SIZE)
goto start_drag;
break;
default:
g_assert_not_reached ();
break;
}
deny_sequence:
gtk_gesture_set_state (GTK_GESTURE (drag),
GTK_EVENT_SEQUENCE_DENIED);
return;
start_drag:
gtk_widget_get_allocation (self->child, &child_alloc);
gtk_widget_get_allocation (GTK_WIDGET (self->handle), &handle_alloc);
if (self->position == GTK_POS_LEFT ||
self->position == GTK_POS_RIGHT)
{
self->drag_orig_size = child_alloc.width + handle_alloc.width;
gtk_widget_set_hexpand (self->child, FALSE);
}
else
{
self->drag_orig_size = child_alloc.height + handle_alloc.height;
gtk_widget_set_vexpand (self->child, FALSE);
}
self->drag_position = self->drag_orig_size;
gtk_widget_queue_resize (GTK_WIDGET (self));
}
static void
egg_resizer_drag_update_cb (EggResizer *self,
double offset_x,
double offset_y,
GtkGestureDrag *drag)
{
g_assert (EGG_IS_RESIZER (self));
g_assert (GTK_IS_GESTURE_DRAG (drag));
if (self->position == GTK_POS_LEFT)
self->drag_position = self->drag_orig_size + offset_x;
else if (self->position == GTK_POS_RIGHT)
self->drag_position = gtk_widget_get_width (GTK_WIDGET (self)) - offset_x;
else if (self->position == GTK_POS_TOP)
self->drag_position = self->drag_orig_size + offset_y;
else if (self->position == GTK_POS_BOTTOM)
self->drag_position = gtk_widget_get_height (GTK_WIDGET (self)) - offset_y;
gtk_widget_queue_resize (GTK_WIDGET (self));
}
static void
egg_resizer_drag_end_cb (EggResizer *self,
double offset_x,
double offset_y,
GtkGestureDrag *drag)
{
g_assert (EGG_IS_RESIZER (self));
g_assert (GTK_IS_GESTURE_DRAG (drag));
}
GtkWidget *
egg_resizer_new (GtkPositionType position)
{
EggResizer *self;
self = g_object_new (EGG_TYPE_RESIZER, NULL);
self->position = position;
self->handle = EGG_HANDLE (egg_handle_new (position));
gtk_widget_set_parent (GTK_WIDGET (self->handle), GTK_WIDGET (self));
return GTK_WIDGET (self);
}
static void
egg_resizer_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
EggResizer *self = (EggResizer *)widget;
g_assert (EGG_IS_RESIZER (self));
*minimum = 0;
*natural = 0;
*minimum_baseline = -1;
*natural_baseline = -1;
if (self->child != NULL)
gtk_widget_measure (self->child,
orientation,
for_size,
minimum, natural,
NULL, NULL);
if ((orientation == GTK_ORIENTATION_HORIZONTAL &&
(self->position == GTK_POS_LEFT ||
self->position == GTK_POS_RIGHT)) ||
(orientation == GTK_ORIENTATION_VERTICAL &&
(self->position == GTK_POS_TOP ||
self->position == GTK_POS_BOTTOM)))
{
int handle_min, handle_nat;
if (self->drag_position > *minimum)
*natural = self->drag_position;
else if (self->drag_position < *minimum)
*natural = *minimum;
if (gtk_widget_get_visible (GTK_WIDGET (self->handle)))
{
gtk_widget_measure (GTK_WIDGET (self->handle),
orientation, for_size,
&handle_min, &handle_nat,
NULL, NULL);
*minimum += handle_min;
*natural += handle_nat;
}
}
}
static void
egg_resizer_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
EggResizer *self = (EggResizer *)widget;
GtkOrientation orientation;
GtkAllocation child_alloc;
GtkAllocation handle_alloc;
int handle_min = 0, handle_nat = 0;
g_assert (EGG_IS_RESIZER (self));
if (self->position == GTK_POS_LEFT ||
self->position == GTK_POS_RIGHT)
orientation = GTK_ORIENTATION_HORIZONTAL;
else
orientation = GTK_ORIENTATION_VERTICAL;
if (gtk_widget_get_visible (GTK_WIDGET (self->handle)))
gtk_widget_measure (GTK_WIDGET (self->handle),
orientation,
-1,
&handle_min, &handle_nat,
NULL, NULL);
switch (self->position)
{
case GTK_POS_LEFT:
handle_alloc.x = width - handle_min;
handle_alloc.width = handle_min;
handle_alloc.y = 0;
handle_alloc.height = height;
child_alloc.x = 0;
child_alloc.y = 0;
child_alloc.width = width - handle_min;
child_alloc.height = height;
break;
case GTK_POS_RIGHT:
handle_alloc.x = 0;
handle_alloc.width = handle_min;
handle_alloc.y = 0;
handle_alloc.height = height;
child_alloc.x = handle_min;
child_alloc.y = 0;
child_alloc.width = width - handle_min;
child_alloc.height = height;
break;
case GTK_POS_TOP:
handle_alloc.x = 0;
handle_alloc.width = width;
handle_alloc.y = height - handle_min;
handle_alloc.height = handle_min;
child_alloc.x = 0;
child_alloc.y = 0;
child_alloc.width = width;
child_alloc.height = height - handle_min;
break;
case GTK_POS_BOTTOM:
handle_alloc.x = 0;
handle_alloc.width = width;
handle_alloc.y = 0;
handle_alloc.height = handle_min;
child_alloc.x = 0;
child_alloc.y = handle_min;
child_alloc.width = width;
child_alloc.height = height - handle_min;
break;
default:
g_assert_not_reached ();
}
if (gtk_widget_get_mapped (GTK_WIDGET (self->handle)))
gtk_widget_size_allocate (GTK_WIDGET (self->handle), &handle_alloc, -1);
if (self->child != NULL &&
gtk_widget_get_mapped (self->child))
gtk_widget_size_allocate (self->child, &child_alloc, -1);
}
static void
egg_resizer_compute_expand (GtkWidget *widget,
gboolean *hexpand,
gboolean *vexpand)
{
EggResizer *self = EGG_RESIZER (widget);
if (self->child != NULL)
{
*hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL);
*vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL);
}
else
{
*hexpand = FALSE;
*vexpand = FALSE;
}
}
static void
egg_resizer_dispose (GObject *object)
{
EggResizer *self = (EggResizer *)object;
g_clear_pointer ((GtkWidget **)&self->handle, gtk_widget_unparent);
g_clear_pointer ((GtkWidget **)&self->child, gtk_widget_unparent);
G_OBJECT_CLASS (egg_resizer_parent_class)->dispose (object);
}
static void
egg_resizer_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EggResizer *self = EGG_RESIZER (object);
switch (prop_id)
{
case PROP_CHILD:
g_value_set_object (value, egg_resizer_get_child (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
egg_resizer_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EggResizer *self = EGG_RESIZER (object);
switch (prop_id)
{
case PROP_CHILD:
egg_resizer_set_child (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
egg_resizer_class_init (EggResizerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = egg_resizer_dispose;
object_class->get_property = egg_resizer_get_property;
object_class->set_property = egg_resizer_set_property;
widget_class->compute_expand = egg_resizer_compute_expand;
widget_class->measure = egg_resizer_measure;
widget_class->size_allocate = egg_resizer_size_allocate;
properties [PROP_CHILD] =
g_param_spec_object ("child",
"Child",
"Child",
GTK_TYPE_WIDGET,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_set_css_name (widget_class, "eggresizer");
}
static void
egg_resizer_init (EggResizer *self)
{
GtkGesture *gesture;
gesture = gtk_gesture_drag_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
g_signal_connect_object (gesture,
"drag-begin",
G_CALLBACK (egg_resizer_drag_begin_cb),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (gesture,
"drag-update",
G_CALLBACK (egg_resizer_drag_update_cb),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (gesture,
"drag-end",
G_CALLBACK (egg_resizer_drag_end_cb),
self,
G_CONNECT_SWAPPED);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
}
/**
* egg_resizer_get_child:
* @self: a #EggResizer
*
* Gets the child widget of the resizer.
*
* Returns: (transfer none) (nullable): A #GtkWidget or %NULL
*/
GtkWidget *
egg_resizer_get_child (EggResizer *self)
{
g_return_val_if_fail (EGG_IS_RESIZER (self), NULL);
return self->child;
}
void
egg_resizer_set_child (EggResizer *self,
GtkWidget *child)
{
g_return_if_fail (EGG_IS_RESIZER (self));
g_return_if_fail (!child || GTK_IS_WIDGET (child));
if (child == self->child)
return;
g_clear_pointer (&self->child, gtk_widget_unparent);
self->child = child;
if (self->child != NULL)
gtk_widget_insert_before (self->child,
GTK_WIDGET (self),
GTK_WIDGET (self->handle));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILD]);
}
GtkPositionType
egg_resizer_get_position (EggResizer *self)
{
g_return_val_if_fail (EGG_IS_RESIZER (self), 0);
return self->position;
}
void
egg_resizer_set_position (EggResizer *self,
GtkPositionType position)
{
g_return_if_fail (EGG_IS_RESIZER (self));
if (position != self->position)
{
self->position = position;
egg_handle_set_position (self->handle, position);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
}
GtkWidget *
egg_resizer_get_handle (EggResizer *self)
{
g_return_val_if_fail (EGG_IS_RESIZER (self), NULL);
return GTK_WIDGET (self->handle);
}

View File

@ -13,7 +13,11 @@ libsysprof_ui_public_sources = [
]
libsysprof_ui_private_sources = [
'egg-handle.c',
'egg-paned.c',
'egg-resizer.c',
'egg-three-grid.c',
'pointcache.c',
'rectangles.c',
'sysprof-aid.c',