lib: Introduce Clapper GTK integration library

An easy to use GTK integration library. Meant as a GtkVideo alternative.

While GtkVideo is more of a simple example of video playback under GTK4, this
acts as a full-fledged video player in the form of a GtkWidget that can be placed
anywhere within application.

The widget offers customization options for both top header and bottom playback
controls panels. The Clapper playback API is exposed under widget "player" property
making it easy for the programmer to set media and control playback programically.

The new library will be distributed with Clapper player. This includes public headers
and GObject Introspection support.

Licensed under LGPL-2.1-or-later.
This commit is contained in:
Rafał Dzięgiel
2024-03-13 20:58:00 +01:00
parent d7f069d6c3
commit 675ddc85a1
91 changed files with 11030 additions and 0 deletions

View File

@@ -0,0 +1,413 @@
/* Clapper GTK Integration Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* 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, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperGtkContainer:
*
* A simple container widget that holds just one child.
*
* It is designed to work well with OSD overlay, adding some useful functionalities
* to it, such as width and height that widget should target. This helps with
* implementing simple adaptive widgets by observing its own width and signalling
* when adaptive threshold is reached.
*
* You can use this when you need to create a widget that is adaptive or should have
* a limited maximal width/height.
*
* If you need to have more then single widget as child, place a widget that
* can hold multiple children such as [class@Gtk.Box] as a single conatiner child
* and then your widgets into that child.
*/
#include "clapper-gtk-container.h"
#include "clapper-gtk-container-private.h"
#include "clapper-gtk-limited-layout-private.h"
#define parent_class clapper_gtk_container_parent_class
G_DEFINE_TYPE (ClapperGtkContainer, clapper_gtk_container, GTK_TYPE_WIDGET)
enum
{
PROP_0,
PROP_WIDTH_TARGET,
PROP_HEIGHT_TARGET,
PROP_ADAPTIVE_WIDTH,
PROP_ADAPTIVE_HEIGHT,
PROP_LAST
};
enum
{
SIGNAL_ADAPT,
SIGNAL_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static guint signals[SIGNAL_LAST] = { 0, };
static inline void
_unparent_child (ClapperGtkContainer *self)
{
GtkWidget *child;
if ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
gtk_widget_unparent (child);
}
/**
* clapper_gtk_container_new:
*
* Creates a new #ClapperGtkContainer instance.
*
* Returns: a new container #GtkWidget.
*/
GtkWidget *
clapper_gtk_container_new (void)
{
return g_object_new (CLAPPER_GTK_TYPE_CONTAINER, NULL);
}
/**
* clapper_gtk_container_set_child:
* @container: a #ClapperGtkContainer
*
* Set a child #GtkWidget of @container.
*/
void
clapper_gtk_container_set_child (ClapperGtkContainer *self, GtkWidget *child)
{
g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self));
g_return_if_fail (GTK_IS_WIDGET (child));
_unparent_child (self);
gtk_widget_set_parent (child, GTK_WIDGET (self));
}
/**
* clapper_gtk_container_get_child:
* @container: a #ClapperGtkContainer
*
* Get a child #GtkWidget of @container.
*
* Returns: (transfer none) (nullable): #GtkWidget set as child.
*/
GtkWidget *
clapper_gtk_container_get_child (ClapperGtkContainer *self)
{
g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), NULL);
return gtk_widget_get_first_child (GTK_WIDGET (self));
}
/**
* clapper_gtk_container_set_width_target:
* @container: a #ClapperGtkContainer
* @width: width to target -1 to restore default behavior
*
* Set a width that @container should target. When set container
* will not stretch beyond set @width while still expanding into
* possible boundaries trying to reach its target.
*/
void
clapper_gtk_container_set_width_target (ClapperGtkContainer *self, gint width)
{
GtkLayoutManager *layout;
g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self));
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
clapper_gtk_limited_layout_set_max_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), width);
}
/**
* clapper_gtk_container_get_width_target:
* @container: a #ClapperGtkContainer
*
* Get a @container width target.
*
* Returns: width target set by user or -1 when none.
*/
gint
clapper_gtk_container_get_width_target (ClapperGtkContainer *self)
{
GtkLayoutManager *layout;
g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1);
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
return clapper_gtk_limited_layout_get_max_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout));
}
/**
* clapper_gtk_container_set_height_target:
* @container: a #ClapperGtkContainer
* @height: height to target or -1 to restore default behavior
*
* Same as clapper_gtk_container_set_width_target() but for widget height.
*/
void
clapper_gtk_container_set_height_target (ClapperGtkContainer *self, gint height)
{
GtkLayoutManager *layout;
g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self));
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
clapper_gtk_limited_layout_set_max_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), height);
}
/**
* clapper_gtk_container_get_height_target:
* @container: a #ClapperGtkContainer
*
* Get a @container height target.
*
* Returns: height target set by user or -1 when none.
*/
gint
clapper_gtk_container_get_height_target (ClapperGtkContainer *self)
{
GtkLayoutManager *layout;
g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1);
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
return clapper_gtk_limited_layout_get_max_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout));
}
/**
* clapper_gtk_container_set_adaptive_width:
* @container: a #ClapperGtkContainer
* @width: a threshold on which adapt signal should be triggered or -1 to disable.
*
* Set an adaptive width threshold. When widget is resized to value or lower,
* an [signal@ClapperGtk.Container::adapt] signal will be emitted with %TRUE to
* notify implementation about mobile adaptation request, otherwise %FALSE when
* both threshold values are exceeded.
*/
void
clapper_gtk_container_set_adaptive_width (ClapperGtkContainer *self, gint width)
{
GtkLayoutManager *layout;
g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self));
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
clapper_gtk_limited_layout_set_adaptive_width (
CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), width);
}
/**
* clapper_gtk_container_get_adaptive_width:
* @container: a #ClapperGtkContainer
*
* Get a @container adaptive width threshold.
*
* Returns: adaptive width set by user or -1 when none.
*/
gint
clapper_gtk_container_get_adaptive_width (ClapperGtkContainer *self)
{
GtkLayoutManager *layout;
g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1);
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
return clapper_gtk_limited_layout_get_adaptive_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout));
}
/**
* clapper_gtk_container_set_adaptive_height:
* @container: a #ClapperGtkContainer
* @height: a threshold on which adapt signal should be triggered or -1 to disable.
*
* Set an adaptive height threshold. When widget is resized to value or lower,
* an [signal@ClapperGtk.Container::adapt] signal will be emitted with %TRUE to
* notify implementation about mobile adaptation request, otherwise %FALSE when
* both threshold values are exceeded.
*/
void
clapper_gtk_container_set_adaptive_height (ClapperGtkContainer *self, gint height)
{
GtkLayoutManager *layout;
g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self));
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
clapper_gtk_limited_layout_set_adaptive_height (
CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), height);
}
/**
* clapper_gtk_container_get_adaptive_height:
* @container: a #ClapperGtkContainer
*
* Get a @container adaptive height threshold.
*
* Returns: adaptive height set by user or -1 when none.
*/
gint
clapper_gtk_container_get_adaptive_height (ClapperGtkContainer *self)
{
GtkLayoutManager *layout;
g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1);
layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
return clapper_gtk_limited_layout_get_adaptive_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout));
}
void
clapper_gtk_container_emit_adapt (ClapperGtkContainer *self, gboolean adapt)
{
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_ADAPT], 0, NULL, NULL, NULL) != 0) {
g_signal_emit (self, signals[SIGNAL_ADAPT], 0, adapt);
}
}
static void
clapper_gtk_container_init (ClapperGtkContainer *self)
{
}
static void
clapper_gtk_container_dispose (GObject *object)
{
ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object);
_unparent_child (self);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_gtk_container_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object);
switch (prop_id) {
case PROP_WIDTH_TARGET:
g_value_set_int (value, clapper_gtk_container_get_width_target (self));
break;
case PROP_HEIGHT_TARGET:
g_value_set_int (value, clapper_gtk_container_get_height_target (self));
break;
case PROP_ADAPTIVE_WIDTH:
g_value_set_int (value, clapper_gtk_container_get_adaptive_width (self));
break;
case PROP_ADAPTIVE_HEIGHT:
g_value_set_int (value, clapper_gtk_container_get_adaptive_height (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_gtk_container_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object);
switch (prop_id) {
case PROP_WIDTH_TARGET:
clapper_gtk_container_set_width_target (self, g_value_get_int (value));
break;
case PROP_HEIGHT_TARGET:
clapper_gtk_container_set_height_target (self, g_value_get_int (value));
break;
case PROP_ADAPTIVE_WIDTH:
clapper_gtk_container_set_adaptive_width (self, g_value_get_int (value));
break;
case PROP_ADAPTIVE_HEIGHT:
clapper_gtk_container_set_adaptive_height (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_gtk_container_class_init (ClapperGtkContainerClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
gobject_class->get_property = clapper_gtk_container_get_property;
gobject_class->set_property = clapper_gtk_container_set_property;
gobject_class->dispose = clapper_gtk_container_dispose;
/**
* ClapperGtkContainer:width-target:
*
* Width that container should target.
*/
param_specs[PROP_WIDTH_TARGET] = g_param_spec_int ("width-target",
NULL, NULL, -1, G_MAXINT, -1,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperGtkContainer:height-target:
*
* Height that container should target.
*/
param_specs[PROP_HEIGHT_TARGET] = g_param_spec_int ("height-target",
NULL, NULL, -1, G_MAXINT, -1,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperGtkContainer:adaptive-width:
*
* Adaptive width threshold that triggers [signal@ClapperGtk.Container::adapt] signal.
*/
param_specs[PROP_ADAPTIVE_WIDTH] = g_param_spec_int ("adaptive-width",
NULL, NULL, -1, G_MAXINT, -1,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperGtkContainer:adaptive-height:
*
* Adaptive height threshold that triggers [signal@ClapperGtk.Container::adapt] signal.
*/
param_specs[PROP_ADAPTIVE_HEIGHT] = g_param_spec_int ("adaptive-height",
NULL, NULL, -1, G_MAXINT, -1,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperGtkContainer::adapt:
* @container: a #ClapperGtkContainer
* @adapt: %TRUE if narrowness reached adaptive threshold, %FALSE otherwise
*
* A helper signal for implementing mobile/narrow adaptive
* behavior on descendants.
*/
signals[SIGNAL_ADAPT] = g_signal_new ("adapt",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
gtk_widget_class_set_layout_manager_type (widget_class, CLAPPER_GTK_TYPE_LIMITED_LAYOUT);
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC);
gtk_widget_class_set_css_name (widget_class, "clapper-gtk-container");
}