[epiphany/wip/exalm/hdywindow: 1/4] Add EphyFullscreenBox




commit fdfd29a520de90b0594a296d1bcc94d4961e8514
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Wed Jan 27 19:42:37 2021 +0500

    Add EphyFullscreenBox

 src/ephy-fullscreen-box.c              | 514 +++++++++++++++++++++++++++++++++
 src/ephy-fullscreen-box.h              |  51 ++++
 src/meson.build                        |   1 +
 src/resources/themes/_shared-base.scss |  16 +
 4 files changed, 582 insertions(+)
---
diff --git a/src/ephy-fullscreen-box.c b/src/ephy-fullscreen-box.c
new file mode 100644
index 000000000..af544ac53
--- /dev/null
+++ b/src/ephy-fullscreen-box.c
@@ -0,0 +1,514 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2021 Purism SPC
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-fullscreen-box.h"
+
+#include <handy.h>
+
+#define INACTIVITY_TIME_MS 3000
+#define SHOW_HEADERBAR_DISTANCE_PX 5
+
+struct _EphyFullscreenBox {
+  GtkEventBox parent_instance;
+
+  HdyFlap *flap;
+  GtkEventController *controller;
+
+  gboolean fullscreen;
+  gboolean autohide;
+
+  guint timeout_id;
+
+  GtkWidget *last_focus;
+  gdouble last_y;
+};
+
+static void ephy_fullscreen_box_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EphyFullscreenBox, ephy_fullscreen_box, GTK_TYPE_EVENT_BOX,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+                                                ephy_fullscreen_box_buildable_init))
+
+enum {
+  PROP_0,
+  PROP_FULLSCREEN,
+  PROP_AUTOHIDE,
+  PROP_TITLEBAR,
+  PROP_REVEALED,
+  LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static void
+remove_timeout (EphyFullscreenBox *self)
+{
+  if (self->timeout_id) {
+    g_source_remove (self->timeout_id);
+    self->timeout_id = 0;
+  }
+}
+
+static void
+show_ui (EphyFullscreenBox *self)
+{
+  remove_timeout (self);
+
+  hdy_flap_set_reveal_flap (self->flap, TRUE);
+}
+
+static void
+hide_ui (EphyFullscreenBox *self)
+{
+  remove_timeout (self);
+
+  if (!self->fullscreen)
+    return;
+
+  hdy_flap_set_reveal_flap (self->flap, FALSE);
+  gtk_widget_grab_focus (GTK_WIDGET (self->flap));
+}
+
+static gboolean
+hide_timeout_cb (EphyFullscreenBox *self)
+{
+  self->timeout_id = 0;
+
+  hide_ui (self);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+start_hide_timeout (EphyFullscreenBox *self)
+{
+  if (!hdy_flap_get_reveal_flap (self->flap))
+    return;
+
+  if (self->timeout_id)
+    return;
+
+  self->timeout_id = g_timeout_add (INACTIVITY_TIME_MS, (GSourceFunc)hide_timeout_cb, self);
+}
+
+static gboolean
+is_descendant_of (GtkWidget *widget,
+                  GtkWidget *target)
+{
+  GtkWidget *parent;
+
+  if (!widget)
+    return FALSE;
+
+  if (widget == target)
+    return TRUE;
+
+  parent = widget;
+
+  while (parent && parent != target && !GTK_IS_POPOVER (parent))
+    parent = gtk_widget_get_parent (parent);
+
+  if (GTK_IS_POPOVER (parent))
+    return is_descendant_of (gtk_popover_get_relative_to (GTK_POPOVER (parent)), target);
+
+  return parent == target;
+}
+
+static void
+update (EphyFullscreenBox *self)
+{
+  gdouble height;
+
+  if (!self->autohide || !self->fullscreen)
+    return;
+
+  height = gtk_widget_get_allocated_height (hdy_flap_get_flap (self->flap));
+  height *= hdy_flap_get_reveal_progress (self->flap);
+  height = MAX (height, SHOW_HEADERBAR_DISTANCE_PX);
+
+  if (self->last_y <= height) {
+    show_ui (self);
+    return;
+  }
+
+  if (!self->last_focus)
+    start_hide_timeout (self);
+  else if (is_descendant_of (self->last_focus, hdy_flap_get_flap (self->flap)))
+    show_ui (self);
+  else
+    hide_ui (self);
+}
+
+static void
+motion_cb (EphyFullscreenBox *self,
+           gdouble            x,
+           gdouble            y)
+{
+  self->last_y = y;
+
+  update (self);
+}
+
+static void
+notify_reveal_cb (EphyFullscreenBox *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEALED]);
+}
+
+static void
+set_focus_cb (EphyFullscreenBox *self,
+              GtkWidget         *widget)
+{
+  self->last_focus = widget;
+
+  update (self);
+}
+
+static void
+ephy_fullscreen_box_hierarchy_changed (GtkWidget *widget,
+                                       GtkWidget *previous_toplevel)
+{
+  GtkWidget *toplevel;
+
+  if (previous_toplevel && GTK_IS_WINDOW (previous_toplevel))
+    g_signal_handlers_disconnect_by_func (previous_toplevel, set_focus_cb, widget);
+
+  toplevel = gtk_widget_get_toplevel (widget);
+
+  if (toplevel && GTK_IS_WINDOW (toplevel))
+    g_signal_connect_object (toplevel, "set-focus",
+                             G_CALLBACK (set_focus_cb), widget,
+                             G_CONNECT_SWAPPED);
+}
+
+static void
+ephy_fullscreen_box_add (GtkContainer *container,
+                         GtkWidget    *widget)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (container);
+
+  if (!self->flap)
+    GTK_CONTAINER_CLASS (ephy_fullscreen_box_parent_class)->add (container, widget);
+  else
+    gtk_container_add (GTK_CONTAINER (self->flap), widget);
+}
+
+static void
+ephy_fullscreen_box_remove (GtkContainer *container,
+                            GtkWidget    *widget)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (container);
+
+  if (widget == GTK_WIDGET (self->flap)) {
+    GTK_CONTAINER_CLASS (ephy_fullscreen_box_parent_class)->remove (container, widget);
+    self->flap = NULL;
+  } else
+    gtk_container_remove (GTK_CONTAINER (self->flap), widget);
+}
+
+static void
+ephy_fullscreen_box_forall (GtkContainer *container,
+                            gboolean      include_internals,
+                            GtkCallback   callback,
+                            gpointer      callback_data)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (container);
+
+  if (include_internals)
+    GTK_CONTAINER_CLASS (ephy_fullscreen_box_parent_class)->forall (container,
+                                                                    include_internals,
+                                                                    callback,
+                                                                    callback_data);
+  else
+    gtk_container_foreach (GTK_CONTAINER (self->flap),
+                           callback,
+                           callback_data);
+}
+
+static void
+ephy_fullscreen_box_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (object);
+
+  switch (prop_id) {
+    case PROP_FULLSCREEN:
+      g_value_set_boolean (value, ephy_fullscreen_box_get_fullscreen (self));
+      break;
+
+    case PROP_AUTOHIDE:
+      g_value_set_boolean (value, ephy_fullscreen_box_get_autohide (self));
+      break;
+
+    case PROP_TITLEBAR:
+      g_value_set_object (value, ephy_fullscreen_box_get_titlebar (self));
+      break;
+
+    case PROP_REVEALED:
+      g_value_set_boolean (value, hdy_flap_get_reveal_flap (self->flap));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_fullscreen_box_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (object);
+
+  switch (prop_id) {
+    case PROP_FULLSCREEN:
+      ephy_fullscreen_box_set_fullscreen (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_AUTOHIDE:
+      ephy_fullscreen_box_set_autohide (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_TITLEBAR:
+      ephy_fullscreen_box_set_titlebar (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ephy_fullscreen_box_dispose (GObject *object)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (object);
+
+  g_clear_object (&self->controller);
+
+  G_OBJECT_CLASS (ephy_fullscreen_box_parent_class)->dispose (object);
+}
+
+static void
+ephy_fullscreen_box_class_init (EphyFullscreenBoxClass *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 = ephy_fullscreen_box_get_property;
+  object_class->set_property = ephy_fullscreen_box_set_property;
+  object_class->dispose = ephy_fullscreen_box_dispose;
+
+  widget_class->hierarchy_changed = ephy_fullscreen_box_hierarchy_changed;
+
+  container_class->add = ephy_fullscreen_box_add;
+  container_class->remove = ephy_fullscreen_box_remove;
+  container_class->forall = ephy_fullscreen_box_forall;
+
+  props[PROP_FULLSCREEN] =
+    g_param_spec_boolean ("fullscreen",
+                          "Fullscreen",
+                          "Fullscreen",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  props[PROP_AUTOHIDE] =
+    g_param_spec_boolean ("autohide",
+                          "Autohide",
+                          "Autohide",
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  props[PROP_TITLEBAR] =
+    g_param_spec_object ("titlebar",
+                         "Titlebar",
+                         "Titlebar",
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  props[PROP_REVEALED] =
+    g_param_spec_boolean ("revealed",
+                          "Revealed",
+                          "Revealed",
+                          TRUE,
+                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_set_css_name (widget_class, "fullscreenbox");
+}
+
+static void
+ephy_fullscreen_box_init (EphyFullscreenBox *self)
+{
+  HdyFlap *flap;
+
+  self->autohide = TRUE;
+
+  gtk_widget_add_events (GTK_WIDGET (self), GDK_ALL_EVENTS_MASK);
+
+  flap = HDY_FLAP (hdy_flap_new ());
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (flap), GTK_ORIENTATION_VERTICAL);
+  hdy_flap_set_flap_position (flap, GTK_PACK_START);
+  hdy_flap_set_fold_policy (flap, HDY_FLAP_FOLD_POLICY_NEVER);
+  hdy_flap_set_locked (flap, TRUE);
+  hdy_flap_set_modal (flap, FALSE);
+  hdy_flap_set_swipe_to_open (flap, FALSE);
+  hdy_flap_set_swipe_to_close (flap, FALSE);
+  hdy_flap_set_transition_type (flap, HDY_FLAP_TRANSITION_TYPE_OVER);
+  gtk_widget_show (GTK_WIDGET (flap));
+
+  g_signal_connect_object (flap, "notify::reveal-flap",
+                           G_CALLBACK (notify_reveal_cb), self, G_CONNECT_SWAPPED);
+
+  gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (flap));
+  self->flap = flap;
+
+  self->controller = gtk_event_controller_motion_new (GTK_WIDGET (self));
+  gtk_event_controller_set_propagation_phase (self->controller, GTK_PHASE_CAPTURE);
+  g_signal_connect_object (self->controller, "motion",
+                           G_CALLBACK (motion_cb), self, G_CONNECT_SWAPPED);
+}
+
+static void
+ephy_fullscreen_box_buildable_add_child (GtkBuildable *buildable,
+                                         GtkBuilder   *builder,
+                                         GObject      *child,
+                                         const gchar  *type)
+{
+  EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (buildable);
+
+  if (!self->flap) {
+    gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (child));
+
+    return;
+  }
+
+  if (!g_strcmp0 (type, "titlebar"))
+    ephy_fullscreen_box_set_titlebar (self, GTK_WIDGET (child));
+  else
+    gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (child));
+}
+
+static void
+ephy_fullscreen_box_buildable_init (GtkBuildableIface *iface)
+{
+  iface->add_child = ephy_fullscreen_box_buildable_add_child;
+}
+
+EphyFullscreenBox *
+ephy_fullscreen_box_new (void)
+{
+  return g_object_new (EPHY_TYPE_FULLSCREEN_BOX, NULL);
+}
+
+gboolean
+ephy_fullscreen_box_get_fullscreen (EphyFullscreenBox *self)
+{
+  g_return_val_if_fail (EPHY_IS_FULLSCREEN_BOX (self), FALSE);
+
+  return self->fullscreen;
+}
+
+void
+ephy_fullscreen_box_set_fullscreen (EphyFullscreenBox *self,
+                                    gboolean           fullscreen)
+{
+  g_return_if_fail (EPHY_IS_FULLSCREEN_BOX (self));
+
+  fullscreen = !!fullscreen;
+
+  if (fullscreen == self->fullscreen)
+    return;
+
+  self->fullscreen = fullscreen;
+
+  if (!self->autohide)
+    return;
+
+  if (fullscreen) {
+    hdy_flap_set_fold_policy (self->flap, HDY_FLAP_FOLD_POLICY_ALWAYS);
+    start_hide_timeout (self);
+  } else {
+    hdy_flap_set_fold_policy (self->flap, HDY_FLAP_FOLD_POLICY_NEVER);
+    show_ui (self);
+  }
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FULLSCREEN]);
+}
+
+gboolean
+ephy_fullscreen_box_get_autohide (EphyFullscreenBox *self)
+{
+  g_return_val_if_fail (EPHY_IS_FULLSCREEN_BOX (self), FALSE);
+
+  return self->autohide;
+}
+
+void
+ephy_fullscreen_box_set_autohide (EphyFullscreenBox *self,
+                                  gboolean           autohide)
+{
+  g_return_if_fail (EPHY_IS_FULLSCREEN_BOX (self));
+
+  autohide = !!autohide;
+
+  if (autohide == self->autohide)
+    return;
+
+  self->autohide = autohide;
+
+  if (!self->fullscreen)
+    return;
+
+  if (autohide)
+    hide_ui (self);
+  else
+    show_ui (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_AUTOHIDE]);
+}
+
+GtkWidget *
+ephy_fullscreen_box_get_titlebar (EphyFullscreenBox *self)
+{
+  g_return_val_if_fail (EPHY_IS_FULLSCREEN_BOX (self), NULL);
+
+  return hdy_flap_get_flap (self->flap);
+}
+
+void
+ephy_fullscreen_box_set_titlebar (EphyFullscreenBox *self,
+                                  GtkWidget         *titlebar)
+{
+  g_return_if_fail (EPHY_IS_FULLSCREEN_BOX (self));
+  g_return_if_fail (GTK_IS_WIDGET (titlebar) || titlebar == NULL);
+
+  if (hdy_flap_get_flap (self->flap) == titlebar)
+    return;
+
+  hdy_flap_set_flap (self->flap, titlebar);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLEBAR]);
+}
diff --git a/src/ephy-fullscreen-box.h b/src/ephy-fullscreen-box.h
new file mode 100644
index 000000000..aefb4b755
--- /dev/null
+++ b/src/ephy-fullscreen-box.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2021 Purism SPC
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <exalm7659 gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_FULLSCREEN_BOX (ephy_fullscreen_box_get_type())
+
+G_DECLARE_FINAL_TYPE (EphyFullscreenBox, ephy_fullscreen_box, EPHY, FULLSCREEN_BOX, GtkEventBox)
+
+EphyFullscreenBox *ephy_fullscreen_box_new            (void);
+
+gboolean           ephy_fullscreen_box_get_fullscreen (EphyFullscreenBox *self);
+void               ephy_fullscreen_box_set_fullscreen (EphyFullscreenBox *self,
+                                                       gboolean          fullscreen);
+
+gboolean           ephy_fullscreen_box_get_autohide   (EphyFullscreenBox *self);
+void               ephy_fullscreen_box_set_autohide   (EphyFullscreenBox *self,
+                                                       gboolean          autohide);
+
+GtkWidget         *ephy_fullscreen_box_get_titlebar   (EphyFullscreenBox *self);
+void               ephy_fullscreen_box_set_titlebar   (EphyFullscreenBox *self,
+                                                       GtkWidget        *titlebar);
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index fa0cc3183..4a78dd75e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,6 +29,7 @@ libephymain_sources = [
   'ephy-encoding-dialog.c',
   'ephy-encoding-row.c',
   'ephy-firefox-sync-dialog.c',
+  'ephy-fullscreen-box.c',
   'ephy-header-bar.c',
   'ephy-history-dialog.c',
   'ephy-link.c',
diff --git a/src/resources/themes/_shared-base.scss b/src/resources/themes/_shared-base.scss
index 868e70964..072dd4185 100644
--- a/src/resources/themes/_shared-base.scss
+++ b/src/resources/themes/_shared-base.scss
@@ -225,3 +225,19 @@ dzlsuggestionpopover > revealer > box > elastic > scrolledwindow > viewport > li
     background: gtkalpha(gray, 0.2);
   }
 }
+
+fullscreenbox > flap {
+  > dimming,
+  > outline,
+  > border {
+    min-height: 0;
+    min-width: 0;
+    background: none;
+  }
+
+  > shadow {
+    min-height: 9px;
+    min-width: 9px;
+    background: linear-gradient(to bottom, gtkalpha(black, .1), gtkalpha(black, .0));
+  }
+}
\ No newline at end of file


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]