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]: <https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gtk/gtkgizmo.c>
[2]: <https://gitlab.gnome.org/GNOME/libadwaita/-/blob/main/src/adw-gizmo.c>
[3]: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/737>
[4]: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/760>

Part-of: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/790>
This commit is contained in:
Hari Rana
2026-09-09 14:57:14 -04:00
parent d1b7d4aa7b
commit 89ff82924b
3 changed files with 369 additions and 0 deletions
+257
View File
@@ -0,0 +1,257 @@
/* gcal-gizmo.c
*
* Copyright (C) 2020 Purism SPC
* Copyright 2025 Hari Rana <theevilskeleton@riseup.net>
*
* 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 <http://www.gnu.org/licenses/>.
*
* 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;
}
+111
View File
@@ -0,0 +1,111 @@
/* gcal-gizmo.h
*
* Copyright (C) 2020 Purism SPC
* Copyright 2025 Hari Rana <theevilskeleton@riseup.net>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
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
+1
View File
@@ -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',