From 89ff82924b092302e8be36bf569ac614d248bd77 Mon Sep 17 00:00:00 2001 From: Hari Rana Date: Wed, 1 Jul 2026 06:53:49 -0400 Subject: [PATCH] gui/gizmo: Introduce GcalGizmo This is a utility widget akin to GtkGizmo[1] and AdwGizmo[2]. It is designed for use in complex widgets which require additional features such as snapshotting, measuring/allocating, and focusing capabilities. We want to implement an entry row widget that allows multi-line content. The first implementation had two widgets: GcalNotesRow for the widget itself, and GcalNotesTitle to contain and animate the title by overriding the measure() and size_allocate() class methods. The GcalNotesTitle widget ended up becoming a basic widget that could easily be handled by GcalGizmo. The new implementation simply uses GcalGizmo.[3] Another potential user of GcalGizmo is GcalDateChooser, where the date grid contains complicated logic for handling focus to a child widget. Since GcalGizmo is designed to give full reign over focusability, we can also use it to simplify focus. The layout manager can quickly be swapped to GtkBinLayout since we don't want to handle custom measure/allocation logic.[4] [1]: [2]: [3]: [4]: Part-of: --- src/gui/gcal-gizmo.c | 257 +++++++++++++++++++++++++++++++++++++++++++ src/gui/gcal-gizmo.h | 111 +++++++++++++++++++ src/gui/meson.build | 1 + 3 files changed, 369 insertions(+) create mode 100644 src/gui/gcal-gizmo.c create mode 100644 src/gui/gcal-gizmo.h diff --git a/src/gui/gcal-gizmo.c b/src/gui/gcal-gizmo.c new file mode 100644 index 00000000..9320f473 --- /dev/null +++ b/src/gui/gcal-gizmo.c @@ -0,0 +1,257 @@ +/* gcal-gizmo.c + * + * Copyright (C) 2020 Purism SPC + * Copyright 2025 Hari Rana + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#include "config.h" + +#include "gcal-gizmo.h" + +/** + * GcalGizmo: + * + * A very basic widget for setting virtual functions affecting layout. + * + * This is only meant to be used for custom widgets that need child widgets with custom + * functions that contribute to layout. + */ + +struct _GcalGizmo +{ + GtkWidget parent_instance; + + GcalGizmoMeasureFunc measure_func; + gpointer measure_user_data; + + GcalGizmoSizeAllocateFunc size_allocate_func; + gpointer size_allocate_user_data; + + GcalGizmoSnapshotFunc snapshot_func; + gpointer snapshot_user_data; + + GcalGizmoFocusFunc focus_func; + gpointer focus_user_data; +}; + +G_DEFINE_FINAL_TYPE (GcalGizmo, gcal_gizmo, GTK_TYPE_WIDGET) + + +/* + * GtkWidget overrides + */ + +static void +gcal_gizmo_real_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GcalGizmo *self = (GcalGizmo *) widget; + + g_assert (GCAL_IS_GIZMO (self)); + + if (self->measure_func) + { + self->measure_func (self, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline, + self->measure_user_data); + } +} + +static void +gcal_gizmo_real_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GcalGizmo *self = (GcalGizmo *) widget; + + g_assert (GCAL_IS_GIZMO (self)); + + if (self->size_allocate_func) + self->size_allocate_func (self, width, height, baseline, self->size_allocate_user_data); +} + +static void +gcal_gizmo_real_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GcalGizmo *self = (GcalGizmo *) widget; + + g_assert (GCAL_IS_GIZMO (self)); + + if (self->snapshot_func) + self->snapshot_func (self, snapshot, self->snapshot_user_data); + else + GTK_WIDGET_CLASS (gcal_gizmo_parent_class)->snapshot (widget, snapshot); +} + +static gboolean +gcal_gizmo_real_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GcalGizmo *self = (GcalGizmo *) widget; + + g_assert (GCAL_IS_GIZMO (self)); + + if (self->focus_func) + return self->focus_func (self, direction, self->focus_user_data); + + return FALSE; +} + + +/* + * GObject overrides + */ + +static void +gcal_gizmo_dispose (GObject *object) +{ + GtkWidget *widget = GTK_WIDGET (object); + GtkWidget *child; + + while ((child = gtk_widget_get_first_child (widget))) + gtk_widget_unparent (child); + + G_OBJECT_CLASS (gcal_gizmo_parent_class)->dispose (object); +} + + +/* + * Initialization + */ + +static void +gcal_gizmo_class_init (GcalGizmoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gcal_gizmo_dispose; + + widget_class->measure = gcal_gizmo_real_measure; + widget_class->size_allocate = gcal_gizmo_real_size_allocate; + widget_class->snapshot = gcal_gizmo_real_snapshot; + widget_class->focus = gcal_gizmo_real_focus; +} + +static void +gcal_gizmo_init (GcalGizmo *self) +{ +} + + +/** + * gcal_gizmo_new: + * + * Create a new #GcalGizmo. + * + * Returns: (transfer full): a newly created #GcalGizmo + */ +GcalGizmo * +gcal_gizmo_new (void) +{ + return g_object_new (GCAL_TYPE_GIZMO, NULL); +} + +/** + * gcal_gizmo_set_measure_func: + * @self: a #GcalGizmo + * @measure_func: (scope call): the function to set + * + * Set the function overriding [vfunc@Gtk.Widget.measure]. + */ +void +gcal_gizmo_set_measure_func (GcalGizmo *self, + GcalGizmoMeasureFunc measure_func, + gpointer user_data) +{ + g_assert (GCAL_IS_GIZMO (self)); + + self->measure_func = measure_func; + self->measure_user_data = user_data; + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +/** + * gcal_gizmo_set_size_allocate_func: + * @self: a #GcalGizmo + * @size_allocate_func: (scope call): the function to set + * + * Set the function overriding [vfunc@Gtk.Widget.size_allocate]. + */ +void +gcal_gizmo_set_size_allocate_func (GcalGizmo *self, + GcalGizmoSizeAllocateFunc size_allocate_func, + gpointer user_data) +{ + g_assert (GCAL_IS_GIZMO (self)); + + self->size_allocate_func = size_allocate_func; + self->size_allocate_user_data = user_data; + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +/** + * gcal_gizmo_set_snapshot_func: + * @self: a #GcalGizmo + * @snapshot_func: (scope call): the function to set + * + * Set the function overriding [vfunc@Gtk.Widget.snapshot]. + */ +void +gcal_gizmo_set_snapshot_func (GcalGizmo *self, + GcalGizmoSnapshotFunc snapshot_func, + gpointer user_data) +{ + g_assert (GCAL_IS_GIZMO (self)); + + self->snapshot_func = snapshot_func; + self->snapshot_user_data = user_data; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +/** + * gcal_gizmo_set_focus_func: + * @self: a #GcalGizmo + * @focus_func: (scope call): the function to set + * + * Set the function overriding [vfunc@Gtk.Widget.focus]. + */ +void +gcal_gizmo_set_focus_func (GcalGizmo *self, + GcalGizmoFocusFunc focus_func, + gpointer user_data) +{ + g_assert (GCAL_IS_GIZMO (self)); + + self->focus_func = focus_func; + self->focus_user_data = user_data; +} + diff --git a/src/gui/gcal-gizmo.h b/src/gui/gcal-gizmo.h new file mode 100644 index 00000000..ff4ed8ab --- /dev/null +++ b/src/gui/gcal-gizmo.h @@ -0,0 +1,111 @@ +/* gcal-gizmo.h + * + * Copyright (C) 2020 Purism SPC + * Copyright 2025 Hari Rana + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_GIZMO (gcal_gizmo_get_type ()) + +G_DECLARE_FINAL_TYPE (GcalGizmo, gcal_gizmo, GCAL, GIZMO, GtkWidget) + +/** + * GcalGizmoMeasureFunc: + * @self: a #GcalGizmo + * @orientation: the orientation to measure + * @for_size: size for the opposite of @orientation, or -1 if unknown + * @minimum: (out) (optional): location to store the minimum size + * @natural: (out) (optional): location to store the natural size + * @minimum_baseline: (out) (optional): + * location to store the baseline position for the minimum size, or -1 to report no baseline + * @natural_baseline: (out) (optional): + * location to store the baseline position for the natural size, or -1 to report no baseline + * @user_data: (nullable): user data + * + * Function wrapper for overriding [vfunc@Gtk.Widget.measure]. + */ +typedef void (*GcalGizmoMeasureFunc) (GcalGizmo *self, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline, + gpointer user_data); + +/** + * GcalGizmoSizeAllocateFunc: + * @self: a #GcalGizmo + * @width: the width + * @height: the height + * @baseline: the baseline, or -1 + * @user_data: (nullable): user data + * + * Function wrapper for overriding [vfunc@Gtk.Widget.size_allocate]. + */ +typedef void (*GcalGizmoSizeAllocateFunc) (GcalGizmo *self, + int width, + int height, + int baseline, + gpointer user_data); + +/** + * GcalGizmoFocusFunc: + * @self: a #GcalGizmo + * @direction: the direction + * @user_data: (nullable): user data + * + * Function wrapper for overriding [vfunc@Gtk.Widget.focus]. + */ +typedef gboolean (*GcalGizmoFocusFunc) (GcalGizmo *self, + GtkDirectionType direction, + gpointer user_data); + +/** + * GcalGizmoSnapshotFunc: + * @self: a #GcalGizmo + * @snapshot: the snapshot + * @user_data: (nullable): user data + * + * Function wrapper for overriding [vfunc@Gtk.Widget.snapshot]. + */ +typedef void (*GcalGizmoSnapshotFunc) (GcalGizmo *self, + GtkSnapshot *snapshot, + gpointer user_data); + +GcalGizmo *gcal_gizmo_new (void); +void gcal_gizmo_set_measure_func (GcalGizmo *self, + GcalGizmoMeasureFunc measure_func, + gpointer user_data); +void gcal_gizmo_set_size_allocate_func (GcalGizmo *self, + GcalGizmoSizeAllocateFunc size_allocate_func, + gpointer user_data); +void gcal_gizmo_set_snapshot_func (GcalGizmo *self, + GcalGizmoSnapshotFunc snapshot_func, + gpointer user_data); +void gcal_gizmo_set_focus_func (GcalGizmo *self, + GcalGizmoFocusFunc focus_func, + gpointer user_data); + +G_END_DECLS + diff --git a/src/gui/meson.build b/src/gui/meson.build index 182b1e0e..12b05de0 100644 --- a/src/gui/meson.build +++ b/src/gui/meson.build @@ -49,6 +49,7 @@ sources += files( 'gcal-event-widget.c', 'gcal-event-widget-pool.c', 'gcal-fading-label.c', + 'gcal-gizmo.c', 'gcal-meeting-row.c', 'gcal-overflow-bin.c', 'gcal-overflow-bin-layout.c',