[gtk/wip/otte/canvas: 136/138] Add GtkCanvas




commit 504d7f4c1e1b72801e938b4208547e8cc32df9cc
Author: Benjamin Otte <otte redhat com>
Date:   Wed Jun 1 05:53:44 2022 +0200

    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.

 demos/gtk-demo/canvas_puzzle.c |  79 ++++++++
 gtk/gtk.h                      |   5 +
 gtk/gtkcanvas.c                | 426 +++++++++++++++++++++++++++++++++++++++++
 gtk/gtkcanvas.h                |  55 ++++++
 gtk/gtkcanvasbox.c             | 286 +++++++++++++++++++++++++++
 gtk/gtkcanvasbox.h             |  58 ++++++
 gtk/gtkcanvasboxprivate.h      |  54 ++++++
 gtk/gtkcanvasitem.c            | 353 ++++++++++++++++++++++++++++++++++
 gtk/gtkcanvasitem.h            |  57 ++++++
 gtk/gtkcanvasitemprivate.h     |  20 ++
 gtk/gtkcanvaslayout.h          |  91 +++++++++
 gtk/gtkcanvaspoint.c           | 297 ++++++++++++++++++++++++++++
 gtk/gtkcanvaspoint.h           |  65 +++++++
 gtk/gtkcanvaspointprivate.h    |  52 +++++
 gtk/gtkcanvassize.c            | 392 +++++++++++++++++++++++++++++++++++++
 gtk/gtkcanvassize.h            |  66 +++++++
 gtk/gtkcanvassizeprivate.h     |  56 ++++++
 gtk/gtktypes.h                 |   5 +
 gtk/meson.build                |  10 +
 19 files changed, 2427 insertions(+)
---
diff --git a/demos/gtk-demo/canvas_puzzle.c b/demos/gtk-demo/canvas_puzzle.c
new file mode 100644
index 0000000000..8fae9fa177
--- /dev/null
+++ b/demos/gtk-demo/canvas_puzzle.c
@@ -0,0 +1,79 @@
+/* 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 void
+bind_item (GtkListItemFactory *factory,
+           GtkCanvasItem      *ci)
+{
+  GtkCanvasPoint *point;
+  GtkCanvasSize *size;
+  GtkCanvasBox *box;
+
+  widget = gtk_picture_new_for_paintable (gtk_canvas_item_get_item (ci));
+  gtk_canvas_item_set_widget (ci, widget);
+
+  /* Also cener the item, so we do something interesting */
+  point = gtk_canvas_point_new (WIDTH / 2.0, HEIGHT / 2.0);
+  size = gtk_canvas_size_new_measure_item (ci, GTK_CANVAS_ITEM_MEASURE_MIN_FOR_MIN);
+  box = gtk_canvas_box_new (point, size, 0.5, 0.5);
+  gtk_canvas_item_set_bounds (ci, box);
+  gtk_canvas_box_free (box);
+  gtk_canvas_size_free (size);
+  gtk_canvas_point_free (point);
+}
+
+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");
+      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;
+}
diff --git a/gtk/gtk.h b/gtk/gtk.h
index bac384a2e0..9ed46e9f96 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -59,6 +59,11 @@
 #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/gtkcanvaspoint.h>
+#include <gtk/gtkcanvassize.h>
 #include <gtk/gtkcellarea.h>
 #include <gtk/gtkcellareabox.h>
 #include <gtk/gtkcellareacontext.h>
