[console/wip/exalm/fullscreen] Implement fullscreen feature




commit 65767a3ac36f0091a9ec01816e68ac0e2fbc626d
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Thu Aug 4 12:48:28 2022 +0400

    Implement fullscreen feature
    
    Fixes https://gitlab.gnome.org/GNOME/console/-/issues/64

 src/kgx-application.c    |  11 +
 src/kgx-fullscreen-box.c | 612 +++++++++++++++++++++++++++++++++++++++++++++++
 src/kgx-fullscreen-box.h |  47 ++++
 src/kgx-window.c         |  20 ++
 src/kgx-window.h         |   1 +
 src/kgx-window.ui        | 270 +++++++++++----------
 src/meson.build          |   3 +
 src/style.css            |  14 ++
 8 files changed, 858 insertions(+), 120 deletions(-)
---
diff --git a/src/kgx-application.c b/src/kgx-application.c
index f2a0f7f..365910d 100644
--- a/src/kgx-application.c
+++ b/src/kgx-application.c
@@ -1175,8 +1175,19 @@ kgx_application_add_terminal (KgxApplication *self,
   if (existing_window) {
     window = GTK_WINDOW (existing_window);
   } else {
+    GtkWindow *active_window =
+      gtk_application_get_active_window (GTK_APPLICATION (self));
+    int width = -1;
+    int height = -1;
+
+    if (active_window) {
+      gtk_window_get_default_size (active_window, &width, &height);
+    }
+
     window = g_object_new (KGX_TYPE_WINDOW,
                            "application", self,
+                           "default-width", width,
+                           "default-height", height,
                            NULL);
   }
 
diff --git a/src/kgx-fullscreen-box.c b/src/kgx-fullscreen-box.c
new file mode 100644
index 0000000..e31d36f
--- /dev/null
+++ b/src/kgx-fullscreen-box.c
@@ -0,0 +1,612 @@
+/* kgx-fullscreen-box.c
+ *
+ * Copyright 2021 Purism SPC
+ *
+ * This program 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.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "kgx-config.h"
+
+#include "kgx-fullscreen-box.h"
+#include <adwaita.h>
+
+
+#define FULLSCREEN_HIDE_DELAY 300
+#define SHOW_HEADERBAR_DISTANCE_PX 5
+
+
+struct _KgxFullscreenBox {
+  GtkWidget parent_instance;
+
+  AdwFlap *flap;
+
+  gboolean fullscreen;
+  gboolean autohide;
+
+  guint timeout_id;
+
+  GtkWidget *last_focus;
+  gdouble last_y;
+  gboolean is_touch;
+};
+
+
+static void kgx_fullscreen_box_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (KgxFullscreenBox, kgx_fullscreen_box, GTK_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+                                                kgx_fullscreen_box_buildable_init))
+
+
+enum {
+  PROP_0,
+  PROP_FULLSCREEN,
+  PROP_AUTOHIDE,
+  PROP_TITLEBAR,
+  PROP_CONTENT,
+  PROP_REVEALED,
+  LAST_PROP
+};
+static GParamSpec *props[LAST_PROP];
+
+
+static void
+show_ui (KgxFullscreenBox *self)
+{
+  g_clear_handle_id (&self->timeout_id, g_source_remove);
+
+  adw_flap_set_reveal_flap (self->flap, TRUE);
+}
+
+
+static void
+hide_ui (KgxFullscreenBox *self)
+{
+  g_clear_handle_id (&self->timeout_id, g_source_remove);
+
+  if (!self->fullscreen) {
+    return;
+  }
+
+  adw_flap_set_reveal_flap (self->flap, FALSE);
+  gtk_widget_grab_focus (GTK_WIDGET (self->flap));
+}
+
+
+static gboolean
+hide_timeout_cb (KgxFullscreenBox *self)
+{
+  self->timeout_id = 0;
+
+  hide_ui (self);
+
+  return G_SOURCE_REMOVE;
+}
+
+
+static void
+start_hide_timeout (KgxFullscreenBox *self)
+{
+  if (!adw_flap_get_reveal_flap (self->flap)) {
+    return;
+  }
+
+  if (self->timeout_id) {
+    return;
+  }
+
+  self->timeout_id = g_timeout_add (FULLSCREEN_HIDE_DELAY,
+                                    (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) {
+    parent = gtk_widget_get_parent (parent);
+  }
+
+  return parent == target;
+}
+
+
+static double
+get_titlebar_area_height (KgxFullscreenBox *self)
+{
+  gdouble height;
+
+  height = gtk_widget_get_allocated_height (adw_flap_get_flap (self->flap));
+  height *= adw_flap_get_reveal_progress (self->flap);
+  height = MAX (height, SHOW_HEADERBAR_DISTANCE_PX);
+
+  return height;
+}
+
+
+static void
+update (KgxFullscreenBox *self,
+        gboolean          hide_immediately)
+{
+  if (!self->autohide || !self->fullscreen) {
+    return;
+  }
+
+  if (!self->is_touch &&
+      self->last_y <= get_titlebar_area_height (self)) {
+    show_ui (self);
+    return;
+  }
+
+  if (self->last_focus && is_descendant_of (self->last_focus,
+                                            adw_flap_get_flap (self->flap))) {
+    show_ui (self);
+  } else if (hide_immediately) {
+    hide_ui (self);
+  } else {
+    start_hide_timeout (self);
+  }
+}
+
+
+static void
+motion_cb (KgxFullscreenBox *self,
+           double            x,
+           double            y)
+{
+  self->is_touch = FALSE;
+  self->last_y = y;
+
+  update (self, TRUE);
+}
+
+
+static void
+enter_cb (KgxFullscreenBox *self,
+          double            x,
+          double            y)
+{
+  motion_cb (self, x, y);
+}
+
+
+static void
+press_cb (KgxFullscreenBox *self,
+          int               n_press,
+          double            x,
+          double            y,
+          GtkGesture       *gesture)
+{
+  gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+
+  self->is_touch = TRUE;
+
+  if (y > get_titlebar_area_height (self)) {
+    update (self, TRUE);
+  }
+}
+
+
+static void
+set_focus (KgxFullscreenBox *self,
+           GtkWidget        *widget)
+{
+  self->last_focus = widget;
+
+  update (self, TRUE);
+}
+
+
+static void
+notify_focus_cb (KgxFullscreenBox *self,
+                 GParamSpec       *pspec,
+                 GtkRoot          *root)
+{
+  set_focus (self, gtk_root_get_focus (root));
+}
+
+
+static void
+notify_reveal_cb (KgxFullscreenBox *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEALED]);
+}
+
+
+static void
+kgx_fullscreen_box_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  KgxFullscreenBox *self = KGX_FULLSCREEN_BOX (object);
+
+  switch (prop_id) {
+    case PROP_FULLSCREEN:
+      g_value_set_boolean (value, kgx_fullscreen_box_get_fullscreen (self));
+      break;
+
+    case PROP_AUTOHIDE:
+      g_value_set_boolean (value, kgx_fullscreen_box_get_autohide (self));
+      break;
+
+    case PROP_TITLEBAR:
+      g_value_set_object (value, kgx_fullscreen_box_get_titlebar (self));
+      break;
+
+    case PROP_CONTENT:
+      g_value_set_object (value, kgx_fullscreen_box_get_content (self));
+      break;
+
+    case PROP_REVEALED:
+      g_value_set_boolean (value, adw_flap_get_reveal_flap (self->flap));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+
+static void
+kgx_fullscreen_box_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  KgxFullscreenBox *self = KGX_FULLSCREEN_BOX (object);
+
+  switch (prop_id) {
+    case PROP_FULLSCREEN:
+      kgx_fullscreen_box_set_fullscreen (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_AUTOHIDE:
+      kgx_fullscreen_box_set_autohide (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_TITLEBAR:
+      kgx_fullscreen_box_set_titlebar (self, g_value_get_object (value));
+      break;
+
+    case PROP_CONTENT:
+      kgx_fullscreen_box_set_content (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+
+static void
+kgx_fullscreen_box_dispose (GObject *object)
+{
+  KgxFullscreenBox *self = KGX_FULLSCREEN_BOX (object);
+
+  if (self->flap) {
+    gtk_widget_unparent (GTK_WIDGET (self->flap));
+    self->flap = NULL;
+  }
+
+  G_OBJECT_CLASS (kgx_fullscreen_box_parent_class)->dispose (object);
+}
+
+
+static void
+kgx_fullscreen_box_root (GtkWidget *widget)
+{
+  KgxFullscreenBox *self = KGX_FULLSCREEN_BOX (widget);
+  GtkRoot *root;
+
+  GTK_WIDGET_CLASS (kgx_fullscreen_box_parent_class)->root (widget);
+
+  root = gtk_widget_get_root (widget);
+
+  if (root && GTK_IS_WINDOW (root)) {
+    g_signal_connect_object (root, "notify::focus-widget",
+                             G_CALLBACK (notify_focus_cb), widget,
+                             G_CONNECT_SWAPPED);
+
+    set_focus (self, gtk_window_get_focus (GTK_WINDOW (root)));
+  } else {
+    set_focus (self, NULL);
+  }
+}
+
+
+static void
+kgx_fullscreen_box_unroot (GtkWidget *widget)
+{
+  KgxFullscreenBox *self = KGX_FULLSCREEN_BOX (widget);
+  GtkRoot *root = gtk_widget_get_root (widget);
+
+  if (root && GTK_IS_WINDOW (root)) {
+    g_signal_handlers_disconnect_by_func (root, notify_focus_cb, widget);
+  }
+
+  set_focus (self, NULL);
+
+  GTK_WIDGET_CLASS (kgx_fullscreen_box_parent_class)->unroot (widget);
+}
+
+
+static void
+kgx_fullscreen_box_class_init (KgxFullscreenBoxClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = kgx_fullscreen_box_get_property;
+  object_class->set_property = kgx_fullscreen_box_set_property;
+  object_class->dispose = kgx_fullscreen_box_dispose;
+
+  widget_class->root = kgx_fullscreen_box_root;
+  widget_class->unroot = kgx_fullscreen_box_unroot;
+
+  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_CONTENT] =
+    g_param_spec_object ("content",
+                         "Content",
+                         "Content",
+                         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");
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+
+static void
+kgx_fullscreen_box_init (KgxFullscreenBox *self)
+{
+  AdwFlap *flap;
+  GtkEventController *controller;
+  GtkGesture *gesture;
+
+  self->autohide = TRUE;
+
+  flap = ADW_FLAP (adw_flap_new ());
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (flap), GTK_ORIENTATION_VERTICAL);
+  adw_flap_set_flap_position (flap, GTK_PACK_START);
+  adw_flap_set_fold_policy (flap, ADW_FLAP_FOLD_POLICY_NEVER);
+  adw_flap_set_locked (flap, TRUE);
+  adw_flap_set_modal (flap, FALSE);
+  adw_flap_set_swipe_to_open (flap, FALSE);
+  adw_flap_set_swipe_to_close (flap, FALSE);
+  adw_flap_set_transition_type (flap, ADW_FLAP_TRANSITION_TYPE_OVER);
+
+  g_signal_connect_object (flap, "notify::reveal-flap",
+                           G_CALLBACK (notify_reveal_cb), self, G_CONNECT_SWAPPED);
+
+  gtk_widget_set_parent (GTK_WIDGET (flap), GTK_WIDGET (self));
+  self->flap = flap;
+
+  controller = gtk_event_controller_motion_new ();
+  gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+  g_signal_connect_object (controller, "enter",
+                           G_CALLBACK (enter_cb), self, G_CONNECT_SWAPPED);
+  g_signal_connect_object (controller, "motion",
+                           G_CALLBACK (motion_cb), self, G_CONNECT_SWAPPED);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
+
+  gesture = gtk_gesture_click_new ();
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
+                                              GTK_PHASE_CAPTURE);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
+  g_signal_connect_object (gesture, "pressed",
+                           G_CALLBACK (press_cb), self, G_CONNECT_SWAPPED);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+}
+
+
+static void
+kgx_fullscreen_box_buildable_add_child (GtkBuildable *buildable,
+                                        GtkBuilder   *builder,
+                                        GObject      *child,
+                                        const gchar  *type)
+{
+  KgxFullscreenBox *self = KGX_FULLSCREEN_BOX (buildable);
+
+  if (!g_strcmp0 (type, "titlebar")) {
+    kgx_fullscreen_box_set_titlebar (self, GTK_WIDGET (child));
+  } else {
+    kgx_fullscreen_box_set_content (self, GTK_WIDGET (child));
+  }
+}
+
+
+static void
+kgx_fullscreen_box_buildable_init (GtkBuildableIface *iface)
+{
+  iface->add_child = kgx_fullscreen_box_buildable_add_child;
+}
+
+
+KgxFullscreenBox *
+kgx_fullscreen_box_new (void)
+{
+  return g_object_new (KGX_TYPE_FULLSCREEN_BOX, NULL);
+}
+
+
+gboolean
+kgx_fullscreen_box_get_fullscreen (KgxFullscreenBox *self)
+{
+  g_return_val_if_fail (KGX_IS_FULLSCREEN_BOX (self), FALSE);
+
+  return self->fullscreen;
+}
+
+
+void
+kgx_fullscreen_box_set_fullscreen (KgxFullscreenBox *self,
+                                   gboolean          fullscreen)
+{
+  g_return_if_fail (KGX_IS_FULLSCREEN_BOX (self));
+
+  fullscreen = !!fullscreen;
+
+  if (fullscreen == self->fullscreen) {
+    return;
+  }
+
+  self->fullscreen = fullscreen;
+
+  if (!self->autohide) {
+    return;
+  }
+
+  if (fullscreen) {
+    adw_flap_set_fold_policy (self->flap, ADW_FLAP_FOLD_POLICY_ALWAYS);
+    update (self, FALSE);
+  } else {
+    adw_flap_set_fold_policy (self->flap, ADW_FLAP_FOLD_POLICY_NEVER);
+    show_ui (self);
+  }
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FULLSCREEN]);
+}
+
+
+gboolean
+kgx_fullscreen_box_get_autohide (KgxFullscreenBox *self)
+{
+  g_return_val_if_fail (KGX_IS_FULLSCREEN_BOX (self), FALSE);
+
+  return self->autohide;
+}
+
+
+void
+kgx_fullscreen_box_set_autohide (KgxFullscreenBox *self,
+                                 gboolean          autohide)
+{
+  g_return_if_fail (KGX_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 *
+kgx_fullscreen_box_get_titlebar (KgxFullscreenBox *self)
+{
+  g_return_val_if_fail (KGX_IS_FULLSCREEN_BOX (self), NULL);
+
+  return adw_flap_get_flap (self->flap);
+}
+
+
+void
+kgx_fullscreen_box_set_titlebar (KgxFullscreenBox *self,
+                                 GtkWidget        *titlebar)
+{
+  g_return_if_fail (KGX_IS_FULLSCREEN_BOX (self));
+  g_return_if_fail (titlebar == NULL || GTK_IS_WIDGET (titlebar));
+
+  if (adw_flap_get_flap (self->flap) == titlebar) {
+    return;
+  }
+
+  adw_flap_set_flap (self->flap, titlebar);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLEBAR]);
+}
+
+
+GtkWidget *
+kgx_fullscreen_box_get_content (KgxFullscreenBox *self)
+{
+  g_return_val_if_fail (KGX_IS_FULLSCREEN_BOX (self), NULL);
+
+  return adw_flap_get_content (self->flap);
+}
+
+
+void
+kgx_fullscreen_box_set_content (KgxFullscreenBox *self,
+                                GtkWidget        *content)
+{
+  g_return_if_fail (KGX_IS_FULLSCREEN_BOX (self));
+  g_return_if_fail (content == NULL || GTK_IS_WIDGET (content));
+
+  if (adw_flap_get_content (self->flap) == content) {
+    return;
+  }
+
+  adw_flap_set_content (self->flap, content);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONTENT]);
+}
diff --git a/src/kgx-fullscreen-box.h b/src/kgx-fullscreen-box.h
new file mode 100644
index 0000000..4361d80
--- /dev/null
+++ b/src/kgx-fullscreen-box.h
@@ -0,0 +1,47 @@
+/* kgx-fullscreen-box.h
+ *
+ * Copyright 2021 Purism SPC
+ *
+ * This program 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.
+ *..
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define KGX_TYPE_FULLSCREEN_BOX (kgx_fullscreen_box_get_type())
+
+G_DECLARE_FINAL_TYPE (KgxFullscreenBox, kgx_fullscreen_box, KGX, FULLSCREEN_BOX, GtkWidget)
+
+KgxFullscreenBox *kgx_fullscreen_box_new            (void);
+
+gboolean          kgx_fullscreen_box_get_fullscreen (KgxFullscreenBox *self);
+void              kgx_fullscreen_box_set_fullscreen (KgxFullscreenBox *self,
+                                                     gboolean          fullscreen);
+
+gboolean          kgx_fullscreen_box_get_autohide   (KgxFullscreenBox *self);
+void              kgx_fullscreen_box_set_autohide   (KgxFullscreenBox *self,
+                                                     gboolean          autohide);
+
+GtkWidget        *kgx_fullscreen_box_get_titlebar   (KgxFullscreenBox *self);
+void              kgx_fullscreen_box_set_titlebar   (KgxFullscreenBox *self,
+                                                     GtkWidget        *titlebar);
+
+GtkWidget        *kgx_fullscreen_box_get_content    (KgxFullscreenBox *self);
+void              kgx_fullscreen_box_set_content    (KgxFullscreenBox *self,
+                                                     GtkWidget        *content);
+
+G_END_DECLS
diff --git a/src/kgx-window.c b/src/kgx-window.c
index ddfa0a2..8315dfa 100644
--- a/src/kgx-window.c
+++ b/src/kgx-window.c
@@ -35,6 +35,7 @@
 
 #include "kgx-window.h"
 #include "kgx-application.h"
+#include "kgx-fullscreen-box.h"
 #include "kgx-process.h"
 #include "kgx-close-dialog.h"
 #include "kgx-pages.h"
@@ -223,6 +224,15 @@ active_changed (GObject *object, GParamSpec *pspec, gpointer data)
 }
 
 
+static void
+fullscreened_changed (KgxWindow *self)
+{
+  gboolean fullscreen = gtk_window_is_fullscreen (GTK_WINDOW (self));
+  gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.fullscreen", !fullscreen);
+  gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.restore", fullscreen);
+}
+
+
 static void
 state_or_size_changed (KgxWindow  *self)
 {
@@ -403,13 +413,20 @@ kgx_window_class_init (KgxWindowClass *klass)
   gtk_widget_class_bind_template_child (widget_class, KgxWindow, tab_switcher);
   gtk_widget_class_bind_template_child (widget_class, KgxWindow, pages);
   gtk_widget_class_bind_template_child (widget_class, KgxWindow, primary_menu);
+  gtk_widget_class_bind_template_child (widget_class, KgxWindow, fullscreen_box);
 
   gtk_widget_class_bind_template_callback (widget_class, active_changed);
+  gtk_widget_class_bind_template_callback (widget_class, fullscreened_changed);
 
   gtk_widget_class_bind_template_callback (widget_class, zoom);
   gtk_widget_class_bind_template_callback (widget_class, status_changed);
   gtk_widget_class_bind_template_callback (widget_class, extra_drag_drop);
   gtk_widget_class_bind_template_callback (widget_class, new_tab_cb);
+
+  gtk_widget_class_install_action (widget_class, "win.fullscreen", NULL,
+                                   (GtkWidgetActionActivateFunc) gtk_window_fullscreen);
+  gtk_widget_class_install_action (widget_class, "win.restore", NULL,
+                                   (GtkWidgetActionActivateFunc) gtk_window_unfullscreen);
 }
 
 
@@ -596,6 +613,7 @@ kgx_window_init (KgxWindow *self)
   g_autoptr (GtkWindowGroup) group = NULL;
   g_autoptr (GPropertyAction) pact = NULL;
 
+  g_type_ensure (KGX_TYPE_FULLSCREEN_BOX);
   g_type_ensure (KGX_TYPE_TAB_BUTTON);
   g_type_ensure (KGX_TYPE_TAB_SWITCHER);
   g_type_ensure (KGX_TYPE_THEME_SWITCHER);
@@ -657,6 +675,8 @@ kgx_window_init (KgxWindow *self)
   gtk_widget_insert_action_group (GTK_WIDGET (self),
                                   "tab",
                                   G_ACTION_GROUP (self->tab_actions));
+
+  fullscreened_changed (self);
 }
 
 
diff --git a/src/kgx-window.h b/src/kgx-window.h
index 94bff51..86350d5 100644
--- a/src/kgx-window.h
+++ b/src/kgx-window.h
@@ -96,6 +96,7 @@ struct _KgxWindow
   GtkWidget            *tab_switcher;
   GtkWidget            *pages;
   GMenu                *primary_menu;
+  GtkWidget            *fullscreen_box;
 
   int                   current_width;
   int                   current_height;
diff --git a/src/kgx-window.ui b/src/kgx-window.ui
index 9c44f25..ea33f23 100644
--- a/src/kgx-window.ui
+++ b/src/kgx-window.ui
@@ -18,6 +18,13 @@
         <attribute name="action">win.new-window</attribute>
       </item>
     </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Fullscreen</attribute>
+        <attribute name="action">win.fullscreen</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+      </item>
+    </section>
     <section>
       <item>
         <attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
@@ -31,147 +38,170 @@
   </menu>
   <template class="KgxWindow" parent="AdwApplicationWindow">
     <signal name="notify::is-active" handler="active_changed" swapped="no"/>
+    <signal name="notify::fullscreened" handler="fullscreened_changed" swapped="yes"/>
     <property name="content">
       <object class="KgxTabSwitcher" id="tab_switcher">
         <signal name="new-tab" handler="new_tab_cb" swapped="no"/>
         <property name="child">
-          <object class="GtkBox">
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkHeaderBar" id="header_bar">
-                <property name="title-widget">
-                  <object class="AdwWindowTitle" id="window_title">
-                    <property name="title" translatable="yes">King’s Cross</property>
-                  </object>
-                </property>
-                <child type="start">
-                  <object class="GtkToggleButton">
-                    <property name="can-focus">0</property>
-                    <property name="receives-default">0</property>
-                    <property name="action-name">win.find</property>
-                    <property name="tooltip-text" translatable="yes">Find in Terminal</property>
-                    <property name="icon-name">edit-find-symbolic</property>
-                  </object>
-                </child>
-                <child type="end">
-                  <object class="GtkMenuButton">
-                    <property name="can-focus">0</property>
-                    <property name="receives-default">1</property>
-                    <property name="tooltip-text" translatable="yes">Menu</property>
-                    <property name="icon-name">open-menu-symbolic</property>
-                    <property name="popover">
-                      <object class="GtkPopoverMenu">
-                        <property name="menu-model">primary_menu</property>
-                        <child type="theme-switcher">
-                          <object class="KgxThemeSwitcher" id="theme_switcher"/>
-                        </child>
-                        <child type="zoom-controls">
-                          <object class="GtkBox">
-                            <property name="orientation">horizontal</property>
-                            <property name="homogeneous">True</property>
-                            <property name="margin-top">3</property>
-                            <property name="margin-bottom">3</property>
-                            <child>
-                              <object class="GtkButton">
-                                <property name="action-name">app.zoom-out</property>
-                                <property name="icon-name">zoom-out-symbolic</property>
-                                <property name="tooltip-text" translatable="yes">Shrink Text</property>
-                                <property name="halign">center</property>
-                                <style>
-                                  <class name="circular"/>
-                                  <class name="flat"/>
-                                </style>
-                              </object>
+          <object class="KgxFullscreenBox" id="fullscreen_box">
+            <property name="fullscreen" bind-source="KgxWindow" bind-property="fullscreened" 
bind-flags="sync-create"/>
+            <property name="autohide" bind-source="primary_menu_popover" bind-property="visible" 
bind-flags="invert-boolean"/>
+            <property name="titlebar">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkHeaderBar" id="header_bar">
+                    <property name="show-title-buttons" bind-source="KgxWindow" bind-property="fullscreened" 
bind-flags="sync-create|invert-boolean"/>
+                    <property name="title-widget">
+                      <object class="AdwWindowTitle" id="window_title">
+                        <property name="title" translatable="yes">King’s Cross</property>
+                      </object>
+                    </property>
+                    <child type="start">
+                      <object class="GtkToggleButton">
+                        <property name="can-focus">0</property>
+                        <property name="receives-default">0</property>
+                        <property name="action-name">win.find</property>
+                        <property name="tooltip-text" translatable="yes">Find in Terminal</property>
+                        <property name="icon-name">edit-find-symbolic</property>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkButton">
+                        <property name="visible" bind-source="KgxWindow" bind-property="fullscreened" 
bind-flags="sync-create"/>
+                        <property name="can-focus">0</property>
+                        <property name="receives-default">0</property>
+                        <property name="icon-name">view-restore-symbolic</property>
+                        <property name="tooltip-text" translatable="yes">Restore</property>
+                        <property name="action-name">win.restore</property>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkMenuButton">
+                        <property name="can-focus">0</property>
+                        <property name="receives-default">1</property>
+                        <property name="tooltip-text" translatable="yes">Menu</property>
+                        <property name="icon-name">open-menu-symbolic</property>
+                        <property name="popover">
+                          <object class="GtkPopoverMenu" id="primary_menu_popover">
+                            <property name="menu-model">primary_menu</property>
+                            <child type="theme-switcher">
+                              <object class="KgxThemeSwitcher" id="theme_switcher"/>
                             </child>
-                            <child>
-                              <object class="GtkButton">
-                                <property name="action-name">app.zoom-normal</property>
-                                <property name="tooltip-text" translatable="yes">Reset Size</property>
-                                <property name="halign">center</property>
-                                <style>
-                                  <class name="flat"/>
-                                  <class name="numeric"/>
-                                </style>
+                            <child type="zoom-controls">
+                              <object class="GtkBox">
+                                <property name="orientation">horizontal</property>
+                                <property name="homogeneous">True</property>
+                                <property name="margin-top">3</property>
+                                <property name="margin-bottom">3</property>
                                 <child>
-                                  <object class="GtkLabel" id="zoom_level">
-                                    <property name="width_chars">5</property>
+                                  <object class="GtkButton">
+                                    <property name="action-name">app.zoom-out</property>
+                                    <property name="icon-name">zoom-out-symbolic</property>
+                                    <property name="tooltip-text" translatable="yes">Shrink Text</property>
+                                    <property name="halign">center</property>
+                                    <style>
+                                      <class name="circular"/>
+                                      <class name="flat"/>
+                                    </style>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkButton">
+                                    <property name="action-name">app.zoom-normal</property>
+                                    <property name="tooltip-text" translatable="yes">Reset Size</property>
+                                    <property name="halign">center</property>
+                                    <style>
+                                      <class name="flat"/>
+                                      <class name="numeric"/>
+                                    </style>
+                                    <child>
+                                      <object class="GtkLabel" id="zoom_level">
+                                        <property name="width_chars">5</property>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkButton">
+                                    <property name="action-name">app.zoom-in</property>
+                                    <property name="icon-name">zoom-in-symbolic</property>
+                                    <property name="tooltip-text" translatable="yes">Enlarge Text</property>
+                                    <property name="halign">center</property>
+                                    <style>
+                                      <class name="circular"/>
+                                      <class name="flat"/>
+                                    </style>
                                   </object>
                                 </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkButton">
-                                <property name="action-name">app.zoom-in</property>
-                                <property name="icon-name">zoom-in-symbolic</property>
-                                <property name="tooltip-text" translatable="yes">Enlarge Text</property>
-                                <property name="halign">center</property>
-                                <style>
-                                  <class name="circular"/>
-                                  <class name="flat"/>
-                                </style>
                               </object>
                             </child>
                           </object>
-                        </child>
+                        </property>
                       </object>
-                    </property>
-                  </object>
-                </child>
-                <child type="end">
-                  <object class="KgxTabButton" id="tab_button">
-                    <property name="visible" bind-source="tab_switcher" bind-property="narrow" 
bind-flags="sync-create"/>
-                    <property name="action-name">win.tab-switcher</property>
+                    </child>
+                    <child type="end">
+                      <object class="KgxTabButton" id="tab_button">
+                        <property name="visible" bind-source="tab_switcher" bind-property="narrow" 
bind-flags="sync-create"/>
+                        <property name="action-name">win.tab-switcher</property>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkButton">
+                        <property name="visible" bind-source="tab_switcher" bind-property="narrow" 
bind-flags="sync-create|invert-boolean"/>
+                        <property name="can-focus">0</property>
+                        <property name="receives-default">0</property>
+                        <property name="action-name">win.new-tab</property>
+                        <property name="tooltip-text" translatable="yes">New Tab</property>
+                        <property name="icon-name">tab-new-symbolic</property>
+                      </object>
+                    </child>
                   </object>
                 </child>
-                <child type="end">
-                  <object class="GtkButton">
-                    <property name="visible" bind-source="tab_switcher" bind-property="narrow" 
bind-flags="sync-create|invert-boolean"/>
-                    <property name="can-focus">0</property>
-                    <property name="receives-default">0</property>
-                    <property name="action-name">win.new-tab</property>
-                    <property name="tooltip-text" translatable="yes">New Tab</property>
-                    <property name="icon-name">tab-new-symbolic</property>
+                <child>
+                  <object class="GtkRevealer">
+                    <property name="reveal-child" bind-source="tab_switcher" bind-property="narrow" 
bind-flags="sync-create|invert-boolean"/>
+                    <property name="child">
+                      <object class="AdwTabBar" id="tab_bar">
+                        <signal name="extra-drag-drop" handler="extra_drag_drop" swapped="no"/>
+                      </object>
+                    </property>
                   </object>
                 </child>
               </object>
-            </child>
-            <child>
-              <object class="GtkRevealer">
-                <property name="reveal-child" bind-source="tab_switcher" bind-property="narrow" 
bind-flags="sync-create|invert-boolean"/>
-                <property name="child">
-                  <object class="AdwTabBar" id="tab_bar">
-                    <signal name="extra-drag-drop" handler="extra_drag_drop" swapped="no"/>
+            </property>
+            <property name="content">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="KgxPages" id="pages">
+                    <property name="is-active" bind-source="KgxWindow" bind-property="is-active" 
bind-flags="sync-create" />
+                    <signal name="zoom" handler="zoom" swapped="no"/>
+                    <signal name="notify::status" handler="status_changed" swapped="yes" />
                   </object>
-                </property>
-              </object>
-            </child>
-            <child>
-              <object class="KgxPages" id="pages">
-                <property name="is-active" bind-source="KgxWindow" bind-property="is-active" 
bind-flags="sync-create" />
-                <signal name="zoom" handler="zoom" swapped="no"/>
-                <signal name="notify::status" handler="status_changed" swapped="yes" />
-              </object>
-            </child>
-            <child>
-              <object class="GtkRevealer" id="exit_info">
-                <property name="reveal_child">False</property>
-                <property name="child">
-                  <object class="GtkBox">
-                    <property name="can_focus">False</property>
-                    <property name="valign">end</property>
-                    <child>
-                      <object class="GtkLabel" id="exit_message">
-                        <property name="halign">start</property>
-                        <property name="hexpand">True</property>
+                </child>
+                <child>
+                  <object class="GtkRevealer" id="exit_info">
+                    <property name="reveal_child">False</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="can_focus">False</property>
+                        <property name="valign">end</property>
+                        <child>
+                          <object class="GtkLabel" id="exit_message">
+                            <property name="halign">start</property>
+                            <property name="hexpand">True</property>
+                          </object>
+                        </child>
+                        <style>
+                          <class name="exit-info"/>
+                        </style>
                       </object>
-                    </child>
-                    <style>
-                      <class name="exit-info"/>
-                    </style>
+                    </property>
                   </object>
-                </property>
+                </child>
               </object>
-            </child>
+            </property>
           </object>
         </property>
       </object>
diff --git a/src/meson.build b/src/meson.build
index f8d5358..e2df326 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,6 +3,9 @@ kgx_sources = [
   'kgx-application.h',
   'fp-vte-util.c',
   'fp-vte-util.h',
+  'kgx-fullscreen-box.c',
+  'kgx-fullscreen-box.h',
+  'kgx-terminal.h',
   'kgx-terminal.c',
   'kgx-terminal.h',
   'kgx-tab.c',
diff --git a/src/style.css b/src/style.css
index debdd16..fd227fb 100644
--- a/src/style.css
+++ b/src/style.css
@@ -200,3 +200,17 @@ themeswitcher checkbutton.dark radio {
   color: white;
   background-color: #1e1e1e;
 }
+
+fullscreenbox > flap > dimming,
+fullscreenbox > flap > outline,
+fullscreenbox > flap > border {
+  min-height: 0;
+  min-width: 0;
+  background: none;
+}
+
+fullscreenbox > flap > shadow {
+  min-height: 9px;
+  min-width: 9px;
+  background: linear-gradient(to bottom, alpha(black, .1), alpha(black, .0));
+}


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