Compare commits

...

22 Commits

Author SHA1 Message Date
Benjamin Otte
1ba2ef82f3 canvas: scale(-1) the widgets when allocation size is negative
Just flip the widgets, because hey, that makes planarity work!
2022-07-08 17:33:58 +02:00
Benjamin Otte
bcd04595dd canvas: Use a signal for compute-bounds
Profiling indicates that this is about 30% slower for this chunk of
code, but considering that it bumped time taken in the Puzzle demo (with
40.000 pieces) from 18% to 22%, it doesn't seem too relevant.

The Puzzle demo does nothing but placing items and drawing them after
all.
2022-07-08 17:33:58 +02:00
Benjamin Otte
ad2f4a27be canvas: Completely rework bounds computation
Instead of using a descriptive language to describe how to compute the
bounds, just set a vfunc and let the application provide a function.

That saves a lot of code - but makes the canvas rely on user code.
2022-07-08 07:29:09 +02:00
Benjamin Otte
fd4a717dda demos: Add the planarity game
See https://en.wikipedia.org/wiki/Planarity for what it is.

Or install gplanarity if your distro is into old-school apps that use
GTK 2.7.2 (or newer).
2022-07-05 07:54:24 +02:00
Benjamin Otte
3adc287703 canvas: Give variable vectors a name
And use it when printing.
2022-07-05 07:54:24 +02:00
Benjamin Otte
290d6b6de4 canvas: Add gtk_canvas_vector_to_string()
For debugging for now, but maybe I'm gonna parse this later.
2022-07-05 07:54:24 +02:00
Benjamin Otte
384a8730df canvas: Make origin a GtkCanvasVector
That way we can use it as a variable and properly track things with it.
2022-07-05 07:54:24 +02:00
Benjamin Otte
366ab8ca7c canvas: CanvasSize => CanvasVector
unify the point and size objects.
2022-07-05 07:38:32 +02:00
Benjamin Otte
068d73f3ae canvas: CanvasPoint => CanvasVector
Rename GtkCanvasPoint to GtkCanvasVector.
2022-07-05 07:38:32 +02:00
Benjamin Otte
7f36e3619d canvas: Rename GtkCanvasVec2 to GtkCanvasVector
I want to export it as public API, so better choose a nice name.

I also want to split the public API from the implementation details, so
this file goes to gtkcanvasvectorimpl.c for now.
2022-07-05 07:38:27 +02:00
Benjamin Otte
eaa9fddfb0 canvas: Change the way boxes work
Instead of normalized rectangles, make eval() return unnormalized ones.

This means that we need to normalize before allocating - but it also
means we can scale(-1) the widget if we want to. That is not implemented
yet, but we can.
2022-07-05 07:37:56 +02:00
Benjamin Otte
e848ead629 canvas: Add variables to canvasitems
This allows referencing the actual allocation as well as the bounds.
2022-07-05 07:37:56 +02:00
Benjamin Otte
9eedd3f5ac canvas: Redo points and sizes
Instead of lots of special cases, just have 3 types:
* sums
* constants
* variables

And implement it in a new GtkCanvasVec2 struct and use it for points and
sizes.

Simplifies te code a lot.

Requires some changes though so that now all variables are
preinitialized instead of queried on demand.
2022-07-05 07:37:47 +02:00
Benjamin Otte
d329c904b8 canvas: Add gtk_canvas_lookup_item() 2022-07-04 08:26:31 +02:00
Benjamin Otte
1d5f54ee96 canvas: Handle oversized widgets properly
If a widget needs more size than given via its bounds, expand it
according to its origin.
2022-07-04 08:26:31 +02:00
Benjamin Otte
35534b7143 canvasbox: Add getters
Since the box is no longer specialized, it's easy to query it.
Make that happen.
2022-07-04 08:26:31 +02:00
Benjamin Otte
5ce5ab4bb6 canvasbox: Get rid of specializing
A box is a point, a size and an origin. All the specializing shall
happen in size and point.
2022-07-04 08:26:31 +02:00
Benjamin Otte
143fd737a0 demos: Add a puzzle demo 2022-06-29 23:53:18 +02:00
Benjamin Otte
8366ba8c23 gtk-demo: Fix typo in comment 2022-06-29 23:53:18 +02:00
Benjamin Otte
c2825317c4 canvas: Add gtk_canvas_get_viewport_size()
A meek attempt at exporting numbers into the sizing magic.

Update the demo to really center the label now
2022-06-29 23:53:18 +02:00
Benjamin Otte
0ff8c77568 demos: Add a Canvas Hello World demo 2022-06-29 23:53:18 +02:00
Benjamin Otte
0e49a24aec Add GtkCanvas
A canvas is awidget that allows placing widgets onto the canvas using
sophisticated relationships expressed via points, sizes and boxes.

This is all very experimental.
2022-06-29 23:53:18 +02:00
17 changed files with 2001 additions and 1 deletions

View File

