clapper-app: Handle alternative headerbar buttons placements

Handle all possible combinations of window state buttons placement.
These are "right" and "left" combined along with either LTR or RTL.

Fixes #402
This commit is contained in:
Rafał Dzięgiel
2024-04-13 18:32:24 +02:00
parent 42fa31cc4b
commit 6447944b21
5 changed files with 182 additions and 70 deletions

View File

@@ -28,13 +28,19 @@ struct _ClapperAppWindowStateButtons
{
GtkBox parent;
GtkWidget *menu_button;
GtkWidget *minimize_button;
GtkWidget *maximize_button;
GtkWidget *close_button;
/* Props */
GtkWidget *menu_button;
GtkPositionType position;
gboolean has_minimize;
gboolean has_maximize;
gboolean has_close;
gboolean has_buttons;
gboolean is_maximized;
gboolean is_fullscreen;
@@ -42,9 +48,19 @@ struct _ClapperAppWindowStateButtons
GtkSettings *settings;
};
enum
{
PROP_0,
PROP_POSITION,
PROP_MENU_BUTTON,
PROP_LAST
};
#define parent_class clapper_app_window_state_buttons_parent_class
G_DEFINE_TYPE (ClapperAppWindowStateButtons, clapper_app_window_state_buttons, GTK_TYPE_BOX)
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
minimize_button_clicked_cb (GtkButton *button, ClapperAppWindowStateButtons *self)
{
@@ -67,12 +83,27 @@ close_button_clicked_cb (GtkButton *button, ClapperAppWindowStateButtons *self)
}
static void
_refresh_min_max_visibility (ClapperAppWindowStateButtons *self)
_refresh_buttons_visibility (ClapperAppWindowStateButtons *self)
{
gtk_widget_set_visible (self->minimize_button,
(self->has_minimize && !self->is_fullscreen));
gtk_widget_set_visible (self->maximize_button,
(self->has_maximize && !self->is_fullscreen));
gboolean show_minimize = (self->has_minimize && !self->is_fullscreen);
gboolean show_maximize = (self->has_maximize && !self->is_fullscreen);
gboolean has_buttons;
gtk_widget_set_visible (self->minimize_button, show_minimize);
gtk_widget_set_visible (self->maximize_button, show_maximize);
gtk_widget_set_visible (self->close_button, self->has_close);
has_buttons = (self->menu_button != NULL || show_minimize
|| show_maximize || self->has_close);
if (self->has_buttons != has_buttons) {
self->has_buttons = has_buttons;
if (self->has_buttons)
gtk_widget_add_css_class (GTK_WIDGET (self), "filled");
else
gtk_widget_remove_css_class (GTK_WIDGET (self), "filled");
}
}
static void
@@ -89,46 +120,57 @@ clapper_app_window_state_buttons_parse_layout (ClapperAppWindowStateButtons *sel
if (G_LIKELY (org_layout != NULL)) {
GtkWidget *last_widget = self->menu_button;
const gchar *layout = org_layout;
gboolean can_parse = (self->position == GTK_POS_LEFT);
gboolean had_sign = can_parse;
guint i;
for (i = 0; layout[i]; ++i) {
GtkWidget *widget = NULL;
const gchar *next = layout + i + 1;
for (i = 0; org_layout[i] != '\0'; ++i) {
const gchar *layout = org_layout + i;
if (next[0] != '\0' && next[0] != ',' && next[0] != ':')
if (layout[0] == ',') {
had_sign = TRUE;
continue;
GST_TRACE_OBJECT (self, "Remaining layout: %s", layout);
if (g_str_has_prefix (layout, "minimize")) {
widget = self->minimize_button;
has_minimize = TRUE;
} else if (g_str_has_prefix (layout, "maximize")) {
widget = self->maximize_button;
has_maximize = TRUE;
} else if (g_str_has_prefix (layout, "close")) {
widget = self->close_button;
has_close = TRUE;
}
if (widget) {
gtk_box_reorder_child_after (GTK_BOX (self), widget, last_widget);
last_widget = widget;
if (layout[0] == ':') {
if (self->position == GTK_POS_LEFT)
break;
else
can_parse = TRUE;
had_sign = TRUE;
continue;
}
if (next[0] == '\0')
break;
if (had_sign && can_parse) {
GtkWidget *widget = NULL;
layout = next + 1;
i = 0;
GST_TRACE_OBJECT (self, "Remaining layout: %s", layout);
if (g_str_has_prefix (layout, "minimize")) {
widget = self->minimize_button;
has_minimize = TRUE;
} else if (g_str_has_prefix (layout, "maximize")) {
widget = self->maximize_button;
has_maximize = TRUE;
} else if (g_str_has_prefix (layout, "close")) {
widget = self->close_button;
has_close = TRUE;
}
if (widget) {
gtk_box_reorder_child_after (GTK_BOX (self), widget, last_widget);
last_widget = widget;
}
had_sign = FALSE;
}
}
}
self->has_minimize = has_minimize;
self->has_maximize = has_maximize;
gtk_widget_set_visible (self->close_button, has_close);
self->has_close = has_close;
GST_DEBUG_OBJECT (self, "Buttons layout parsed");
@@ -140,7 +182,7 @@ _decoration_layout_changed_cb (GtkSettings *settings,
GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindowStateButtons *self)
{
clapper_app_window_state_buttons_parse_layout (self);
_refresh_min_max_visibility (self);
_refresh_buttons_visibility (self);
}
static void
@@ -162,16 +204,31 @@ _surface_state_changed_cb (GdkSurface *surface,
}
if (self->is_fullscreen != is_fullscreen) {
self->is_fullscreen = is_fullscreen;
_refresh_min_max_visibility (self);
_refresh_buttons_visibility (self);
}
}
static void
clapper_app_window_state_buttons_init (ClapperAppWindowStateButtons *self)
{
self->position = GTK_POS_RIGHT;
gtk_widget_init_template (GTK_WIDGET (self));
}
static void
clapper_app_window_state_buttons_constructed (GObject *object)
{
ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (object);
if (self->position == GTK_POS_RIGHT)
gtk_widget_add_css_class (GTK_WIDGET (self), "right");
else
gtk_widget_add_css_class (GTK_WIDGET (self), "left");
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
_clear_stored_settings (ClapperAppWindowStateButtons *self)
{
@@ -245,24 +302,57 @@ clapper_app_window_state_buttons_dispose (GObject *object)
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_app_window_state_buttons_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (object);
switch (prop_id) {
case PROP_POSITION:
self->position = g_value_get_enum (value);
break;
case PROP_MENU_BUTTON:
if ((self->menu_button = GTK_WIDGET (g_value_get_object (value)))) {
gtk_box_prepend (GTK_BOX (self), self->menu_button);
_refresh_buttons_visibility (self);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_app_window_state_buttons_class_init (ClapperAppWindowStateButtonsClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkwindowstatebuttons", 0,
"Clapper GTK Window State Buttons");
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappwindowstatebuttons", 0,
"Clapper App Window State Buttons");
gobject_class->constructed = clapper_app_window_state_buttons_constructed;
gobject_class->set_property = clapper_app_window_state_buttons_set_property;
gobject_class->dispose = clapper_app_window_state_buttons_dispose;
widget_class->realize = clapper_app_window_state_buttons_realize;
widget_class->unrealize = clapper_app_window_state_buttons_unrealize;
param_specs[PROP_POSITION] = g_param_spec_enum ("position",
NULL, NULL, GTK_TYPE_POSITION_TYPE, GTK_POS_RIGHT,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
param_specs[PROP_MENU_BUTTON] = g_param_spec_object ("menu-button",
NULL, NULL, GTK_TYPE_MENU_BUTTON,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
gtk_widget_class_set_template_from_resource (widget_class,
CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-window-state-buttons.ui");
gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, menu_button);
gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, minimize_button);
gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, maximize_button);
gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, close_button);

View File

@@ -54,12 +54,18 @@ clapper-app-headerbar .titlelabel label {
clapper-app-headerbar clapper-app-window-state-buttons {
margin-top: 8px;
}
clapper-app-headerbar clapper-app-window-state-buttons:dir(ltr) {
clapper-app-headerbar clapper-app-window-state-buttons.filled.right:dir(ltr) {
padding-right: 6px;
}
clapper-app-headerbar clapper-app-window-state-buttons:dir(rtl) {
clapper-app-headerbar clapper-app-window-state-buttons.filled.right:dir(rtl) {
padding-left: 6px;
}
clapper-app-headerbar clapper-app-window-state-buttons.filled.left:dir(ltr) {
padding-left: 6px;
}
clapper-app-headerbar clapper-app-window-state-buttons.filled.left:dir(rtl) {
padding-right: 6px;
}
clapper-app-queue-list .topseparator {
margin-top: 2px;

View File

@@ -1,4 +1,5 @@
src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui
src/bin/clapper-app/ui/clapper-app-headerbar.ui
src/bin/clapper-app/ui/clapper-app-help-overlay.ui
src/bin/clapper-app/ui/clapper-app-info-window.ui
src/bin/clapper-app/ui/clapper-app-initial-state.ui
@@ -7,7 +8,6 @@ src/bin/clapper-app/ui/clapper-app-queue-list.ui
src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui
src/bin/clapper-app/ui/clapper-app-uri-dialog.ui
src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui
src/bin/clapper-app/ui/clapper-app-window-state-buttons.ui
src/bin/clapper-app/clapper-app-about-window.c
src/bin/clapper-app/clapper-app-info-window.c

View File

@@ -8,6 +8,22 @@
<property name="orientation">horizontal</property>
<property name="halign">fill</property>
<property name="valign">start</property>
<child type="start">
<object class="GtkRevealer">
<property name="halign">start</property>
<property name="valign">start</property>
<property name="transition-type">slide-right</property>
<property name="transition-duration">500</property>
<property name="reveal-child" bind-source="win_buttons_revealer" bind-property="reveal-child" bind-flags="sync-create"/>
<child>
<object class="ClapperAppWindowStateButtons">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="position">left</property>
</object>
</child>
</object>
</child>
<child type="center">
<object class="ClapperGtkLeadContainer">
<property name="blocked-actions">toggle-play|seek-request</property>
@@ -143,6 +159,17 @@
<object class="ClapperAppWindowStateButtons">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="menu-button">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">app_menu</property>
<style>
<class name="osd"/>
<class name="flat"/>
<class name="circular"/>
</style>
</object>
</property>
</object>
</child>
</object>
@@ -150,4 +177,23 @@
</object>
</child>
</template>
<menu id="app_menu">
<section>
<item>
<attribute name="label" translatable="yes">Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
<attribute name="action">win.show-help-overlay</attribute>
</item>
</section>
<section>
<item>
<!-- TRANSLATORS: Please do not translate application name -->
<attribute name="label" translatable="yes">About Clapper</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
</interface>

View File

@@ -3,17 +3,6 @@
<template class="ClapperAppWindowStateButtons" parent="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<child>
<object class="GtkMenuButton" id="menu_button">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">app_menu</property>
<style>
<class name="osd"/>
<class name="flat"/>
<class name="circular"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="minimize_button">
<property name="icon_name">window-minimize-symbolic</property>
@@ -51,23 +40,4 @@
</object>
</child>
</template>
<menu id="app_menu">
<section>
<item>
<attribute name="label" translatable="yes">Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
<attribute name="action">win.show-help-overlay</attribute>
</item>
</section>
<section>
<item>
<!-- TRANSLATORS: Please do not translate application name -->
<attribute name="label" translatable="yes">About Clapper</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
</interface>