[libadwaita/wip/exalm/adaptive-states: 2/7] wip: Adaptive states
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/exalm/adaptive-states: 2/7] wip: Adaptive states
- Date: Tue, 4 Oct 2022 13:26:11 +0000 (UTC)
commit 44237706436f341043184de6177ce1517d63de38
Author: Alexander Mikhaylenko <alexm gnome org>
Date: Mon Oct 3 18:50:51 2022 +0400
wip: Adaptive states
src/adw-adaptive-state-private.h | 26 +
src/adw-adaptive-state.c | 1058 ++++++++++++++++++++++++++++++++++++++
src/adw-adaptive-state.h | 91 ++++
src/adw-application-window.c | 86 ++++
src/adw-application-window.h | 9 +
src/adw-window-mixin-private.h | 12 +
src/adw-window-mixin.c | 328 +++++++++++-
src/adw-window.c | 86 ++++
src/adw-window.h | 9 +
src/adwaita.h | 1 +
src/meson.build | 3 +
11 files changed, 1702 insertions(+), 7 deletions(-)
---
diff --git a/src/adw-adaptive-state-private.h b/src/adw-adaptive-state-private.h
new file mode 100644
index 00000000..7b5723d5
--- /dev/null
+++ b/src/adw-adaptive-state-private.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include "adw-adaptive-state.h"
+
+G_BEGIN_DECLS
+
+void adw_adaptive_state_apply (AdwAdaptiveState *self);
+void adw_adaptive_state_unapply (AdwAdaptiveState *self);
+
+gboolean adw_adaptive_state_check_condition (AdwAdaptiveState *self,
+ int width,
+ int height);
+
+G_END_DECLS
diff --git a/src/adw-adaptive-state.c b/src/adw-adaptive-state.c
new file mode 100644
index 00000000..c1066813
--- /dev/null
+++ b/src/adw-adaptive-state.c
@@ -0,0 +1,1058 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include "config.h"
+
+#include "adw-adaptive-state-private.h"
+
+#include "adw-gtkbuilder-utils-private.h"
+#include "adw-macros-private.h"
+
+#include <gobject/gvaluecollector.h>
+
+G_DEFINE_BOXED_TYPE (AdwAdaptiveCondition, adw_adaptive_condition,
+ adw_adaptive_condition_copy, adw_adaptive_condition_free)
+
+typedef struct {
+ AdwAdaptiveConditionType type;
+ double value;
+} AdwAdaptiveConditionDataSingle;
+
+typedef struct {
+ AdwAdaptiveMultiConditionType type;
+ AdwAdaptiveCondition **children;
+ int n_children;
+} AdwAdaptiveConditionDataMulti;
+
+struct _AdwAdaptiveCondition
+{
+ gboolean multi_condition;
+
+ union {
+ AdwAdaptiveConditionDataSingle single;
+ AdwAdaptiveConditionDataMulti multi;
+ } data;
+};
+
+static gboolean
+check_condition (AdwAdaptiveCondition *self,
+ int width,
+ int height)
+{
+ g_assert (self != NULL);
+
+ if (self->multi_condition) {
+ int i;
+ gboolean ret = (self->data.multi.type == ADW_MULTI_CONDITION_ALL);
+
+ for (i = 0; i < self->data.multi.n_children; i++) {
+ AdwAdaptiveCondition *child = self->data.multi.children[i];
+
+ if (self->data.multi.type == ADW_MULTI_CONDITION_ALL)
+ ret &= check_condition (child, width, height);
+ else
+ ret |= check_condition (child, width, height);
+ }
+
+ return ret;
+ }
+
+ switch (self->data.single.type) {
+ case ADW_CONDITION_MIN_WIDTH:
+ return width >= self->data.single.value;
+ case ADW_CONDITION_MAX_WIDTH:
+ return width <= self->data.single.value;
+ case ADW_CONDITION_MIN_HEIGHT:
+ return height >= self->data.single.value;
+ case ADW_CONDITION_MAX_HEIGHT:
+ return height <= self->data.single.value;
+ case ADW_CONDITION_MIN_ASPECT_RATIO:
+ return (double) width / height >= self->data.single.value;
+ case ADW_CONDITION_MAX_ASPECT_RATIO:
+ return (double) width / height <= self->data.single.value;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return FALSE;
+}
+
+AdwAdaptiveCondition *
+adw_adaptive_condition_new (AdwAdaptiveConditionType type,
+ double value)
+{
+ AdwAdaptiveCondition *self;
+
+ g_return_val_if_fail (type >= 0, NULL);
+ g_return_val_if_fail (type <= ADW_CONDITION_MAX_ASPECT_RATIO, NULL);
+ g_return_val_if_fail (value >= 0, NULL);
+
+ self = g_new0 (AdwAdaptiveCondition, 1);
+ self->multi_condition = FALSE;
+ self->data.single.type = type;
+ self->data.single.value = value;
+
+ return self;
+}
+
+AdwAdaptiveCondition *
+adw_adaptive_multi_condition_new (AdwAdaptiveMultiConditionType type,
+ AdwAdaptiveCondition *first_condition,
+ ...)
+{
+ AdwAdaptiveCondition *self;
+ GPtrArray *children;
+ va_list args;
+ AdwAdaptiveCondition *child;
+
+ g_return_val_if_fail (type >= 0, NULL);
+ g_return_val_if_fail (type <= ADW_MULTI_CONDITION_ANY, NULL);
+ g_return_val_if_fail (first_condition != NULL, NULL);
+
+ children = g_ptr_array_new ();
+ child = first_condition;
+
+ va_start (args, first_condition);
+
+ while (child) {
+ g_ptr_array_add (children, child);
+
+ child = va_arg (args, AdwAdaptiveCondition *);
+ }
+
+ va_end (args);
+
+ self = adw_adaptive_multi_condition_newv (type,
+ (AdwAdaptiveCondition **) children->pdata,
+ children->len);
+
+ g_ptr_array_free (children, FALSE);
+
+ return self;
+}
+
+AdwAdaptiveCondition *
+adw_adaptive_multi_condition_newv (AdwAdaptiveMultiConditionType type,
+ AdwAdaptiveCondition **conditions,
+ int n_conditions)
+{
+ AdwAdaptiveCondition *self;
+
+ g_return_val_if_fail (type >= 0, NULL);
+ g_return_val_if_fail (type <= ADW_MULTI_CONDITION_ANY, NULL);
+ g_return_val_if_fail (conditions != NULL, NULL);
+ g_return_val_if_fail (n_conditions > 0, NULL);
+
+ self = g_new0 (AdwAdaptiveCondition, 1);
+ self->multi_condition = TRUE;
+ self->data.multi.type = type;
+ self->data.multi.children = conditions;
+ self->data.multi.n_children = n_conditions;
+
+ return self;
+}
+
+AdwAdaptiveCondition *
+adw_adaptive_condition_copy (AdwAdaptiveCondition *self)
+{
+ AdwAdaptiveCondition *copy;
+
+ g_return_val_if_fail (self != NULL, NULL);
+
+ if (self->multi_condition) {
+#if GLIB_CHECK_VERSION(2, 67, 3)
+ AdwAdaptiveCondition **children =
+ g_memdup2 (self->data.multi.children,
+ self->data.multi.n_children * sizeof (AdwAdaptiveCondition *));
+#else
+ AdwAdaptiveCondition **children =
+ g_memdup (self->data.multi.children,
+ self->data.multi.n_children * sizeof (AdwAdaptiveCondition *));
+#endif
+
+ copy = adw_adaptive_multi_condition_newv (self->data.multi.type,
+ children,
+ self->data.multi.n_children);
+ } else {
+ copy = adw_adaptive_condition_new (self->data.single.type,
+ self->data.single.value);
+ }
+
+ return copy;
+}
+
+void
+adw_adaptive_condition_free (AdwAdaptiveCondition *self)
+{
+ g_return_if_fail (self != NULL);
+
+ if (self->multi_condition)
+ g_free (self->data.multi.children);
+
+ g_free (self);
+}
+
+char *
+adw_adaptive_condition_to_string (AdwAdaptiveCondition *self)
+{
+ const char *type;
+
+ g_return_val_if_fail (self != NULL, NULL);
+
+ if (self->multi_condition) {
+ char **children;
+ char *joined;
+ char *ret;
+ int i;
+
+ switch (self->data.multi.type) {
+ case ADW_MULTI_CONDITION_ALL:
+ type = "all";
+ break;
+ case ADW_MULTI_CONDITION_ANY:
+ type = "any";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ children = g_new0 (char *, self->data.multi.n_children + 1);
+
+ for (i = 0; i < self->data.multi.n_children; i++) {
+ AdwAdaptiveCondition *child = self->data.multi.children[i];
+
+ children[i] = adw_adaptive_condition_to_string (child);
+ }
+
+ joined = g_strjoinv (", ", children);
+
+ ret = g_strdup_printf ("%s(%s)", type, joined);
+
+ g_strfreev (children);
+ g_free (joined);
+
+ return ret;
+ }
+
+ switch (self->data.single.type) {
+ case ADW_CONDITION_MIN_WIDTH:
+ type = "min-width";
+ break;
+ case ADW_CONDITION_MAX_WIDTH:
+ type = "max-width";
+ break;
+ case ADW_CONDITION_MIN_HEIGHT:
+ type = "min-height";
+ break;
+ case ADW_CONDITION_MAX_HEIGHT:
+ type = "max-height";
+ break;
+ case ADW_CONDITION_MIN_ASPECT_RATIO:
+ type = "min-aspect-ratio";
+ break;
+ case ADW_CONDITION_MAX_ASPECT_RATIO:
+ type = "max-aspect-ratio";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return g_strdup_printf ("%s: %lf", type, self->data.single.value);
+}
+
+typedef struct {
+ GObject *object;
+ GParamSpec *pspec;
+ GValue value;
+ GValue original_value;
+} SetterData;
+
+struct _AdwAdaptiveState
+{
+ GObject parent_instance;
+
+ AdwAdaptiveCondition *condition;
+ GList *setters;
+ gboolean active;
+};
+
+static void adw_adaptive_state_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (AdwAdaptiveState, adw_adaptive_state, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_adaptive_state_buildable_init))
+
+static GtkBuildableIface *parent_buildable_iface;
+
+enum {
+ PROP_0,
+ PROP_CONDITION,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+enum {
+ SIGNAL_ENTER,
+ SIGNAL_EXIT,
+ SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+static inline GParamSpec *
+find_pspec (GObject *object,
+ const char *name)
+{
+ GParamSpec *pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), name);
+
+ if (!pspec) {
+ g_critical ("Type '%s' does not have a property named '%s'",
+ G_OBJECT_TYPE_NAME (object), name);
+ return NULL;
+ }
+
+ return pspec;
+}
+
+static SetterData *
+find_setter_by_object (AdwAdaptiveState *self,
+ GObject *object)
+{
+ GList *l;
+
+ for (l = self->setters; l; l = l->next) {
+ SetterData *setter = l->data;
+
+ if (setter->object == object)
+ return setter;
+ }
+
+ return NULL;
+}
+
+static void free_setter_data (AdwAdaptiveState *self,
+ SetterData *data);
+
+static void
+setter_weak_notify (AdwAdaptiveState *self,
+ GObject *where_the_object_was)
+{
+ SetterData *setter = find_setter_by_object (self, where_the_object_was);
+
+ g_assert (setter);
+
+ setter->object = NULL;
+ free_setter_data (self, setter);
+ self->setters = g_list_remove (self->setters, setter);
+}
+
+static void
+free_setter_data (AdwAdaptiveState *self,
+ SetterData *setter)
+{
+ if (setter->object)
+ g_object_weak_unref (setter->object,
+ (GWeakNotify) setter_weak_notify,
+ self);
+
+ g_param_spec_unref (setter->pspec);
+ g_value_unset (&setter->value);
+ g_value_unset (&setter->original_value);
+
+ g_free (setter);
+}
+
+static void
+adw_adaptive_state_dispose (GObject *object)
+{
+ AdwAdaptiveState *self = ADW_ADAPTIVE_STATE (object);
+ GList *l;
+
+ g_clear_pointer (&self->condition, adw_adaptive_condition_free);
+
+ for (l = self->setters; l; l = l->next) {
+ SetterData *setter = l->data;
+
+ free_setter_data (self, setter);
+ }
+
+ g_clear_pointer (&self->setters, g_list_free);
+
+ G_OBJECT_CLASS (adw_adaptive_state_parent_class)->dispose (object);
+}
+
+static void
+adw_adaptive_state_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwAdaptiveState *self = ADW_ADAPTIVE_STATE (object);
+
+ switch (prop_id) {
+ case PROP_CONDITION:
+ g_value_set_boxed (value, adw_adaptive_state_get_condition (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_adaptive_state_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwAdaptiveState *self = ADW_ADAPTIVE_STATE (object);
+
+ switch (prop_id) {
+ case PROP_CONDITION:
+ adw_adaptive_state_set_condition (self, g_value_get_boxed (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_adaptive_state_class_init (AdwAdaptiveStateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = adw_adaptive_state_dispose;
+ object_class->get_property = adw_adaptive_state_get_property;
+ object_class->set_property = adw_adaptive_state_set_property;
+
+ props[PROP_CONDITION] =
+ g_param_spec_boxed ("condition", NULL, NULL,
+ ADW_TYPE_ADAPTIVE_CONDITION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ signals[SIGNAL_ENTER] =
+ g_signal_new ("enter",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+ signals[SIGNAL_EXIT] =
+ g_signal_new ("exit",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+adw_adaptive_state_init (AdwAdaptiveState *self)
+{
+}
+
+typedef struct {
+ AdwAdaptiveMultiConditionType type;
+ GPtrArray *children;
+} ConditionParserMultiData;
+
+typedef struct {
+ GObject *object;
+ GtkBuilder *builder;
+
+ gboolean single_condition;
+ AdwAdaptiveConditionType single_type;
+ GString *value;
+ GList *multi_conditions;
+
+ AdwAdaptiveCondition *result;
+} ConditionParserData;
+
+static void
+condition_data_free (gpointer data)
+{
+ ConditionParserData *setter = data;
+
+ g_string_free (setter->value, TRUE);
+ g_free (setter);
+}
+
+static void
+condition_start_element (GtkBuildableParseContext *context,
+ const char *element_name,
+ const char **names,
+ const char **values,
+ gpointer user_data,
+ GError **error)
+{
+ ConditionParserData *data = user_data;
+
+ if (strcmp (element_name, "conditions") == 0)
+ return;
+
+ if (strcmp (element_name, "condition") == 0) {
+ const char *type_str = NULL;
+ gboolean multi_condition = FALSE;
+ AdwAdaptiveMultiConditionType multi_type;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_STRING, "type", &type_str,
+ G_MARKUP_COLLECT_INVALID)) {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ if (!g_strcmp0 (type_str, "all")) {
+ multi_condition = TRUE;
+ multi_type = ADW_MULTI_CONDITION_ALL;
+ } else if (!g_strcmp0 (type_str, "any")) {
+ multi_condition = TRUE;
+ multi_type = ADW_MULTI_CONDITION_ANY;
+ } else if (!g_strcmp0 (type_str, "min-width")) {
+ data->single_type = ADW_CONDITION_MIN_WIDTH;
+ } else if (!g_strcmp0 (type_str, "max-width")) {
+ data->single_type = ADW_CONDITION_MAX_WIDTH;
+ } else if (!g_strcmp0 (type_str, "min-height")) {
+ data->single_type = ADW_CONDITION_MIN_HEIGHT;
+ } else if (!g_strcmp0 (type_str, "max-height")) {
+ data->single_type = ADW_CONDITION_MAX_HEIGHT;
+ } else if (!g_strcmp0 (type_str, "min-aspect-ratio")) {
+ data->single_type = ADW_CONDITION_MIN_ASPECT_RATIO;
+ } else if (!g_strcmp0 (type_str, "max-aspect-ratio")) {
+ data->single_type = ADW_CONDITION_MAX_ASPECT_RATIO;
+ } else {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_VALUE,
+ "Invalid condition type '%s'",
+ type_str);
+ return;
+ }
+
+ data->single_condition = !multi_condition;
+
+ if (multi_condition) {
+ ConditionParserMultiData *multi_data;
+
+ multi_data = g_new0 (ConditionParserMultiData, 1);
+ multi_data->type = multi_type;
+ multi_data->children = g_ptr_array_new ();
+
+ data->multi_conditions = g_list_prepend (data->multi_conditions, multi_data);
+ }
+
+ return;
+ }
+
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "AdwAdaptiveState", element_name,
+ error);
+}
+
+static void
+condition_end_element (GtkBuildableParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ConditionParserData *data = user_data;
+
+ if (strcmp (element_name, "conditions") == 0)
+ return;
+
+ if (strcmp (element_name, "condition") == 0) {
+ AdwAdaptiveCondition *condition;
+
+ if (data->single_condition) {
+ double value;
+ char *endptr = NULL;
+
+ data->single_condition = FALSE;
+
+ value = g_ascii_strtod (data->value->str, &endptr);
+
+ if (endptr == data->value->str) {
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_VALUE,
+ "Could not parse double '%s'",
+ data->value->str);
+ g_string_erase (data->value, 0, -1);
+ return;
+ }
+
+ g_string_erase (data->value, 0, -1);
+
+ condition = adw_adaptive_condition_new (data->single_type, value);
+ } else {
+ ConditionParserMultiData *multi_data = data->multi_conditions->data;
+
+ condition = adw_adaptive_multi_condition_newv (multi_data->type,
+ (AdwAdaptiveCondition **) multi_data->children->pdata,
+ multi_data->children->len);
+
+ data->multi_conditions = g_list_remove (data->multi_conditions, multi_data);
+
+ g_ptr_array_free (multi_data->children, FALSE);
+ g_free (multi_data);
+ }
+
+ if (data->multi_conditions) {
+ ConditionParserMultiData *multi_data = data->multi_conditions->data;
+
+ g_ptr_array_add (multi_data->children, condition);
+ } else {
+ g_clear_pointer (&data->result, adw_adaptive_condition_free);
+ data->result = condition;
+ }
+
+ return;
+ }
+
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "AdwAdaptiveState", element_name,
+ error);
+}
+
+static void
+condition_text (GtkBuildableParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ConditionParserData *data = user_data;
+
+ if (data->single_condition)
+ g_string_append_len (data->value, text, text_len);
+}
+
+static const GtkBuildableParser condition_parser = {
+ condition_start_element,
+ condition_end_element,
+ condition_text,
+ NULL
+};
+
+typedef struct {
+ GObject *object;
+ GtkBuilder *builder;
+
+ char *object_id;
+ char *property_name;
+ GString *value;
+
+ char *context;
+ gboolean translatable;
+} SetterParserData;
+
+static void
+setter_data_free (gpointer data)
+{
+ SetterParserData *setter = data;
+
+ g_free (setter->object_id);
+ g_free (setter->property_name);
+ g_string_free (setter->value, TRUE);
+ g_free (setter->context);
+ g_free (setter);
+}
+
+static void
+setter_start_element (GtkBuildableParseContext *context,
+ const char *element_name,
+ const char **names,
+ const char **values,
+ gpointer user_data,
+ GError **error)
+{
+ SetterParserData *data = user_data;
+ const char *object_str = NULL;
+ const char *property_str = NULL;
+ const char *msg_context = NULL;
+ gboolean translatable = FALSE;
+
+ if (strcmp (element_name, "setter") != 0) {
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "AdwAdaptiveState", element_name,
+ error);
+ return;
+ }
+
+ if (!_gtk_builder_check_parent (data->builder, context, "object", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_STRING, "object", &object_str,
+ G_MARKUP_COLLECT_STRING, "property", &property_str,
+ G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, "translatable",
&translatable,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "context",
&msg_context,
+ G_MARKUP_COLLECT_INVALID)) {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ data->object_id = g_strdup (object_str);
+ data->property_name = g_strdup (property_str);
+ data->translatable = translatable;
+ data->context = g_strdup (msg_context);
+}
+
+static void
+setter_text (GtkBuildableParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ SetterParserData *data = user_data;
+
+ g_string_append_len (data->value, text, text_len);
+}
+
+static const GtkBuildableParser setter_parser = {
+ setter_start_element,
+ NULL,
+ setter_text,
+ NULL
+};
+
+static gboolean
+adw_adaptive_state_buildable_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *tagname,
+ GtkBuildableParser *parser,
+ gpointer *parser_data)
+{
+ if (child)
+ return FALSE;
+
+ if (strcmp (tagname, "setter") == 0) {
+ SetterParserData *data;
+
+ data = g_new0 (SetterParserData, 1);
+ data->object = G_OBJECT (buildable);
+ data->builder = builder;
+ data->value = g_string_new ("");
+
+ *parser = setter_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ if (strcmp (tagname, "conditions") == 0) {
+ ConditionParserData *data;
+
+ data = g_new0 (ConditionParserData, 1);
+ data->object = G_OBJECT (buildable);
+ data->builder = builder;
+ data->value = g_string_new ("");
+
+ *parser = condition_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+adw_adaptive_state_buildable_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *tagname,
+ gpointer user_data)
+{
+ if (strcmp (tagname, "setter") == 0) {
+ SetterParserData *data = user_data;
+ GObject *object;
+ GParamSpec *pspec;
+ const char *value_str;
+ GValue value = G_VALUE_INIT;
+ GError *error = NULL;
+ GType type;
+
+ object = gtk_builder_get_object (data->builder, data->object_id);
+
+ if (!object) {
+ g_critical ("Unable to find object '%s' for setter", data->object_id);
+ setter_data_free (data);
+ return;
+ }
+
+ pspec = find_pspec (object, data->property_name);
+
+ if (!pspec) {
+ setter_data_free (data);
+ return;
+ }
+
+ if (data->translatable && data->value->len)
+ value_str = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder),
+ data->context,
+ data->value->str);
+ else
+ value_str = data->value->str;
+
+ type = G_TYPE_FUNDAMENTAL (G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ /* Treat empty strings like NULL */
+ if ((type == G_TYPE_OBJECT || type == G_TYPE_INTERFACE) && !g_strcmp0 (value_str, "")) {
+ g_value_init (&value, type);
+
+ g_value_set_object (&value, NULL);
+ } else if (!gtk_builder_value_from_string (builder, pspec, value_str, &value, &error)) {
+ g_warning ("Invalid value %s for property %s: %s",
+ value_str, data->property_name, error->message);
+ g_error_free (error);
+ setter_data_free (data);
+ return;
+ }
+
+ adw_adaptive_state_add_setter (ADW_ADAPTIVE_STATE (data->object),
+ object,
+ data->property_name,
+ &value);
+
+ g_value_unset (&value);
+
+ setter_data_free (data);
+ return;
+ }
+
+ if (strcmp (tagname, "conditions") == 0) {
+ ConditionParserData *data = user_data;
+
+ if (data->result)
+ adw_adaptive_state_set_condition (ADW_ADAPTIVE_STATE (data->object),
+ data->result);
+
+ condition_data_free (data);
+ return;
+ }
+
+ parent_buildable_iface->custom_finished (buildable, builder, child,
+ tagname, user_data);
+}
+
+static void
+adw_adaptive_state_buildable_init (GtkBuildableIface *iface)
+{
+ parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+ iface->custom_tag_start = adw_adaptive_state_buildable_custom_tag_start;
+ iface->custom_finished = adw_adaptive_state_buildable_custom_finished;
+}
+
+AdwAdaptiveState *
+adw_adaptive_state_new (void)
+{
+ return g_object_new (ADW_TYPE_ADAPTIVE_STATE, NULL);
+}
+
+AdwAdaptiveCondition *
+adw_adaptive_state_get_condition (AdwAdaptiveState *self)
+{
+ g_return_val_if_fail (ADW_IS_ADAPTIVE_STATE (self), NULL);
+
+ return self->condition;
+}
+
+void
+adw_adaptive_state_set_condition (AdwAdaptiveState *self,
+ AdwAdaptiveCondition *condition)
+{
+ g_return_if_fail (ADW_IS_ADAPTIVE_STATE (self));
+
+ if (self->condition == condition)
+ return;
+
+ g_clear_pointer (&self->condition, adw_adaptive_condition_free);
+ self->condition = adw_adaptive_condition_copy (condition);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONDITION]);
+}
+
+void
+adw_adaptive_state_add_setter (AdwAdaptiveState *self,
+ GObject *object,
+ const char *property,
+ const GValue *value)
+{
+ SetterData *setter;
+ GParamSpec *pspec;
+ GValue validated_value = G_VALUE_INIT;
+ GValue original_value = G_VALUE_INIT;
+
+ g_return_if_fail (ADW_IS_ADAPTIVE_STATE (self));
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (property != NULL);
+ g_return_if_fail (G_IS_VALUE (value));
+
+ pspec = find_pspec (object, property);
+
+ if (!pspec)
+ return;
+
+ g_value_init (&validated_value, pspec->value_type);
+
+ if (!g_value_transform (value, &validated_value)) {
+ g_error ("Unable to add setter for property '%s' of type '%s' from value of type '%s'",
+ pspec->name,
+ g_type_name (pspec->value_type),
+ G_VALUE_TYPE_NAME (value));
+
+ g_value_unset (&validated_value);
+
+ return;
+ } else if (g_param_value_validate (pspec, &validated_value) &&
+ !(pspec->flags & G_PARAM_LAX_VALIDATION)) {
+ char *contents = g_strdup_value_contents (value);
+
+ g_warning ("Unable to add setter: value \"%s\" of type '%s' is invalid or"
+ "out of range for property '%s' of type '%s'",
+ contents,
+ G_VALUE_TYPE_NAME (value),
+ pspec->name,
+ g_type_name (pspec->value_type));
+
+ g_free (contents);
+ g_value_unset (&validated_value);
+
+ return;
+ }
+
+ g_object_get_property (object, property, &original_value);
+
+ setter = g_new0 (SetterData, 1);
+
+ setter->object = object;
+ setter->pspec = g_param_spec_ref (pspec);
+ setter->value = validated_value;
+ setter->original_value = original_value;
+
+ g_object_weak_ref (object,
+ (GWeakNotify) setter_weak_notify,
+ self);
+
+ self->setters = g_list_append (self->setters, setter);
+
+ if (self->active)
+ g_object_set_property (setter->object,
+ setter->pspec->name,
+ &setter->value);
+}
+
+void
+adw_adaptive_state_add_setters (AdwAdaptiveState *self,
+ GObject *first_object,
+ const char *first_property,
+ ...)
+{
+ GObject *object = first_object;
+ const char *property = first_property;
+ va_list args;
+
+ g_return_if_fail (ADW_IS_ADAPTIVE_STATE (self));
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (property != NULL);
+
+ va_start (args, first_property);
+
+ while (object && property) {
+ GValue value = G_VALUE_INIT;
+ GParamSpec *pspec;
+ char *error;
+
+ pspec = find_pspec (object, property);
+
+ if (!pspec)
+ break;
+
+ G_VALUE_COLLECT_INIT (&value, pspec->value_type, args,
+ G_VALUE_NOCOPY_CONTENTS, &error);
+
+ if (error) {
+ g_warning ("%s: %s", G_STRLOC, error);
+ g_free (error);
+ break;
+ }
+
+ adw_adaptive_state_add_setter (self, object, property, &value);
+
+ g_value_unset (&value);
+
+ object = va_arg (args, GObject *);
+
+ if (object)
+ property = va_arg (args, const char *);
+ }
+
+ va_end (args);
+}
+
+void
+adw_adaptive_state_apply (AdwAdaptiveState *self)
+{
+ GList *l;
+
+ g_assert (ADW_IS_ADAPTIVE_STATE (self));
+
+ self->active = TRUE;
+
+ for (l = self->setters; l; l = l->next) {
+ SetterData *setter = l->data;
+
+ g_object_set_property (setter->object,
+ setter->pspec->name,
+ &setter->value);
+ }
+
+ g_signal_emit (self, signals[SIGNAL_ENTER], 0);
+}
+
+void
+adw_adaptive_state_unapply (AdwAdaptiveState *self)
+{
+ GList *l;
+
+ g_assert (ADW_IS_ADAPTIVE_STATE (self));
+
+ g_signal_emit (self, signals[SIGNAL_EXIT], 0);
+
+ for (l = self->setters; l; l = l->next) {
+ SetterData *setter = l->data;
+
+ g_object_set_property (setter->object,
+ setter->pspec->name,
+ &setter->original_value);
+ }
+
+ self->active = FALSE;
+}
+
+gboolean
+adw_adaptive_state_check_condition (AdwAdaptiveState *self,
+ int width,
+ int height)
+{
+ g_assert (ADW_IS_ADAPTIVE_STATE (self));
+
+ if (!self->condition)
+ return FALSE;
+
+ return check_condition (self->condition, width, height);
+}
diff --git a/src/adw-adaptive-state.h b/src/adw-adaptive-state.h
new file mode 100644
index 00000000..a628bceb
--- /dev/null
+++ b/src/adw-adaptive-state.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include "adw-version.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_ADAPTIVE_CONDITION (adw_adaptive_condition_get_type ())
+
+typedef enum {
+ ADW_CONDITION_MIN_WIDTH,
+ ADW_CONDITION_MAX_WIDTH,
+ ADW_CONDITION_MIN_HEIGHT,
+ ADW_CONDITION_MAX_HEIGHT,
+ ADW_CONDITION_MIN_ASPECT_RATIO,
+ ADW_CONDITION_MAX_ASPECT_RATIO,
+} AdwAdaptiveConditionType;
+
+typedef enum {
+ ADW_MULTI_CONDITION_ALL,
+ ADW_MULTI_CONDITION_ANY,
+} AdwAdaptiveMultiConditionType;
+
+typedef struct _AdwAdaptiveCondition AdwAdaptiveCondition;
+
+ADW_AVAILABLE_IN_1_3
+GType adw_adaptive_condition_get_type (void) G_GNUC_CONST;
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveCondition *adw_adaptive_condition_new (AdwAdaptiveConditionType type,
+ double value) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveCondition *adw_adaptive_multi_condition_new (AdwAdaptiveMultiConditionType type,
+ AdwAdaptiveCondition *first_condition,
+ ...) G_GNUC_NULL_TERMINATED
G_GNUC_WARN_UNUSED_RESULT;
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveCondition *adw_adaptive_multi_condition_newv (AdwAdaptiveMultiConditionType type,
+ AdwAdaptiveCondition **conditions,
+ int n_conditions)
G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveCondition *adw_adaptive_condition_copy (AdwAdaptiveCondition *self);
+ADW_AVAILABLE_IN_1_3
+void adw_adaptive_condition_free (AdwAdaptiveCondition *self);
+
+ADW_AVAILABLE_IN_1_3
+char *adw_adaptive_condition_to_string (AdwAdaptiveCondition *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (AdwAdaptiveCondition, adw_adaptive_condition_free)
+
+#define ADW_TYPE_ADAPTIVE_STATE (adw_adaptive_state_get_type())
+
+ADW_AVAILABLE_IN_1_3
+G_DECLARE_FINAL_TYPE (AdwAdaptiveState, adw_adaptive_state, ADW, ADAPTIVE_STATE, GObject)
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveState *adw_adaptive_state_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveCondition *adw_adaptive_state_get_condition (AdwAdaptiveState *self);
+ADW_AVAILABLE_IN_1_3
+void adw_adaptive_state_set_condition (AdwAdaptiveState *self,
+ AdwAdaptiveCondition *condition);
+
+ADW_AVAILABLE_IN_1_3
+void adw_adaptive_state_add_setter (AdwAdaptiveState *self,
+ GObject *object,
+ const char *property,
+ const GValue *value);
+
+ADW_AVAILABLE_IN_1_3
+void adw_adaptive_state_add_setters (AdwAdaptiveState *self,
+ GObject *first_object,
+ const char *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
diff --git a/src/adw-application-window.c b/src/adw-application-window.c
index c218e0d0..5f21e4ef 100644
--- a/src/adw-application-window.c
+++ b/src/adw-application-window.c
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2020 Alexander Mikhaylenko <alexm gnome org>
+ * Copyright (C) 2022 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
@@ -7,6 +8,8 @@
#include "config.h"
#include "adw-application-window.h"
+
+#include "adw-adaptive-state.h"
#include "adw-window-mixin-private.h"
/**
@@ -46,6 +49,7 @@ static GtkBuildableIface *parent_buildable_iface;
enum {
PROP_0,
PROP_CONTENT,
+ PROP_CURRENT_STATE,
LAST_PROP,
};
@@ -53,6 +57,12 @@ static GParamSpec *props[LAST_PROP];
#define ADW_GET_WINDOW_MIXIN(obj) (((AdwApplicationWindowPrivate *)
adw_application_window_get_instance_private (ADW_APPLICATION_WINDOW (obj)))->mixin)
+static void
+notify_current_state_cb (AdwApplicationWindow *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_STATE]);
+}
+
static void
adw_application_window_size_allocate (GtkWidget *widget,
int width,
@@ -65,6 +75,19 @@ adw_application_window_size_allocate (GtkWidget *widget,
baseline);
}
+static void
+adw_application_window_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ adw_window_mixin_snapshot (ADW_GET_WINDOW_MIXIN (widget), snapshot);
+}
+
+static void
+adw_application_window_realize (GtkWidget *widget)
+{
+ adw_window_mixin_realize (ADW_GET_WINDOW_MIXIN (widget));
+}
+
static void
adw_application_window_finalize (GObject *object)
{
@@ -88,6 +111,9 @@ adw_application_window_get_property (GObject *object,
case PROP_CONTENT:
g_value_set_object (value, adw_application_window_get_content (self));
break;
+ case PROP_CURRENT_STATE:
+ g_value_set_object (value, adw_application_window_get_current_state (self));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -119,7 +145,10 @@ adw_application_window_class_init (AdwApplicationWindowClass *klass)
object_class->finalize = adw_application_window_finalize;
object_class->get_property = adw_application_window_get_property;
object_class->set_property = adw_application_window_set_property;
+
widget_class->size_allocate = adw_application_window_size_allocate;
+ widget_class->snapshot = adw_application_window_snapshot;
+ widget_class->realize = adw_application_window_realize;
/**
* AdwApplicationWindow:content: (attributes org.gtk.Property.get=adw_application_window_get_content
org.gtk.Property.set=adw_application_window_set_content)
@@ -135,6 +164,18 @@ adw_application_window_class_init (AdwApplicationWindowClass *klass)
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * AdwApplicationWindow:current-state: (attributes
org.gtk.Property.get=adw_application_window_get_current_state)
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+ props[PROP_CURRENT_STATE] =
+ g_param_spec_object ("current-state", NULL, NULL,
+ ADW_TYPE_ADAPTIVE_STATE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
g_object_class_install_properties (object_class, LAST_PROP, props);}
static void
@@ -145,6 +186,11 @@ adw_application_window_init (AdwApplicationWindow *self)
priv->mixin = adw_window_mixin_new (GTK_WINDOW (self),
GTK_WINDOW_CLASS (adw_application_window_parent_class));
+ g_signal_connect_swapped (priv->mixin,
+ "state-changed",
+ G_CALLBACK (notify_current_state_cb),
+ self);
+
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (self), FALSE);
}
@@ -158,6 +204,9 @@ adw_application_window_buildable_add_child (GtkBuildable *buildable,
GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
else if (GTK_IS_WIDGET (child))
adw_application_window_set_content (ADW_APPLICATION_WINDOW (buildable), GTK_WIDGET (child));
+ else if (ADW_IS_ADAPTIVE_STATE (child))
+ adw_application_window_add_adaptive_state (ADW_APPLICATION_WINDOW (buildable),
+ g_object_ref (ADW_ADAPTIVE_STATE (child)));
else
parent_buildable_iface->add_child (buildable, builder, child, type);
}
@@ -233,3 +282,40 @@ adw_application_window_get_content (AdwApplicationWindow *self)
return adw_window_mixin_get_content (ADW_GET_WINDOW_MIXIN (self));
}
+
+/**
+ * adw_application_window_add_adaptive_state:
+ * @self: an application window
+ * @state: (transfer full): TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_application_window_add_adaptive_state (AdwApplicationWindow *self,
+ AdwAdaptiveState *state)
+{
+ g_return_if_fail (ADW_IS_APPLICATION_WINDOW (self));
+ g_return_if_fail (ADW_IS_ADAPTIVE_STATE (state));
+
+ adw_window_mixin_add_adaptive_state (ADW_GET_WINDOW_MIXIN (self), state);
+}
+
+/**
+ * adw_application_window_get_current_state: (attributes org.gtk.Method.get_property=current-state)
+ * @self: an application window
+ *
+ * TODO
+ *
+ * Returns: (nullable) (transfer none): TODO
+ *
+ * Since: 1.3
+ */
+AdwAdaptiveState *
+adw_application_window_get_current_state (AdwApplicationWindow *self)
+{
+ g_return_val_if_fail (ADW_IS_APPLICATION_WINDOW (self), NULL);
+
+ return adw_window_mixin_get_current_state (ADW_GET_WINDOW_MIXIN (self));
+}
diff --git a/src/adw-application-window.h b/src/adw-application-window.h
index 5c330e85..aeb092c6 100644
--- a/src/adw-application-window.h
+++ b/src/adw-application-window.h
@@ -14,6 +14,8 @@
#include <gtk/gtk.h>
+#include "adw-adaptive-state.h"
+
G_BEGIN_DECLS
#define ADW_TYPE_APPLICATION_WINDOW (adw_application_window_get_type())
@@ -38,4 +40,11 @@ void adw_application_window_set_content (AdwApplicationWindow *self,
ADW_AVAILABLE_IN_ALL
GtkWidget *adw_application_window_get_content (AdwApplicationWindow *self);
+ADW_AVAILABLE_IN_1_3
+void adw_application_window_add_adaptive_state (AdwApplicationWindow *self,
+ AdwAdaptiveState *state);
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveState *adw_application_window_get_current_state (AdwApplicationWindow *self);
+
G_END_DECLS
diff --git a/src/adw-window-mixin-private.h b/src/adw-window-mixin-private.h
index e64b1957..1e9cfa21 100644
--- a/src/adw-window-mixin-private.h
+++ b/src/adw-window-mixin-private.h
@@ -12,6 +12,8 @@
#include <gtk/gtk.h>
+#include "adw-adaptive-state.h"
+
G_BEGIN_DECLS
#define ADW_TYPE_WINDOW_MIXIN (adw_window_mixin_get_type())
@@ -26,8 +28,18 @@ void adw_window_mixin_size_allocate (AdwWindowMixin *self,
int height,
int baseline);
+void adw_window_mixin_snapshot (AdwWindowMixin *self,
+ GtkSnapshot *snapshot);
+
+void adw_window_mixin_realize (AdwWindowMixin *self);
+
GtkWidget *adw_window_mixin_get_content (AdwWindowMixin *self);
void adw_window_mixin_set_content (AdwWindowMixin *self,
GtkWidget *content);
+void adw_window_mixin_add_adaptive_state (AdwWindowMixin *self,
+ AdwAdaptiveState *state);
+
+AdwAdaptiveState *adw_window_mixin_get_current_state (AdwWindowMixin *self);
+
G_END_DECLS
diff --git a/src/adw-window-mixin.c b/src/adw-window-mixin.c
index f7fc47df..d2a27e55 100644
--- a/src/adw-window-mixin.c
+++ b/src/adw-window-mixin.c
@@ -1,15 +1,21 @@
/*
* Copyright (C) 2020 Alexander Mikhaylenko <alexm gnome org>
+ * Copyright (C) 2022 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
+#include "adw-window-mixin-private.h"
+
+#include "adw-adaptive-state-private.h"
#include "adw-gizmo-private.h"
#include "adw-macros-private.h"
+#include "adw-timed-animation.h"
#include "adw-widget-utils-private.h"
-#include "adw-window-mixin-private.h"
+
+#define TRANSITION_DURATION 250
struct _AdwWindowMixin
{
@@ -22,16 +28,161 @@ struct _AdwWindowMixin
GtkWidget *child;
GtkWidget *content;
+
+ GList *states;
+ AdwAdaptiveState *current_state;
+
+ gboolean state_changing;
+ GskRenderNode *old_node;
+ int old_width;
+ int old_height;
+ AdwAnimation *animation;
+ gboolean first_allocation;
+ guint tick_cb_id;
+ gboolean block_warnings;
};
G_DEFINE_FINAL_TYPE (AdwWindowMixin, adw_window_mixin, G_TYPE_OBJECT)
+enum {
+ SIGNAL_STATE_CHANGED,
+ SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+static void
+animation_cb (double value,
+ AdwWindowMixin *self)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (self->window));
+}
+
+static void
+animation_done_cb (AdwWindowMixin *self)
+{
+ self->old_node = NULL;
+ self->old_width = 0;
+ self->old_height = 0;
+}
+
+static void
+measure_content (AdwGizmo *gizmo,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ AdwWindowMixin *self = g_object_get_data (G_OBJECT (gizmo), "adw-window-mixin");
+ int min, nat;
+
+ if (self->content)
+ gtk_widget_measure (self->content, orientation, for_size,
+ &min, &nat, NULL, NULL);
+ else
+ min = nat = 0;
+
+ if (self->states)
+ min = 0;
+
+ if (minimum)
+ *minimum = min;
+ if (natural)
+ *natural = nat;
+ if (minimum_baseline)
+ *minimum_baseline = -1;
+ if (natural_baseline)
+ *natural_baseline = -1;
+}
+
+static void
+allocate_content (AdwGizmo *gizmo,
+ int width,
+ int height,
+ int baseline)
+{
+ AdwWindowMixin *self = g_object_get_data (G_OBJECT (gizmo), "adw-window-mixin");
+ int min_width, min_height;
+
+ if (self->state_changing)
+ return;
+
+ if (!self->content)
+ return;
+
+ if (!self->block_warnings && self->states) {
+ int window_width, window_height;
+
+ gtk_widget_get_size_request (GTK_WIDGET (self->window), &window_width, &window_height);
+
+ if (window_width <= 0 && window_height <= 0)
+ g_warning ("%s %p does not have a minimum size",
+ G_OBJECT_TYPE_NAME (self->window), self->window);
+ else if (window_width <= 0)
+ g_warning ("%s %p does not have a minimum width",
+ G_OBJECT_TYPE_NAME (self->window), self->window);
+ else if (window_height <= 0)
+ g_warning ("%s %p does not have a minimum height",
+ G_OBJECT_TYPE_NAME (self->window), self->window);
+ }
+
+ gtk_widget_measure (self->content, GTK_ORIENTATION_HORIZONTAL, -1,
+ &min_width, NULL, NULL, NULL);
+ gtk_widget_measure (self->content, GTK_ORIENTATION_VERTICAL, -1,
+ &min_height, NULL, NULL, NULL);
+
+ if (width >= min_width && height >= min_height) {
+ gtk_widget_allocate (self->content, width, height, baseline, NULL);
+
+ return;
+ }
+
+ if (!self->block_warnings) {
+ if (min_width > width && min_height > height)
+ g_warning ("%s %p exceeds its window size: requested %d×%d px, %d×%d px available",
+ G_OBJECT_TYPE_NAME (self->content), self->content, min_width, min_height, width, height);
+ else if (min_width > width)
+ g_warning ("%s %p exceeds its window width: requested %d px, %d px available",
+ G_OBJECT_TYPE_NAME (self->content), self->content, min_width, width);
+ else
+ g_warning ("%s %p exceeds its window height: requested %d px, %d px available",
+ G_OBJECT_TYPE_NAME (self->content), self->content, min_height, height);
+ }
+
+ width = MAX (width, min_width);
+ height = MAX (height, min_height);
+
+ gtk_widget_allocate (self->content, width, height, baseline, NULL);
+}
+
+static gboolean
+state_changed_tick_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ AdwWindowMixin *self)
+{
+ self->tick_cb_id = 0;
+ self->state_changing = FALSE;
+ gtk_widget_set_child_visible (self->child, TRUE);
+ gtk_widget_queue_resize (GTK_WIDGET (self->window));
+
+ adw_animation_reset (self->animation);
+ adw_animation_play (self->animation);
+
+ return G_SOURCE_REMOVE;
+}
+
void
adw_window_mixin_size_allocate (AdwWindowMixin *self,
int width,
int height,
int baseline)
{
+ GList *l;
+ GtkSnapshot *snapshot;
+ AdwAdaptiveState *new_state = NULL;
+
/* We don't want to allow any other titlebar */
if (gtk_window_get_titlebar (self->window) != self->titlebar)
g_error ("gtk_window_set_titlebar() is not supported for AdwWindow");
@@ -39,15 +190,154 @@ adw_window_mixin_size_allocate (AdwWindowMixin *self,
if (gtk_window_get_child (self->window) != self->child)
g_error ("gtk_window_set_child() is not supported for AdwWindow");
- GTK_WIDGET_CLASS (self->klass)->size_allocate (GTK_WIDGET (self->window),
- width,
- height,
- baseline);
+ for (l = self->states; l; l = l->next) {
+ AdwAdaptiveState *state = l->data;
+
+ if (adw_adaptive_state_check_condition (state, width, height)) {
+ new_state = state;
+ break;
+ }
+ }
+
+ if (new_state == self->current_state) {
+ GTK_WIDGET_CLASS (self->klass)->size_allocate (GTK_WIDGET (self->window),
+ width,
+ height,
+ baseline);
+
+ self->first_allocation = FALSE;
+
+ return;
+ }
+
+ if (!self->first_allocation) {
+ self->block_warnings = TRUE;
+ GTK_WIDGET_CLASS (self->klass)->size_allocate (GTK_WIDGET (self->window),
+ width,
+ height,
+ baseline);
+ self->block_warnings = FALSE;
+
+ snapshot = gtk_snapshot_new ();
+ adw_window_mixin_snapshot (self, snapshot);
+
+ self->old_node = gtk_snapshot_free_to_node (snapshot);
+ self->old_width = gtk_widget_get_width (GTK_WIDGET (self->window));
+ self->old_height = gtk_widget_get_height (GTK_WIDGET (self->window));
+
+ self->state_changing = TRUE;
+ gtk_widget_set_child_visible (self->child, FALSE);
+ }
+
+ if (self->current_state)
+ adw_adaptive_state_unapply (self->current_state);
+
+ self->current_state = new_state;
+ g_signal_emit (self, signals[SIGNAL_STATE_CHANGED], 0);
+
+ if (self->current_state)
+ adw_adaptive_state_apply (self->current_state);
+
+ if (self->first_allocation) {
+ self->block_warnings = TRUE;
+ GTK_WIDGET_CLASS (self->klass)->size_allocate (GTK_WIDGET (self->window),
+ width,
+ height,
+ baseline);
+ self->block_warnings = FALSE;
+ self->first_allocation = FALSE;
+ } else {
+ self->tick_cb_id = gtk_widget_add_tick_callback (GTK_WIDGET (self->window),
+ (GtkTickCallback) state_changed_tick_cb,
+ self, NULL);
+ }
+}
+
+static inline void
+draw_old_node (AdwWindowMixin *self,
+ GtkSnapshot *snapshot)
+{
+ float scale_x, scale_y;
+
+ if (!self->old_node)
+ return;
+
+ scale_x = (float) gtk_widget_get_width (GTK_WIDGET (self->window)) / self->old_width;
+ scale_y = (float) gtk_widget_get_height (GTK_WIDGET (self->window)) / self->old_height;
+
+ gtk_snapshot_save (snapshot);
+ gtk_snapshot_scale (snapshot, scale_x, scale_y);
+ gtk_snapshot_append_node (snapshot, self->old_node);
+ gtk_snapshot_restore (snapshot);
+}
+
+void
+adw_window_mixin_snapshot (AdwWindowMixin *self,
+ GtkSnapshot *snapshot)
+{
+ if (self->state_changing) {
+ draw_old_node (self, snapshot);
+ return;
+ }
+
+ if (!self->old_node ||
+ adw_animation_get_state (self->animation) != ADW_ANIMATION_PLAYING) {
+ GTK_WIDGET_CLASS (self->klass)->snapshot (GTK_WIDGET (self->window), snapshot);
+ return;
+ }
+
+ gtk_snapshot_push_cross_fade (snapshot, adw_animation_get_value (self->animation));
+
+ draw_old_node (self, snapshot);
+ gtk_snapshot_pop (snapshot);
+
+ GTK_WIDGET_CLASS (self->klass)->snapshot (GTK_WIDGET (self->window), snapshot);
+ gtk_snapshot_pop (snapshot);
+}
+
+void
+adw_window_mixin_realize (AdwWindowMixin *self)
+{
+ self->first_allocation = TRUE;
+
+ GTK_WIDGET_CLASS (self->klass)->realize (GTK_WIDGET (self->window));
+}
+
+static void
+adw_window_mixin_dispose (GObject *object)
+{
+ AdwWindowMixin *self = ADW_WINDOW_MIXIN (object);
+
+ if (self->tick_cb_id) {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (self->window), self->tick_cb_id);
+ self->tick_cb_id = 0;
+ }
+
+ g_clear_object (&self->animation);
+
+ if (self->states) {
+ g_list_free_full (self->states, g_object_unref);
+ self->states = NULL;
+ }
+
+ G_OBJECT_CLASS (adw_window_mixin_parent_class)->dispose (object);
}
static void
adw_window_mixin_class_init (AdwWindowMixinClass *klass)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = adw_window_mixin_dispose;
+
+ signals[SIGNAL_STATE_CHANGED] =
+ g_signal_new ("state-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
}
static void
@@ -60,6 +350,7 @@ adw_window_mixin_new (GtkWindow *window,
GtkWindowClass *klass)
{
AdwWindowMixin *self;
+ AdwAnimationTarget *target;
g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
g_return_val_if_fail (GTK_IS_WINDOW_CLASS (klass), NULL);
@@ -76,12 +367,17 @@ adw_window_mixin_new (GtkWindow *window,
gtk_window_set_titlebar (self->window, self->titlebar);
self->child = adw_gizmo_new_with_role ("contents", GTK_ACCESSIBLE_ROLE_GROUP,
- NULL, NULL, NULL, NULL,
+ measure_content, allocate_content,
+ NULL, NULL,
(AdwGizmoFocusFunc) adw_widget_focus_child,
(AdwGizmoGrabFocusFunc) adw_widget_grab_focus_child);
- gtk_widget_set_layout_manager (self->child, gtk_bin_layout_new ());
+ g_object_set_data (G_OBJECT (self->child), "adw-window-mixin", self);
gtk_window_set_child (window, self->child);
+ target = adw_callback_animation_target_new ((AdwAnimationTargetFunc) animation_cb, self, NULL);
+ self->animation = adw_timed_animation_new (GTK_WIDGET (window), 0, 1, TRANSITION_DURATION, target);
+ g_signal_connect_swapped (self->animation, "done", G_CALLBACK (animation_done_cb), self);
+
return self;
}
@@ -102,3 +398,21 @@ adw_window_mixin_get_content (AdwWindowMixin *self)
{
return self->content;
}
+
+void
+adw_window_mixin_add_adaptive_state (AdwWindowMixin *self,
+ AdwAdaptiveState *state)
+{
+ self->states = g_list_prepend (self->states, state);
+
+ gtk_widget_queue_allocate (GTK_WIDGET (self->window));
+
+ g_signal_connect_swapped (state, "notify::condition",
+ G_CALLBACK (gtk_widget_queue_allocate), self->window);
+}
+
+AdwAdaptiveState *
+adw_window_mixin_get_current_state (AdwWindowMixin *self)
+{
+ return self->current_state;
+}
diff --git a/src/adw-window.c b/src/adw-window.c
index 074a1cae..02fbe633 100644
--- a/src/adw-window.c
+++ b/src/adw-window.c
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2020 Alexander Mikhaylenko <alexm gnome org>
+ * Copyright (C) 2022 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
@@ -7,6 +8,8 @@
#include "config.h"
#include "adw-window.h"
+
+#include "adw-adaptive-state.h"
#include "adw-window-mixin-private.h"
/**
@@ -61,6 +64,7 @@ static GtkBuildableIface *parent_buildable_iface;
enum {
PROP_0,
PROP_CONTENT,
+ PROP_CURRENT_STATE,
LAST_PROP,
};
@@ -68,6 +72,12 @@ static GParamSpec *props[LAST_PROP];
#define ADW_GET_WINDOW_MIXIN(obj) (((AdwWindowPrivate *) adw_window_get_instance_private (ADW_WINDOW
(obj)))->mixin)
+static void
+notify_current_state_cb (AdwWindow *self)
+{
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_STATE]);
+}
+
static void
adw_window_size_allocate (GtkWidget *widget,
int width,
@@ -80,6 +90,19 @@ adw_window_size_allocate (GtkWidget *widget,
baseline);
}
+static void
+adw_window_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ adw_window_mixin_snapshot (ADW_GET_WINDOW_MIXIN (widget), snapshot);
+}
+
+static void
+adw_window_realize (GtkWidget *widget)
+{
+ adw_window_mixin_realize (ADW_GET_WINDOW_MIXIN (widget));
+}
+
static void
adw_window_finalize (GObject *object)
{
@@ -103,6 +126,9 @@ adw_window_get_property (GObject *object,
case PROP_CONTENT:
g_value_set_object (value, adw_window_get_content (self));
break;
+ case PROP_CURRENT_STATE:
+ g_value_set_object (value, adw_window_get_current_state (self));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -134,7 +160,10 @@ adw_window_class_init (AdwWindowClass *klass)
object_class->finalize = adw_window_finalize;
object_class->get_property = adw_window_get_property;
object_class->set_property = adw_window_set_property;
+
widget_class->size_allocate = adw_window_size_allocate;
+ widget_class->snapshot = adw_window_snapshot;
+ widget_class->realize = adw_window_realize;
/**
* AdwWindow:content: (attributes org.gtk.Property.get=adw_window_get_content
org.gtk.Property.set=adw_window_set_content)
@@ -150,6 +179,18 @@ adw_window_class_init (AdwWindowClass *klass)
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * AdwWindow:current-state: (attributes org.gtk.Property.get=adw_window_get_current_state)
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+ props[PROP_CURRENT_STATE] =
+ g_param_spec_object ("current-state", NULL, NULL,
+ ADW_TYPE_ADAPTIVE_STATE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
g_object_class_install_properties (object_class, LAST_PROP, props);
}
@@ -160,6 +201,11 @@ adw_window_init (AdwWindow *self)
priv->mixin = adw_window_mixin_new (GTK_WINDOW (self),
GTK_WINDOW_CLASS (adw_window_parent_class));
+
+ g_signal_connect_swapped (priv->mixin,
+ "state-changed",
+ G_CALLBACK (notify_current_state_cb),
+ self);
}
static void
@@ -172,6 +218,9 @@ adw_window_buildable_add_child (GtkBuildable *buildable,
GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
else if (GTK_IS_WIDGET (child))
adw_window_set_content (ADW_WINDOW (buildable), GTK_WIDGET (child));
+ else if (ADW_IS_ADAPTIVE_STATE (child))
+ adw_window_add_adaptive_state (ADW_WINDOW (buildable),
+ g_object_ref (ADW_ADAPTIVE_STATE (child)));
else
parent_buildable_iface->add_child (buildable, builder, child, type);
}
@@ -244,3 +293,40 @@ adw_window_get_content (AdwWindow *self)
return adw_window_mixin_get_content (ADW_GET_WINDOW_MIXIN (self));
}
+
+/**
+ * adw_window_add_adaptive_state:
+ * @self: a window
+ * @state: (transfer full): TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_window_add_adaptive_state (AdwWindow *self,
+ AdwAdaptiveState *state)
+{
+ g_return_if_fail (ADW_IS_WINDOW (self));
+ g_return_if_fail (ADW_IS_ADAPTIVE_STATE (state));
+
+ adw_window_mixin_add_adaptive_state (ADW_GET_WINDOW_MIXIN (self), state);
+}
+
+/**
+ * adw_window_get_current_state: (attributes org.gtk.Method.get_property=current-state)
+ * @self: a window
+ *
+ * TODO
+ *
+ * Returns: (nullable) (transfer none): TODO
+ *
+ * Since: 1.3
+ */
+AdwAdaptiveState *
+adw_window_get_current_state (AdwWindow *self)
+{
+ g_return_val_if_fail (ADW_IS_WINDOW (self), NULL);
+
+ return adw_window_mixin_get_current_state (ADW_GET_WINDOW_MIXIN (self));
+}
diff --git a/src/adw-window.h b/src/adw-window.h
index dc6be3c5..4e404923 100644
--- a/src/adw-window.h
+++ b/src/adw-window.h
@@ -14,6 +14,8 @@
#include <gtk/gtk.h>
+#include "adw-adaptive-state.h"
+
G_BEGIN_DECLS
#define ADW_TYPE_WINDOW (adw_window_get_type())
@@ -38,4 +40,11 @@ ADW_AVAILABLE_IN_ALL
void adw_window_set_content (AdwWindow *self,
GtkWidget *content);
+ADW_AVAILABLE_IN_1_3
+void adw_window_add_adaptive_state (AdwWindow *self,
+ AdwAdaptiveState *state);
+
+ADW_AVAILABLE_IN_1_3
+AdwAdaptiveState *adw_window_get_current_state (AdwWindow *self);
+
G_END_DECLS
diff --git a/src/adwaita.h b/src/adwaita.h
index 9e277ded..077b6521 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -23,6 +23,7 @@ G_BEGIN_DECLS
#include "adw-version.h"
#include "adw-about-window.h"
#include "adw-action-row.h"
+#include "adw-adaptive-state.h"
#include "adw-animation.h"
#include "adw-animation-target.h"
#include "adw-animation-util.h"
diff --git a/src/meson.build b/src/meson.build
index e14484ca..ade587ef 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -11,6 +11,7 @@ libadwaita_resources = gnome.compile_resources(
adw_public_enum_headers = [
'adw-animation.h',
+ 'adw-adaptive-state.h',
'adw-flap.h',
'adw-fold-threshold-policy.h',
'adw-easing.h',
@@ -84,6 +85,7 @@ libadwaita_generated_headers += [adw_public_enums[1]]
src_headers = [
'adw-about-window.h',
'adw-action-row.h',
+ 'adw-adaptive-state.h',
'adw-animation.h',
'adw-animation-target.h',
'adw-animation-util.h',
@@ -151,6 +153,7 @@ libadwaita_init_public_types = custom_target('adw-public-types.c',
src_sources = [
'adw-about-window.c',
'adw-action-row.c',
+ 'adw-adaptive-state.c',
'adw-animation.c',
'adw-animation-target.c',
'adw-animation-util.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]