diff options
| author | Hannes Schulze <haschu0103@outlook.de> | 2024-05-06 13:42:55 +0200 |
|---|---|---|
| committer | Hannes Schulze <haschu0103@outlook.de> | 2024-05-06 14:33:54 +0200 |
| commit | 37d5ef75306c45ca95550f4af40d9750c5b9cb88 (patch) | |
| tree | 7125c8e8445eeecb86097bc99bb2d1febfb7e2a5 /src/widgets/control-center | |
initial commit
yup, i definitely wrote all of this in a single commit
Diffstat (limited to 'src/widgets/control-center')
| -rw-r--r-- | src/widgets/control-center/control-button.c | 496 | ||||
| -rw-r--r-- | src/widgets/control-center/control-button.h | 31 | ||||
| -rw-r--r-- | src/widgets/control-center/control-details-item.c | 307 | ||||
| -rw-r--r-- | src/widgets/control-center/control-details-item.h | 32 | ||||
| -rw-r--r-- | src/widgets/control-center/control-details.c | 289 | ||||
| -rw-r--r-- | src/widgets/control-center/control-details.h | 22 | ||||
| -rw-r--r-- | src/widgets/control-center/control-slider.c | 428 | ||||
| -rw-r--r-- | src/widgets/control-center/control-slider.h | 28 | ||||
| -rw-r--r-- | src/widgets/control-center/meson.build | 6 |
9 files changed, 1639 insertions, 0 deletions
diff --git a/src/widgets/control-center/control-button.c b/src/widgets/control-center/control-button.c new file mode 100644 index 0000000..f535a61 --- /dev/null +++ b/src/widgets/control-center/control-button.c @@ -0,0 +1,496 @@ +#include "widgets/control-center/control-button.h" + +// +// FoobarControlButton: +// +// A button in the "controls" section of the control center. This can be a regular button or a toggle button with an +// optional secondary button to toggle a details view. +// + +struct _FoobarControlButton +{ + GtkWidget parent_instance; + gchar* icon_name; + gchar* label; + gboolean can_expand; + gboolean can_toggle; + gboolean is_expanded; + gboolean is_toggled; +}; + +enum +{ + PROP_ICON_NAME = 1, + PROP_LABEL, + PROP_CAN_EXPAND, + PROP_CAN_TOGGLE, + PROP_IS_EXPANDED, + PROP_IS_TOGGLED, + N_PROPS, +}; + +static GParamSpec* props[N_PROPS] = { 0 }; + +static void foobar_control_button_class_init ( FoobarControlButtonClass* klass ); +static void foobar_control_button_init ( FoobarControlButton* self ); +static void foobar_control_button_get_property ( GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ); +static void foobar_control_button_set_property ( GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ); +static void foobar_control_button_dispose ( GObject* object ); +static void foobar_control_button_finalize ( GObject* object ); +static void foobar_control_button_handle_primary_clicked( GtkButton* button, + gpointer userdata ); +static void foobar_control_button_handle_expand_clicked ( GtkButton* button, + gpointer userdata ); + +G_DEFINE_FINAL_TYPE( FoobarControlButton, foobar_control_button, GTK_TYPE_WIDGET ) + +// --------------------------------------------------------------------------------------------------------------------- +// Widget Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for control buttons. +// +void foobar_control_button_class_init( FoobarControlButtonClass* klass ) +{ + GtkWidgetClass* widget_klass = GTK_WIDGET_CLASS( klass ); + gtk_widget_class_set_css_name( widget_klass, "control-button" ); + gtk_widget_class_set_layout_manager_type( widget_klass, GTK_TYPE_BOX_LAYOUT ); + + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->get_property = foobar_control_button_get_property; + object_klass->set_property = foobar_control_button_set_property; + object_klass->dispose = foobar_control_button_dispose; + object_klass->finalize = foobar_control_button_finalize; + + props[PROP_ICON_NAME] = g_param_spec_string( + "icon-name", + "Icon Name", + "Name of the icon to be displayed next to the label.", + NULL, + G_PARAM_READWRITE ); + props[PROP_LABEL] = g_param_spec_string( + "label", + "Label", + "The button's main label.", + NULL, + G_PARAM_READWRITE ); + props[PROP_CAN_EXPAND] = g_param_spec_boolean( + "can-expand", + "Can Expand", + "Indicates whether some details are hidden and can be revealed by a toggle button next to the main action.", + FALSE, + G_PARAM_READWRITE ); + props[PROP_CAN_TOGGLE] = g_param_spec_boolean( + "can-toggle", + "Can Toggle", + "Indicates whether the main button can be toggled.", + FALSE, + G_PARAM_READWRITE ); + props[PROP_IS_EXPANDED] = g_param_spec_boolean( + "is-expanded", + "Is Expanded", + "Indicates whether additional details should be shown.", + FALSE, + G_PARAM_READWRITE ); + props[PROP_IS_TOGGLED] = g_param_spec_boolean( + "is-toggled", + "Is Toggled", + "Indicates whether the main button is currently toggled.", + FALSE, + G_PARAM_READWRITE ); + g_object_class_install_properties( object_klass, N_PROPS, props ); +} + +// +// Instance initialization for control buttons. +// +void foobar_control_button_init( FoobarControlButton* self ) +{ + GtkWidget* icon = gtk_image_new( ); + gtk_widget_set_valign( icon, GTK_ALIGN_CENTER ); + g_object_bind_property( + self, + "icon-name", + icon, + "icon-name", + G_BINDING_SYNC_CREATE ); + + GtkWidget* label = gtk_label_new( NULL ); + gtk_widget_set_valign( label, GTK_ALIGN_CENTER ); + gtk_widget_set_hexpand( label, TRUE ); + gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); + gtk_label_set_xalign( GTK_LABEL( label ), 0 ); + g_object_bind_property( + self, + "label", + label, + "label", + G_BINDING_SYNC_CREATE ); + + GtkWidget* content = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); + gtk_box_append( GTK_BOX( content ), icon ); + gtk_box_append( GTK_BOX( content ), label ); + + GtkWidget* primary_button = gtk_button_new( ); + gtk_widget_add_css_class( primary_button, "primary" ); + gtk_widget_set_hexpand( primary_button, TRUE ); + gtk_button_set_child( GTK_BUTTON( primary_button ), content ); + g_object_bind_property( + self, + "can-toggle", + primary_button, + "sensitive", + G_BINDING_SYNC_CREATE ); + g_signal_connect( primary_button, "clicked", G_CALLBACK( foobar_control_button_handle_primary_clicked ), self ); + + GtkWidget* expand_button = gtk_button_new_from_icon_name( "fluent-chevron-right-symbolic" ); + gtk_widget_add_css_class( expand_button, "expand" ); + g_object_bind_property( + self, + "can-expand", + expand_button, + "visible", + G_BINDING_SYNC_CREATE ); + g_signal_connect( expand_button, "clicked", G_CALLBACK( foobar_control_button_handle_expand_clicked ), self ); + + GtkLayoutManager* layout = gtk_widget_get_layout_manager( GTK_WIDGET( self ) ); + gtk_orientable_set_orientation( GTK_ORIENTABLE( layout ), GTK_ORIENTATION_HORIZONTAL ); + gtk_widget_insert_before( primary_button, GTK_WIDGET( self ), NULL ); + gtk_widget_insert_before( expand_button, GTK_WIDGET( self ), NULL ); +} + +// +// Property getter implementation, mapping a property id to a method. +// +void foobar_control_button_get_property( + GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ) +{ + FoobarControlButton* self = (FoobarControlButton*)object; + + switch ( prop_id ) + { + case PROP_ICON_NAME: + g_value_set_string( value, foobar_control_button_get_icon_name( self ) ); + break; + case PROP_LABEL: + g_value_set_string( value, foobar_control_button_get_label( self ) ); + break; + case PROP_CAN_EXPAND: + g_value_set_boolean( value, foobar_control_button_can_expand( self ) ); + break; + case PROP_CAN_TOGGLE: + g_value_set_boolean( value, foobar_control_button_can_toggle( self ) ); + break; + case PROP_IS_EXPANDED: + g_value_set_boolean( value, foobar_control_button_is_expanded( self ) ); + break; + case PROP_IS_TOGGLED: + g_value_set_boolean( value, foobar_control_button_is_toggled( 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_control_button_set_property( + GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ) +{ + FoobarControlButton* self = (FoobarControlButton*)object; + + switch ( prop_id ) + { + case PROP_ICON_NAME: + foobar_control_button_set_icon_name( self, g_value_get_string( value ) ); + break; + case PROP_LABEL: + foobar_control_button_set_label( self, g_value_get_string( value ) ); + break; + case PROP_CAN_EXPAND: + foobar_control_button_set_can_expand( self, g_value_get_boolean( value ) ); + break; + case PROP_CAN_TOGGLE: + foobar_control_button_set_can_toggle( self, g_value_get_boolean( value ) ); + break; + case PROP_IS_EXPANDED: + foobar_control_button_set_expanded( self, g_value_get_boolean( value ) ); + break; + case PROP_IS_TOGGLED: + foobar_control_button_set_toggled( self, g_value_get_boolean( value ) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + break; + } +} + +// +// Instance de-initialization for control buttons. +// +void foobar_control_button_dispose( GObject* object ) +{ + FoobarControlButton* self = (FoobarControlButton*)object; + + GtkWidget* child; + while ( ( child = gtk_widget_get_first_child( GTK_WIDGET( self ) ) ) ) + { + gtk_widget_unparent( child ); + } + + G_OBJECT_CLASS( foobar_control_button_parent_class )->dispose( object ); +} + +// +// Instance cleanup for control buttons. +// +void foobar_control_button_finalize( GObject* object ) +{ + FoobarControlButton* self = (FoobarControlButton*)object; + + g_clear_pointer( &self->icon_name, g_free ); + g_clear_pointer( &self->label, g_free ); + + G_OBJECT_CLASS( foobar_control_button_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new control button instance. +// +GtkWidget* foobar_control_button_new( void ) +{ + return g_object_new( FOOBAR_TYPE_CONTROL_BUTTON, NULL ); +} + +// +// Get the name of the icon to be displayed next to the label. +// +gchar const* foobar_control_button_get_icon_name( FoobarControlButton* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ), NULL ); + return self->icon_name; +} + +// +// Get the button's main label. +// +gchar const* foobar_control_button_get_label( FoobarControlButton* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ), NULL ); + return self->label; +} + +// +// Check whether some details are hidden and can be revealed by a toggle button next to the main action. +// +gboolean foobar_control_button_can_expand( FoobarControlButton* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ), FALSE ); + return self->can_expand; +} + +// +// Check whether the main button can be toggled. +// +gboolean foobar_control_button_can_toggle( FoobarControlButton* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ), FALSE ); + return self->can_toggle; +} + +// +// Check whether additional details should currently be shown. +// +gboolean foobar_control_button_is_expanded( FoobarControlButton* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ), FALSE ); + return self->is_expanded; +} + +// +// Check whether the main button is currently toggled. +// +gboolean foobar_control_button_is_toggled( FoobarControlButton* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ), FALSE ); + return self->is_toggled; +} + +// +// Update the name of the icon to be displayed next to the label. +// +void foobar_control_button_set_icon_name( + FoobarControlButton* self, + gchar const* value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ) ); + + if ( g_strcmp0( self->icon_name, value ) ) + { + g_clear_pointer( &self->icon_name, g_free ); + self->icon_name = g_strdup( value ); + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_ICON_NAME] ); + } +} + +// +// Update the button's main label. +// +void foobar_control_button_set_label( + FoobarControlButton* self, + gchar const* value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ) ); + + if ( g_strcmp0( self->label, value ) ) + { + g_clear_pointer( &self->label, g_free ); + self->label = g_strdup( value ); + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_LABEL] ); + } +} + +// +// Update whether some details are hidden and can be revealed by a toggle button next to the main action. +// +void foobar_control_button_set_can_expand( + FoobarControlButton* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ) ); + + if ( !value ) { foobar_control_button_set_expanded( self, FALSE ); } + + value = !!value; + if ( self->can_expand != value ) + { + self->can_expand = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_CAN_EXPAND] ); + } +} + +// +// Update whether the main button can be toggled. +// +void foobar_control_button_set_can_toggle( + FoobarControlButton* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ) ); + + if ( !value ) { foobar_control_button_set_toggled( self, FALSE ); } + + value = !!value; + if ( self->can_toggle != value ) + { + self->can_toggle = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_CAN_TOGGLE] ); + } +} + +// +// Update whether additional details are currently shown. +// +void foobar_control_button_set_expanded( + FoobarControlButton* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ) ); + + value = value && self->can_expand; + if ( self->is_expanded != value ) + { + self->is_expanded = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_EXPANDED] ); + + if ( self->is_expanded ) + { + gtk_widget_add_css_class( GTK_WIDGET( self ), "expanded" ); + } + else + { + gtk_widget_remove_css_class( GTK_WIDGET( self ), "expanded" ); + } + } +} + +// +// Update whether the main button is currently toggled. +// +void foobar_control_button_set_toggled( + FoobarControlButton* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_BUTTON( self ) ); + + value = value && self->can_toggle; + if ( self->is_toggled != value ) + { + self->is_toggled = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_TOGGLED] ); + + if ( self->is_toggled ) + { + gtk_widget_add_css_class( GTK_WIDGET( self ), "toggled" ); + } + else + { + gtk_widget_remove_css_class( GTK_WIDGET( self ), "toggled" ); + } + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// Signal Handlers +// --------------------------------------------------------------------------------------------------------------------- + +// +// Called when the primary button was clicked. +// +// If allowed, this will toggle the button. +// +void foobar_control_button_handle_primary_clicked( GtkButton* button, gpointer userdata ) +{ + (void)button; + FoobarControlButton* self = (FoobarControlButton*)userdata; + + if ( foobar_control_button_can_toggle( self ) ) + { + foobar_control_button_set_toggled( self, !foobar_control_button_is_toggled( self ) ); + } +} + +// +// Called when the expand button was clicked. +// +// If allowed, this will toggle the "expanded" state of the button. +// +void foobar_control_button_handle_expand_clicked( GtkButton* button, gpointer userdata ) +{ + (void)button; + FoobarControlButton* self = (FoobarControlButton*)userdata; + + if ( foobar_control_button_can_expand( self ) ) + { + foobar_control_button_set_expanded( self, !foobar_control_button_is_expanded( self ) ); + } +}
\ No newline at end of file diff --git a/src/widgets/control-center/control-button.h b/src/widgets/control-center/control-button.h new file mode 100644 index 0000000..48dcfb1 --- /dev/null +++ b/src/widgets/control-center/control-button.h @@ -0,0 +1,31 @@ +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define FOOBAR_TYPE_CONTROL_BUTTON foobar_control_button_get_type( ) + +G_DECLARE_FINAL_TYPE( FoobarControlButton, foobar_control_button, FOOBAR, CONTROL_BUTTON, GtkWidget ) + +GtkWidget* foobar_control_button_new ( void ); +gchar const* foobar_control_button_get_icon_name ( FoobarControlButton* self ); +gchar const* foobar_control_button_get_label ( FoobarControlButton* self ); +gboolean foobar_control_button_can_expand ( FoobarControlButton* self ); +gboolean foobar_control_button_can_toggle ( FoobarControlButton* self ); +gboolean foobar_control_button_is_expanded ( FoobarControlButton* self ); +gboolean foobar_control_button_is_toggled ( FoobarControlButton* self ); +void foobar_control_button_set_icon_name ( FoobarControlButton* self, + gchar const* value ); +void foobar_control_button_set_label ( FoobarControlButton* self, + gchar const* value ); +void foobar_control_button_set_can_expand( FoobarControlButton* self, + gboolean value ); +void foobar_control_button_set_can_toggle( FoobarControlButton* self, + gboolean value ); +void foobar_control_button_set_expanded ( FoobarControlButton* self, + gboolean value ); +void foobar_control_button_set_toggled ( FoobarControlButton* self, + gboolean value ); + +G_END_DECLS
\ No newline at end of file diff --git a/src/widgets/control-center/control-details-item.c b/src/widgets/control-center/control-details-item.c new file mode 100644 index 0000000..f8858d5 --- /dev/null +++ b/src/widgets/control-center/control-details-item.c @@ -0,0 +1,307 @@ +#include "widgets/control-center/control-details-item.h" + +// +// FoobarControlDetailsAccessory: +// +// The accessory shown next to an item in the control center details list (if any). +// + +G_DEFINE_ENUM_TYPE( + FoobarControlDetailsAccessory, + foobar_control_details_accessory, + G_DEFINE_ENUM_VALUE( FOOBAR_CONTROL_DETAILS_ACCESSORY_NONE, "none" ), + G_DEFINE_ENUM_VALUE( FOOBAR_CONTROL_DETAILS_ACCESSORY_PROGRESS, "progress" ), + G_DEFINE_ENUM_VALUE( FOOBAR_CONTROL_DETAILS_ACCESSORY_CHECKED, "checked" ) ) + +// +// FoobarControlDetailsItem: +// +// Standard row layout for items in a FoobarControlDetails list. The rows are made up of a label and an accessory that +// is shown or hidden depending on the item's state. +// + +struct _FoobarControlDetailsItem +{ + GtkWidget parent_instance; + GtkWidget* spinner; + GtkWidget* checkmark; + gchar* label; + FoobarControlDetailsAccessory accessory; +}; + +enum +{ + PROP_LABEL = 1, + PROP_ACCESSORY, + PROP_IS_CHECKED, + N_PROPS, +}; + +static GParamSpec* props[N_PROPS] = { 0 }; + +static void foobar_control_details_item_class_init ( FoobarControlDetailsItemClass* klass ); +static void foobar_control_details_item_init ( FoobarControlDetailsItem* self ); +static void foobar_control_details_item_get_property( GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ); +static void foobar_control_details_item_set_property( GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ); +static void foobar_control_details_item_dispose ( GObject* object ); +static void foobar_control_details_item_finalize ( GObject* object ); + +G_DEFINE_FINAL_TYPE( FoobarControlDetailsItem, foobar_control_details_item, GTK_TYPE_WIDGET ) + +// --------------------------------------------------------------------------------------------------------------------- +// Widget Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for control details items. +// +void foobar_control_details_item_class_init( FoobarControlDetailsItemClass* klass ) +{ + GtkWidgetClass* widget_klass = GTK_WIDGET_CLASS( klass ); + gtk_widget_class_set_css_name( widget_klass, "control-details-item" ); + gtk_widget_class_set_layout_manager_type( widget_klass, GTK_TYPE_BOX_LAYOUT ); + + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->get_property = foobar_control_details_item_get_property; + object_klass->set_property = foobar_control_details_item_set_property; + object_klass->dispose = foobar_control_details_item_dispose; + object_klass->finalize = foobar_control_details_item_finalize; + + props[PROP_LABEL] = g_param_spec_string( + "label", + "Label", + "Label shown for the item.", + NULL, + G_PARAM_READWRITE ); + props[PROP_ACCESSORY] = g_param_spec_enum( + "accessory", + "Accessory", + "Accessory shown for the item.", + FOOBAR_TYPE_CONTROL_DETAILS_ACCESSORY, + 0, + G_PARAM_READWRITE ); + props[PROP_IS_CHECKED] = g_param_spec_boolean( + "is-checked", + "Is Checked", + "Current checked-state for the item.", + FALSE, + G_PARAM_READWRITE ); + g_object_class_install_properties( object_klass, N_PROPS, props ); +} + +// +// Instance initialization for control details items. +// +void foobar_control_details_item_init( FoobarControlDetailsItem* self ) +{ + GtkWidget* label = gtk_label_new( NULL ); + gtk_widget_set_hexpand( label, TRUE ); + gtk_widget_set_valign( label, GTK_ALIGN_CENTER ); + gtk_label_set_ellipsize( GTK_LABEL( label ), PANGO_ELLIPSIZE_END ); + gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); + gtk_label_set_xalign( GTK_LABEL( label ), 0 ); + gtk_label_set_wrap( GTK_LABEL( label ), FALSE ); + g_object_bind_property( + self, + "label", + label, + "label", + G_BINDING_SYNC_CREATE ); + + self->spinner = gtk_spinner_new( ); + gtk_spinner_set_spinning( GTK_SPINNER( self->spinner ), TRUE ); + gtk_widget_add_css_class( self->spinner, "accessory" ); + gtk_widget_set_valign( self->spinner, GTK_ALIGN_CENTER ); + gtk_widget_set_visible( self->spinner, FALSE ); + + self->checkmark = gtk_image_new_from_icon_name( "fluent-checkmark-symbolic" ); + gtk_widget_add_css_class( self->checkmark, "checkmark" ); + gtk_widget_add_css_class( self->checkmark, "accessory" ); + gtk_widget_set_valign( self->checkmark, GTK_ALIGN_CENTER ); + gtk_widget_set_visible( self->checkmark, FALSE ); + + gtk_widget_insert_before( label, GTK_WIDGET( self ), NULL ); + gtk_widget_insert_before( self->spinner, GTK_WIDGET( self ), NULL ); + gtk_widget_insert_before( self->checkmark, GTK_WIDGET( self ), NULL ); +} + +// +// Property getter implementation, mapping a property id to a method. +// +void foobar_control_details_item_get_property( + GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ) +{ + FoobarControlDetailsItem* self = (FoobarControlDetailsItem*)object; + + switch ( prop_id ) + { + case PROP_LABEL: + g_value_set_string( value, foobar_control_details_item_get_label( self ) ); + break; + case PROP_ACCESSORY: + g_value_set_enum( value, foobar_control_details_item_get_accessory( self ) ); + break; + case PROP_IS_CHECKED: + g_value_set_boolean( value, foobar_control_details_item_is_checked( 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_control_details_item_set_property( + GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ) +{ + FoobarControlDetailsItem* self = (FoobarControlDetailsItem*)object; + + switch ( prop_id ) + { + case PROP_LABEL: + foobar_control_details_item_set_label( self, g_value_get_string( value ) ); + break; + case PROP_ACCESSORY: + foobar_control_details_item_set_accessory( self, g_value_get_enum( value ) ); + break; + case PROP_IS_CHECKED: + foobar_control_details_item_set_checked( self, g_value_get_boolean( value ) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + break; + } +} + +// +// Instance de-initialization for control details items. +// +void foobar_control_details_item_dispose( GObject* object ) +{ + FoobarControlDetailsItem* self = (FoobarControlDetailsItem*)object; + + GtkWidget* child; + while ( ( child = gtk_widget_get_first_child( GTK_WIDGET( self ) ) ) ) + { + gtk_widget_unparent( child ); + } + + G_OBJECT_CLASS( foobar_control_details_item_parent_class )->dispose( object ); +} + +// +// Instance cleanup for control details items. +// +void foobar_control_details_item_finalize( GObject* object ) +{ + FoobarControlDetailsItem* self = (FoobarControlDetailsItem*)object; + + g_clear_pointer( &self->label, g_free ); + + G_OBJECT_CLASS( foobar_control_details_item_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new control details item instance. +// +GtkWidget* foobar_control_details_item_new( void ) +{ + return g_object_new( FOOBAR_TYPE_CONTROL_DETAILS_ITEM, NULL ); +} + +// +// Get the label shown for the item. +// +gchar const* foobar_control_details_item_get_label( FoobarControlDetailsItem* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_DETAILS_ITEM( self ), NULL ); + return self->label; +} + +// +// Get the accessory shown for the item. +// +FoobarControlDetailsAccessory foobar_control_details_item_get_accessory( FoobarControlDetailsItem* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_DETAILS_ITEM( self ), 0 ); + return self->accessory; +} + +// +// Get the current checked-state for the item. +// +gboolean foobar_control_details_item_is_checked( FoobarControlDetailsItem* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_DETAILS_ITEM( self ), FALSE ); + return self->accessory == FOOBAR_CONTROL_DETAILS_ACCESSORY_CHECKED; +} + +// +// Update the label shown for the item. +// +void foobar_control_details_item_set_label( + FoobarControlDetailsItem* self, + gchar const* value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_DETAILS_ITEM( self ) ); + + if ( g_strcmp0( self->label, value ) ) + { + g_clear_pointer( &self->label, g_free ); + self->label = g_strdup( value ); + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_LABEL] ); + } +} + +// +// Update the current accessory shown for the item. +// +void foobar_control_details_item_set_accessory( + FoobarControlDetailsItem* self, + FoobarControlDetailsAccessory value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_DETAILS_ITEM( self ) ); + + if ( self->accessory != value ) + { + self->accessory = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_ACCESSORY] ); + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_CHECKED] ); + + gtk_widget_set_visible( self->spinner, self->accessory == FOOBAR_CONTROL_DETAILS_ACCESSORY_PROGRESS ); + gtk_widget_set_visible( self->checkmark, self->accessory == FOOBAR_CONTROL_DETAILS_ACCESSORY_CHECKED ); + } +} + +// +// Update the current checked-state for the item. +// +void foobar_control_details_item_set_checked( + FoobarControlDetailsItem* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_DETAILS_ITEM( self ) ); + + FoobarControlDetailsAccessory accessory = value + ? FOOBAR_CONTROL_DETAILS_ACCESSORY_CHECKED + : FOOBAR_CONTROL_DETAILS_ACCESSORY_NONE; + foobar_control_details_item_set_accessory( self, accessory ); +}
\ No newline at end of file diff --git a/src/widgets/control-center/control-details-item.h b/src/widgets/control-center/control-details-item.h new file mode 100644 index 0000000..b9be4a7 --- /dev/null +++ b/src/widgets/control-center/control-details-item.h @@ -0,0 +1,32 @@ +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define FOOBAR_TYPE_CONTROL_DETAILS_ACCESSORY foobar_control_details_accessory_get_type( ) +#define FOOBAR_TYPE_CONTROL_DETAILS_ITEM foobar_control_details_item_get_type( ) + +typedef enum +{ + FOOBAR_CONTROL_DETAILS_ACCESSORY_NONE, + FOOBAR_CONTROL_DETAILS_ACCESSORY_CHECKED, + FOOBAR_CONTROL_DETAILS_ACCESSORY_PROGRESS, +} FoobarControlDetailsAccessory; + +GType foobar_control_details_accessory_get_type( void ); + +G_DECLARE_FINAL_TYPE( FoobarControlDetailsItem, foobar_control_details_item, FOOBAR, CONTROL_DETAILS_ITEM, GtkWidget ) + +GtkWidget* foobar_control_details_item_new ( void ); +gchar const* foobar_control_details_item_get_label ( FoobarControlDetailsItem* self ); +FoobarControlDetailsAccessory foobar_control_details_item_get_accessory( FoobarControlDetailsItem* self ); +gboolean foobar_control_details_item_is_checked ( FoobarControlDetailsItem* self ); +void foobar_control_details_item_set_label ( FoobarControlDetailsItem* self, + gchar const* value ); +void foobar_control_details_item_set_accessory( FoobarControlDetailsItem* self, + FoobarControlDetailsAccessory value ); +void foobar_control_details_item_set_checked ( FoobarControlDetailsItem* self, + gboolean value ); + +G_END_DECLS
\ No newline at end of file diff --git a/src/widgets/control-center/control-details.c b/src/widgets/control-center/control-details.c new file mode 100644 index 0000000..4f0d422 --- /dev/null +++ b/src/widgets/control-center/control-details.c @@ -0,0 +1,289 @@ +#include "widgets/control-center/control-details.h" + +// +// FoobarControlDetails: +// +// A revealer for details in the "controls" section of the control center. This widget is usually used to embed a list +// view. It takes care of scrolling by embedding the child in a GtkScrolledWindow. +// + +struct _FoobarControlDetails +{ + GtkWidget parent_instance; + GtkWidget* child; + gboolean is_expanded; + gint inset_top; +}; + +enum +{ + PROP_CHILD = 1, + PROP_IS_EXPANDED, + PROP_INSET_TOP, + N_PROPS, +}; + +static GParamSpec* props[N_PROPS] = { 0 }; + +static void foobar_control_details_class_init ( FoobarControlDetailsClass* klass ); +static void foobar_control_details_init ( FoobarControlDetails* self ); +static void foobar_control_details_get_property( GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ); +static void foobar_control_details_set_property( GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ); +static void foobar_control_details_dispose ( GObject* object ); + +G_DEFINE_FINAL_TYPE( FoobarControlDetails, foobar_control_details, GTK_TYPE_WIDGET ) + +// --------------------------------------------------------------------------------------------------------------------- +// Widget Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for control details. +// +void foobar_control_details_class_init( FoobarControlDetailsClass* klass ) +{ + GtkWidgetClass* widget_klass = GTK_WIDGET_CLASS( klass ); + gtk_widget_class_set_css_name( widget_klass, "control-details" ); + gtk_widget_class_set_layout_manager_type( widget_klass, GTK_TYPE_BIN_LAYOUT ); + + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->get_property = foobar_control_details_get_property; + object_klass->set_property = foobar_control_details_set_property; + object_klass->dispose = foobar_control_details_dispose; + + props[PROP_CHILD] = g_param_spec_object( + "child", + "Child", + "Child widget for the details revealer.", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE ); + props[PROP_IS_EXPANDED] = g_param_spec_boolean( + "is-expanded", + "Is Expanded", + "Current state of the details revealer.", + FALSE, + G_PARAM_READWRITE ); + props[PROP_INSET_TOP] = g_param_spec_int( + "inset-top", + "Inset Top", + "Inset from the top edge (only visible when expanded).", + 0, + INT_MAX, + 0, + G_PARAM_READWRITE ); + g_object_class_install_properties( object_klass, N_PROPS, props ); +} + +// +// Instance initialization for control details. +// +void foobar_control_details_init( FoobarControlDetails* self ) +{ + GtkWidget* scrolled_window = gtk_scrolled_window_new( ); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_window ), GTK_POLICY_NEVER, GTK_POLICY_EXTERNAL ); + gtk_scrolled_window_set_propagate_natural_height( GTK_SCROLLED_WINDOW( scrolled_window ), TRUE ); + g_object_bind_property( + self, + "child", + scrolled_window, + "child", + G_BINDING_SYNC_CREATE ); + g_object_bind_property( + self, + "inset-top", + scrolled_window, + "margin-top", + G_BINDING_SYNC_CREATE ); + + GtkWidget* revealer = gtk_revealer_new( ); + gtk_revealer_set_transition_type( GTK_REVEALER( revealer ), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN ); + gtk_revealer_set_transition_duration( GTK_REVEALER( revealer ), 100 ); + gtk_revealer_set_child( GTK_REVEALER( revealer ), scrolled_window ); + g_object_bind_property( + self, + "is-expanded", + revealer, + "reveal-child", + G_BINDING_SYNC_CREATE ); + + gtk_widget_set_parent( revealer, GTK_WIDGET( self ) ); +} + +// +// Property getter implementation, mapping a property id to a method. +// +void foobar_control_details_get_property( + GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ) +{ + FoobarControlDetails* self = (FoobarControlDetails*)object; + + switch ( prop_id ) + { + case PROP_CHILD: + g_value_set_object( value, foobar_control_details_get_child( self ) ); + break; + case PROP_IS_EXPANDED: + g_value_set_boolean( value, foobar_control_details_is_expanded( self ) ); + break; + case PROP_INSET_TOP: + g_value_set_int( value, foobar_control_details_get_inset_top( 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_control_details_set_property( + GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ) +{ + FoobarControlDetails* self = (FoobarControlDetails*)object; + + switch ( prop_id ) + { + case PROP_CHILD: + foobar_control_details_set_child( self, g_value_get_object( value ) ); + break; + case PROP_IS_EXPANDED: + foobar_control_details_set_expanded( self, g_value_get_boolean( value ) ); + break; + case PROP_INSET_TOP: + foobar_control_details_set_inset_top( self, g_value_get_int( value ) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + break; + } +} + +// +// Instance de-initialization for control details. +// +void foobar_control_details_dispose( GObject* object ) +{ + FoobarControlDetails* self = (FoobarControlDetails*)object; + + GtkWidget* child; + while ( ( child = gtk_widget_get_first_child( GTK_WIDGET( self ) ) ) ) + { + gtk_widget_unparent( child ); + } + + g_clear_object( &self->child ); + + G_OBJECT_CLASS( foobar_control_details_parent_class )->dispose( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new control details instance. +// +GtkWidget* foobar_control_details_new( void ) +{ + return g_object_new( FOOBAR_TYPE_CONTROL_DETAILS, NULL ); +} + +// +// Get the child widget for the details revealer. +// +GtkWidget* foobar_control_details_get_child( FoobarControlDetails* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_DETAILS( self ), NULL ); + return self->child; +} + +// +// Get the current state of the details revealer. +// +gboolean foobar_control_details_is_expanded( FoobarControlDetails* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_DETAILS( self ), FALSE ); + return self->is_expanded; +} + +// +// Get the inset from the top edge (only visible when expanded). +// +gint foobar_control_details_get_inset_top( FoobarControlDetails* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_DETAILS( self ), FALSE ); + return self->inset_top; +} + +// +// Update the child widget for the details revealer. +// +void foobar_control_details_set_child( + FoobarControlDetails* self, + GtkWidget* value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_DETAILS( self ) ); + + if ( self->child != value ) + { + g_clear_object( &self->child ); + if ( value ) { self->child = g_object_ref( value ); } + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_CHILD] ); + } +} + +// +// Update the current state of the details revealer. +// +void foobar_control_details_set_expanded( + FoobarControlDetails* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_DETAILS( self ) ); + + value = !!value; + if ( self->is_expanded != value ) + { + self->is_expanded = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_EXPANDED] ); + + if ( self->is_expanded ) + { + gtk_widget_add_css_class( GTK_WIDGET( self ), "expanded" ); + } + else + { + gtk_widget_remove_css_class( GTK_WIDGET( self ), "expanded" ); + } + } +} + +// +// Update the inset from the top edge (only visible when expanded). +// +void foobar_control_details_set_inset_top( + FoobarControlDetails* self, + gint value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_DETAILS( self ) ); + + value = MAX( value, 0 ); + if ( self->inset_top != value ) + { + self->inset_top = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_INSET_TOP] ); + } +}
\ No newline at end of file diff --git a/src/widgets/control-center/control-details.h b/src/widgets/control-center/control-details.h new file mode 100644 index 0000000..71df683 --- /dev/null +++ b/src/widgets/control-center/control-details.h @@ -0,0 +1,22 @@ +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define FOOBAR_TYPE_CONTROL_DETAILS foobar_control_details_get_type( ) + +G_DECLARE_FINAL_TYPE( FoobarControlDetails, foobar_control_details, FOOBAR, CONTROL_DETAILS, GtkWidget ) + +GtkWidget* foobar_control_details_new ( void ); +GtkWidget* foobar_control_details_get_child ( FoobarControlDetails* self ); +gboolean foobar_control_details_is_expanded ( FoobarControlDetails* self ); +gint foobar_control_details_get_inset_top( FoobarControlDetails* self ); +void foobar_control_details_set_child ( FoobarControlDetails* self, + GtkWidget* value ); +void foobar_control_details_set_expanded ( FoobarControlDetails* self, + gboolean value ); +void foobar_control_details_set_inset_top( FoobarControlDetails* self, + gint value ); + +G_END_DECLS
\ No newline at end of file diff --git a/src/widgets/control-center/control-slider.c b/src/widgets/control-center/control-slider.c new file mode 100644 index 0000000..d98dc9e --- /dev/null +++ b/src/widgets/control-center/control-slider.c @@ -0,0 +1,428 @@ +#include "widgets/control-center/control-slider.h" + +// +// FoobarControlSlider: +// +// A slider in the "controls" section of the control center with a title and icon, as well as an optional secondary +// button to toggle a details view. +// + +struct _FoobarControlSlider +{ + GtkWidget parent_instance; + gchar* icon_name; + gchar* label; + gboolean can_expand; + gboolean is_expanded; + gint percentage; +}; + +enum +{ + PROP_ICON_NAME = 1, + PROP_LABEL, + PROP_CAN_EXPAND, + PROP_IS_EXPANDED, + PROP_PERCENTAGE, + N_PROPS, +}; + +static GParamSpec* props[N_PROPS] = { 0 }; + +static void foobar_control_slider_class_init ( FoobarControlSliderClass* klass ); +static void foobar_control_slider_init ( FoobarControlSlider* self ); +static void foobar_control_slider_get_property ( GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ); +static void foobar_control_slider_set_property ( GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ); +static void foobar_control_slider_dispose ( GObject* object ); +static void foobar_control_slider_finalize ( GObject* object ); +static void foobar_control_slider_handle_expand_clicked( GtkButton* button, + gpointer userdata ); + +G_DEFINE_FINAL_TYPE( FoobarControlSlider, foobar_control_slider, GTK_TYPE_WIDGET ) + +// --------------------------------------------------------------------------------------------------------------------- +// Widget Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for control sliders. +// +void foobar_control_slider_class_init( FoobarControlSliderClass* klass ) +{ + GtkWidgetClass* widget_klass = GTK_WIDGET_CLASS( klass ); + gtk_widget_class_set_css_name( widget_klass, "control-slider" ); + gtk_widget_class_set_layout_manager_type( widget_klass, GTK_TYPE_BOX_LAYOUT ); + + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->get_property = foobar_control_slider_get_property; + object_klass->set_property = foobar_control_slider_set_property; + object_klass->dispose = foobar_control_slider_dispose; + object_klass->finalize = foobar_control_slider_finalize; + + props[PROP_ICON_NAME] = g_param_spec_string( + "icon-name", + "Icon Name", + "Name of the icon to be displayed next to the label.", + NULL, + G_PARAM_READWRITE ); + props[PROP_LABEL] = g_param_spec_string( + "label", + "Label", + "The slider's main label.", + NULL, + G_PARAM_READWRITE ); + props[PROP_CAN_EXPAND] = g_param_spec_boolean( + "can-expand", + "Can Expand", + "Indicates whether some details are hidden and can be revealed by a toggle button next to the slider.", + FALSE, + G_PARAM_READWRITE ); + props[PROP_IS_EXPANDED] = g_param_spec_boolean( + "is-expanded", + "Is Expanded", + "Indicates whether additional details should be shown.", + FALSE, + G_PARAM_READWRITE ); + props[PROP_PERCENTAGE] = g_param_spec_int( + "percentage", + "Percentage", + "Value shown by the slider.", + 0, + 100, + 0, + G_PARAM_READWRITE ); + g_object_class_install_properties( object_klass, N_PROPS, props ); +} + +// +// Instance initialization for control sliders. +// +void foobar_control_slider_init( FoobarControlSlider* self ) +{ + GtkWidget* icon = gtk_image_new( ); + gtk_widget_set_valign( icon, GTK_ALIGN_CENTER ); + g_object_bind_property( + self, + "icon-name", + icon, + "icon-name", + G_BINDING_SYNC_CREATE ); + + GtkWidget* label = gtk_label_new( NULL ); + gtk_widget_set_valign( label, GTK_ALIGN_CENTER ); + gtk_widget_set_hexpand( label, TRUE ); + gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); + gtk_label_set_xalign( GTK_LABEL( label ), 0 ); + g_object_bind_property( + self, + "label", + label, + "label", + G_BINDING_SYNC_CREATE ); + + GtkWidget* header = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); + gtk_box_append( GTK_BOX( header ), icon ); + gtk_box_append( GTK_BOX( header ), label ); + + GtkWidget* scale = gtk_scale_new_with_range( GTK_ORIENTATION_HORIZONTAL, 0, 100, 2 ); + g_object_bind_property( + self, + "percentage", + gtk_range_get_adjustment( GTK_RANGE( scale ) ), + "value", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL ); + + GtkWidget* primary_content = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); + gtk_widget_add_css_class( primary_content, "primary" ); + gtk_box_append( GTK_BOX( primary_content ), header ); + gtk_box_append( GTK_BOX( primary_content ), scale ); + + GtkWidget* expand_button = gtk_button_new_from_icon_name( "fluent-chevron-right-symbolic" ); + gtk_widget_add_css_class( expand_button, "expand" ); + g_object_bind_property( + self, + "can-expand", + expand_button, + "visible", + G_BINDING_SYNC_CREATE ); + g_signal_connect( expand_button, "clicked", G_CALLBACK( foobar_control_slider_handle_expand_clicked ), self ); + + GtkLayoutManager* layout = gtk_widget_get_layout_manager( GTK_WIDGET( self ) ); + gtk_orientable_set_orientation( GTK_ORIENTABLE( layout ), GTK_ORIENTATION_HORIZONTAL ); + gtk_widget_insert_before( primary_content, GTK_WIDGET( self ), NULL ); + gtk_widget_insert_before( expand_button, GTK_WIDGET( self ), NULL ); +} + +// +// Property getter implementation, mapping a property id to a method. +// +void foobar_control_slider_get_property( + GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ) +{ + FoobarControlSlider* self = (FoobarControlSlider*)object; + + switch ( prop_id ) + { + case PROP_ICON_NAME: + g_value_set_string( value, foobar_control_slider_get_icon_name( self ) ); + break; + case PROP_LABEL: + g_value_set_string( value, foobar_control_slider_get_label( self ) ); + break; + case PROP_CAN_EXPAND: + g_value_set_boolean( value, foobar_control_slider_can_expand( self ) ); + break; + case PROP_IS_EXPANDED: + g_value_set_boolean( value, foobar_control_slider_is_expanded( self ) ); + break; + case PROP_PERCENTAGE: + g_value_set_int( value, foobar_control_slider_get_percentage( 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_control_slider_set_property( + GObject* object, + guint prop_id, + GValue const* value, + GParamSpec* pspec ) +{ + FoobarControlSlider* self = (FoobarControlSlider*)object; + + switch ( prop_id ) + { + case PROP_ICON_NAME: + foobar_control_slider_set_icon_name( self, g_value_get_string( value ) ); + break; + case PROP_LABEL: + foobar_control_slider_set_label( self, g_value_get_string( value ) ); + break; + case PROP_CAN_EXPAND: + foobar_control_slider_set_can_expand( self, g_value_get_boolean( value ) ); + break; + case PROP_IS_EXPANDED: + foobar_control_slider_set_expanded( self, g_value_get_boolean( value ) ); + break; + case PROP_PERCENTAGE: + foobar_control_slider_set_percentage( self, g_value_get_int( value ) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + break; + } +} + +// +// Instance de-initialization for control sliders. +// +void foobar_control_slider_dispose( GObject* object ) +{ + FoobarControlSlider* self = (FoobarControlSlider*)object; + + GtkWidget* child; + while ( ( child = gtk_widget_get_first_child( GTK_WIDGET( self ) ) ) ) + { + gtk_widget_unparent( child ); + } + + G_OBJECT_CLASS( foobar_control_slider_parent_class )->dispose( object ); +} + +// +// Instance cleanup for control sliders. +// +void foobar_control_slider_finalize( GObject* object ) +{ + FoobarControlSlider* self = (FoobarControlSlider*)object; + + g_clear_pointer( &self->icon_name, g_free ); + g_clear_pointer( &self->label, g_free ); + + G_OBJECT_CLASS( foobar_control_slider_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new control slider instance. +// +GtkWidget* foobar_control_slider_new( void ) +{ + return g_object_new( FOOBAR_TYPE_CONTROL_SLIDER, NULL ); +} + +// +// Get the name of the icon to be displayed next to the label. +// +gchar const* foobar_control_slider_get_icon_name( FoobarControlSlider* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ), NULL ); + return self->icon_name; +} + +// +// Get the slider's main label. +// +gchar const* foobar_control_slider_get_label( FoobarControlSlider* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ), NULL ); + return self->label; +} + +// +// Check whether some details are hidden and can be revealed by a toggle button next to the slider. +// +gboolean foobar_control_slider_can_expand( FoobarControlSlider* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ), FALSE ); + return self->can_expand; +} + +// +// Check whether additional details should currently be shown. +// +gboolean foobar_control_slider_is_expanded( FoobarControlSlider* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ), FALSE ); + return self->is_expanded; +} + +// +// Get the value shown by the slider. +// +gint foobar_control_slider_get_percentage( FoobarControlSlider* self ) +{ + g_return_val_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ), FALSE ); + return self->percentage; +} + +// +// Update the name of the icon to be displayed next to the label. +// +void foobar_control_slider_set_icon_name( + FoobarControlSlider* self, + gchar const* value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ) ); + + if ( g_strcmp0( self->icon_name, value ) ) + { + g_clear_pointer( &self->icon_name, g_free ); + self->icon_name = g_strdup( value ); + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_ICON_NAME] ); + } +} + +// +// Update the slider's main label. +// +void foobar_control_slider_set_label( + FoobarControlSlider* self, + gchar const* value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ) ); + + if ( g_strcmp0( self->label, value ) ) + { + g_clear_pointer( &self->label, g_free ); + self->label = g_strdup( value ); + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_LABEL] ); + } +} + +// +// Update whether some details are hidden and can be revealed by a toggle button next to the slider. +// +void foobar_control_slider_set_can_expand( + FoobarControlSlider* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ) ); + + if ( !value ) { foobar_control_slider_set_expanded( self, FALSE ); } + + value = !!value; + if ( self->can_expand != value ) + { + self->can_expand = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_CAN_EXPAND] ); + } +} + +// +// Update whether additional details are currently shown. +// +void foobar_control_slider_set_expanded( + FoobarControlSlider* self, + gboolean value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ) ); + + value = value && self->can_expand; + if ( self->is_expanded != value ) + { + self->is_expanded = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_EXPANDED] ); + + if ( self->is_expanded ) + { + gtk_widget_add_css_class( GTK_WIDGET( self ), "expanded" ); + } + else + { + gtk_widget_remove_css_class( GTK_WIDGET( self ), "expanded" ); + } + } +} + +// +// Update the value shown by the slider. +// +void foobar_control_slider_set_percentage( FoobarControlSlider* self, gint value ) +{ + g_return_if_fail( FOOBAR_IS_CONTROL_SLIDER( self ) ); + + value = CLAMP( value, 0, 100 ); + if ( self->percentage != value ) + { + self->percentage = value; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_PERCENTAGE] ); + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// Signal Handlers +// --------------------------------------------------------------------------------------------------------------------- + +// +// Called when the expand button was clicked. +// +// If allowed, this will toggle the "expanded" state of the slider. +// +void foobar_control_slider_handle_expand_clicked( GtkButton* button, gpointer userdata ) +{ + (void)button; + FoobarControlSlider* self = (FoobarControlSlider*)userdata; + + if ( foobar_control_slider_can_expand( self ) ) + { + foobar_control_slider_set_expanded( self, !foobar_control_slider_is_expanded( self ) ); + } +}
\ No newline at end of file diff --git a/src/widgets/control-center/control-slider.h b/src/widgets/control-center/control-slider.h new file mode 100644 index 0000000..e7dfac0 --- /dev/null +++ b/src/widgets/control-center/control-slider.h @@ -0,0 +1,28 @@ +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define FOOBAR_TYPE_CONTROL_SLIDER foobar_control_slider_get_type( ) + +G_DECLARE_FINAL_TYPE( FoobarControlSlider, foobar_control_slider, FOOBAR, CONTROL_SLIDER, GtkWidget ) + +GtkWidget* foobar_control_slider_new ( void ); +gchar const* foobar_control_slider_get_icon_name ( FoobarControlSlider* self ); +gchar const* foobar_control_slider_get_label ( FoobarControlSlider* self ); +gboolean foobar_control_slider_can_expand ( FoobarControlSlider* self ); +gboolean foobar_control_slider_is_expanded ( FoobarControlSlider* self ); +gint foobar_control_slider_get_percentage( FoobarControlSlider* self ); +void foobar_control_slider_set_icon_name ( FoobarControlSlider* self, + gchar const* value ); +void foobar_control_slider_set_label ( FoobarControlSlider* self, + gchar const* value ); +void foobar_control_slider_set_can_expand( FoobarControlSlider* self, + gboolean value ); +void foobar_control_slider_set_expanded ( FoobarControlSlider* self, + gboolean value ); +void foobar_control_slider_set_percentage( FoobarControlSlider* self, + gint value ); + +G_END_DECLS
\ No newline at end of file diff --git a/src/widgets/control-center/meson.build b/src/widgets/control-center/meson.build new file mode 100644 index 0000000..5b1252d --- /dev/null +++ b/src/widgets/control-center/meson.build @@ -0,0 +1,6 @@ +foobar_sources += files( + 'control-button.c', + 'control-slider.c', + 'control-details.c', + 'control-details-item.c', +)
\ No newline at end of file |
