summaryrefslogtreecommitdiff
path: root/src/widgets/inset-container.c
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2024-05-06 13:42:55 +0200
committerHannes Schulze <haschu0103@outlook.de>2024-05-06 14:33:54 +0200
commit37d5ef75306c45ca95550f4af40d9750c5b9cb88 (patch)
tree7125c8e8445eeecb86097bc99bb2d1febfb7e2a5 /src/widgets/inset-container.c
initial commit
yup, i definitely wrote all of this in a single commit
Diffstat (limited to 'src/widgets/inset-container.c')
-rw-r--r--src/widgets/inset-container.c363
1 files changed, 363 insertions, 0 deletions
diff --git a/src/widgets/inset-container.c b/src/widgets/inset-container.c
new file mode 100644
index 0000000..9b9b132
--- /dev/null
+++ b/src/widgets/inset-container.c
@@ -0,0 +1,363 @@
+#include "widgets/inset-container.h"
+
+//
+// FoobarInsetContainer:
+//
+// A custom widget which renders a child with an inset/padding.
+//
+// Notably, it also supports negative inset values which is useful in order to provide uniform spacing between widgets.
+//
+
+struct _FoobarInsetContainer
+{
+ GtkWidget parent_instance;
+ GtkWidget* child;
+ gint inset_horizontal;
+ gint inset_vertical;
+};
+
+enum
+{
+ PROP_CHILD = 1,
+ PROP_INSET_HORIZONTAL,
+ PROP_INSET_VERTICAL,
+ N_PROPS,
+};
+
+static GParamSpec* props[N_PROPS] = { 0 };
+
+static void foobar_inset_container_class_init ( FoobarInsetContainerClass* klass );
+static void foobar_inset_container_init ( FoobarInsetContainer* self );
+static void foobar_inset_container_get_property ( GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec );
+static void foobar_inset_container_set_property ( GObject* object,
+ guint prop_id,
+ GValue const* value,
+ GParamSpec* pspec );
+static void foobar_inset_container_dispose ( GObject* object );
+static GtkSizeRequestMode foobar_inset_container_get_request_mode( GtkWidget* widget );
+static void foobar_inset_container_measure ( GtkWidget* widget,
+ GtkOrientation orientation,
+ int for_size,
+ int* minimum,
+ int* natural,
+ int* minimum_baseline,
+ int* natural_baseline );
+static void foobar_inset_container_size_allocate ( GtkWidget* widget,
+ int width,
+ int height,
+ int baseline );
+
+G_DEFINE_FINAL_TYPE( FoobarInsetContainer, foobar_inset_container, GTK_TYPE_WIDGET )
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Widget Implementation
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Static initialization for inset containers.
+//
+void foobar_inset_container_class_init( FoobarInsetContainerClass* klass )
+{
+ GtkWidgetClass* widget_klass = GTK_WIDGET_CLASS( klass );
+ widget_klass->get_request_mode = foobar_inset_container_get_request_mode;
+ widget_klass->measure = foobar_inset_container_measure;
+ widget_klass->size_allocate = foobar_inset_container_size_allocate;
+ gtk_widget_class_set_css_name( widget_klass, "inset-container" );
+
+ GObjectClass* object_klass = G_OBJECT_CLASS( klass );
+ object_klass->get_property = foobar_inset_container_get_property;
+ object_klass->set_property = foobar_inset_container_set_property;
+ object_klass->dispose = foobar_inset_container_dispose;
+
+ props[PROP_CHILD] = g_param_spec_object(
+ "child",
+ "Child",
+ "Child widget for the panel item.",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE );
+ props[PROP_INSET_HORIZONTAL] = g_param_spec_int(
+ "inset-horizontal",
+ "Inset Horizontal",
+ "The horizontal offset (may be negative).",
+ INT_MIN,
+ INT_MAX,
+ 0,
+ G_PARAM_READWRITE );
+ props[PROP_INSET_VERTICAL] = g_param_spec_int(
+ "inset-vertical",
+ "Inset Vertical",
+ "The vertical offset (may be negative).",
+ INT_MIN,
+ INT_MAX,
+ 0,
+ G_PARAM_READWRITE );
+ g_object_class_install_properties( object_klass, N_PROPS, props );
+}
+
+//
+// Instance initialization for inset containers.
+//
+void foobar_inset_container_init( FoobarInsetContainer* self )
+{
+ (void)self;
+}
+
+//
+// Property getter implementation, mapping a property id to a method.
+//
+void foobar_inset_container_get_property(
+ GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec )
+{
+ FoobarInsetContainer* self = (FoobarInsetContainer*)object;
+
+ switch ( prop_id )
+ {
+ case PROP_CHILD:
+ g_value_set_object( value, foobar_inset_container_get_child( self ) );
+ break;
+ case PROP_INSET_HORIZONTAL:
+ g_value_set_int( value, foobar_inset_container_get_inset_horizontal( self ) );
+ break;
+ case PROP_INSET_VERTICAL:
+ g_value_set_int( value, foobar_inset_container_get_inset_vertical( self ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Property setter implementation, mapping a property id to a method.
+//
+void foobar_inset_container_set_property(
+ GObject* object,
+ guint prop_id,
+ GValue const* value,
+ GParamSpec* pspec )
+{
+ FoobarInsetContainer* self = (FoobarInsetContainer*)object;
+
+ switch ( prop_id )
+ {
+ case PROP_CHILD:
+ foobar_inset_container_set_child( self, g_value_get_object( value ) );
+ break;
+ case PROP_INSET_HORIZONTAL:
+ foobar_inset_container_set_inset_horizontal( self, g_value_get_int( value ) );
+ break;
+ case PROP_INSET_VERTICAL:
+ foobar_inset_container_set_inset_vertical( self, g_value_get_int( value ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Instance de-initialization for inset containers.
+//
+void foobar_inset_container_dispose( GObject* object )
+{
+ FoobarInsetContainer* self = (FoobarInsetContainer*)object;
+
+ g_clear_pointer( &self->child, gtk_widget_unparent );
+
+ G_OBJECT_CLASS( foobar_inset_container_parent_class )->dispose( object );
+}
+
+//
+// Determine the size request mode used to measure the widget.
+//
+// This implementation just forwards the request mode from the child widget.
+//
+GtkSizeRequestMode foobar_inset_container_get_request_mode( GtkWidget* widget )
+{
+ FoobarInsetContainer* self = (FoobarInsetContainer*)widget;
+ if ( !self->child ) { return GTK_SIZE_REQUEST_CONSTANT_SIZE; }
+
+ return gtk_widget_get_request_mode( self->child );
+}
+
+//
+// Compute the requested size of the widget.
+//
+// This implementation uses the size requested by the child widget and then adds the inset to it.
+//
+void foobar_inset_container_measure(
+ GtkWidget* widget,
+ GtkOrientation orientation,
+ int for_size,
+ int* minimum,
+ int* natural,
+ int* minimum_baseline,
+ int* natural_baseline )
+{
+ FoobarInsetContainer* self = (FoobarInsetContainer*)widget;
+ if ( !self->child )
+ {
+ *minimum = 0;
+ *natural = 0;
+ *minimum_baseline = 0;
+ *natural_baseline = 0;
+ return;
+ }
+
+ // Get the inset for the given orientation and the other one.
+
+ gint inset;
+ gint other_inset;
+ switch ( orientation )
+ {
+ case GTK_ORIENTATION_HORIZONTAL:
+ inset = self->inset_horizontal;
+ other_inset = self->inset_vertical;
+ break;
+ case GTK_ORIENTATION_VERTICAL:
+ inset = self->inset_vertical;
+ other_inset = self->inset_horizontal;
+ break;
+ default:
+ g_warn_if_reached( );
+ return;
+ }
+
+ // Compute the new size, taking into consideration that the inset might have already been added to measure the other
+ // side and needs to be subtracted before passing it to the child widget's measure function.
+
+ for_size = MAX( 0, for_size - ( 2 * other_inset ) );
+ gtk_widget_measure( self->child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline );
+ *minimum = MAX( 0, *minimum + ( 2 * inset ) );
+ *natural = MAX( 0, *natural + ( 2 * inset ) );
+ if ( orientation == GTK_ORIENTATION_VERTICAL )
+ {
+ *minimum_baseline = MAX( 0, *minimum_baseline + inset );
+ *natural_baseline = MAX( 0, *natural_baseline + inset );
+ }
+}
+
+//
+// Arrange the child widget.
+//
+void foobar_inset_container_size_allocate(
+ GtkWidget* widget,
+ int width,
+ int height,
+ int baseline )
+{
+ FoobarInsetContainer* self = (FoobarInsetContainer*)widget;
+ if ( !self->child ) { return; }
+
+ GtkAllocation allocation =
+ {
+ .width = MAX( 0, width - 2 * self->inset_horizontal ),
+ .height = MAX( 0, height - 2 * self->inset_vertical ),
+ .x = self->inset_horizontal,
+ .y = self->inset_vertical,
+ };
+ gtk_widget_size_allocate( self->child, &allocation, MAX( 0, baseline + self->inset_vertical ) );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Public API
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Create a new inset container instance.
+//
+GtkWidget* foobar_inset_container_new( void )
+{
+ return g_object_new( FOOBAR_TYPE_INSET_CONTAINER, NULL );
+}
+
+//
+// Get the container's child widget.
+//
+GtkWidget* foobar_inset_container_get_child( FoobarInsetContainer* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_INSET_CONTAINER( self ), NULL );
+ return self->child;
+}
+
+//
+// Get the inset applied to the left and right side of the widget.
+//
+gint foobar_inset_container_get_inset_horizontal( FoobarInsetContainer* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_INSET_CONTAINER( self ), 0 );
+ return self->inset_horizontal;
+}
+
+//
+// Get the inset applied to the top and bottom side of the widget.
+//
+gint foobar_inset_container_get_inset_vertical( FoobarInsetContainer* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_INSET_CONTAINER( self ), 0 );
+ return self->inset_vertical;
+}
+
+//
+// Update the container's child widget.
+//
+void foobar_inset_container_set_child(
+ FoobarInsetContainer* self,
+ GtkWidget* child )
+{
+ g_return_if_fail( FOOBAR_IS_INSET_CONTAINER( self ) );
+ g_return_if_fail( child == NULL || self->child == child || gtk_widget_get_parent( child ) == NULL );
+
+ if ( self->child != child )
+ {
+ g_clear_pointer( &self->child, gtk_widget_unparent );
+
+ if ( child )
+ {
+ self->child = child;
+ gtk_widget_set_parent( child, GTK_WIDGET( self ) );
+ }
+
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_CHILD] );
+ }
+}
+
+//
+// Update the inset applied to the left and right side of the widget.
+//
+void foobar_inset_container_set_inset_horizontal(
+ FoobarInsetContainer* self,
+ gint value )
+{
+ g_return_if_fail( FOOBAR_IS_INSET_CONTAINER( self ) );
+
+ if ( self->inset_horizontal != value )
+ {
+ self->inset_horizontal = value;
+ gtk_widget_queue_resize( GTK_WIDGET( self ) );
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_INSET_HORIZONTAL] );
+ }
+}
+
+//
+// Update the inset applied to the top and bottom side of the widget.
+//
+void foobar_inset_container_set_inset_vertical(
+ FoobarInsetContainer* self,
+ gint value )
+{
+ g_return_if_fail( FOOBAR_IS_INSET_CONTAINER( self ) );
+
+ if ( self->inset_vertical != value )
+ {
+ self->inset_vertical = value;
+ gtk_widget_queue_resize( GTK_WIDGET( self ) );
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_INSET_VERTICAL] );
+ }
+} \ No newline at end of file