@@ -0,0 +1,104 @@
/* Canvas/Intro
*
* GtkCanvas is a very powerful canvas widget. Here is
* a simple Hello World demo to get accustomed to how
* it works.
*/
#include <gtk/gtk.h>
#define WIDTH 400
#define HEIGHT 300
static gboolean
center_item (GtkCanvasItem *ci,
GtkCanvasBox *out_box,
gpointer unused)
{
const GtkCanvasBox *viewport;
GtkWidget *widget;
int width, height;
/* We need to check if the viewport is available.
* If the canvas was scrolling, it might not be avaiable yet.
*/
viewport = gtk_canvas_get_viewport (gtk_canvas_item_get_canvas (ci));
if (viewport == NULL)
return FALSE;
/* Measure the widget min for min so that it will line-break. */
widget = gtk_canvas_item_get_widget (ci);
gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, &width, NULL, NULL, NULL);
gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, width, &height, NULL, NULL, NULL);
/* Initialize the bounds for this widget:
* - the point is the center of the canvas' viewport
* - the size is the one we just computed
* - we want the origin point to be at the center
*/
gtk_canvas_box_init (out_box,
viewport->size.width * 0.5,
viewport->size.height * 0.5,
0, 0,
0.5, 0.5);
return TRUE;
}
static void
bind_item (GtkListItemFactory *factory,
GtkCanvasItem *ci)
{
gtk_canvas_item_set_widget (ci, gtk_canvas_item_get_item (ci));
/* Set a function to compute the position */
g_signal_connect (ci, "compute-bounds", G_CALLBACK (center_item), NULL);
}
GtkWidget *
do_canvas_intro (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *canvas, *widget;
GListStore *store;
GtkListItemFactory *factory;
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_default_size (GTK_WINDOW (window), WIDTH, HEIGHT);
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
/* GtkCanvas manages its items using an external list.
* We do a very simple thing and put the widgets in the list
* that the canvas should display.
*/
store = g_list_store_new (GTK_TYPE_WIDGET);
widget = gtk_label_new ("Hello World");
gtk_label_set_wrap (GTK_LABEL (widget), TRUE);
g_list_store_append (store, widget);
/* GtkCanvas maps the items from the list to the canvas using factories.
* Set up a simple factory here that just maps the widget directly
* onto the canvas.
*/
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
/* Create the canvas.
* We hand it the factory and the model, and then everything happens by itself.
*/
canvas = gtk_canvas_new (G_LIST_MODEL (store), factory);
gtk_window_set_child (GTK_WINDOW (window), canvas);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,330 @@
/* Canvas/Planarity
*
* This demonstrates how the canvas can be used to display different
* types of data (vertices and edges) and how to operate on them.
*/
#include <gtk/gtk.h>
#define GTK_TYPE_DIAGONAL_LINE (gtk_diagonal_line_get_type ())
G_DECLARE_FINAL_TYPE (GtkDiagonalLine, gtk_diagonal_line, GTK, DIAGONAL_LINE, GtkWidget)
struct _GtkDiagonalLine
{
GtkWidget parent_instance;
};
struct _GtkDiagonalLineClass
{
GtkWidgetClass parent_class;
};
static void
gtk_diagonal_line_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
const float line_width = 6;
const int width = gtk_widget_get_width (widget);
const int height = gtk_widget_get_height (widget);
const double length = sqrt (width * width + height * height);
GskRoundedRect clip;
GdkRGBA color;
gtk_snapshot_save (snapshot);
gtk_snapshot_rotate (snapshot, atan2 (height, width) * 180 / G_PI);
gsk_rounded_rect_init_from_rect (&clip,
&GRAPHENE_RECT_INIT (
-0.5f * line_width,
-0.5f * line_width,
length + line_width,
line_width),
0.5f * line_width);
gtk_snapshot_push_rounded_clip (snapshot, &clip);
gtk_style_context_get_color (gtk_widget_get_style_context (widget), &color);
gtk_snapshot_append_color (snapshot,
&color,
&clip.bounds);
gtk_snapshot_pop (snapshot);
gtk_snapshot_restore (snapshot);
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE (GtkDiagonalLine, gtk_diagonal_line, GTK_TYPE_WIDGET)
/* Here's the boilerplate for the GObject declaration.
*/
static void
gtk_diagonal_line_class_init (GtkDiagonalLineClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
widget_class->snapshot = gtk_diagonal_line_snapshot;
}
static void
gtk_diagonal_line_init (GtkDiagonalLine *self)
{
}
static GtkWidget *
gtk_diagonal_line_new (void)
{
return g_object_new (GTK_TYPE_DIAGONAL_LINE, NULL);
}
typedef struct _PlanarityVertex PlanarityVertex;
struct _PlanarityVertex
{
GObject parent_instance;
GtkOrigin position;
};
#define PLANARITY_TYPE_VERTEX (planarity_vertex_get_type ())
G_DECLARE_FINAL_TYPE (PlanarityVertex, planarity_vertex, PLANARITY, VERTEX, GObject);
G_DEFINE_TYPE (PlanarityVertex, planarity_vertex, G_TYPE_OBJECT);
static void
planarity_vertex_class_init (PlanarityVertexClass *klass)
{
}
static void
planarity_vertex_init (PlanarityVertex *self)
{
}
static PlanarityVertex *
planarity_vertex_new (float horizontal,
float vertical)
{
PlanarityVertex *self;
self = g_object_new (PLANARITY_TYPE_VERTEX, NULL);
self->position.horizontal = horizontal;
self->position.vertical = vertical;
return self;
}
typedef struct _PlanarityEdge PlanarityEdge;
struct _PlanarityEdge
{
GObject parent_instance;
PlanarityVertex *from;
PlanarityVertex *to;
};
#define PLANARITY_TYPE_EDGE (planarity_edge_get_type ())
G_DECLARE_FINAL_TYPE (PlanarityEdge, planarity_edge, PLANARITY, EDGE, GObject);
G_DEFINE_TYPE (PlanarityEdge, planarity_edge, G_TYPE_OBJECT);
static void
planarity_edge_class_init (PlanarityEdgeClass *klass)
{
}
static void
planarity_edge_init (PlanarityEdge *self)
{
}
static PlanarityEdge *
planarity_edge_new (PlanarityVertex *from,
PlanarityVertex *to)
{
PlanarityEdge *edge;
edge = g_object_new (PLANARITY_TYPE_EDGE, NULL);
edge->from = g_object_ref (from);
edge->to = g_object_ref (to);
return edge;
}
static gboolean
set_vertex_bounds (GtkCanvasItem *ci,
GtkCanvasBox *out_box,
gpointer user_data)
{
PlanarityVertex *vertex = gtk_canvas_item_get_item (ci);
const GtkCanvasBox *viewport;
viewport = gtk_canvas_get_viewport (gtk_canvas_item_get_canvas (ci));
if (viewport == NULL)
return FALSE;
gtk_canvas_box_init (out_box,
viewport->size.width * vertex->position.horizontal,
viewport->size.height * vertex->position.vertical,
0, 0,
vertex->position.horizontal,
vertex->position.vertical);
return TRUE;
}
static void
move_vertex (GtkGestureDrag *gesture,
double x,
double y,
GtkCanvasItem *ci)
{
GtkCanvas *canvas = gtk_canvas_item_get_canvas (ci);
PlanarityVertex *vertex = gtk_canvas_item_get_item (ci);
x /= gtk_widget_get_width (GTK_WIDGET (canvas));
y /= gtk_widget_get_height (GTK_WIDGET (canvas));
vertex->position.horizontal = CLAMP (vertex->position.horizontal + x, 0, 1);
vertex->position.vertical = CLAMP (vertex->position.vertical + y, 0, 1);
gtk_canvas_item_invalidate_bounds (ci);
}
static gboolean
set_edge_bounds (GtkCanvasItem *ci,
GtkCanvasBox *out_box,
gpointer user_data)
{
PlanarityEdge *edge = gtk_canvas_item_get_item (ci);
GtkCanvas *canvas = gtk_canvas_item_get_canvas (ci);
GtkCanvasItem *from_item, *to_item;
const GtkCanvasBox *from_box, *to_box;
graphene_rect_t from_rect, to_rect;
graphene_point_t from_center, to_center;
from_item = gtk_canvas_lookup_item (canvas, edge->from);
to_item = gtk_canvas_lookup_item (canvas, edge->to);
from_box = gtk_canvas_item_get_allocation (from_item);
to_box = gtk_canvas_item_get_allocation (to_item);
if (from_box == NULL || to_box == NULL)
return FALSE;
gtk_canvas_box_to_rect (from_box, &from_rect);
gtk_canvas_box_to_rect (to_box, &to_rect);
graphene_rect_get_center (&from_rect, &from_center);
graphene_rect_get_center (&to_rect, &to_center);
gtk_canvas_box_init (out_box,
from_center.x, from_center.y,
to_center.x - from_center.x,
to_center.y - from_center.y,
0, 0);
return TRUE;
}
static void
bind_item (GtkListItemFactory *factory,
GtkCanvasItem *ci)
{
gpointer item;
item = gtk_canvas_item_get_item (ci);
if (PLANARITY_IS_VERTEX (item))
{
GtkWidget *widget;
GtkGesture *gesture;
widget = gtk_image_new_from_icon_name ("media-record-symbolic");
gtk_image_set_icon_size (GTK_IMAGE (widget), GTK_ICON_SIZE_LARGE);
gesture = gtk_gesture_drag_new ();
g_signal_connect (gesture, "drag-update", G_CALLBACK (move_vertex), ci);
g_signal_connect (gesture, "drag-end", G_CALLBACK (move_vertex), ci);
gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (gesture));
gtk_canvas_item_set_widget (ci, widget);
g_signal_connect (ci, "compute-bounds", G_CALLBACK (set_vertex_bounds), NULL);
}
else if (PLANARITY_IS_EDGE (item))
{
gtk_canvas_item_set_widget (ci, gtk_diagonal_line_new ());
g_signal_connect (ci, "compute-bounds", G_CALLBACK (set_edge_bounds), NULL);
}
}
static GListModel *
create_model (void)
{
GListStore *result, *vertices, *edges;
guint n = 10;
guint i, j;
vertices = g_list_store_new (PLANARITY_TYPE_VERTEX);
edges = g_list_store_new (PLANARITY_TYPE_EDGE);
for (i = 0; i < n; i++)
{
PlanarityVertex *vertex = planarity_vertex_new (g_random_double (), g_random_double ());
g_list_store_append (vertices, vertex);
for (j = 0; j < i; j++)
{
PlanarityEdge *edge;
PlanarityVertex *other;
if (g_random_boolean ())
continue;
other = g_list_model_get_item (G_LIST_MODEL (vertices), j);
edge = planarity_edge_new (vertex, other);
g_object_unref (other);
g_list_store_append (edges, edge);
g_object_unref (edge);
}
g_object_unref (vertex);
}
result = g_list_store_new (G_TYPE_LIST_MODEL);
/* put edges before vertices due to staking order */
g_list_store_append (result, edges);
g_object_unref (edges);
g_list_store_append (result, vertices);
g_object_unref (vertices);
return G_LIST_MODEL (gtk_flatten_list_model_new (G_LIST_MODEL (result)));
}
GtkWidget *
do_canvas_planarity (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *canvas;
GListModel *model;
GtkListItemFactory *factory;
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
model = create_model ();
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
/* Create the canvas.
* We hand it the factory and the model, and then everything happens by itself.
*/
canvas = gtk_canvas_new (model, factory);
gtk_window_set_child (GTK_WINDOW (window), canvas);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,136 @@
/* Canvas/Intro
*
* GtkCanvas is a very powerful canvas widget. Here is
* a simple Hello World demo to get accustomed to how
* it works.
*/
#include <gtk/gtk.h>
#include "puzzlepiece.h"
static gboolean
set_position_from_origin (GtkCanvasItem *ci,
GtkCanvasBox *out_box,
gpointer user_data)
{
GtkOrigin *origin = g_object_get_data (G_OBJECT (ci), "position");
const GtkCanvasBox *viewport = gtk_canvas_get_viewport (gtk_canvas_item_get_canvas (ci));
if (viewport == NULL)
return FALSE;
gtk_canvas_box_init (out_box,
viewport->size.width * origin->horizontal,
viewport->size.height * origin->vertical,
0, 0,
origin->horizontal,
origin->vertical);
return TRUE;
}
static void
move_item (GtkGestureDrag *gesture,
double x,
double y,
GtkCanvasItem *ci)
{
GtkCanvas *canvas = gtk_canvas_item_get_canvas (ci);
GtkOrigin *origin = g_object_get_data (G_OBJECT (ci), "position");
origin->horizontal += x / gtk_widget_get_width (GTK_WIDGET (canvas));
origin->vertical += y / gtk_widget_get_height (GTK_WIDGET (canvas));
origin->horizontal = CLAMP (origin->horizontal, 0, 1);
origin->vertical = CLAMP (origin->vertical, 0, 1);
gtk_canvas_item_invalidate_bounds (ci);
}
static void
bind_item (GtkListItemFactory *factory,
GtkCanvasItem *ci)
{
GtkWidget *widget;
GtkGesture *gesture;
GtkOrigin *origin;
widget = gtk_picture_new_for_paintable (gtk_canvas_item_get_item (ci));
gtk_picture_set_can_shrink (GTK_PICTURE (widget), FALSE);
gesture = gtk_gesture_drag_new ();
g_signal_connect (gesture, "drag-update", G_CALLBACK (move_item), ci);
g_signal_connect (gesture, "drag-end", G_CALLBACK (move_item), ci);
gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (gesture));
gtk_canvas_item_set_widget (ci, widget);
/* Set a random position */
origin = g_new (GtkOrigin, 1);
origin->horizontal = g_random_double ();
origin->vertical = g_random_double ();
g_object_set_data_full (G_OBJECT (ci), "position", origin, g_free);
g_signal_connect (ci, "compute-bounds", G_CALLBACK (set_position_from_origin), NULL);
}
static GListModel *
create_puzzle (GdkPaintable *puzzle)
{
GListStore *store = g_list_store_new (GDK_TYPE_PAINTABLE);
int width = 5;
int height = 5;
int x, y;
/* add a picture for every cell */
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
GdkPaintable *piece;
piece = gtk_puzzle_piece_new (puzzle,
x, y,
width, height);
g_list_store_append (store, piece);
g_object_unref (piece);
}
}
return G_LIST_MODEL (store);
}
GtkWidget *
do_canvas_puzzle (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *canvas;
GListModel *model;
GtkListItemFactory *factory;
GdkPaintable *puzzle;
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
puzzle = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
model = create_puzzle (puzzle);
g_object_unref (puzzle);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
canvas = gtk_canvas_new (model, factory);
gtk_window_set_child (GTK_WINDOW (window), canvas);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -254,6 +254,8 @@
<file>application_demo.c</file>
<file>assistant.c</file>
<file>builder.c</file>
<file>canvas_intro.c</file>
<file>canvas_puzzle.c</file>
<file>clipboard.c</file>
<file>combobox.c</file>
<file>constraints.c</file>

View File

@@ -4,6 +4,9 @@ demos = files([
'application_demo.c',
'assistant.c',
'builder.c',
'canvas_intro.c',
'canvas_planarity.c',
'canvas_puzzle.c',
'clipboard.c',
'combobox.c',
'constraints.c',

View File

@@ -322,7 +322,7 @@ start_puzzle (GdkPaintable *paintable)
{
GdkPaintable *piece;
/* Don't paint anything for the lsiding part of the video */
/* Don't paint anything for the sliding part of the video */
if (x == pos_x && y == pos_y)
piece = NULL;
else

View File

@@ -59,6 +59,9 @@
#include <gtk/gtkbuilderscope.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkcalendar.h>
#include <gtk/gtkcanvas.h>
#include <gtk/gtkcanvasbox.h>
#include <gtk/gtkcanvasitem.h>
#include <gtk/gtkcellarea.h>
#include <gtk/gtkcellareabox.h>
#include <gtk/gtkcellareacontext.h>

525
gtk/gtkcanvas.c Normal file
View File

@@ -0,0 +1,525 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkcanvas.h"
#include "gtkcanvasbox.h"
#include "gtkcanvasitemprivate.h"
#include "gtkintl.h"
#include "gtklistitemfactory.h"
#include "gtkwidgetprivate.h"
#define GDK_ARRAY_NAME gtk_canvas_items
#define GDK_ARRAY_TYPE_NAME GtkCanvasItems
#define GDK_ARRAY_ELEMENT_TYPE GtkCanvasItem *
#define GDK_ARRAY_FREE_FUNC g_object_unref
#include "gdk/gdkarrayimpl.c"
/**
* GtkCanvas:
*
* `GtkCanvas` is a widget that allows developers to place a list of items
* using their own method.
*
* ![An example GtkCanvas](canvas.png)
*/
struct _GtkCanvas
{
GtkWidget parent_instance;
GListModel *model;
GtkListItemFactory *factory;
GtkCanvasItems items;
GHashTable *item_lookup;
GtkCanvasBox viewport;
guint viewport_valid : 1;
};
enum
{
PROP_0,
PROP_FACTORY,
PROP_MODEL,
N_PROPS
};
G_DEFINE_FINAL_TYPE (GtkCanvas, gtk_canvas, GTK_TYPE_WIDGET)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_canvas_clear_factory (GtkCanvas *self)
{
if (self->factory == NULL)
return;
g_clear_object (&self->factory);
}
static void
gtk_canvas_remove_items (GtkCanvas *self,
guint pos,
guint n_items)
{
guint i;
/* We first remove the items, and then do teardown. That way we have a consistent
* state in the canvas.
*/
for (i = pos; i < pos + n_items; i++)
{
GtkCanvasItem *ci = gtk_canvas_items_get (&self->items, i);
g_hash_table_remove (self->item_lookup, gtk_canvas_item_get_item (ci));
}
for (i = pos; i < pos + n_items; i++)
{
gtk_canvas_item_teardown (gtk_canvas_items_get (&self->items, i), self->factory);
}
}
static void
gtk_canvas_add_items (GtkCanvas *self,
guint pos,
guint n_items)
{
guint i;
/* We first create all the items and then run the factory code
* on them, so that the factory code can reference the items.
*/
for (i = pos; i < pos + n_items; i++)
{
gpointer item = g_list_model_get_item (self->model, i);
*gtk_canvas_items_index (&self->items, i) = gtk_canvas_item_new (self, item);
g_hash_table_insert (self->item_lookup, item, gtk_canvas_items_get (&self->items, i));
}
for (i = pos; i < pos + n_items; i++)
{
gtk_canvas_item_setup (gtk_canvas_items_get (&self->items, i), self->factory);
}
}
static void
gtk_canvas_items_changed_cb (GListModel *model,
guint pos,
guint removed,
guint added,
GtkCanvas *self)
{
gtk_canvas_remove_items (self, pos, removed);
gtk_canvas_items_splice (&self->items, pos, removed, FALSE, NULL, added);
gtk_canvas_add_items (self, pos, added);
}
static void
gtk_canvas_clear_model (GtkCanvas *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model,
gtk_canvas_items_changed_cb,
self);
g_clear_object (&self->model);
}
static void
gtk_canvas_dispose (GObject *object)
{
GtkCanvas *self = GTK_CANVAS (object);
gtk_canvas_clear_model (self);
gtk_canvas_clear_factory (self);
G_OBJECT_CLASS (gtk_canvas_parent_class)->dispose (object);
}
static void
gtk_canvas_finalize (GObject *object)
{
GtkCanvas *self = GTK_CANVAS (object);
g_hash_table_unref (self->item_lookup);
G_OBJECT_CLASS (gtk_canvas_parent_class)->finalize (object);
}
static void
gtk_canvas_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkCanvas *self = GTK_CANVAS (object);
switch (property_id)
{
case PROP_FACTORY:
g_value_set_object (value, self->factory);
break;
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_canvas_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkCanvas *self = GTK_CANVAS (object);
switch (property_id)
{
case PROP_FACTORY:
gtk_canvas_set_factory (self, g_value_get_object (value));
break;
case PROP_MODEL:
gtk_canvas_set_model (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_canvas_invalidate_allocation (GtkCanvas *self)
{
int i;
self->viewport_valid = FALSE;
for (i = 0; i < gtk_canvas_items_get_size (&self->items); i++)
{
GtkCanvasItem *ci = gtk_canvas_items_get (&self->items, i);
gtk_canvas_item_invalidate_allocation (ci);
}
}
static void
gtk_canvas_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkCanvas *self = GTK_CANVAS (widget);
gboolean missing, force, success;
gsize i;
gtk_canvas_invalidate_allocation (self);
self->viewport_valid = TRUE;
gtk_canvas_box_init (&self->viewport,
0, 0,
width, height,
0, 0);
force = FALSE;
do
{
/* Try to allocate items in a loop */
success = FALSE;
missing = FALSE;
for (i = 0; i < gtk_canvas_items_get_size (&self->items); i++)
{
GtkCanvasItem *ci = gtk_canvas_items_get (&self->items, i);
if (gtk_canvas_item_has_allocation (ci))
continue;
if (!gtk_canvas_item_allocate (ci, force))
{
g_assert (!force);
missing = TRUE;
continue;
}
success = TRUE;
}
if (!success)
{
/* We didn't allocate a single widget in this loop, do something! */
g_warning ("Could not allocate all Canvas items");
force = TRUE;
}
}
while (missing);
for (i = 0; i < gtk_canvas_items_get_size (&self->items); i++)
{
GtkCanvasItem *ci = gtk_canvas_items_get (&self->items, i);
gtk_canvas_item_allocate_widget (ci, 0, 0);
}
}
static void
gtk_canvas_class_init (GtkCanvasClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
widget_class->size_allocate = gtk_canvas_allocate;
gobject_class->dispose = gtk_canvas_dispose;
gobject_class->finalize = gtk_canvas_finalize;
gobject_class->get_property = gtk_canvas_get_property;
gobject_class->set_property = gtk_canvas_set_property;
/**
* GtkCanvas:factory: (attributes org.gtk.Property.get=gtk_canvas_get_factory org.gtk.Property.set=gtk_canvas_set_factory)
*
* The factory used to set up canvasitems from items in the model.
*/
properties[PROP_FACTORY] =
g_param_spec_object ("factory", NULL, NULL,
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkCanvas:model: (attributes org.gtk.Property.get=gtk_canvas_get_model org.gtk.Property.set=gtk_canvas_set_model)
*
* The model with the items to display
*/
properties[PROP_MODEL] =
g_param_spec_object ("model", NULL, NULL,
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
gtk_widget_class_set_css_name (widget_class, I_("canvas"));
}
static void
gtk_canvas_init (GtkCanvas *self)
{
self->item_lookup = g_hash_table_new (g_direct_hash, g_direct_equal);
}
/**
* gtk_canvas_new:
* @model: (nullable) (transfer full): the model to use
* @factory: (nullable) (transfer full): The factory to populate items with
*
* Creates a new `GtkCanvas` that uses the given @factory for
* mapping items to widgets.
*
* The function takes ownership of the
* arguments, so you can write code like
* ```c
* canvas = gtk_canvas_new (create_model (),
* gtk_builder_list_item_factory_new_from_resource ("/resource.ui"));
* ```
*
* Returns: a new `GtkCanvas` using the given @model and @factory
*/
GtkWidget *
gtk_canvas_new (GListModel *model,
GtkListItemFactory *factory)
{
GtkWidget *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
result = g_object_new (GTK_TYPE_CANVAS,
"factory", factory,
"model", model,
NULL);
g_clear_object (&model);
g_clear_object (&factory);
return result;
}
/**
* gtk_canvas_set_factory: (attributes org.gtk.Method.set_property=factory)
* @self: a `GtkCanvas`
* @factory: (nullable) (transfer none): the factory to use
*
* Sets the `GtkListItemFactory` to use for populating canvas items.
*/
void
gtk_canvas_set_factory (GtkCanvas *self,
GtkListItemFactory *factory)
{
guint n_items;
g_return_if_fail (GTK_IS_CANVAS (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
if (self->factory == factory)
return;
n_items = self->model ? g_list_model_get_n_items (G_LIST_MODEL (self->model)) : 0;
gtk_canvas_remove_items (self, 0, n_items);
g_set_object (&self->factory, factory);
gtk_canvas_items_splice (&self->items, 0, n_items, FALSE, NULL, n_items);
gtk_canvas_add_items (self, 0, n_items);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_canvas_get_factory: (attributes org.gtk.Method.get_property=factory)
* @self: a `GtkCanvas`
*
* Gets the factory that's currently used to populate canvas items.
*
* Returns: (nullable) (transfer none): The factory in use
*/
GtkListItemFactory *
gtk_canvas_get_factory (GtkCanvas *self)
{
g_return_val_if_fail (GTK_IS_CANVAS (self), NULL);
return self->factory;
}
/**
* gtk_canvas_set_model: (attributes org.gtk.Method.set_property=model)
* @self: a `GtkCanvas`
* @model: (nullable) (transfer none): the model to use
*
* Sets the model containing the items to populate the canvas with.
*/
void
gtk_canvas_set_model (GtkCanvas *self,
GListModel *model)
{
g_return_if_fail (GTK_IS_CANVAS (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
gtk_canvas_clear_model (self);
if (model)
{
guint added;
self->model = g_object_ref (model);
g_signal_connect (model,
"items-changed",
G_CALLBACK (gtk_canvas_items_changed_cb),
self);
added = g_list_model_get_n_items (G_LIST_MODEL (model));
gtk_canvas_items_splice (&self->items, 0, gtk_canvas_items_get_size (&self->items), FALSE, NULL, added);
gtk_canvas_add_items (self, 0, added);
}
else
{
gtk_canvas_items_clear (&self->items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_canvas_get_model: (attributes org.gtk.Method.get_property=model)
* @self: a `GtkCanvas`
*
* Gets the model that's currently used for the displayed items.
*
* Returns: (nullable) (transfer none): The model in use
*/
GListModel *
gtk_canvas_get_model (GtkCanvas *self)
{
g_return_val_if_fail (GTK_IS_CANVAS (self), NULL);
return self->model;
}
/**
* gtk_canvas_lookup_item:
* @self: a `GtkCanvas`
* @item: an item in the model
*
* Gets the `GtkCanvasItem` that manages the given item.
* If the item is not part of the model, %NULL will be returned.
*
* The resulting canvas item will return @item from a call to
* [method@Gtk.CanvasItem.get_item].
*
* During addition of multiple items, this function will work
* but may return potentially uninitialized canvasitems when the
* factory has not run on them yet.
* During item removal, all removed items can not be queried with
* this function, even if the factory has not unbound the yet.
*
* Returns: (nullable) (transfer none): The canvasitem for item
**/
GtkCanvasItem *
gtk_canvas_lookup_item (GtkCanvas *self,
gpointer item)
{
g_return_val_if_fail (GTK_IS_CANVAS (self), NULL);
g_return_val_if_fail (G_IS_OBJECT (item), NULL);
return g_hash_table_lookup (self->item_lookup, item);
}
/**
* gtk_canvas_get_viewport:
* @self: a `GtkCanvas`
*
* Gets the viewport of the canvas. If no viewport is available,
* in particular if it has not been determined during size
* allocation, %NULL is returned.
*
* Returns: The viewport
**/
const GtkCanvasBox *
gtk_canvas_get_viewport (GtkCanvas *self)
{
g_return_val_if_fail (GTK_IS_CANVAS (self), NULL);
if (!self->viewport_valid)
return NULL;
return &self->viewport;
}

61
gtk/gtkcanvas.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CANVAS_H__
#define __GTK_CANVAS_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_CANVAS (gtk_canvas_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkCanvas, gtk_canvas, GTK, CANVAS, GtkWidget)
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_canvas_new (GListModel *children,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_set_model (GtkCanvas *self,
GListModel *children);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_canvas_get_model (GtkCanvas *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_set_factory (GtkCanvas *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_ALL
GtkListItemFactory * gtk_canvas_get_factory (GtkCanvas *self);
GDK_AVAILABLE_IN_ALL
GtkCanvasItem * gtk_canvas_lookup_item (GtkCanvas *self,
gpointer item);
GDK_AVAILABLE_IN_ALL
const GtkCanvasBox * gtk_canvas_get_viewport (GtkCanvas *self);
G_END_DECLS
#endif /* __GTK_CANVAS_H__ */

81
gtk/gtkcanvasbox.c Normal file
View File

@@ -0,0 +1,81 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/**
* GtkCanvasBox:
*
* `GtkCanvasBox` describes an axis-aligned rectangular box inside
* a `GtkCanvas`.
*
* A box can have no size and be just a single point.
*/
#include "config.h"
#include "gtkcanvasbox.h"
G_DEFINE_BOXED_TYPE (GtkCanvasBox, gtk_canvas_box,
gtk_canvas_box_copy,
gtk_canvas_box_free)
GtkCanvasBox *
gtk_canvas_box_copy (const GtkCanvasBox *self)
{
GtkCanvasBox *copy;
g_return_val_if_fail (self != NULL, NULL);
copy = g_slice_dup (GtkCanvasBox, self);
return copy;
}
void
gtk_canvas_box_free (GtkCanvasBox *self)
{
g_slice_free (GtkCanvasBox, self);
}
void
gtk_canvas_box_init (GtkCanvasBox *self,
float point_x,
float point_y,
float width,
float height,
float origin_horizontal,
float origin_vertical)
{
self->point.x = point_x;
self->point.y = point_y;
self->size.width = width;
self->size.height = height;
self->origin.horizontal = origin_horizontal;
self->origin.vertical = origin_vertical;
}
void
gtk_canvas_box_to_rect (const GtkCanvasBox *self,
graphene_rect_t *rect)
{
rect->size = self->size;
rect->origin = GRAPHENE_POINT_INIT (self->point.x - self->origin.horizontal * self->size.width,
self->point.y - self->origin.vertical * self->size.height);
graphene_rect_normalize (rect);
}

71
gtk/gtkcanvasbox.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CANVAS_BOX_H__
#define __GTK_CANVAS_BOX_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
#include <graphene.h>
G_BEGIN_DECLS
typedef struct _GtkOrigin GtkOrigin;
struct _GtkOrigin {
float horizontal;
float vertical;
};
struct _GtkCanvasBox
{
graphene_point_t point;
graphene_size_t size;
GtkOrigin origin;
};
#define GTK_TYPE_CANVAS_BOX (gtk_canvas_box_get_type ())
GDK_AVAILABLE_IN_ALL
GType gtk_canvas_box_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GtkCanvasBox * gtk_canvas_box_copy (const GtkCanvasBox *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_box_free (GtkCanvasBox *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_box_init (GtkCanvasBox *self,
float point_x,
float point_y,
float width,
float height,
float origin_horizontal,
float origin_vertical);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_box_to_rect (const GtkCanvasBox *self,
graphene_rect_t *rect);
G_END_DECLS
#endif /* __GTK_BOX_H__ */

497
gtk/gtkcanvasitem.c Normal file
View File

@@ -0,0 +1,497 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkcanvasitemprivate.h"
#include "gtkcanvas.h"
#include "gtkcanvasbox.h"
#include "gtkintl.h"
#include "gtklistitemfactoryprivate.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkwidget.h"
/**
* GtkCanvasItem:
*
* `GtkCanvasItem` holds all information relevant for placing a widget
* onto the canvas.
*/
struct _GtkCanvasItem
{
GObject parent_instance;
GtkCanvas *canvas;
gpointer item;
GtkWidget *widget;
GtkCanvasBox bounds;
GtkCanvasBox allocation;
guint has_allocation : 1;
};
enum
{
PROP_0,
PROP_CANVAS,
PROP_ITEM,
PROP_WIDGET,
N_PROPS
};
enum {
COMPUTE_BOUNDS,
LAST_SIGNAL
};
G_DEFINE_FINAL_TYPE (GtkCanvasItem, gtk_canvas_item, G_TYPE_OBJECT)
static GParamSpec *properties[N_PROPS] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static void
gtk_canvas_item_dispose (GObject *object)
{
GtkCanvasItem *self = GTK_CANVAS_ITEM (object);
/* holds a reference */
g_assert (self->canvas == NULL);
/* must have been deleted in teardown */
g_assert (self->item == NULL);
g_assert (self->widget == NULL);
G_OBJECT_CLASS (gtk_canvas_item_parent_class)->dispose (object);
}
static void
gtk_canvas_item_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkCanvasItem *self = GTK_CANVAS_ITEM (object);
switch (property_id)
{
case PROP_CANVAS:
g_value_set_object (value, self->canvas);
break;
case PROP_ITEM:
g_value_set_object (value, self->item);
break;
case PROP_WIDGET:
g_value_set_object (value, self->widget);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_canvas_item_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkCanvasItem *self = GTK_CANVAS_ITEM (object);
switch (property_id)
{
case PROP_WIDGET:
gtk_canvas_item_set_widget (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_canvas_item_class_init (GtkCanvasItemClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gtk_canvas_item_dispose;
gobject_class->get_property = gtk_canvas_item_get_property;
gobject_class->set_property = gtk_canvas_item_set_property;
/**
* GtkCanvasItem:canvas: (attributes org.gtk.Property.get=gtk_canvas_item_get_canvas org.gtk.Property.set=gtk_canvas_item_set_canvas)
*
* The canvas this item belongs to or %NULL if the canvas has been destroyed
*/
properties[PROP_CANVAS] =
g_param_spec_object ("canvas", NULL, NULL,
GTK_TYPE_CANVAS,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkCanvasItem:item: (attributes org.gtk.Property.get=gtk_canvas_item_get_item org.gtk.Property.set=gtk_canvas_item_set_item)
*
* The item represented by this canvas item.
*/
properties[PROP_ITEM] =
g_param_spec_object ("item", NULL, NULL,
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkCanvasItem:widget: (attributes org.gtk.Property.get=gtk_canvas_item_get_widget org.gtk.Property.set=gtk_canvas_item_set_widget)
*
* The widget managed.
*/
properties[PROP_WIDGET] =
g_param_spec_object ("widget", NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
* GtkCanvasItem::compute-bounds
* @self: the `GtkCanvasItem`
* @bounds: (type Gtk.CanvasBox) (out caller-allocates): return
* location for the bounds
*
* Emitted to determine the bounds for the widget of this canvasitem
* during a size allocation cycle.
*
* A handler for this signal should fill @bounds with
* the desired box to place the widget in.
*
* If the size depends on other items and cannot be computed yet,
* handlers should return %FALSE and the signal will then be emitted
* again once more items have been allocated.
*
* Because of that signal handlers are expected to be pure - not set
* any properties or have other side effects - and idempotent -
* return the same result if called multiple times in order.
*
* returns: %TRUE if @bounds was set successfully
*/
signals[COMPUTE_BOUNDS] =
g_signal_new (I_("compute-bounds"),
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
0,
_gtk_boolean_handled_accumulator, NULL,
_gtk_marshal_BOOLEAN__BOXED,
G_TYPE_BOOLEAN, 1,
GTK_TYPE_CANVAS_BOX | G_SIGNAL_TYPE_STATIC_SCOPE);
g_signal_set_va_marshaller (signals[COMPUTE_BOUNDS],
G_TYPE_FROM_CLASS (gobject_class),
_gtk_marshal_BOOLEAN__BOXEDv);
}
static void
gtk_canvas_item_init (GtkCanvasItem *self)
{
}
GtkCanvasItem *
gtk_canvas_item_new (GtkCanvas *canvas,
gpointer item)
{
GtkCanvasItem *self;
self = g_object_new (GTK_TYPE_CANVAS_ITEM,
NULL);
/* no reference, the canvas references us */
self->canvas = canvas;
/* transfer full */
self->item = item;
return self;
}
void
gtk_canvas_item_invalidate_allocation (GtkCanvasItem *self)
{
self->has_allocation = FALSE;
}
gboolean
gtk_canvas_item_allocate (GtkCanvasItem *self,
gboolean force)
{
gboolean result;
int w, h;
g_assert (!self->has_allocation);
g_signal_emit (self, signals[COMPUTE_BOUNDS], 0, &self->bounds, &result);
if (!result)
{
if (!force)
return FALSE;
gtk_canvas_box_init (&self->bounds, 0, 0, 0, 0, 0.5, 0.5);
}
if (self->widget)
{
if (gtk_widget_get_request_mode (self->widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
{
gtk_widget_measure (self->widget, GTK_ORIENTATION_HORIZONTAL, -1, &w, NULL, NULL, NULL);
w = MAX (w, ceil (ABS (self->bounds.size.width)));
gtk_widget_measure (self->widget, GTK_ORIENTATION_VERTICAL, w, &h, NULL, NULL, NULL);
h = MAX (h, ceil (ABS (self->bounds.size.height)));
}
else
{
gtk_widget_measure (self->widget, GTK_ORIENTATION_VERTICAL, -1, &h, NULL, NULL, NULL);
h = MAX (h, ceil (ABS (self->bounds.size.height)));
gtk_widget_measure (self->widget, GTK_ORIENTATION_HORIZONTAL, h, &w, NULL, NULL, NULL);
w = MAX (w, ceil (ABS (self->bounds.size.width)));
}
if (self->bounds.size.width >= 0)
w = MAX (self->bounds.size.width, w);
else
w = MIN (self->bounds.size.width, -w);
if (self->bounds.size.height >= 0)
h = MAX (self->bounds.size.height, h);
else
h = MIN (self->bounds.size.height, -h);
}
else
{
w = 0;
h = 0;
}
gtk_canvas_box_init (&self->allocation,
round (self->bounds.point.x - self->bounds.origin.horizontal * w)
+ self->bounds.origin.horizontal * w,
round (self->bounds.point.y - self->bounds.origin.vertical * h)
+ self->bounds.origin.vertical * h,
w, h,
self->bounds.origin.horizontal, self->bounds.origin.vertical);
self->has_allocation = TRUE;
return TRUE;
}
void
gtk_canvas_item_allocate_widget (GtkCanvasItem *self,
float dx,
float dy)
{
graphene_rect_t allocation;
GskTransform *transform;
if (self->widget == NULL)
return;
gtk_canvas_box_to_rect (&self->allocation, &allocation);
transform = gsk_transform_translate (NULL,
&GRAPHENE_POINT_INIT (
allocation.origin.x - dx
+ (signbit (self->allocation.size.width) ? allocation.size.width : 0),
allocation.origin.y - dy
+ (signbit (self->allocation.size.height) ? allocation.size.height : 0)));
transform = gsk_transform_scale (transform,
signbit (self->allocation.size.width) ? -1 : 1,
signbit (self->allocation.size.height) ? -1 : 1);
gtk_widget_allocate (self->widget,
allocation.size.width,
allocation.size.height,
-1,
transform);
}
gboolean
gtk_canvas_item_has_allocation (GtkCanvasItem *self)
{
return self->has_allocation;
}
/**
* gtk_canvas_item_get_canvas: (attributes org.gtk.Method.get_property=canvas)
* @self: a `GtkCanvasItem`
*
* Gets the canvas this item belongs to.
*
* If the canvas has discarded this item, this property willbe set to %NULL.
*
* Returns: (nullable) (transfer none): The canvas
*/
GtkCanvas *
gtk_canvas_item_get_canvas (GtkCanvasItem *self)
{
g_return_val_if_fail (GTK_IS_CANVAS_ITEM (self), NULL);
return self->canvas;
}
/**
* gtk_canvas_item_get_item: (attributes org.gtk.Method.get_property=item)
* @self: a `GtkCanvasItem`
*
* Gets the item that is associated with this canvasitem or %NULL if the canvas has
* discarded this canvasitem.
*
* Returns: (transfer none) (nullable) (type GObject): The item.
*/
gpointer
gtk_canvas_item_get_item (GtkCanvasItem *self)
{
g_return_val_if_fail (GTK_IS_CANVAS_ITEM (self), NULL);
return self->item;
}
void
gtk_canvas_item_invalidate_bounds (GtkCanvasItem *self)
{
g_return_if_fail (GTK_IS_CANVAS_ITEM (self));
if (self->canvas)
gtk_widget_queue_allocate (GTK_WIDGET (self->canvas));
}
/**
* gtk_canvas_item_get_bounds:
* @self: a `GtkCanvasItem`
*
* Gets the bounds that are used to allocate the widget
*
* If the bounds are not known yet - for example when called during the size
* allocation phase before this item has succesfully computed its bounds -
* this function returns %NULL.
*
* See also gtk_canvas_item_get_allocation().
*
* Returns: (transfer none) (nullable): The bounds
*/
const GtkCanvasBox *
gtk_canvas_item_get_bounds (GtkCanvasItem *self)
{
g_return_val_if_fail (GTK_IS_CANVAS_ITEM (self), NULL);
if (!self->has_allocation)
return NULL;
return &self->bounds;
}
/**
* gtk_canvas_item_get_allocation:
* @self: a `GtkCanvasItem`
*
* Gets the allocation assigned to the widget.
*
* If the bounds are not known yet - for example when called during the size
* allocation phase before this item has succesfully computed its bounds -
* this function returns %NULL.
*
* Compared with gtk_canvas_item_get_bounds(), this function returns the actual
* box used to allocate the widget, which may be different from the bounds
* to conform to its size requirements.
*
* Returns: (transfer none) (nullable): The allocation
*/
const GtkCanvasBox *
gtk_canvas_item_get_allocation (GtkCanvasItem *self)
{
g_return_val_if_fail (GTK_IS_CANVAS_ITEM (self), NULL);
if (!self->has_allocation)
return NULL;
return &self->allocation;
}
/**
* gtk_canvas_item_set_widget: (attributes org.gtk.Method.set_property=widget)
* @self: a `GtkCanvasItem`
* @widget: (nullable) (transfer none): the widget to use
*
* Sets the widget to be displayed by this item.
*/
void
gtk_canvas_item_set_widget (GtkCanvasItem *self,
GtkWidget *widget)
{
g_return_if_fail (GTK_IS_CANVAS_ITEM (self));
g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
if (self->widget == widget)
return;
if (self->widget)
{
if (self->canvas)
gtk_widget_unparent (self->widget);
g_object_unref (self->widget);
}
self->widget = g_object_ref_sink (widget);
if (self->canvas)
{
/* FIXME: Put in right spot */
gtk_widget_set_parent (widget, GTK_WIDGET (self->canvas));
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WIDGET]);
}
/**
* gtk_canvas_item_get_widget: (attributes org.gtk.Method.get_property=widget)
* @self: a `GtkCanvasItem`
*
* Gets the widget that's currently displayed by this canvasitem
*
* Returns: (nullable) (transfer none): The widget in use
*/
GtkWidget *
gtk_canvas_item_get_widget (GtkCanvasItem *self)
{
g_return_val_if_fail (GTK_IS_CANVAS_ITEM (self), NULL);
return self->widget;
}
void
gtk_canvas_item_setup (GtkCanvasItem *self,
GtkListItemFactory *factory)
{
gtk_list_item_factory_setup (factory, G_OBJECT (self), TRUE, NULL, NULL);
}
void
gtk_canvas_item_teardown (GtkCanvasItem *self,
GtkListItemFactory *factory)
{
gtk_list_item_factory_teardown (factory, G_OBJECT (self), TRUE, NULL, NULL);
}

59
gtk/gtkcanvasitem.h Normal file
View File

@@ -0,0 +1,59 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CANVAS_ITEM_H__
#define __GTK_CANVAS_ITEM_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_CANVAS_ITEM (gtk_canvas_item_get_type ())
/* GtkCanvasItem */
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkCanvasItem, gtk_canvas_item, GTK, CANVAS_ITEM, GObject)
GDK_AVAILABLE_IN_ALL
GtkCanvas * gtk_canvas_item_get_canvas (GtkCanvasItem *self);
GDK_AVAILABLE_IN_ALL
gpointer gtk_canvas_item_get_item (GtkCanvasItem *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_item_set_widget (GtkCanvasItem *self,
GtkWidget *widget);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_canvas_item_get_widget (GtkCanvasItem *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_item_invalidate_bounds (GtkCanvasItem *self);
GDK_AVAILABLE_IN_ALL
const GtkCanvasBox * gtk_canvas_item_get_bounds (GtkCanvasItem *self);
GDK_AVAILABLE_IN_ALL
const GtkCanvasBox * gtk_canvas_item_get_allocation (GtkCanvasItem *self);
G_END_DECLS
#endif /* __GTK_CANVAS_ITEM_H__ */

View File

@@ -0,0 +1,28 @@
#ifndef __GTK_CANVAS_ITEM_PRIVATE_H__
#define __GTK_CANVAS_ITEM_PRIVATE_H__
#include "gtkcanvasitem.h"
G_BEGIN_DECLS
GtkCanvasItem * gtk_canvas_item_new (GtkCanvas *canvas,
gpointer item);
void gtk_canvas_item_invalidate_allocation (GtkCanvasItem *self);
gboolean gtk_canvas_item_allocate (GtkCanvasItem *self,
gboolean force);
void gtk_canvas_item_allocate_widget (GtkCanvasItem *self,
float dx,
float dy);
gboolean gtk_canvas_item_has_allocation (GtkCanvasItem *self);
void gtk_canvas_item_clear_canvas (GtkCanvasItem *self);
void gtk_canvas_item_setup (GtkCanvasItem *self,
GtkListItemFactory *factory);
void gtk_canvas_item_teardown (GtkCanvasItem *self,
GtkListItemFactory *factory);
G_END_DECLS
#endif /* __GTK_CANVAS_ITEM_PRIVATE_H__ */

91
gtk/gtkcanvaslayout.h Normal file
View File

@@ -0,0 +1,91 @@
/*
* Copyright © 2022 Benjamin Otte
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_CANVAS_H__
#define __GTK_CANVAS_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtklayoutmanager.h>
G_BEGIN_DECLS
struct _GtkPosition {
float relative;
float absolute;
};
#define GTK_TYPE_CANVAS_LAYOUT (gtk_canvas_layout_get_type ())
#define GTK_TYPE_CANVAS_LAYOUT_CHILD (gtk_canvas_layout_child_get_type ())
/* GtkCanvasLayout */
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkCanvasLayout, gtk_canvas_layout, GTK, CANVAS_LAYOUT, GtkLayoutManager)
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_canvas_layout_new (void);
/* GtkCanvasLayoutChild */
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkCanvasLayoutChild, gtk_canvas_layout_child, GTK, CANVAS_LAYOUT_CHILD, GtkLayoutChild)
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_x (GtkCanvasLayoutChild *self,
const GtkPosition *position);
GDK_AVAILABLE_IN_ALL
const GtkPosition * gtk_canvas_layout_child_get_x (GtkCanvasLayoutChild *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_y (GtkCanvasLayoutChild *self,
const GtkPosition *position);
GDK_AVAILABLE_IN_ALL
const GtkPosition * gtk_canvas_layout_child_get_y (GtkCanvasLayoutChild *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_origin_x (GtkCanvasLayoutChild *self,
const GtkPosition *position);
GDK_AVAILABLE_IN_ALL
const GtkPosition * gtk_canvas_layout_child_get_origin_x (GtkCanvasLayoutChild *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_origin_y (GtkCanvasLayoutChild *self,
const GtkPosition *position);
GDK_AVAILABLE_IN_ALL
const GtkPosition * gtk_canvas_layout_child_get_origin_y (GtkCanvasLayoutChild *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_transform (GtkCanvasLayoutChild *self,
GskTransform *transform);
GDK_AVAILABLE_IN_ALL
const GskTransform * gtk_canvas_layout_child_get_transform (GtkCanvasLayoutChild *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_hpolicy (GtkCanvasLayoutChild *self,
GtkScrollablePolicy policy);
GDK_AVAILABLE_IN_ALL
GtkScrollablePolicy gtk_canvas_layout_child_get_hpolicy (GtkCanvasLayoutChild *self);
GDK_AVAILABLE_IN_ALL
void gtk_canvas_layout_child_set_vpolicy (GtkCanvasLayoutChild *self,
GtkScrollablePolicy policy);
GDK_AVAILABLE_IN_ALL
GtkScrollablePolicy gtk_canvas_layout_child_get_vpolicy (GtkCanvasLayoutChild *self);
G_END_DECLS
#endif /* __GTK_CANVAS_H__ */

View File

@@ -37,6 +37,9 @@ typedef struct _GtkAdjustment GtkAdjustment;
typedef struct _GtkBitset GtkBitset;
typedef struct _GtkBuilder GtkBuilder;
typedef struct _GtkBuilderScope GtkBuilderScope;
typedef struct _GtkCanvas GtkCanvas;
typedef struct _GtkCanvasBox GtkCanvasBox;
typedef struct _GtkCanvasItem GtkCanvasItem;
typedef struct _GtkCssStyleChange GtkCssStyleChange;
typedef struct _GtkEventController GtkEventController;
typedef struct _GtkGesture GtkGesture;

View File

@@ -182,6 +182,9 @@ gtk_public_sources = files([
'gtkbuilderscope.c',
'gtkbutton.c',
'gtkcalendar.c',
'gtkcanvas.c',
'gtkcanvasbox.c',
'gtkcanvasitem.c',
'gtkcellarea.c',
'gtkcellareabox.c',
'gtkcellareacontext.c',
@@ -473,6 +476,9 @@ gtk_public_headers = files([
'gtkbuilderscope.h',
'gtkbutton.h',
'gtkcalendar.h',
'gtkcanvas.h',
'gtkcanvasbox.h',
'gtkcanvasitem.h',
'gtkcenterbox.h',
'gtkcenterlayout.h',
'gtkcellarea.h',