diff --git a/gtk/gtkcanvas.c b/gtk/gtkcanvas.c
new file mode 100644
index 0000000000..b69ca50429
--- /dev/null
+++ b/gtk/gtkcanvas.c
@@ -0,0 +1,426 @@
+/*
+ * 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;
+};
+
+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 run the factory code on all items, so that the
+   * factory code can reference the items.
+   * Only then do we get rid of them.
+   */
+  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++)
+    {
+      *gtk_canvas_items_index (&self->items, i) = gtk_canvas_item_new (self,
+                                                                       g_list_model_get_item (self->model, 
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_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_allocate (GtkWidget *widget,
+                     int        width,
+                     int        height,
+                     int        baseline)
+{
+  GtkCanvas *self = GTK_CANVAS (widget);
+  gsize i;
+
+  for (i = 0; i < gtk_canvas_items_get_size (&self->items); i++)
+    {
+      GtkCanvasItem *ci = gtk_canvas_items_get (&self->items, i);
+      GtkWidget *child = gtk_canvas_item_get_widget (ci);
+      graphene_rect_t rect;
+      int x, y, w, h;
+
+      if (child == NULL)
+        continue;
+
+      if (!gtk_canvas_box_eval (gtk_canvas_item_get_bounds (ci), &rect))
+        rect = *graphene_rect_zero ();
+
+      if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+        {
+          gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1, &w, NULL, NULL, NULL);
+          w= MAX (w, ceil (rect.size.width));
+          gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, w, &h, NULL, NULL, NULL);
+          h= MAX (h, ceil (rect.size.height));
+        }
+      else
+        {
+          gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, -1, &h, NULL, NULL, NULL);
+          h= MAX (h, ceil (rect.size.height));
+          gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, h, &w, NULL, NULL, NULL);
+          w= MAX (w, ceil (rect.size.width));
+        }
+      /* FIXME: Adapt to growing rect */
+      x = round (rect.origin.x);
+      y = round (rect.origin.y);
+
+      gtk_widget_size_allocate (child, &(GtkAllocation) { x, y, w, h }, -1);
+    }
+}
+
+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->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)
+{
+}
+
+/**
+ * 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;
+}
+
diff --git a/gtk/gtkcanvas.h b/gtk/gtkcanvas.h
new file mode 100644
index 0000000000..d8425997a3
--- /dev/null
+++ b/gtk/gtkcanvas.h
@@ -0,0 +1,55 @@
+/*
+ * 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);
+
+
+G_END_DECLS
+
+#endif  /* __GTK_CANVAS_H__ */
diff --git a/gtk/gtkcanvasbox.c b/gtk/gtkcanvasbox.c
new file mode 100644
index 0000000000..4260990d78
--- /dev/null
+++ b/gtk/gtkcanvasbox.c
@@ -0,0 +1,286 @@
+/*
+ * 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 "gtkcanvasboxprivate.h"
+
+#include "gtkcanvaspointprivate.h"
+#include "gtkcanvassizeprivate.h"
+
+/* {{{ Boilerplate */
+
+struct _GtkCanvasBoxClass
+{
+  const char *type_name;
+
+  void                  (* copy)                (GtkCanvasBox         *self,
+                                                 const GtkCanvasBox   *source);
+  void                  (* finish)              (GtkCanvasBox         *self);
+  gboolean              (* eval)                (const GtkCanvasBox   *self,
+                                                 graphene_rect_t      *rect);
+};
+
+G_DEFINE_BOXED_TYPE (GtkCanvasBox, gtk_canvas_box,
+                     gtk_canvas_box_copy,
+                     gtk_canvas_box_free)
+
+static gpointer
+gtk_canvas_box_alloc (const GtkCanvasBoxClass *class)
+{
+  GtkCanvasBox *self = g_slice_new (GtkCanvasBox);
+
+  self->class = class;
+
+  return self;
+}
+
+void
+gtk_canvas_box_init_copy (GtkCanvasBox       *self,
+                          const GtkCanvasBox *source)
+{
+  self->class = source->class;
+  self->class->copy (self, source);
+}
+
+void
+gtk_canvas_box_finish (GtkCanvasBox *self)
+{
+  self->class->finish (self);
+}
+
+/* }}} */
+/* {{{ POINTS */
+
+static void
+gtk_canvas_box_points_copy (GtkCanvasBox       *box,
+                            const GtkCanvasBox *source_box)
+{
+  GtkCanvasBoxPoints *self = &box->points;
+  const GtkCanvasBoxPoints *source = &source_box->points;
+
+  gtk_canvas_point_init_copy (&self->point1, &source->point1);
+  gtk_canvas_point_init_copy (&self->point2, &source->point2);
+}
+
+static void
+gtk_canvas_box_points_finish (GtkCanvasBox *box)
+{
+  GtkCanvasBoxPoints *self = &box->points;
+
+  gtk_canvas_point_finish (&self->point1);
+  gtk_canvas_point_finish (&self->point2);
+}
+
+static gboolean
+gtk_canvas_box_points_eval (const GtkCanvasBox *box,
+                            graphene_rect_t    *rect)
+{
+  const GtkCanvasBoxPoints *self = &box->points;
+  float x1, x2, y1, y2;
+
+  if (!gtk_canvas_point_eval (&self->point1, &x1, &y1) ||
+      !gtk_canvas_point_eval (&self->point2, &x2, &y2))
+    return FALSE;
+
+  graphene_rect_init (rect, x1, y1, x2 - x1, y2 - y1);
+  return TRUE;
+}
+
+static const GtkCanvasBoxClass GTK_CANVAS_BOX_POINTS_CLASS =
+{
+  "GtkCanvasBoxPoints",
+  gtk_canvas_box_points_copy,
+  gtk_canvas_box_points_finish,
+  gtk_canvas_box_points_eval,
+};
+
+/**
+ * gtk_canvas_box_new_points:
+ * @point1: the first point
+ * @point2: the second point
+ *
+ * Creates a new box describing the rectangle between the two
+ * points
+ *
+ * Returns: a new box
+ **/
+GtkCanvasBox *
+gtk_canvas_box_new_points (const GtkCanvasPoint *point1,
+                           const GtkCanvasPoint *point2)
+{
+  GtkCanvasBoxPoints *self;
+
+  g_return_val_if_fail (point1 != NULL, NULL);
+  g_return_val_if_fail (point2 != NULL, NULL);
+
+  self = gtk_canvas_box_alloc (&GTK_CANVAS_BOX_POINTS_CLASS);
+
+  gtk_canvas_point_init_copy (&self->point1, point1);
+  gtk_canvas_point_init_copy (&self->point2, point2);
+
+  return (GtkCanvasBox *) self;
+}
+
+/* }}} */
+/* {{{ SIZE */
+
+static void
+gtk_canvas_box_size_copy (GtkCanvasBox       *box,
+                          const GtkCanvasBox *source_box)
+{
+  const GtkCanvasBoxSize *source = &source_box->size;
+
+  gtk_canvas_box_init (box, &source->point, &source->size, source->origin_x, source->origin_y);
+}
+
+static void
+gtk_canvas_box_size_finish (GtkCanvasBox *box)
+{
+  GtkCanvasBoxSize *self = &box->size;
+
+  gtk_canvas_point_finish (&self->point);
+  gtk_canvas_size_finish (&self->size);
+}
+
+static gboolean
+gtk_canvas_box_size_eval (const GtkCanvasBox *box,
+                          graphene_rect_t    *rect)
+{
+  const GtkCanvasBoxSize *self = &box->size;
+  float x, y, width, height;
+
+  if (!gtk_canvas_point_eval (&self->point, &x, &y) ||
+      !gtk_canvas_size_eval (&self->size, &width, &height))
+    return FALSE;
+
+  graphene_rect_init (rect,
+                      x - width * self->origin_x,
+                      y - height * self->origin_y,
+                      width, height);
+
+  return TRUE;
+}
+
+static const GtkCanvasBoxClass GTK_CANVAS_BOX_SIZE_CLASS =
+{
+  "GtkCanvasBoxSize",
+  gtk_canvas_box_size_copy,
+  gtk_canvas_box_size_finish,
+  gtk_canvas_box_size_eval,
+};
+
+void
+gtk_canvas_box_init (GtkCanvasBox         *box,
+                     const GtkCanvasPoint *point,
+                     const GtkCanvasSize  *size,
+                     float                 origin_x,
+                     float                 origin_y)
+{
+  GtkCanvasBoxSize *self = &box->size;
+
+  self->class = &GTK_CANVAS_BOX_SIZE_CLASS;
+
+  gtk_canvas_point_init_copy (&self->point, point);
+  gtk_canvas_size_init_copy (&self->size, size);
+  self->origin_x = origin_x;
+  self->origin_y = origin_y;
+}
+
+/**
+ * gtk_canvas_box_new:
+ * @point: the origin point of the box
+ * @size: size of the box
+ * @origin_x: x coordinate of origin
+ * @origin_y: y coordinate of origin
+ *
+ * Creates a new box of the given size relative to the given point.
+ * The origin describes where in the box the point is located.
+ * (0, 0) means the point describes the top left of the box, (1, 1)
+ * describes the bottom right, and (0.5, 0.5) is the center.
+ *
+ * Returns: a new box
+ **/
+GtkCanvasBox *
+gtk_canvas_box_new (const GtkCanvasPoint *point,
+                    const GtkCanvasSize  *size,
+                    float                 origin_x,
+                    float                 origin_y)
+{
+  GtkCanvasBox *self;
+
+  g_return_val_if_fail (point != NULL, NULL);
+  g_return_val_if_fail (size != NULL, NULL);
+
+  self = gtk_canvas_box_alloc (&GTK_CANVAS_BOX_SIZE_CLASS);
+
+  gtk_canvas_box_init (self, point, size, origin_x, origin_y);
+
+  return self;
+}
+
+/* }}} */
+/* {{{ PUBLIC API */
+
+GtkCanvasBox *
+gtk_canvas_box_copy (const GtkCanvasBox *self)
+{
+  GtkCanvasBox *copy;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  copy = gtk_canvas_box_alloc (self->class);
+
+  gtk_canvas_box_init_copy (copy, self);
+
+  return copy;
+}
+
+void
+gtk_canvas_box_free (GtkCanvasBox *self)
+{
+  gtk_canvas_box_finish (self);
+
+  g_slice_free (GtkCanvasBox, self);
+}
+
+gboolean
+gtk_canvas_box_eval (const GtkCanvasBox *self,
+                     graphene_rect_t    *rect)
+{
+  g_return_val_if_fail (self != NULL, FALSE);
+  g_return_val_if_fail (rect != NULL, FALSE);
+
+  if (self->class->eval (self, rect))
+    return TRUE;
+
+  *rect = *graphene_rect_zero ();
+  return FALSE;
+}
+
diff --git a/gtk/gtkcanvasbox.h b/gtk/gtkcanvasbox.h
new file mode 100644
index 0000000000..4211ca323c
--- /dev/null
+++ b/gtk/gtkcanvasbox.h
@@ -0,0 +1,58 @@
+/*
+ * 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
+
+#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
+gboolean                gtk_canvas_box_eval                   (const GtkCanvasBox   *self,
+                                                               graphene_rect_t      *rect) 
G_GNUC_WARN_UNUSED_RESULT;
+
+GDK_AVAILABLE_IN_ALL
+GtkCanvasBox *          gtk_canvas_box_new                    (const GtkCanvasPoint     *point,
+                                                               const GtkCanvasSize      *size,
+                                                               float                     origin_x,
+                                                               float                     origin_y);
+GDK_AVAILABLE_IN_ALL
+GtkCanvasBox *          gtk_canvas_box_new_points             (const GtkCanvasPoint     *point1,
+                                                               const GtkCanvasPoint     *point2);
+
+G_END_DECLS
+
+#endif /* __GTK_BOX_H__ */
diff --git a/gtk/gtkcanvasboxprivate.h b/gtk/gtkcanvasboxprivate.h
new file mode 100644
index 0000000000..f28610c38d
--- /dev/null
+++ b/gtk/gtkcanvasboxprivate.h
@@ -0,0 +1,54 @@
+#ifndef __GTK_CANVAS_BOX_PRIVATE_H__
+#define __GTK_CANVAS_BOX_PRIVATE_H__
+
+#include "gtkcanvasbox.h"
+
+#include "gtkcanvaspointprivate.h"
+#include "gtkcanvassizeprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCanvasBoxClass GtkCanvasBoxClass;
+typedef struct _GtkCanvasBoxPoints GtkCanvasBoxPoints;
+typedef struct _GtkCanvasBoxSize GtkCanvasBoxSize;
+
+struct _GtkCanvasBoxPoints
+{
+  const GtkCanvasBoxClass *class;
+
+  GtkCanvasPoint point1;
+  GtkCanvasPoint point2;
+};
+
+struct _GtkCanvasBoxSize
+{
+  const GtkCanvasBoxClass *class;
+
+  GtkCanvasPoint point;
+  GtkCanvasSize size;
+  float origin_x;
+  float origin_y;
+};
+
+struct _GtkCanvasBox
+{
+  union {
+    const GtkCanvasBoxClass *class;
+    GtkCanvasBoxPoints points;
+    GtkCanvasBoxSize size;
+  };
+};
+
+
+void                    gtk_canvas_box_init                     (GtkCanvasBox           *self,
+                                                                 const GtkCanvasPoint   *point,
+                                                                 const GtkCanvasSize    *size,
+                                                                 float                   origin_x,
+                                                                 float                   origin_y);
+void                    gtk_canvas_box_init_copy                (GtkCanvasBox           *self,
+                                                                 const GtkCanvasBox     *source);
+void                    gtk_canvas_box_finish                   (GtkCanvasBox           *self);
+
+G_END_DECLS
+
+#endif /* __GTK_CANVAS_BOX_PRIVATE_H__ */
diff --git a/gtk/gtkcanvasitem.c b/gtk/gtkcanvasitem.c
new file mode 100644
index 0000000000..1a56374a9b
--- /dev/null
+++ b/gtk/gtkcanvasitem.c
@@ -0,0 +1,353 @@
+/*
+ * 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 "gtkcanvasboxprivate.h"
+#include "gtkcanvaspointprivate.h"
+#include "gtkcanvassizeprivate.h"
+#include "gtkintl.h"
+#include "gtklistitemfactoryprivate.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;
+};
+
+enum
+{
+  PROP_0,
+  PROP_BOUNDS,
+  PROP_CANVAS,
+  PROP_ITEM,
+  PROP_WIDGET,
+
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GtkCanvasItem, gtk_canvas_item, G_TYPE_OBJECT)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+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);
+
+  gtk_canvas_box_finish (&self->bounds);
+
+  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_BOUNDS:
+      g_value_set_boxed (value, &self->bounds);
+      break;
+
+    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_BOUNDS:
+      gtk_canvas_item_set_bounds (self, g_value_get_boxed (value));
+      break;
+
+    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:bounds: (attributes org.gtk.Property.get=gtk_canvas_item_get_bounds 
org.gtk.Property.set=gtk_canvas_item_set_bounds)
+   *
+   * The bounds to place the widget into.
+   */
+  properties[PROP_BOUNDS] =
+    g_param_spec_boxed ("bounds", NULL, NULL,
+                        GTK_TYPE_CANVAS_BOX,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * 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);
+}
+
+static void
+gtk_canvas_item_init (GtkCanvasItem *self)
+{
+  GtkCanvasPoint point;
+  GtkCanvasSize size;
+
+  gtk_canvas_point_init (&point, 0, 0);
+  gtk_canvas_size_init_measure_item (&size, self, GTK_CANVAS_ITEM_MEASURE_NAT_FOR_NAT);
+  gtk_canvas_box_init (&self->bounds, &point, &size, 0, 0);
+  gtk_canvas_size_finish (&size);
+  gtk_canvas_point_finish (&point);
+}
+
+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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * gtk_canvas_item_set_bounds: (attributes org.gtk.Method.set_property=bounds)
+ * @self: a `GtkCanvasItem`
+ * @bounds: (transfer none): the bounds to allocate the widget in
+ *
+ * Sets the box to allocate the widget into.
+ */
+void
+gtk_canvas_item_set_bounds (GtkCanvasItem      *self,
+                            const GtkCanvasBox *bounds)
+{
+  g_return_if_fail (GTK_IS_CANVAS_ITEM (self));
+  g_return_if_fail (bounds != NULL);
+
+  gtk_canvas_box_init_copy (&self->bounds, bounds);
+  if (self->canvas)
+    gtk_widget_queue_allocate (GTK_WIDGET (self->canvas));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BOUNDS]);
+}
+
+/**
+ * gtk_canvas_item_get_bounds: (attributes org.gtk.Method.get_property=bounds)
+ * @self: a `GtkCanvasItem`
+ *
+ * Gets the bounds that are used to allocate the widget
+ *
+ * Returns: (transfer none): The bounds
+ */
+const GtkCanvasBox *
+gtk_canvas_item_get_bounds (GtkCanvasItem *self)
+{
+  g_return_val_if_fail (GTK_IS_CANVAS_ITEM (self), NULL);
+
+  return &self->bounds;
+}
+
+/**
+ * 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);
+}
diff --git a/gtk/gtkcanvasitem.h b/gtk/gtkcanvasitem.h
new file mode 100644
index 0000000000..15f3ae7bf4
--- /dev/null
+++ b/gtk/gtkcanvasitem.h
@@ -0,0 +1,57 @@
+/*
+ * 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_set_bounds              (GtkCanvasItem          *self,
+                                                                 const GtkCanvasBox     *box);
+GDK_AVAILABLE_IN_ALL
+const GtkCanvasBox *    gtk_canvas_item_get_bounds              (GtkCanvasItem          *self);
+
+G_END_DECLS
+
+#endif  /* __GTK_CANVAS_ITEM_H__ */
diff --git a/gtk/gtkcanvasitemprivate.h b/gtk/gtkcanvasitemprivate.h
new file mode 100644
index 0000000000..f22916e925
--- /dev/null
+++ b/gtk/gtkcanvasitemprivate.h
@@ -0,0 +1,20 @@
+#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_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__ */
diff --git a/gtk/gtkcanvaslayout.h b/gtk/gtkcanvaslayout.h
new file mode 100644
index 0000000000..c85e64ba2a
--- /dev/null
+++ b/gtk/gtkcanvaslayout.h
@@ -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__ */
diff --git a/gtk/gtkcanvaspoint.c b/gtk/gtkcanvaspoint.c
new file mode 100644
index 0000000000..df812b5cf4
--- /dev/null
+++ b/gtk/gtkcanvaspoint.c
@@ -0,0 +1,297 @@
+/*
+ * 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>
+ */
+
+
+/**
+ * GtkCanvasPoint:
+ *
+ * `GtkCanvasPoint` describes a point in a `GtkCanvas`.
+ */
+
+#include "config.h"
+
+#include "gtkcanvaspointprivate.h"
+
+#include "gtkcanvasbox.h"
+
+/* {{{ Boilerplate */
+
+struct _GtkCanvasPointClass
+{
+  const char *type_name;
+
+  void                  (* copy)                (GtkCanvasPoint         *self,
+                                                 const GtkCanvasPoint   *source);
+  void                  (* finish)              (GtkCanvasPoint         *self);
+  gboolean              (* eval)                (const GtkCanvasPoint   *self,
+                                                 float                  *x,
+                                                 float                  *y);
+};
+
+G_DEFINE_BOXED_TYPE (GtkCanvasPoint, gtk_canvas_point,
+                     gtk_canvas_point_copy,
+                     gtk_canvas_point_free)
+
+static gpointer
+gtk_canvas_point_alloc (const GtkCanvasPointClass *class)
+{
+  GtkCanvasPoint *self = g_slice_new (GtkCanvasPoint);
+
+  self->class = class;
+
+  return self;
+}
+
+void
+gtk_canvas_point_init_copy (GtkCanvasPoint       *self,
+                            const GtkCanvasPoint *source)
+{
+  self->class = source->class;
+  self->class->copy (self, source);
+}
+
+void
+gtk_canvas_point_finish (GtkCanvasPoint *self)
+{
+  self->class->finish (self);
+}
+
+/* }}} */
+/* {{{ OFFSET */
+
+static void
+gtk_canvas_point_offset_copy (GtkCanvasPoint       *point,
+                              const GtkCanvasPoint *source_point)
+{
+  GtkCanvasPointOffset *self = &point->offset;
+  const GtkCanvasPointOffset *source = &source_point->offset;
+
+  *self = *source;
+
+  if (source->other)
+    self->other = gtk_canvas_point_copy (source->other);
+}
+
+static void
+gtk_canvas_point_offset_finish (GtkCanvasPoint *point)
+{
+  GtkCanvasPointOffset *self = &point->offset;
+
+  if (self->other)
+    gtk_canvas_point_free (self->other);
+}
+
+static gboolean
+gtk_canvas_point_offset_eval (const GtkCanvasPoint *point,
+                              float                *x,
+                              float                *y)
+{
+  const GtkCanvasPointOffset *self = &point->offset;
+
+  if (self->other != NULL)
+    {
+      if (!gtk_canvas_point_eval (self->other, x, y))
+        return FALSE;
+
+      *x += self->dx;
+      *y += self->dy;
+    }
+  else
+    {
+      *x = self->dx;
+      *y = self->dy;
+    }
+
+  return TRUE;
+}
+
+static const GtkCanvasPointClass GTK_CANVAS_POINT_OFFSET_CLASS =
+{
+  "GtkCanvasPointOffset",
+  gtk_canvas_point_offset_copy,
+  gtk_canvas_point_offset_finish,
+  gtk_canvas_point_offset_eval,
+};
+
+void
+gtk_canvas_point_init (GtkCanvasPoint *point,
+                       float           x,
+                       float           y)
+{
+  GtkCanvasPointOffset *self = &point->offset;
+
+  self->class = &GTK_CANVAS_POINT_OFFSET_CLASS;
+
+  self->other = NULL;
+  self->dx = x;
+  self->dy = y;
+}
+
+/**
+ * gtk_canvas_point_new:
+ * @x: x coordinate
+ * @y: y coordinate
+ *
+ * Creates a new point at the given coordinate.
+ *
+ * Returns: a new point
+ **/
+GtkCanvasPoint *
+gtk_canvas_point_new (float x,
+                      float y)
+{
+  GtkCanvasPoint *self;
+
+  self = gtk_canvas_point_alloc (&GTK_CANVAS_POINT_OFFSET_CLASS);
+
+  gtk_canvas_point_init (self, x, y);
+
+  return self;
+}
+
+/* }}} */
+/* {{{ BOX */
+
+static void
+gtk_canvas_point_box_copy (GtkCanvasPoint       *point,
+                           const GtkCanvasPoint *source_point)
+{
+  GtkCanvasPointBox *self = &point->box;
+  const GtkCanvasPointBox *source = &source_point->box;
+
+  *self = *source;
+
+  self->box = gtk_canvas_box_copy (source->box);
+}
+
+static void
+gtk_canvas_point_box_finish (GtkCanvasPoint *point)
+{
+  GtkCanvasPointBox *self = &point->box;
+
+  gtk_canvas_box_free (self->box);
+}
+
+static gboolean
+gtk_canvas_point_box_eval (const GtkCanvasPoint *point,
+                           float                *x,
+                           float                *y)
+{
+  const GtkCanvasPointBox *self = &point->box;
+  graphene_rect_t rect;
+
+  if (!gtk_canvas_box_eval (self->box, &rect))
+    return FALSE;
+
+  *x = rect.origin.x + self->offset_x + self->origin_x * rect.size.width;
+  *y = rect.origin.y + self->offset_y + self->origin_y * rect.size.height;
+
+  return TRUE;
+}
+
+static const GtkCanvasPointClass GTK_CANVAS_POINT_BOX_CLASS =
+{
+  "GtkCanvasPointBox",
+  gtk_canvas_point_box_copy,
+  gtk_canvas_point_box_finish,
+  gtk_canvas_point_box_eval,
+};
+
+/**
+ * gtk_canvas_point_new_from_box:
+ * @box: a box
+ * @origin_x: x coordinate of box origin
+ * @origin_y: y coordinate of box origin
+ * @offset_x: offset in x direction
+ * @offset_y: offset in y direction
+ *
+ * Creates a point relative to the given box.
+ *
+ * The origin describes where in the box the point is, with
+ * (0, 0) being the top left and (1, 1) being the bottom right
+ * corner of the box.
+ *
+ * The offset is then added to the origin. It may be negative.
+ *
+ * Returns: a new point
+ **/
+GtkCanvasPoint *
+gtk_canvas_point_new_from_box (const GtkCanvasBox *box,
+                               float               origin_x,
+                               float               origin_y,
+                               float               offset_x,
+                               float               offset_y)
+{
+  GtkCanvasPointBox *self;
+
+  g_return_val_if_fail (box != NULL, NULL);
+
+  self = gtk_canvas_point_alloc (&GTK_CANVAS_POINT_BOX_CLASS);
+
+  self->box = gtk_canvas_box_copy (box);
+  self->origin_x = origin_x;
+  self->origin_y = origin_y;
+  self->offset_x = offset_x;
+  self->offset_y = offset_y;
+
+  return (GtkCanvasPoint *) self;
+}
+
+/* }}} */
+/* {{{ PUBLIC API */
+
+GtkCanvasPoint *
+gtk_canvas_point_copy (const GtkCanvasPoint *self)
+{
+  GtkCanvasPoint *copy;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  copy = gtk_canvas_point_alloc (self->class);
+
+  gtk_canvas_point_init_copy (copy, self);
+
+  return copy;
+}
+
+void
+gtk_canvas_point_free (GtkCanvasPoint *self)
+{
+  gtk_canvas_point_finish (self);
+
+  g_slice_free (GtkCanvasPoint, self);
+}
+
+gboolean
+gtk_canvas_point_eval (const GtkCanvasPoint *self,
+                       float                *x,
+                       float                *y)
+{
+  g_return_val_if_fail (self != NULL, FALSE);
+  g_return_val_if_fail (x != NULL, FALSE);
+  g_return_val_if_fail (y != NULL, FALSE);
+
+  if (self->class->eval (self, x, y))
+    return TRUE;
+
+  *x = 0;
+  *y = 0;
+  return FALSE;
+}
+
diff --git a/gtk/gtkcanvaspoint.h b/gtk/gtkcanvaspoint.h
new file mode 100644
index 0000000000..5a35ba3423
--- /dev/null
+++ b/gtk/gtkcanvaspoint.h
@@ -0,0 +1,65 @@
+/*
+ * 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_POINT_H__
+#define __GTK_CANVAS_POINT_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_POINT (gtk_canvas_point_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+GType                   gtk_canvas_point_get_type               (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GtkCanvasPoint *        gtk_canvas_point_copy                   (const GtkCanvasPoint   *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_canvas_point_free                   (GtkCanvasPoint         *self);
+
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_canvas_point_eval                   (const GtkCanvasPoint   *self,
+                                                                 float                  *x,
+                                                                 float                  *y) 
G_GNUC_WARN_UNUSED_RESULT;
+
+GDK_AVAILABLE_IN_ALL
+GtkCanvasPoint *        gtk_canvas_point_new                    (float                   x,
+                                                                 float                   y);
+GDK_AVAILABLE_IN_ALL
+GtkCanvasPoint *        gtk_canvas_point_new_from_box           (const GtkCanvasBox     *box,
+                                                                 float                   origin_x,
+                                                                 float                   origin_y,
+                                                                 float                   offset_x,
+                                                                 float                   offset_y);
+GDK_AVAILABLE_IN_ALL
+GtkCanvasPoint *        gtk_canvas_point_new_from_item          (GtkCanvasItem          *item,
+                                                                 float                   origin_x,
+                                                                 float                   origin_y,
+                                                                 float                   offset_x,
+                                                                 float                   offset_y);
+
+G_END_DECLS
+
+#endif /* __GTK_POINT_H__ */
diff --git a/gtk/gtkcanvaspointprivate.h b/gtk/gtkcanvaspointprivate.h
new file mode 100644
index 0000000000..1d10efa6b3
--- /dev/null
+++ b/gtk/gtkcanvaspointprivate.h
@@ -0,0 +1,52 @@
+#ifndef __GTK_CANVAS_POINT_PRIVATE_H__
+#define __GTK_CANVAS_POINT_PRIVATE_H__
+
+#include "gtkcanvaspoint.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCanvasPointClass GtkCanvasPointClass;
+typedef struct _GtkCanvasPointBox GtkCanvasPointBox;
+typedef struct _GtkCanvasPointItem GtkCanvasPointItem;
+typedef struct _GtkCanvasPointOffset GtkCanvasPointOffset;
+
+struct _GtkCanvasPointBox
+{
+  const GtkCanvasPointClass *class;
+
+  GtkCanvasBox *box;
+  float origin_x;
+  float origin_y;
+  float offset_x;
+  float offset_y;
+};
+
+struct _GtkCanvasPointOffset
+{
+  const GtkCanvasPointClass *class;
+
+  GtkCanvasPoint *other;
+  float dx;
+  float dy;
+};
+
+struct _GtkCanvasPoint
+{
+  union {
+    const GtkCanvasPointClass *class;
+    GtkCanvasPointBox box;
+    GtkCanvasPointOffset offset;
+  };
+};
+
+
+void                    gtk_canvas_point_init                   (GtkCanvasPoint *        point,
+                                                                 float                   x,
+                                                                 float                   y);
+void                    gtk_canvas_point_init_copy              (GtkCanvasPoint         *self,
+                                                                 const GtkCanvasPoint   *source);
+void                    gtk_canvas_point_finish                 (GtkCanvasPoint         *self);
+
+G_END_DECLS
+
+#endif /* __GTK_CANVAS_POINT_PRIVATE_H__ */
diff --git a/gtk/gtkcanvassize.c b/gtk/gtkcanvassize.c
new file mode 100644
index 0000000000..b67b870eec
--- /dev/null
+++ b/gtk/gtkcanvassize.c
@@ -0,0 +1,392 @@
+/*
+ * 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>
+ */
+
+
+/**
+ * GtkCanvasSize:
+ *
+ * `GtkCanvasSize` describes a size in a `GtkCanvas`.
+ */
+
+#include "config.h"
+
+#include "gtkcanvassizeprivate.h"
+
+#include "gtkcanvasbox.h"
+#include "gtkcanvasitem.h"
+#include "gtkwidget.h"
+
+/* {{{ Boilerplate */
+
+struct _GtkCanvasSizeClass
+{
+  const char *type_name;
+
+  void                  (* copy)                (GtkCanvasSize          *self,
+                                                 const GtkCanvasSize    *source);
+  void                  (* finish)              (GtkCanvasSize          *self);
+  gboolean              (* eval)                (const GtkCanvasSize    *self,
+                                                 float                  *width,
+                                                 float                  *height);
+};
+
+G_DEFINE_BOXED_TYPE (GtkCanvasSize, gtk_canvas_size,
+                     gtk_canvas_size_copy,
+                     gtk_canvas_size_free)
+
+static gpointer
+gtk_canvas_size_alloc (const GtkCanvasSizeClass *class)
+{
+  GtkCanvasSize *self = g_slice_new (GtkCanvasSize);
+
+  self->class = class;
+
+  return self;
+}
+
+void
+gtk_canvas_size_init_copy (GtkCanvasSize       *self,
+                           const GtkCanvasSize *source)
+{
+  self->class = source->class;
+  self->class->copy (self, source);
+}
+
+void
+gtk_canvas_size_finish (GtkCanvasSize *self)
+{
+  self->class->finish (self);
+}
+
+/* }}} */
+/* {{{ ABSOLUTE */
+
+static void
+gtk_canvas_size_absolute_copy (GtkCanvasSize       *size,
+                               const GtkCanvasSize *source_size)
+{
+  GtkCanvasSizeAbsolute *self = &size->absolute;
+  const GtkCanvasSizeAbsolute *source = &source_size->absolute;
+
+  *self = *source;
+}
+
+static void
+gtk_canvas_size_absolute_finish (GtkCanvasSize *size)
+{
+}
+
+static gboolean
+gtk_canvas_size_absolute_eval (const GtkCanvasSize *size,
+                               float               *width,
+                               float               *height)
+{
+  const GtkCanvasSizeAbsolute *self = &size->absolute;
+
+  *width = self->width;
+  *height = self->height;
+
+  return TRUE;
+}
+
+static const GtkCanvasSizeClass GTK_CANVAS_SIZE_ABSOLUTE_CLASS =
+{
+  "GtkCanvasSizeAbsolute",
+  gtk_canvas_size_absolute_copy,
+  gtk_canvas_size_absolute_finish,
+  gtk_canvas_size_absolute_eval,
+};
+
+/**
+ * gtk_canvas_size_new:
+ * @width: width of the size
+ * @height: height of the size
+ *
+ * Creates a new size with the given dimensions
+ *
+ * Returns: a new size
+ **/
+GtkCanvasSize *
+gtk_canvas_size_new (float width,
+                     float height)
+{
+  GtkCanvasSizeAbsolute *self;
+
+  self = gtk_canvas_size_alloc (&GTK_CANVAS_SIZE_ABSOLUTE_CLASS);
+  self->width = width;
+  self->height = height;
+
+  return (GtkCanvasSize *) self;
+}
+
+/* }}} */
+/* {{{ BOX */
+
+static void
+gtk_canvas_size_box_copy (GtkCanvasSize       *size,
+                          const GtkCanvasSize *source_size)
+{
+  GtkCanvasSizeBox *self = &size->box;
+  const GtkCanvasSizeBox *source = &source_size->box;
+
+  *self = *source;
+
+  self->box = gtk_canvas_box_copy (source->box);
+}
+
+static void
+gtk_canvas_size_box_finish (GtkCanvasSize *size)
+{
+  GtkCanvasSizeBox *self = &size->box;
+
+  gtk_canvas_box_free (self->box);
+}
+
+static gboolean
+gtk_canvas_size_box_eval (const GtkCanvasSize *size,
+                          float               *width,
+                          float               *height)
+{
+  const GtkCanvasSizeBox *self = &size->box;
+  graphene_rect_t rect;
+
+  if (!gtk_canvas_box_eval (self->box, &rect))
+    return FALSE;
+
+  *width = rect.size.width;
+  *height = rect.size.height;
+
+  return TRUE;
+}
+
+static const GtkCanvasSizeClass GTK_CANVAS_SIZE_BOX_CLASS =
+{
+  "GtkCanvasSizeBox",
+  gtk_canvas_size_box_copy,
+  gtk_canvas_size_box_finish,
+  gtk_canvas_size_box_eval,
+};
+
+/**
+ * gtk_canvas_size_new_from_box:
+ * @box: a box
+ *
+ * Creates a size for the given box.
+ *
+ * Returns: a new size
+ **/
+GtkCanvasSize *
+gtk_canvas_size_new_from_box (const GtkCanvasBox *box)
+{
+  GtkCanvasSizeBox *self;
+
+  g_return_val_if_fail (box != NULL, NULL);
+
+  self = gtk_canvas_size_alloc (&GTK_CANVAS_SIZE_BOX_CLASS);
+
+  /* FIXME: We could potentially just copy the box's size here */
+  self->box = gtk_canvas_box_copy (box);
+
+  return (GtkCanvasSize *) self;
+}
+
+/* }}} */
+/* {{{ MEASURE */
+
+static void
+gtk_canvas_size_measure_copy (GtkCanvasSize       *size,
+                              const GtkCanvasSize *source_size)
+{
+  const GtkCanvasSizeMeasure *source = &source_size->measure;
+
+  gtk_canvas_size_init_measure_item (size, source->item, source->measure);
+}
+
+static void
+gtk_canvas_size_measure_finish (GtkCanvasSize *size)
+{
+}
+
+static gboolean
+gtk_canvas_size_measure_eval (const GtkCanvasSize *size,
+                              float               *width,
+                              float               *height)
+{
+  const GtkCanvasSizeMeasure *self = &size->measure;
+  GtkWidget *widget;
+  int w, h;
+
+  if (self->item == NULL)
+    return FALSE;
+
+  widget = gtk_canvas_item_get_widget (self->item);
+  if (widget == NULL)
+    {
+      *width = 0;
+      *height = 0;
+      return TRUE;
+    }
+
+  if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+    {
+      switch (self->measure)
+      {
+        case GTK_CANVAS_ITEM_MEASURE_MIN_FOR_MIN:
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, &w, NULL, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, w, &h, NULL, NULL, NULL);
+          break;
+        case GTK_CANVAS_ITEM_MEASURE_MIN_FOR_NAT:
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, NULL, &w, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, w, &h, NULL, NULL, NULL);
+          break;
+        case GTK_CANVAS_ITEM_MEASURE_NAT_FOR_MIN:
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, &w, NULL, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, w, NULL, &h, NULL, NULL);
+          break;
+        case GTK_CANVAS_ITEM_MEASURE_NAT_FOR_NAT:
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1, NULL, &w, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, w, NULL, &h, NULL, NULL);
+          break;
+        default:
+          g_assert_not_reached ();
+          w = h = 0;
+          break;
+      }
+    }
+  else
+    {
+      switch (self->measure)
+      {
+        case GTK_CANVAS_ITEM_MEASURE_MIN_FOR_MIN:
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, -1, &h, NULL, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, h, &w, NULL, NULL, NULL);
+          break;
+        case GTK_CANVAS_ITEM_MEASURE_MIN_FOR_NAT:
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, -1, NULL, &h, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, h, &w, NULL, NULL, NULL);
+          break;
+        case GTK_CANVAS_ITEM_MEASURE_NAT_FOR_MIN:
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, -1, &h, NULL, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, h, NULL, &w, NULL, NULL);
+          break;
+        case GTK_CANVAS_ITEM_MEASURE_NAT_FOR_NAT:
+          gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, -1, NULL, &h, NULL, NULL);
+          gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, h, NULL, &w, NULL, NULL);
+          break;
+        default:
+          g_assert_not_reached ();
+          w = h = 0;
+          break;
+      }
+    }
+
+  *width = w;
+  *height = h;
+
+  return TRUE;
+}
+
+static const GtkCanvasSizeClass GTK_CANVAS_SIZE_MEASURE_CLASS =
+{
+  "GtkCanvasSizeMeasure",
+  gtk_canvas_size_measure_copy,
+  gtk_canvas_size_measure_finish,
+  gtk_canvas_size_measure_eval,
+};
+
+void
+gtk_canvas_size_init_measure_item (GtkCanvasSize            *size,
+                                   GtkCanvasItem            *item,
+                                   GtkCanvasItemMeasurement  measure)
+{
+  GtkCanvasSizeMeasure *self = &size->measure;
+
+  self->class = &GTK_CANVAS_SIZE_MEASURE_CLASS;
+  
+  self->item = item;
+  self->measure = measure;
+}
+
+/**
+ * gtk_canvas_size_new_measure_item:
+ * @item: the item
+ * @measure: how to measure the item
+ *
+ * Measures the widget of @item with the given method to determine
+ * a size.
+ *
+ * Returns: a new size
+ **/
+GtkCanvasSize *
+gtk_canvas_size_new_measure_item (GtkCanvasItem            *item,
+                                  GtkCanvasItemMeasurement  measure)
+{
+  GtkCanvasSize *self;
+
+  g_return_val_if_fail (GTK_IS_CANVAS_ITEM (item), NULL);
+
+  self = gtk_canvas_size_alloc (&GTK_CANVAS_SIZE_MEASURE_CLASS);
+
+  gtk_canvas_size_init_measure_item (self, item, measure);
+
+  return self;
+}
+
+/* }}} */
+/* {{{ PUBLIC API */
+
+GtkCanvasSize *
+gtk_canvas_size_copy (const GtkCanvasSize *self)
+{
+  GtkCanvasSize *copy;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  copy = gtk_canvas_size_alloc (self->class);
+
+  gtk_canvas_size_init_copy (copy, self);
+
+  return copy;
+}
+
+void
+gtk_canvas_size_free (GtkCanvasSize *self)
+{
+  gtk_canvas_size_finish (self);
+
+  g_slice_free (GtkCanvasSize, self);
+}
+
+gboolean
+gtk_canvas_size_eval (const GtkCanvasSize *self,
+                      float                *width,
+                      float                *height)
+{
+  g_return_val_if_fail (self != NULL, FALSE);
+  g_return_val_if_fail (width != NULL, FALSE);
+  g_return_val_if_fail (height != NULL, FALSE);
+
+  if (self->class->eval (self, width, height))
+    return TRUE;
+
+  *width = 0;
+  *height = 0;
+  return FALSE;
+}
+
diff --git a/gtk/gtkcanvassize.h b/gtk/gtkcanvassize.h
new file mode 100644
index 0000000000..63a9903e6a
--- /dev/null
+++ b/gtk/gtkcanvassize.h
@@ -0,0 +1,66 @@
+/*
+ * 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_SIZE_H__
+#define __GTK_CANVAS_SIZE_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_SIZE (gtk_canvas_size_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+GType                   gtk_canvas_size_get_type                (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GtkCanvasSize *         gtk_canvas_size_copy                    (const GtkCanvasSize    *self);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_canvas_size_free                    (GtkCanvasSize          *self);
+
+GDK_AVAILABLE_IN_ALL
+gboolean                gtk_canvas_size_eval                    (const GtkCanvasSize    *self,
+                                                                 float                  *width,
+                                                                 float                  *height) 
G_GNUC_WARN_UNUSED_RESULT;
+
+GDK_AVAILABLE_IN_ALL
+GtkCanvasSize *         gtk_canvas_size_new                     (float                   width,
+                                                                 float                   height);
+GDK_AVAILABLE_IN_ALL
+GtkCanvasSize *         gtk_canvas_size_new_from_box            (const GtkCanvasBox     *box);
+
+typedef enum {
+  GTK_CANVAS_ITEM_MEASURE_MIN_FOR_MIN,
+  GTK_CANVAS_ITEM_MEASURE_MIN_FOR_NAT,
+  GTK_CANVAS_ITEM_MEASURE_NAT_FOR_MIN,
+  GTK_CANVAS_ITEM_MEASURE_NAT_FOR_NAT
+} GtkCanvasItemMeasurement;
+
+GDK_AVAILABLE_IN_ALL
+GtkCanvasSize *         gtk_canvas_size_new_measure_item        (GtkCanvasItem          *item,
+                                                                 GtkCanvasItemMeasurement measure);
+
+G_END_DECLS
+
+#endif /* __GTK_SIZE_H__ */
diff --git a/gtk/gtkcanvassizeprivate.h b/gtk/gtkcanvassizeprivate.h
new file mode 100644
index 0000000000..83e14800fa
--- /dev/null
+++ b/gtk/gtkcanvassizeprivate.h
@@ -0,0 +1,56 @@
+#ifndef __GTK_CANVAS_SIZE_PRIVATE_H__
+#define __GTK_CANVAS_SIZE_PRIVATE_H__
+
+#include "gtkcanvassize.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkCanvasSizeClass GtkCanvasSizeClass;
+typedef struct _GtkCanvasSizeAbsolute GtkCanvasSizeAbsolute;
+typedef struct _GtkCanvasSizeBox GtkCanvasSizeBox;
+typedef struct _GtkCanvasSizeMeasure GtkCanvasSizeMeasure;
+
+struct _GtkCanvasSizeAbsolute
+{
+  const GtkCanvasSizeClass *class;
+
+  float width;
+  float height;
+};
+
+struct _GtkCanvasSizeBox
+{
+  const GtkCanvasSizeClass *class;
+
+  GtkCanvasBox *box;
+};
+
+struct _GtkCanvasSizeMeasure
+{
+  const GtkCanvasSizeClass *class;
+
+  GtkCanvasItem *item;
+  GtkCanvasItemMeasurement measure;
+};
+
+struct _GtkCanvasSize
+{
+  union {
+    const GtkCanvasSizeClass *class;
+    GtkCanvasSizeAbsolute absolute;
+    GtkCanvasSizeBox box;
+    GtkCanvasSizeMeasure measure;
+  };
+};
+
+
+void                    gtk_canvas_size_init_measure_item       (GtkCanvasSize          *self,
+                                                                 GtkCanvasItem          *item,
+                                                                 GtkCanvasItemMeasurement measure);
+void                    gtk_canvas_size_init_copy               (GtkCanvasSize         *self,
+                                                                 const GtkCanvasSize   *source);
+void                    gtk_canvas_size_finish                  (GtkCanvasSize         *self);
+
+G_END_DECLS
+
+#endif /* __GTK_CANVAS_SIZE_PRIVATE_H__ */
diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h
index 71afde94b0..0bfe629a89 100644
--- a/gtk/gtktypes.h
+++ b/gtk/gtktypes.h
@@ -37,6 +37,11 @@ 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 _GtkCanvasPoint         GtkCanvasPoint;
+typedef struct _GtkCanvasSize          GtkCanvasSize;
 typedef struct _GtkCssStyleChange      GtkCssStyleChange;
 typedef struct _GtkEventController     GtkEventController;
 typedef struct _GtkGesture             GtkGesture;
diff --git a/gtk/meson.build b/gtk/meson.build
index 2e404f2ac4..63f01bbea8 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -182,6 +182,11 @@ gtk_public_sources = files([
   'gtkbuilderscope.c',
   'gtkbutton.c',
   'gtkcalendar.c',
+  'gtkcanvas.c',
+  'gtkcanvasbox.c',
+  'gtkcanvasitem.c',
+  'gtkcanvaspoint.c',
+  'gtkcanvassize.c',
   'gtkcellarea.c',
   'gtkcellareabox.c',
   'gtkcellareacontext.c',
@@ -473,6 +478,11 @@ gtk_public_headers = files([
   'gtkbuilderscope.h',
   'gtkbutton.h',
   'gtkcalendar.h',
+  'gtkcanvas.h',
+  'gtkcanvasbox.h',
+  'gtkcanvasitem.h',
+  'gtkcanvaspoint.h',
+  'gtkcanvassize.h',
   'gtkcenterbox.h',
   'gtkcenterlayout.h',
   'gtkcellarea.h',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]