Thanks both @zbrown and @ebassi for your answers. Unfortunately I am still confused. Therefore I will try to be more precise.
Back then (GTK3) I needed something quite similar to GtkFlowBox
, but without aligning things in columns – I really wanted a “pure” flow of widgets. So I created my own GtkFlow
custom widget, derived from GtkOverlay
. I will post the code below. The result of the test will look like the following picture:
(please check also the effect of resizing the window)
The GTK3 code was also a draft, because I knew I would eventually have to port it to GTK4 – but it was a functioning draft.
Here is the code:
gtk_flow.h:
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
#ifndef __GTK_FLOW_H__
#define __GTK_FLOW_H__
#include <glib.h>
#include <glib-object.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define GTK_FLOW_TYPE \
(gtk_flow_get_type())
#define GTK_FLOW(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_FLOW_TYPE, GtkFlow))
#define GTK_FLOW_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), GTK_FLOW_TYPE, GtkFlowClass))
#define IS_GTK_FLOW(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_FLOW_TYPE))
#define IS_GTK_FLOW_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass), GTK_FLOW_TYPE))
#define GTK_FLOW_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FLOW, GtkFlowClass))
typedef struct _GtkFlow GtkFlow;
typedef struct _GtkFlowClass GtkFlowClass;
extern GType gtk_flow_get_type (void);
extern GtkWidget * gtk_flow_new (void);
extern void gtk_flow_add_widget (
GtkFlow * flow,
GtkWidget * widget
);
extern void gtk_flow_remove_widget (
GtkFlow * flow,
GtkWidget * widget
);
void gtk_flow_set_underlay (
GtkFlow * flow,
GtkWidget * widget
);
G_END_DECLS
#endif
gtk_flow.c:
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
#include <gtk/gtk.h>
#include "gtk_flow.h"
typedef struct {
GtkWidget * widget;
GdkRectangle allocation;
} GtkFlowAllocatedChild;
enum {
TEST_SIGNAL = 0,
LAST_SIGNAL
};
struct _GtkFlow {
GtkOverlay the_flow;
GtkAlign valign;
GtkAlign halign;
GtkRequisition flow_shape;
GList * allocated_children;
};
struct _GtkFlowClass {
GtkOverlayClass parent_class;
void (* gtk_flow) (GtkFlow * flow);
void (* set_underlay) (GtkFlow * flow, GtkWidget * widget);
};
static guint SIGNALS[LAST_SIGNAL] = { 0 };
typedef enum {
PROP_HALIGN = 1,
PROP_VALIGN,
N_PROPERTIES
} GtkFlowProperty;
static GParamSpec * obj_properties[N_PROPERTIES] = { NULL };
static void gtk_flow_set_property (
GObject * object,
guint property_id,
const GValue * value,
GParamSpec * pspec
) {
GtkFlow * self = GTK_FLOW(object);
switch ((GtkFlowProperty) property_id) {
case PROP_HALIGN:
self->halign = g_value_get_enum(value);
g_print ("halign: %s\n", self->halign);
break;
case PROP_VALIGN:
self->valign = g_value_get_enum(value);
g_print ("valign: %s\n", self->valign);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void gtk_flow_get_property (
GObject * object,
guint property_id,
GValue * value,
GParamSpec * pspec
) {
GtkFlow * self = GTK_FLOW(object);
switch ((GtkFlowProperty) property_id) {
case PROP_HALIGN:
g_value_set_enum(value, self->halign);
break;
case PROP_VALIGN:
g_value_set_enum(value, self->valign);
break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean pos_callback (
GtkFlow * flow,
GtkWidget * child,
GdkRectangle * allocation,
gpointer v_unused_data
) {
GList * iter;
for (
iter = flow->allocated_children;
iter && ((GtkFlowAllocatedChild *) iter->data)->widget != child;
iter = iter->next
);
if (!iter) {
return FALSE;
}
GdkRectangle last_pos =
iter->prev ?
((GtkFlowAllocatedChild *) iter->prev->data)->allocation
:
(GdkRectangle) { 0 };
GtkRequisition child_min_size;
GtkRequisition parent_min_size;
gint
parent_w = gtk_widget_get_allocated_width(GTK_WIDGET(flow)),
parent_h = gtk_widget_get_allocated_height(GTK_WIDGET(flow)),
child_w = gtk_widget_get_allocated_width(child),
child_h = gtk_widget_get_allocated_height(child);
gtk_widget_get_preferred_size(child, &child_min_size, NULL);
gtk_widget_get_preferred_size(GTK_WIDGET(flow), &parent_min_size, NULL);
allocation->width = child_min_size.width;
allocation->height = child_min_size.height;
allocation->x =
last_pos.x + last_pos.width + child_w > parent_w ?
0
:
last_pos.x + last_pos.width;
allocation->y =
last_pos.x + last_pos.width + child_w > parent_w ?
last_pos.y + last_pos.height
:
last_pos.y;
((GtkFlowAllocatedChild *) iter->data)->allocation = *allocation;
if (iter->prev) {
if (allocation->width > flow->flow_shape.width) {
flow->flow_shape.width = allocation->width;
gtk_widget_set_size_request(
GTK_WIDGET(flow),
flow->flow_shape.width,
parent_min_size.height
);
}
if (allocation->y + allocation->height > flow->flow_shape.height) {
flow->flow_shape.height = allocation->y + allocation->height;
}
gtk_widget_set_size_request(
GTK_WIDGET(flow),
flow->flow_shape.width,
flow->flow_shape.height
);
} else {
flow->flow_shape.width = allocation->width;
flow->flow_shape.height = allocation->height;
}
g_signal_emit(G_OBJECT(flow), SIGNALS[TEST_SIGNAL], 0);
return TRUE;
}
GtkWidget * gtk_flow_new () {
return GTK_WIDGET(g_object_new(gtk_flow_get_type(), NULL));
}
void gtk_flow_remove_widget (
GtkFlow * flow,
GtkWidget * widget
) {
// DO SOMETHING
}
void gtk_flow_set_underlay (
GtkFlow * flow,
GtkWidget * widget
) {
GTK_FLOW_GET_CLASS(flow)->set_underlay(flow, widget);
}
void gtk_flow_add_widget (
GtkFlow * flow,
GtkWidget * widget
) {
GtkFlowAllocatedChild * child_allocation = g_new(GtkFlowAllocatedChild, 1);
*child_allocation = (GtkFlowAllocatedChild) {
.widget = widget,
.allocation = { 0 }
};
flow->allocated_children = g_list_append(
flow->allocated_children,
child_allocation
);
gtk_overlay_add_overlay(GTK_OVERLAY(flow), widget);
}
static void gtk_flow_clear (
GtkFlow * flow
) {
g_list_free_full(flow->allocated_children, g_free);
}
static void gtk_flow_free_memory (GObject * object) {
g_list_free_full(g_steal_pointer(>K_FLOW(object)->allocated_children), g_free);
}
static void gtk_flow_class_init (
GtkFlowClass * klass
) {
GObjectClass * object_class;
GtkWidgetClass * widget_class;
GtkContainerClass * parent_class;
object_class = (GObjectClass*) klass;
widget_class = (GtkWidgetClass*) klass;
parent_class = (GtkContainerClass *) klass;
klass->set_underlay = (void (*) (GtkFlow *, GtkWidget *)) parent_class->add;
object_class->dispose = gtk_flow_free_memory;
parent_class->add = (void (*) (GtkContainer *, GtkWidget *)) gtk_flow_add_widget;
parent_class->remove = (void (*) (GtkContainer *, GtkWidget *)) gtk_flow_remove_widget;
object_class->set_property = gtk_flow_set_property;
object_class->get_property = gtk_flow_get_property;
obj_properties[PROP_HALIGN] = g_param_spec_enum(
"halign",
"Horizontal Alignment",
"How to position in extra horizontal space",
GTK_TYPE_ALIGN,
GTK_ALIGN_START,
G_PARAM_READWRITE
);
obj_properties[PROP_VALIGN] = g_param_spec_enum(
"valign",
"Vertical Alignment",
"How to position in extra vertical space",
GTK_TYPE_ALIGN,
GTK_ALIGN_START,
G_PARAM_READWRITE
);
g_object_class_install_properties(
object_class,
N_PROPERTIES,
obj_properties
);
g_signal_override_class_handler(
"get-child-position",
GTK_TYPE_OVERLAY,
G_CALLBACK(pos_callback)
);
// TODO: add a GList and two GdkRectangle to parameters
SIGNALS[TEST_SIGNAL] = g_signal_new(
"test-signal",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkFlowClass, gtk_flow),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0
);
}
static void gtk_flow_init (
GtkFlow * flow
) {
flow->allocated_children = NULL;
}
GType gtk_flow_get_type (void) {
static GType flow_type = 0;
if (!flow_type) {
static const GTypeInfo flow_info = {
sizeof(GtkFlowClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gtk_flow_class_init,
(GClassFinalizeFunc) NULL,
(gconstpointer) NULL,
sizeof(GtkFlow),
0,
(GInstanceInitFunc) gtk_flow_init,
(const GTypeValueTable *) NULL
};
flow_type = g_type_register_static(GTK_TYPE_OVERLAY, "GtkFlow", &flow_info, 0);
}
return flow_type;
}
test.c:
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
#include <gtk/gtk.h>
#include "gtk_flow.h"
static void activate (
GtkApplication * app,
gpointer user_data
) {
GtkWidget
* const window = gtk_application_window_new(app),
* const my_gtk_flow = gtk_flow_new();
gtk_window_set_title(GTK_WINDOW(window), "Window");
gtk_window_set_default_size(GTK_WINDOW(window), 200, 200);
gtk_widget_set_valign(GTK_WIDGET(my_gtk_flow), GTK_ALIGN_FILL);
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("One"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Two"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Three"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Four"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Five"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Six"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Seven"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Eight"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Nine"));
gtk_flow_add_widget(GTK_FLOW(my_gtk_flow), gtk_label_new("Ten"));
gtk_container_add(GTK_CONTAINER(window), my_gtk_flow);
gtk_widget_show_all(window);
}
int main (
int argc,
char ** argv
) {
int status;
GtkApplication * app = gtk_application_new(
"org.gtk.example",
G_APPLICATION_DEFAULT_FLAGS
);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}
So the question is: How can I create my GtkFlow
custom widget using GTK4?