luminate-old/Sources/CGtkWidgets/flow_grid.c

431 lines
12 KiB
C

#include "flow_grid.h"
#include <math.h>
struct _FlowGrid {
GtkWidget parent_instance;
int minimum_size;
int column_spacing;
int row_spacing;
FlowGridJustify justify;
};
enum {
PROP_0,
PROP_MINIMUM_SIZE,
PROP_COLUMN_SPACING,
PROP_ROW_SPACING,
PROP_JUSTIFY,
LAST_PROP
};
static GParamSpec *props[LAST_PROP];
G_DEFINE_TYPE(FlowGrid, flow_grid, GTK_TYPE_WIDGET)
static void
flow_grid_set_property(GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
FlowGrid *self = FLOW_GRID(object);
switch (prop_id) {
case PROP_MINIMUM_SIZE:
flow_grid_set_minimum_size(self, g_value_get_int(value));
break;
case PROP_COLUMN_SPACING:
flow_grid_set_column_spacing(self, g_value_get_int(value));
break;
case PROP_ROW_SPACING:
flow_grid_set_row_spacing(self, g_value_get_int(value));
break;
case PROP_JUSTIFY:
flow_grid_set_justify(self, (FlowGridJustify)g_value_get_int(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
flow_grid_get_property(GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
FlowGrid *self = FLOW_GRID(object);
switch (prop_id) {
case PROP_MINIMUM_SIZE:
g_value_set_int(value, self->minimum_size);
break;
case PROP_COLUMN_SPACING:
g_value_set_int(value, self->column_spacing);
break;
case PROP_ROW_SPACING:
g_value_set_int(value, self->row_spacing);
break;
case PROP_JUSTIFY:
g_value_set_int(value, self->justify);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
flow_grid_dispose(GObject *object)
{
GtkWidget *widget = GTK_WIDGET(object);
GtkWidget *child;
while ((child = gtk_widget_get_first_child(widget)))
gtk_widget_unparent(child);
G_OBJECT_CLASS(flow_grid_parent_class)->dispose(object);
}
static int
flow_grid_count_visible_children(GtkWidget *widget)
{
int count = 0;
GtkWidget *child;
for (child = gtk_widget_get_first_child(widget);
child != NULL;
child = gtk_widget_get_next_sibling(child)) {
if (gtk_widget_should_layout(child))
count++;
}
return count;
}
static void
flow_grid_collect_visible_children(GtkWidget *widget, GtkWidget **children, int max)
{
int i = 0;
GtkWidget *child;
for (child = gtk_widget_get_first_child(widget);
child != NULL && i < max;
child = gtk_widget_get_next_sibling(child)) {
if (gtk_widget_should_layout(child))
children[i++] = child;
}
}
static void
flow_grid_compute_layout(GtkWidget *widget, int for_size,
int *out_num_columns, int *out_num_rows,
double *out_column_width)
{
FlowGrid *self = FLOW_GRID(widget);
int n_children = flow_grid_count_visible_children(widget);
if (n_children == 0) {
*out_num_columns = 1;
*out_num_rows = 0;
*out_column_width = (double)self->minimum_size;
return;
}
int num_columns = (for_size + self->column_spacing)
/ (self->minimum_size + self->column_spacing);
num_columns = num_columns < 1 ? 1 : num_columns;
num_columns = num_columns > n_children ? n_children : num_columns;
double column_width;
if (n_children == num_columns)
column_width = (double)self->minimum_size;
else {
column_width = (for_size - self->column_spacing * (num_columns - 1))
/ (double)num_columns;
if (column_width < self->minimum_size)
column_width = self->minimum_size;
}
int num_rows = (int)ceil((double)n_children / num_columns);
*out_num_columns = num_columns;
*out_num_rows = num_rows;
*out_column_width = column_width;
}
static GtkSizeRequestMode
flow_grid_get_request_mode(GtkWidget *widget)
{
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}
static void
flow_grid_measure(GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
FlowGrid *self = FLOW_GRID(widget);
if (minimum_baseline)
*minimum_baseline = -1;
if (natural_baseline)
*natural_baseline = -1;
if (for_size <= 0) {
if (minimum) *minimum = 0;
if (natural) *natural = 0;
return;
}
int n_children = flow_grid_count_visible_children(widget);
if (n_children == 0) {
if (minimum) *minimum = 0;
if (natural) *natural = 0;
return;
}
GtkWidget **children = g_newa(GtkWidget*, n_children);
flow_grid_collect_visible_children(widget, children, n_children);
int num_columns, num_rows;
double column_width;
flow_grid_compute_layout(widget, for_size, &num_columns, &num_rows, &column_width);
int *min_row_heights = g_newa(int, num_rows);
int *nat_row_heights = g_newa(int, num_rows);
memset(min_row_heights, 0, num_rows * sizeof(int));
memset(nat_row_heights, 0, num_rows * sizeof(int));
for (int i = 0; i < n_children; i++) {
int cw = (int)(column_width + 0.5);
if (cw < 0) cw = -1;
int child_min = 0, child_nat = 0;
gtk_widget_measure(children[i], orientation, cw,
&child_min, &child_nat, NULL, NULL);
int row = i / num_columns;
if (child_min > min_row_heights[row])
min_row_heights[row] = child_min;
if (child_nat > nat_row_heights[row])
nat_row_heights[row] = child_nat;
}
int spacing = self->row_spacing * (num_rows - 1);
int total_min = 0, total_nat = 0;
for (int r = 0; r < num_rows; r++) {
total_min += min_row_heights[r];
total_nat += nat_row_heights[r];
}
total_min += spacing;
total_nat += spacing;
if (minimum) *minimum = total_min;
if (natural) *natural = total_nat > total_min ? total_nat : total_min;
}
static void
flow_grid_size_allocate(GtkWidget *widget,
int width, int height, int baseline)
{
FlowGrid *self = FLOW_GRID(widget);
int n_children = flow_grid_count_visible_children(widget);
if (n_children == 0)
return;
GtkWidget **children = g_newa(GtkWidget*, n_children);
flow_grid_collect_visible_children(widget, children, n_children);
int num_columns, num_rows;
double column_width;
flow_grid_compute_layout(widget, width, &num_columns, &num_rows, &column_width);
int *row_heights = g_newa(int, num_rows);
memset(row_heights, 0, num_rows * sizeof(int));
for (int i = 0; i < n_children; i++) {
int cw = (int)(column_width + 0.5);
if (cw < 0) cw = -1;
int child_min = 0;
gtk_widget_measure(children[i], GTK_ORIENTATION_VERTICAL, cw,
&child_min, NULL, NULL, NULL);
int row = i / num_columns;
if (child_min > row_heights[row])
row_heights[row] = child_min;
}
for (int i = 0; i < n_children; i++) {
int idx = i;
int column = idx % num_columns;
int row = idx / num_columns;
int x_offset = 0;
if (row + 1 == num_rows) {
int n_in_last_row = n_children % num_columns;
if (n_in_last_row == 0)
n_in_last_row = num_columns;
double row_width = column_width * n_in_last_row
+ self->column_spacing * (n_in_last_row - 1);
double empty_space = width - row_width;
switch (self->justify) {
case FLOW_GRID_JUSTIFY_CENTER:
x_offset = (int)(empty_space / 2.0 + 0.5);
break;
case FLOW_GRID_JUSTIFY_END:
x_offset = (int)(empty_space + 0.5);
break;
default:
x_offset = 0;
break;
}
}
int y_offset = 0;
for (int r = 0; r < row; r++)
y_offset += row_heights[r];
int x = x_offset
+ (int)(column * column_width + 0.5)
+ column * self->column_spacing;
int y = y_offset + row * self->row_spacing;
int child_w = (int)(column_width + 0.5);
int child_h = row_heights[row];
GtkAllocation alloc = {
.x = x,
.y = y,
.width = child_w,
.height = child_h,
};
gtk_widget_size_allocate(children[i], &alloc, -1);
}
}
static void
flow_grid_class_init(FlowGridClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
gobject_class->set_property = flow_grid_set_property;
gobject_class->get_property = flow_grid_get_property;
gobject_class->dispose = flow_grid_dispose;
widget_class->get_request_mode = flow_grid_get_request_mode;
widget_class->measure = flow_grid_measure;
widget_class->size_allocate = flow_grid_size_allocate;
props[PROP_MINIMUM_SIZE] = g_param_spec_int(
"minimum-size", NULL, NULL,
1, G_MAXINT, 200,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_COLUMN_SPACING] = g_param_spec_int(
"column-spacing", NULL, NULL,
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_ROW_SPACING] = g_param_spec_int(
"row-spacing", NULL, NULL,
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_JUSTIFY] = g_param_spec_int(
"justify", NULL, NULL,
0, 2, 0,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties(gobject_class, LAST_PROP, props);
gtk_widget_class_set_css_name(widget_class, "flowgrid");
gtk_widget_class_set_accessible_role(widget_class, GTK_ACCESSIBLE_ROLE_GROUP);
}
static void
flow_grid_init(FlowGrid *self)
{
self->minimum_size = 200;
self->column_spacing = 0;
self->row_spacing = 0;
self->justify = FLOW_GRID_JUSTIFY_START;
}
GtkWidget *
flow_grid_new(int minimum_size, int column_spacing, int row_spacing)
{
return g_object_new(FLOW_TYPE_GRID,
"minimum-size", minimum_size,
"column-spacing", column_spacing,
"row-spacing", row_spacing,
NULL);
}
void
flow_grid_set_minimum_size(FlowGrid *self, int size)
{
g_return_if_fail(FLOW_IS_GRID(self));
if (self->minimum_size == size)
return;
self->minimum_size = size;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_MINIMUM_SIZE]);
gtk_widget_queue_resize(GTK_WIDGET(self));
}
int
flow_grid_get_minimum_size(FlowGrid *self)
{
g_return_val_if_fail(FLOW_IS_GRID(self), 200);
return self->minimum_size;
}
void
flow_grid_set_column_spacing(FlowGrid *self, int spacing)
{
g_return_if_fail(FLOW_IS_GRID(self));
if (self->column_spacing == spacing)
return;
self->column_spacing = spacing;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_COLUMN_SPACING]);
gtk_widget_queue_resize(GTK_WIDGET(self));
}
int
flow_grid_get_column_spacing(FlowGrid *self)
{
g_return_val_if_fail(FLOW_IS_GRID(self), 0);
return self->column_spacing;
}
void
flow_grid_set_row_spacing(FlowGrid *self, int spacing)
{
g_return_if_fail(FLOW_IS_GRID(self));
if (self->row_spacing == spacing)
return;
self->row_spacing = spacing;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ROW_SPACING]);
gtk_widget_queue_resize(GTK_WIDGET(self));
}
int
flow_grid_get_row_spacing(FlowGrid *self)
{
g_return_val_if_fail(FLOW_IS_GRID(self), 0);
return self->row_spacing;
}
void
flow_grid_set_justify(FlowGrid *self, FlowGridJustify justify)
{
g_return_if_fail(FLOW_IS_GRID(self));
if (self->justify == justify)
return;
self->justify = justify;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_JUSTIFY]);
gtk_widget_queue_resize(GTK_WIDGET(self));
}
FlowGridJustify
flow_grid_get_justify(FlowGrid *self)
{
g_return_val_if_fail(FLOW_IS_GRID(self), FLOW_GRID_JUSTIFY_START);
return self->justify;
}