[libhandy] Add HdyStatusPage widget
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libhandy] Add HdyStatusPage widget
- Date: Fri, 18 Dec 2020 07:40:15 +0000 (UTC)
commit 8d0fdb9b879e54763a75b3f7e0b6f5b744525ad5
Author: Yetizone <andreii lisita gmail com>
Date: Mon Nov 2 13:49:20 2020 +0200
Add HdyStatusPage widget
Fixes https://gitlab.gnome.org/GNOME/libhandy/-/issues/138
debian/libhandy-1-0.symbols | 8 +
doc/handy-docs.xml | 1 +
glade/libhandy.xml | 14 ++
src/handy.gresources.xml | 1 +
src/handy.h | 1 +
src/hdy-status-page.c | 397 +++++++++++++++++++++++++++++++++++++
src/hdy-status-page.h | 45 +++++
src/hdy-status-page.ui | 62 ++++++
src/meson.build | 2 +
src/themes/Adwaita-dark.css | 14 +-
src/themes/Adwaita.css | 14 +-
src/themes/HighContrast.css | 14 +-
src/themes/HighContrastInverse.css | 14 +-
src/themes/_fallback-base.scss | 23 +++
src/themes/fallback.css | 8 +
tests/meson.build | 1 +
tests/test-status-page.c | 110 ++++++++++
17 files changed, 717 insertions(+), 12 deletions(-)
---
diff --git a/debian/libhandy-1-0.symbols b/debian/libhandy-1-0.symbols
index 395860c9..ce6a22ad 100644
--- a/debian/libhandy-1-0.symbols
+++ b/debian/libhandy-1-0.symbols
@@ -102,6 +102,14 @@ libhandy-1.so.0 libhandy-1-0 #MINVER#
hdy_deck_set_visible_child_name@LIBHANDY_1_0 0.80.0
hdy_deck_transition_type_get_type@LIBHANDY_1_0 0.80.0
hdy_ease_out_cubic@LIBHANDY_1_0 0.0.11
+ hdy_status_page_get_description@LIBHANDY_1_0 1.1.0
+ hdy_status_page_get_icon_name@LIBHANDY_1_0 1.1.0
+ hdy_status_page_get_title@LIBHANDY_1_0 1.1.0
+ hdy_status_page_get_type@LIBHANDY_1_0 1.1.0
+ hdy_status_page_new@LIBHANDY_1_0 1.1.0
+ hdy_status_page_set_description@LIBHANDY_1_0 1.1.0
+ hdy_status_page_set_icon_name@LIBHANDY_1_0 1.1.0
+ hdy_status_page_set_title@LIBHANDY_1_0 1.1.0
hdy_enum_value_object_get_name@LIBHANDY_1_0 0.0.6
hdy_enum_value_object_get_nick@LIBHANDY_1_0 0.0.6
hdy_enum_value_object_get_type@LIBHANDY_1_0 0.0.6
diff --git a/doc/handy-docs.xml b/doc/handy-docs.xml
index 3d1531c8..310a9f0d 100644
--- a/doc/handy-docs.xml
+++ b/doc/handy-docs.xml
@@ -59,6 +59,7 @@
<xi:include href="xml/hdy-preferences-window.xml"/>
<xi:include href="xml/hdy-search-bar.xml"/>
<xi:include href="xml/hdy-squeezer.xml"/>
+ <xi:include href="xml/hdy-status-page.xml"/>
<xi:include href="xml/hdy-swipeable.xml"/>
<xi:include href="xml/hdy-swipe-group.xml"/>
<xi:include href="xml/hdy-swipe-tracker.xml"/>
diff --git a/glade/libhandy.xml b/glade/libhandy.xml
index 6069105d..122db125 100644
--- a/glade/libhandy.xml
+++ b/glade/libhandy.xml
@@ -370,6 +370,19 @@
</properties>
</glade-widget-class>
<glade-widget-class name="HdySqueezer" generic-name="squeezer" title="Squeezer" since="0.0.10"/>
+ <glade-widget-class name="HdyStatusPage" generic-name="statuspage" title="Status Page" since="1.1"
use-placeholders="False">
+ <post-create-function>glade_hdy_bin_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_bin_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_bin_add_child</add-child-function>
+ <remove-child-function>glade_hdy_bin_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_bin_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_bin_get_children</get-children-function>
+ <properties>
+ <property id="title" translatable="True" />
+ <property id="description" translatable="True" />
+ <property id="icon-name" themed-icon="True" />
+ </properties>
+ </glade-widget-class>
<glade-widget-class name="HdySwipeGroup" generic-name="swipegroup" title="Swipe Group" toplevel="True">
<read-widget-function>glade_hdy_swipe_group_read_widget</read-widget-function>
<write-widget-function>glade_hdy_swipe_group_write_widget</write-widget-function>
@@ -444,6 +457,7 @@
<glade-widget-class-ref name="HdyPreferencesWindow"/>
<glade-widget-class-ref name="HdySearchBar"/>
<glade-widget-class-ref name="HdySqueezer"/>
+ <glade-widget-class-ref name="HdyStatusPage"/>
<glade-widget-class-ref name="HdySwipeGroup"/>
<glade-widget-class-ref name="HdyTitleBar"/>
<glade-widget-class-ref name="HdyViewSwitcher"/>
diff --git a/src/handy.gresources.xml b/src/handy.gresources.xml
index b1d948ab..2d50728b 100644
--- a/src/handy.gresources.xml
+++ b/src/handy.gresources.xml
@@ -23,6 +23,7 @@
<file preprocess="xml-stripblanks">hdy-preferences-page.ui</file>
<file preprocess="xml-stripblanks">hdy-preferences-window.ui</file>
<file preprocess="xml-stripblanks">hdy-search-bar.ui</file>
+ <file preprocess="xml-stripblanks">hdy-status-page.ui</file>
<file preprocess="xml-stripblanks">hdy-view-switcher-bar.ui</file>
<file preprocess="xml-stripblanks">hdy-view-switcher-button.ui</file>
<file preprocess="xml-stripblanks">hdy-view-switcher-title.ui</file>
diff --git a/src/handy.h b/src/handy.h
index c02fa52a..d688e762 100644
--- a/src/handy.h
+++ b/src/handy.h
@@ -47,6 +47,7 @@ G_BEGIN_DECLS
#include "hdy-preferences-window.h"
#include "hdy-search-bar.h"
#include "hdy-squeezer.h"
+#include "hdy-status-page.h"
#include "hdy-swipe-group.h"
#include "hdy-swipe-tracker.h"
#include "hdy-swipeable.h"
diff --git a/src/hdy-status-page.c b/src/hdy-status-page.c
new file mode 100644
index 00000000..a5b0e644
--- /dev/null
+++ b/src/hdy-status-page.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2020 Andrei Lișiță <andreii lisita gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "hdy-status-page.h"
+
+/**
+ * SECTION:hdy-status-page
+ * @short_description: A page used for empty/error states and similar use-cases.
+ * @Title: HdyStatusPage
+ *
+ * The #HdyStatusPage widget can have an icon, a title, a description and a
+ * custom widget which is displayed below them.
+ *
+ * # CSS nodes
+ *
+ * #HdyStatusPage has a main CSS node with name statuspage.
+ *
+ * Since: 1.1
+ */
+
+enum {
+ PROP_0,
+ PROP_ICON_NAME,
+ PROP_TITLE,
+ PROP_DESCRIPTION,
+ LAST_PROP,
+};
+
+static GParamSpec *props[LAST_PROP];
+
+struct _HdyStatusPage
+{
+ GtkBin parent_instance;
+
+ GtkWidget *scrolled_window;
+ GtkBox *toplevel_box;
+ GtkImage *image;
+ gchar *icon_name;
+ GtkLabel *title_label;
+ GtkLabel *description_label;
+
+ GtkWidget *user_widget;
+};
+
+G_DEFINE_TYPE (HdyStatusPage, hdy_status_page, GTK_TYPE_BIN)
+
+static void
+hdy_status_page_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (object);
+
+ switch (prop_id) {
+ case PROP_ICON_NAME:
+ g_value_set_string (value, hdy_status_page_get_icon_name (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, hdy_status_page_get_title (self));
+ break;
+
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, hdy_status_page_get_description (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+hdy_status_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (object);
+
+ switch (prop_id) {
+ case PROP_ICON_NAME:
+ hdy_status_page_set_icon_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_TITLE:
+ hdy_status_page_set_title (self, g_value_get_string (value));
+ break;
+
+ case PROP_DESCRIPTION:
+ hdy_status_page_set_description (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+hdy_status_page_finalize (GObject *object)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (object);
+
+ g_clear_pointer (&self->icon_name, g_free);
+
+ G_OBJECT_CLASS (hdy_status_page_parent_class)->finalize (object);
+}
+
+static void
+hdy_status_page_destroy (GtkWidget *widget)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (widget);
+
+ if (self->scrolled_window) {
+ gtk_container_remove (GTK_CONTAINER (self), self->scrolled_window);
+ self->toplevel_box = NULL;
+ self->image = NULL;
+ self->title_label = NULL;
+ self->description_label = NULL;
+ self->user_widget = NULL;
+ }
+
+ GTK_WIDGET_CLASS (hdy_status_page_parent_class)->destroy (widget);
+}
+
+static void
+hdy_status_page_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (container);
+
+ if (!self->scrolled_window) {
+ GTK_CONTAINER_CLASS (hdy_status_page_parent_class)->add (container, child);
+ } else if (!self->user_widget) {
+ gtk_container_add (GTK_CONTAINER (self->toplevel_box), child);
+ self->user_widget = child;
+ } else {
+ g_warning ("Attempting to add a second child to a HdyStatusPage, but a HdyStatusPage can only have one
child");
+ }
+}
+
+static void
+hdy_status_page_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (container);
+
+ if (child == self->scrolled_window) {
+ GTK_CONTAINER_CLASS (hdy_status_page_parent_class)->remove (container, child);
+ } else if (child == self->user_widget) {
+ gtk_container_remove (GTK_CONTAINER (self->toplevel_box), child);
+ self->user_widget = NULL;
+ } else {
+ g_return_if_reached ();
+ }
+}
+
+static void
+hdy_status_page_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ HdyStatusPage *self = HDY_STATUS_PAGE (container);
+
+ if (include_internals)
+ GTK_CONTAINER_CLASS (hdy_status_page_parent_class)->forall (container,
+ include_internals,
+ callback,
+ callback_data);
+ else if (self->user_widget)
+ callback (self->user_widget, callback_data);
+}
+
+static void
+hdy_status_page_class_init (HdyStatusPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->get_property = hdy_status_page_get_property;
+ object_class->set_property = hdy_status_page_set_property;
+ object_class->finalize = hdy_status_page_finalize;
+ widget_class->destroy = hdy_status_page_destroy;
+ container_class->add = hdy_status_page_add;
+ container_class->remove = hdy_status_page_remove;
+ container_class->forall = hdy_status_page_forall;
+
+ /**
+ * HdyStatusPage:icon-name:
+ *
+ * The name of the icon to be used.
+ *
+ * Since: 1.1
+ */
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ _("Icon name"),
+ _("The name of the icon to be used"),
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdyStatusPage:title:
+ *
+ * The title to be displayed below the icon.
+ *
+ * Since: 1.1
+ */
+ props[PROP_TITLE] =
+ g_param_spec_string ("title",
+ _("Title"),
+ _("The title to be displayed below the icon"),
+ "",
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdyStatusPage:description:
+ *
+ * The description to be displayed below the title.
+ *
+ * Since: 1.1
+ */
+ props[PROP_DESCRIPTION] =
+ g_param_spec_string ("description",
+ _("Description"),
+ _("The description to be displayed below the title"),
+ "",
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/sm/puri/handy/ui/hdy-status-page.ui");
+ gtk_widget_class_bind_template_child (widget_class, HdyStatusPage, scrolled_window);
+ gtk_widget_class_bind_template_child (widget_class, HdyStatusPage, toplevel_box);
+ gtk_widget_class_bind_template_child (widget_class, HdyStatusPage, image);
+ gtk_widget_class_bind_template_child (widget_class, HdyStatusPage, title_label);
+ gtk_widget_class_bind_template_child (widget_class, HdyStatusPage, description_label);
+
+ gtk_widget_class_set_css_name (widget_class, "statuspage");
+}
+
+static void
+hdy_status_page_init (HdyStatusPage *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+/**
+ * hdy_status_page_new:
+ *
+ * Creates a new #HdyStatusPage.
+ *
+ * Returns: a new #HdyStatusPage
+ *
+ * Since: 1.1
+ */
+GtkWidget *
+hdy_status_page_new (void)
+{
+ return g_object_new (HDY_TYPE_STATUS_PAGE, NULL);
+}
+
+/**
+ * hdy_status_page_get_icon_name:
+ * @self: a #HdyStatusPage
+ *
+ * Gets the icon name for @self.
+ *
+ * Returns: (transfer none) (nullable): the icon name for @self.
+ *
+ * Since: 1.1
+ */
+const gchar *
+hdy_status_page_get_icon_name (HdyStatusPage *self)
+{
+ return self->icon_name;
+}
+
+/**
+ * hdy_status_page_set_icon_name:
+ * @self: a #HdyStatusPage
+ * @icon_name: (nullable): the icon name
+ *
+ * Sets the icon name for @self.
+ *
+ * Since: 1.1
+ */
+void
+hdy_status_page_set_icon_name (HdyStatusPage *self,
+ const gchar *icon_name)
+{
+ g_return_if_fail (HDY_IS_STATUS_PAGE (self));
+
+ if (g_strcmp0 (self->icon_name, icon_name) == 0)
+ return;
+
+ g_free (self->icon_name);
+ self->icon_name = g_strdup (icon_name);
+
+ if (!icon_name)
+ g_object_set (G_OBJECT (self->image), "icon-name", "image-missing", NULL);
+ else
+ g_object_set (G_OBJECT (self->image), "icon-name", icon_name, NULL);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
+}
+
+/**
+ * hdy_status_page_get_title:
+ * @self: a #HdyStatusPage
+ *
+ * Gets the title for @self.
+ *
+ * Returns: (transfer none) (nullable): the title for @self, or %NULL.
+ *
+ * Since: 1.1
+ */
+const gchar *
+hdy_status_page_get_title (HdyStatusPage *self)
+{
+ g_return_val_if_fail (HDY_IS_STATUS_PAGE (self), NULL);
+
+ return gtk_label_get_label (self->title_label);
+}
+
+/**
+ * hdy_status_page_set_title:
+ * @self: a #HdyStatusPage
+ * @title: (nullable): the title
+ *
+ * Sets the title for @self.
+ *
+ * Since: 1.1
+ */
+void
+hdy_status_page_set_title (HdyStatusPage *self,
+ const gchar *title)
+{
+ g_return_if_fail (HDY_IS_STATUS_PAGE (self));
+
+ if (g_strcmp0 (title, hdy_status_page_get_title (self)) == 0)
+ return;
+
+ gtk_label_set_label (self->title_label, title);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]);
+}
+
+/**
+ * hdy_status_page_get_description:
+ * @self: a #HdyStatusPage
+ *
+ * Gets the description for @self.
+ *
+ * Returns: (transfer none) (nullable): the description for @self, or %NULL.
+ *
+ * Since: 1.1
+ */
+const gchar *
+hdy_status_page_get_description (HdyStatusPage *self)
+{
+ g_return_val_if_fail (HDY_IS_STATUS_PAGE (self), NULL);
+
+ return gtk_label_get_label (self->description_label);
+}
+
+/**
+ * hdy_status_page_set_description:
+ * @self: a #HdyStatusPage
+ * @description: (nullable): the description
+ *
+ * Sets the description for @self.
+ *
+ * Since: 1.1
+ */
+void
+hdy_status_page_set_description (HdyStatusPage *self,
+ const gchar *description)
+{
+ g_return_if_fail (HDY_IS_STATUS_PAGE (self));
+
+ if (g_strcmp0 (description, hdy_status_page_get_description (self)) == 0)
+ return;
+
+ gtk_label_set_label (self->description_label, description);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DESCRIPTION]);
+}
diff --git a/src/hdy-status-page.h b/src/hdy-status-page.h
new file mode 100644
index 00000000..62b45d8f
--- /dev/null
+++ b/src/hdy-status-page.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Andrei Lișiță <andreii lisita gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION)
+#error "Only <handy.h> can be included directly."
+#endif
+
+#include "hdy-version.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define HDY_TYPE_STATUS_PAGE (hdy_status_page_get_type())
+
+HDY_AVAILABLE_IN_1_1
+G_DECLARE_FINAL_TYPE (HdyStatusPage, hdy_status_page, HDY, STATUS_PAGE, GtkBin)
+
+HDY_AVAILABLE_IN_1_1
+GtkWidget *hdy_status_page_new (void);
+
+HDY_AVAILABLE_IN_1_1
+const gchar *hdy_status_page_get_icon_name (HdyStatusPage *self);
+HDY_AVAILABLE_IN_1_1
+void hdy_status_page_set_icon_name (HdyStatusPage *self,
+ const gchar *icon_name);
+
+HDY_AVAILABLE_IN_1_1
+const gchar *hdy_status_page_get_title (HdyStatusPage *self);
+HDY_AVAILABLE_IN_1_1
+void hdy_status_page_set_title (HdyStatusPage *self,
+ const gchar *title);
+
+HDY_AVAILABLE_IN_1_1
+const gchar *hdy_status_page_get_description (HdyStatusPage *self);
+HDY_AVAILABLE_IN_1_1
+void hdy_status_page_set_description (HdyStatusPage *self,
+ const gchar *description);
+
+G_END_DECLS
diff --git a/src/hdy-status-page.ui b/src/hdy-status-page.ui
new file mode 100644
index 00000000..771ee80e
--- /dev/null
+++ b/src/hdy-status-page.ui
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="HdyStatusPage" parent="GtkBin">
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="visible">True</property>
+ <property name="hscrollbar-policy">never</property>
+ <child>
+ <object class="GtkBox" id="toplevel_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="HdyClamp">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="visible">True</property>
+ <property name="pixel-size">128</property>
+ <property name="icon-name">image-missing</property>
+ <style>
+ <class name="icon"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="title_label">
+ <property name="visible">True</property>
+ <property name="wrap">True</property>
+ <style>
+ <class name="title"/>
+ <class name="large-title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="description_label">
+ <property name="visible">True</property>
+ <property name="wrap">True</property>
+ <property name="justify">center</property>
+ <property name="use-markup">True</property>
+ <style>
+ <class name="body"/>
+ <class name="description"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/meson.build b/src/meson.build
index af850b0d..dc164ca3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -91,6 +91,7 @@ src_headers = [
'hdy-preferences-window.h',
'hdy-search-bar.h',
'hdy-squeezer.h',
+ 'hdy-status-page.h',
'hdy-swipe-group.h',
'hdy-swipe-tracker.h',
'hdy-swipeable.h',
@@ -148,6 +149,7 @@ src_sources = [
'hdy-shadow-helper.c',
'hdy-squeezer.c',
'hdy-stackable-box.c',
+ 'hdy-status-page.c',
'hdy-swipe-group.c',
'hdy-swipe-tracker.c',
'hdy-swipeable.c',
diff --git a/src/themes/Adwaita-dark.css b/src/themes/Adwaita-dark.css
index f4db9595..2577143f 100644
--- a/src/themes/Adwaita-dark.css
+++ b/src/themes/Adwaita-dark.css
@@ -75,6 +75,14 @@ avatar.image { background: none; }
viewswitchertitle viewswitcher { margin-left: 12px; margin-right: 12px; }
+statuspage > scrolledwindow > viewport > box { margin: 36px 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .icon { margin-bottom: 36px; opacity: 0.5; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .title { margin-bottom: 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .description { margin-bottom: 36px; }
+
/*************************** Check and Radio buttons * */
popover.combo list { min-width: 200px; }
@@ -170,11 +178,11 @@ list.content, list.content list { background-color: transparent; }
list.content list.nested > row:not(:active):not(:hover):not(:selected), list.content list.nested >
row:not(:active):hover:not(.activatable):not(:selected) { background-color: mix(#353535, #2d2d2d, 0.5); }
-list.content list.nested > row.activatable:not(:active):hover:not(:selected) { background-color:
mix(#eeeeec, #2d2d2d, 0.95); }
+list.content list.nested > row:not(:active):hover.activatable:not(:selected) { background-color:
mix(#eeeeec, #2d2d2d, 0.95); }
list.content > row:not(.expander):not(:active):not(:hover):not(:selected), list.content >
row:not(.expander):not(:active):hover:not(.activatable):not(:selected), list.content > row.expander
row.header:not(:active):not(:hover):not(:selected), list.content > row.expander
row.header:not(:active):hover:not(.activatable):not(:selected) { background-color: #2d2d2d; }
-list.content > row.activatable:not(.expander):not(:active):hover:not(:selected), list.content > row.expander
row.header.activatable:not(:active):hover:not(:selected) { background-color: mix(#eeeeec, #2d2d2d, 0.95); }
+list.content > row:not(.expander):not(:active):hover.activatable:not(:selected), list.content > row.expander
row.header:not(:active):hover.activatable:not(:selected) { background-color: mix(#eeeeec, #2d2d2d, 0.95); }
list.content > row, list.content > row list > row { border-color: alpha(#1b1b1b, 0.7); border-style: solid;
transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); }
@@ -184,7 +192,7 @@ list.content > row:first-child, list.content > row.expander:first-child row.head
list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked { border-width: 1px; }
-list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander.checked-expander-row-previous-sibling:not(:checked) row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
+list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander:not(:checked).checked-expander-row-previous-sibling row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
list.content > row.expander:checked:not(:first-child), list.content > row.expander:checked + row {
margin-top: 6px; }
diff --git a/src/themes/Adwaita.css b/src/themes/Adwaita.css
index 2d8fe364..9824ca68 100644
--- a/src/themes/Adwaita.css
+++ b/src/themes/Adwaita.css
@@ -75,6 +75,14 @@ avatar.image { background: none; }
viewswitchertitle viewswitcher { margin-left: 12px; margin-right: 12px; }
+statuspage > scrolledwindow > viewport > box { margin: 36px 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .icon { margin-bottom: 36px; opacity: 0.5; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .title { margin-bottom: 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .description { margin-bottom: 36px; }
+
/*************************** Check and Radio buttons * */
popover.combo list { min-width: 200px; }
@@ -170,11 +178,11 @@ list.content, list.content list { background-color: transparent; }
list.content list.nested > row:not(:active):not(:hover):not(:selected), list.content list.nested >
row:not(:active):hover:not(.activatable):not(:selected) { background-color: mix(#f6f5f4, #ffffff, 0.5); }
-list.content list.nested > row.activatable:not(:active):hover:not(:selected) { background-color:
mix(#2e3436, #ffffff, 0.95); }
+list.content list.nested > row:not(:active):hover.activatable:not(:selected) { background-color:
mix(#2e3436, #ffffff, 0.95); }
list.content > row:not(.expander):not(:active):not(:hover):not(:selected), list.content >
row:not(.expander):not(:active):hover:not(.activatable):not(:selected), list.content > row.expander
row.header:not(:active):not(:hover):not(:selected), list.content > row.expander
row.header:not(:active):hover:not(.activatable):not(:selected) { background-color: #ffffff; }
-list.content > row.activatable:not(.expander):not(:active):hover:not(:selected), list.content > row.expander
row.header.activatable:not(:active):hover:not(:selected) { background-color: mix(#2e3436, #ffffff, 0.95); }
+list.content > row:not(.expander):not(:active):hover.activatable:not(:selected), list.content > row.expander
row.header:not(:active):hover.activatable:not(:selected) { background-color: mix(#2e3436, #ffffff, 0.95); }
list.content > row, list.content > row list > row { border-color: alpha(#cdc7c2, 0.7); border-style: solid;
transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); }
@@ -184,7 +192,7 @@ list.content > row:first-child, list.content > row.expander:first-child row.head
list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked { border-width: 1px; }
-list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander.checked-expander-row-previous-sibling:not(:checked) row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
+list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander:not(:checked).checked-expander-row-previous-sibling row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
list.content > row.expander:checked:not(:first-child), list.content > row.expander:checked + row {
margin-top: 6px; }
diff --git a/src/themes/HighContrast.css b/src/themes/HighContrast.css
index 4d535ab3..7d295e26 100644
--- a/src/themes/HighContrast.css
+++ b/src/themes/HighContrast.css
@@ -75,6 +75,14 @@ avatar.image { background: none; }
viewswitchertitle viewswitcher { margin-left: 12px; margin-right: 12px; }
+statuspage > scrolledwindow > viewport > box { margin: 36px 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .icon { margin-bottom: 36px; opacity: 0.5; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .title { margin-bottom: 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .description { margin-bottom: 36px; }
+
/*************************** Check and Radio buttons * */
popover.combo list { min-width: 200px; }
@@ -170,11 +178,11 @@ list.content, list.content list { background-color: transparent; }
list.content list.nested > row:not(:active):not(:hover):not(:selected), list.content list.nested >
row:not(:active):hover:not(.activatable):not(:selected) { background-color: mix(#fdfdfc, #ffffff, 0.5); }
-list.content list.nested > row.activatable:not(:active):hover:not(:selected) { background-color:
mix(#272c2e, #ffffff, 0.95); }
+list.content list.nested > row:not(:active):hover.activatable:not(:selected) { background-color:
mix(#272c2e, #ffffff, 0.95); }
list.content > row:not(.expander):not(:active):not(:hover):not(:selected), list.content >
row:not(.expander):not(:active):hover:not(.activatable):not(:selected), list.content > row.expander
row.header:not(:active):not(:hover):not(:selected), list.content > row.expander
row.header:not(:active):hover:not(.activatable):not(:selected) { background-color: #ffffff; }
-list.content > row.activatable:not(.expander):not(:active):hover:not(:selected), list.content > row.expander
row.header.activatable:not(:active):hover:not(:selected) { background-color: mix(#272c2e, #ffffff, 0.95); }
+list.content > row:not(.expander):not(:active):hover.activatable:not(:selected), list.content > row.expander
row.header:not(:active):hover.activatable:not(:selected) { background-color: mix(#272c2e, #ffffff, 0.95); }
list.content > row, list.content > row list > row { border-color: alpha(#877b6e, 0.7); border-style: solid;
transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); }
@@ -184,7 +192,7 @@ list.content > row:first-child, list.content > row.expander:first-child row.head
list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked { border-width: 1px; }
-list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander.checked-expander-row-previous-sibling:not(:checked) row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
+list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander:not(:checked).checked-expander-row-previous-sibling row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
list.content > row.expander:checked:not(:first-child), list.content > row.expander:checked + row {
margin-top: 6px; }
diff --git a/src/themes/HighContrastInverse.css b/src/themes/HighContrastInverse.css
index 554e5a9f..a1735558 100644
--- a/src/themes/HighContrastInverse.css
+++ b/src/themes/HighContrastInverse.css
@@ -75,6 +75,14 @@ avatar.image { background: none; }
viewswitchertitle viewswitcher { margin-left: 12px; margin-right: 12px; }
+statuspage > scrolledwindow > viewport > box { margin: 36px 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .icon { margin-bottom: 36px; opacity: 0.5; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .title { margin-bottom: 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .description { margin-bottom: 36px; }
+
/*************************** Check and Radio buttons * */
popover.combo list { min-width: 200px; }
@@ -170,11 +178,11 @@ list.content, list.content list { background-color: transparent; }
list.content list.nested > row:not(:active):not(:hover):not(:selected), list.content list.nested >
row:not(:active):hover:not(.activatable):not(:selected) { background-color: mix(#303030, #2d2d2d, 0.5); }
-list.content list.nested > row.activatable:not(:active):hover:not(:selected) { background-color:
mix(#f3f3f1, #2d2d2d, 0.95); }
+list.content list.nested > row:not(:active):hover.activatable:not(:selected) { background-color:
mix(#f3f3f1, #2d2d2d, 0.95); }
list.content > row:not(.expander):not(:active):not(:hover):not(:selected), list.content >
row:not(.expander):not(:active):hover:not(.activatable):not(:selected), list.content > row.expander
row.header:not(:active):not(:hover):not(:selected), list.content > row.expander
row.header:not(:active):hover:not(.activatable):not(:selected) { background-color: #2d2d2d; }
-list.content > row.activatable:not(.expander):not(:active):hover:not(:selected), list.content > row.expander
row.header.activatable:not(:active):hover:not(:selected) { background-color: mix(#f3f3f1, #2d2d2d, 0.95); }
+list.content > row:not(.expander):not(:active):hover.activatable:not(:selected), list.content > row.expander
row.header:not(:active):hover.activatable:not(:selected) { background-color: mix(#f3f3f1, #2d2d2d, 0.95); }
list.content > row, list.content > row list > row { border-color: alpha(#686868, 0.7); border-style: solid;
transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); }
@@ -184,7 +192,7 @@ list.content > row:first-child, list.content > row.expander:first-child row.head
list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked { border-width: 1px; }
-list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander.checked-expander-row-previous-sibling:not(:checked) row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
+list.content > row:last-child, list.content > row.checked-expander-row-previous-sibling, list.content >
row.expander:checked, list.content > row.expander:not(:checked):last-child row.header, list.content >
row.expander:not(:checked).checked-expander-row-previous-sibling row.header, list.content >
row.expander.empty:checked row.header, list.content > row.expander list.nested > row:last-child {
border-bottom-left-radius: 8px; -gtk-outline-bottom-left-radius: 7px; border-bottom-right-radius: 8px;
-gtk-outline-bottom-right-radius: 7px; }
list.content > row.expander:checked:not(:first-child), list.content > row.expander:checked + row {
margin-top: 6px; }
diff --git a/src/themes/_fallback-base.scss b/src/themes/_fallback-base.scss
index 66f977b0..0f5cb6cf 100644
--- a/src/themes/_fallback-base.scss
+++ b/src/themes/_fallback-base.scss
@@ -147,3 +147,26 @@ viewswitchertitle viewswitcher {
margin-left: 12px;
margin-right: 12px;
}
+
+// HdyStatusPage
+
+statuspage > scrolledwindow > viewport > box {
+ margin: 36px 12px;
+
+ > clamp > box {
+ > .icon {
+ margin-bottom: 36px;
+ opacity: 0.5;
+ }
+
+ > .title {
+ margin-bottom: 12px;
+ }
+
+ > .description {
+ margin-bottom: 36px;
+ }
+ }
+}
+
+
diff --git a/src/themes/fallback.css b/src/themes/fallback.css
index 13d103ff..cffb9221 100644
--- a/src/themes/fallback.css
+++ b/src/themes/fallback.css
@@ -74,3 +74,11 @@ avatar.contrasted { color: #fff; }
avatar.image { background: none; }
viewswitchertitle viewswitcher { margin-left: 12px; margin-right: 12px; }
+
+statuspage > scrolledwindow > viewport > box { margin: 36px 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .icon { margin-bottom: 36px; opacity: 0.5; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .title { margin-bottom: 12px; }
+
+statuspage > scrolledwindow > viewport > box > clamp > box > .description { margin-bottom: 36px; }
diff --git a/tests/meson.build b/tests/meson.build
index 83abefda..ebde011e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -39,6 +39,7 @@ test_names = [
'test-preferences-window',
'test-search-bar',
'test-squeezer',
+ 'test-status-page',
'test-swipe-group',
'test-value-object',
'test-view-switcher',
diff --git a/tests/test-status-page.c b/tests/test-status-page.c
new file mode 100644
index 00000000..a3874cef
--- /dev/null
+++ b/tests/test-status-page.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 Andrei Lișiță <andreii lisita gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include <handy.h>
+
+gint notified;
+
+static void
+notify_cb (GtkWidget *widget, gpointer data)
+{
+ notified++;
+}
+
+static void
+test_hdy_status_page_icon_name (void)
+{
+ g_autoptr (HdyStatusPage) status_page = NULL;
+ const gchar *icon_name = NULL;
+
+ status_page = HDY_STATUS_PAGE (g_object_ref_sink (hdy_status_page_new ()));
+ g_assert_nonnull (status_page);
+
+ notified = 0;
+ g_signal_connect (status_page, "notify::icon-name", G_CALLBACK (notify_cb), NULL);
+
+ g_object_get (status_page, "icon-name", &icon_name, NULL);
+ g_assert_cmpstr (icon_name, ==, NULL);
+
+ hdy_status_page_set_icon_name (status_page, NULL);
+ g_assert_cmpint (notified, ==, 0);
+
+ hdy_status_page_set_icon_name (status_page, "some-icon-symbolic");
+ g_assert_cmpstr (hdy_status_page_get_icon_name (status_page), ==, "some-icon-symbolic");
+ g_assert_cmpint (notified, ==, 1);
+
+ g_object_set (status_page, "icon-name", "other-icon-symbolic", NULL);
+ g_assert_cmpstr (hdy_status_page_get_icon_name (status_page), ==, "other-icon-symbolic");
+ g_assert_cmpint (notified, ==, 2);
+}
+
+static void
+test_hdy_status_page_title (void)
+{
+ g_autoptr (HdyStatusPage) status_page = NULL;
+ const gchar *title = NULL;
+
+ status_page = HDY_STATUS_PAGE (g_object_ref_sink (hdy_status_page_new ()));
+ g_assert_nonnull (status_page);
+
+ notified = 0;
+ g_signal_connect (status_page, "notify::title", G_CALLBACK (notify_cb), NULL);
+
+ g_object_get (status_page, "title", &title, NULL);
+ g_assert_cmpstr (title, ==, "");
+
+ hdy_status_page_set_title (status_page, "");
+ g_assert_cmpint (notified, ==, 0);
+
+ hdy_status_page_set_title (status_page, "Some Title");
+ g_assert_cmpstr (hdy_status_page_get_title (status_page), ==, "Some Title");
+ g_assert_cmpint (notified, ==, 1);
+
+ g_object_set (status_page, "title", "Other Title", NULL);
+ g_assert_cmpstr (hdy_status_page_get_title (status_page), ==, "Other Title");
+ g_assert_cmpint (notified, ==, 2);
+}
+
+static void
+test_hdy_status_page_description (void)
+{
+ g_autoptr (HdyStatusPage) status_page = NULL;
+ const gchar *description = NULL;
+
+ status_page = HDY_STATUS_PAGE (g_object_ref_sink (hdy_status_page_new ()));
+ g_assert_nonnull (status_page);
+
+ notified = 0;
+ g_signal_connect (status_page, "notify::description", G_CALLBACK (notify_cb), NULL);
+
+ g_object_get (status_page, "description", &description, NULL);
+ g_assert_cmpstr (description, ==, "");
+
+ hdy_status_page_set_description (status_page, "");
+ g_assert_cmpint (notified, ==, 0);
+
+ hdy_status_page_set_description (status_page, "Some description");
+ g_assert_cmpstr (hdy_status_page_get_description (status_page), ==, "Some description");
+ g_assert_cmpint (notified, ==, 1);
+
+ g_object_set (status_page, "description", "Other description", NULL);
+ g_assert_cmpstr (hdy_status_page_get_description (status_page), ==, "Other description");
+ g_assert_cmpint (notified, ==, 2);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ gtk_test_init (&argc, &argv, NULL);
+ hdy_init ();
+
+ g_test_add_func ("/Handy/StatusPage/icon_name", test_hdy_status_page_icon_name);
+ g_test_add_func ("/Handy/StatusPage/title", test_hdy_status_page_title);
+ g_test_add_func ("/Handy/StatusPage/description", test_hdy_status_page_description);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]