From 7f943af4f01631a40013cbb500602ac797c9db37 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Wed, 27 Jan 2021 19:42:37 +0500 Subject: Add EphyFullscreenBox Bump libhandy version requirement to 1.1.0, since we use a new widget from there. --- meson.build | 2 +- src/ephy-fullscreen-box.c | 514 +++++++++++++++++++++++++++++++++ src/ephy-fullscreen-box.h | 45 +++ src/meson.build | 1 + src/resources/themes/_shared-base.scss | 16 + 5 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 src/ephy-fullscreen-box.c create mode 100644 src/ephy-fullscreen-box.h diff --git a/meson.build b/meson.build index 2872cd348..0cb1259e2 100644 --- a/meson.build +++ b/meson.build @@ -92,7 +92,7 @@ iso_codes_dep = dependency('iso-codes', version: '>= 0.35') json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4') libarchive_dep = dependency('libarchive') libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.37.1') -libhandy_dep = dependency('libhandy-1', version: '>= 1.0.0') +libhandy_dep = dependency('libhandy-1', version: '>= 1.1.0') libsecret_dep = dependency('libsecret-1', version: '>= 0.19.0') libsoup_dep = dependency('libsoup-2.4', version: '>= 2.48.0') libxml_dep = dependency('libxml-2.0', version: '>= 2.6.12') diff --git a/src/ephy-fullscreen-box.c b/src/ephy-fullscreen-box.c new file mode 100644 index 000000000..c8e3fe9ce --- /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 . + */ + +#include "config.h" +#include "ephy-fullscreen-box.h" + +#include + +#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 +show_ui (EphyFullscreenBox *self) +{ + g_clear_handle_id (&self->timeout_id, g_source_remove); + + hdy_flap_set_reveal_flap (self->flap, TRUE); +} + +static void +hide_ui (EphyFullscreenBox *self) +{ + g_clear_handle_id (&self->timeout_id, g_source_remove); + + 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, + gboolean hide_immediately) +{ + 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 && is_descendant_of (self->last_focus, + hdy_flap_get_flap (self->flap))) + show_ui (self); + else if (hide_immediately) + hide_ui (self); + else + start_hide_timeout (self); +} + +static void +motion_cb (EphyFullscreenBox *self, + gdouble x, + gdouble y) +{ + self->last_y = y; + + update (self, FALSE); +} + +static void +set_focus_cb (EphyFullscreenBox *self, + GtkWidget *widget) +{ + self->last_focus = widget; + + update (self, TRUE); +} + +static void +notify_reveal_cb (EphyFullscreenBox *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEALED]); +} + +static void +ephy_fullscreen_box_hierarchy_changed (GtkWidget *widget, + GtkWidget *previous_toplevel) +{ + EphyFullscreenBox *self = EPHY_FULLSCREEN_BOX (widget); + 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); + + set_focus_cb (self, gtk_window_get_focus (GTK_WINDOW (toplevel))); + } else { + set_focus_cb (self, NULL); + } +} + +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..55a1ad93b --- /dev/null +++ b/src/ephy-fullscreen-box.h @@ -0,0 +1,45 @@ +/* -*- 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 . + */ + +#pragma once + +#include + +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 c43113a12..9f1a710c4 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)); + } +} -- cgit v1.2.1