Merge: app.nit on GNU/Linux: implement multiple windows support using a GtkStack
authorJean Privat <jean@pryen.org>
Tue, 22 Mar 2016 00:02:12 +0000 (20:02 -0400)
committerJean Privat <jean@pryen.org>
Tue, 22 Mar 2016 00:02:12 +0000 (20:02 -0400)
This PR implement support for mobile-like multiple windows on GNU/Linux with GTK. It uses a single GTK window and a GtkStack showing one app.nit window at a time. This results in a single window with changing content.

The change to ListView provides a scrollable list of items, which is closer to the mobile equivalent.

Support for a back button will be introduced in a future PR.

Pull-Request: #1990
Reviewed-by: Jean Privat <jean@pryen.org>

lib/gtk/v3_4/gtk_core.nit
lib/gtk/v3_4/gtk_widgets_ext.nit
lib/linux/ui.nit

index f7d2c78..b9270f3 100644 (file)
@@ -92,7 +92,8 @@ extern class GtkWidget `{GtkWidget *`}
                return self == o;
        `}
 
-       fun request_size(width, height: Int) `{
+       # Set the minimum dimension of this widget
+       fun set_size_request(width, height: Int) `{
                gtk_widget_set_size_request(self, width, height);
        `}
 
@@ -137,6 +138,21 @@ extern class GtkWidget `{GtkWidget *`}
 
        # Hide the widget (reverse the effects of `show`)
        fun hide `{ gtk_widget_hide(self); `}
+
+       # Vertical alignement of this widget
+       fun valign=(value: GtkAlign) `{ gtk_widget_set_valign(self, value); `}
+
+       # Horizontal alignement of this widget
+       fun halign=(value: GtkAlign) `{ gtk_widget_set_halign(self, value); `}
+end
+
+# How a widget deals with extra space
+extern class GtkAlign `{ GtkAlign `}
+       new fill `{ return GTK_ALIGN_FILL; `}
+       new start `{ return GTK_ALIGN_START; `}
+       new align_end `{ return GTK_ALIGN_END; `}
+       new center `{ return GTK_ALIGN_CENTER; `}
+       new baseline `{ return GTK_ALIGN_BASELINE; `}
 end
 
 # Base class for widgets which contain other widgets
@@ -199,6 +215,11 @@ extern class GtkWindow `{GtkWindow *`}
                signal_connect("destroy", to_call, user_data)
        end
 
+       # Resize the window as if the user had done so
+       fun resize(width, height: Int) `{
+               return gtk_window_resize(self, width, height);
+       `}
+
        fun resizable: Bool `{
                return gtk_window_get_resizable(self);
        `}
index 95bcc2c..25265c4 100644 (file)
@@ -236,6 +236,18 @@ extern class GtkLockButton
        super GtkButton
 end
 
+# Contains a single widget and scrollbars
+extern class GtkScrolledWindow `{ GtkScrolledWindow * `}
+       super GtkBin
+
+       new `{ return (GtkScrolledWindow *)gtk_scrolled_window_new(NULL, NULL); `}
+
+       # Set horizontal and vertical scrollbar policies
+       fun set_policy(hscrollbar_policy, vscrollbar_policy: GtkPolicyType) `{
+               gtk_scrolled_window_set_policy(self, hscrollbar_policy, vscrollbar_policy);
+       `}
+end
+
 # A button to launch a color selection dialog
 # See: https://developer.gnome.org/gtk3/stable/GtkColorButton.html
 extern class GtkColorButton `{GtkColorButton *`}
index ed9fef5..23c1f91 100644 (file)
@@ -20,9 +20,41 @@ import gtk::v3_10
 
 import data_store
 
+# Request width of the GTK window for an app.nit application
+#
+# This is the minimum width of the window, it may grow bigger to fit content.
+fun gtk_window_width_request: Int do return 480
+
 redef class App
        redef fun setup do gtk_init
 
+       # Single GTK window of this application
+       var native_window: GtkWindow is lazy do
+               var win = new GtkWindow(new GtkWindowType.toplevel)
+               win.connect_destroy_signal_to_quit
+               win.titlebar = native_header_bar
+               win.add native_stack
+               return win
+       end
+
+       # GTK 3 header bar
+       var native_header_bar: GtkHeaderBar is lazy do
+               var bar = new GtkHeaderBar
+               bar.title = "app.nit" # TODO offer a portable API to name windows
+               bar.show_close_button = true
+
+               # TODO add back button
+
+               return bar
+       end
+
+       # Root `GtkStack` used to simulate the many app.nit windows
+       var native_stack: GtkStack is lazy do
+               var stack = new GtkStack
+               stack.homogeneous = false
+               return stack
+       end
+
        # On GNU/Linux, we go through all the callbacks once,
        # there is no complex life-cycle.
        redef fun run
@@ -32,8 +64,7 @@ redef class App
                app.on_start
                app.on_resume
 
-               var window = window
-               window.native.show_all
+               native_window.show_all
                gtk_main
 
                app.on_pause
@@ -44,6 +75,21 @@ redef class App
 
        # Spacing between GTK controls, default at 2
        var control_spacing = 2 is writable
+
+       redef fun window=(window)
+       do
+               var root_view = window.view
+               assert root_view != null
+               native_stack.add root_view.native
+               native_stack.visible_child = root_view.native
+
+               # FIXME These settings forces the GTK window to resize to its minimum
+               # size when changing app.nit windows. It is not pretty, but it could be
+               # improved with GTK 3.18 and interpolate_size.
+               native_window.resizable = false
+
+               super
+       end
 end
 
 redef class Control
@@ -64,21 +110,23 @@ redef class Control
 end
 
 redef class CompositeControl
-       redef type NATIVE: GtkContainer
-
-       redef fun add(item)
-       do
-               super
-               native.add item.native
-       end
 end
 
+# On GNU/Linux, a window is implemented by placing the `view` in a `GtkStack` in the single GTK window
 redef class Window
-       redef type NATIVE: GtkWindow
-       redef var native do
-               var win = new GtkWindow(new GtkWindowType.toplevel)
-               win.connect_destroy_signal_to_quit
-               return win
+
+       # Root view of this window
+       var view: nullable View = null
+
+       redef fun add(view)
+       do
+               if view isa View then
+                       self.view = view
+                       view.native.valign = new GtkAlign.start
+                       view.native.set_size_request(gtk_window_width_request, 0)
+               end
+
+               super
        end
 end
 
@@ -91,10 +139,17 @@ end
 
 redef class Layout
        redef type NATIVE: GtkBox
-       redef fun remove(view)
+
+       redef fun add(item)
+       do
+               super
+               if item isa View then native.add item.native
+       end
+
+       redef fun remove(item)
        do
                super
-               native.remove view.native
+               if item isa View then native.remove item.native
        end
 end
 
@@ -104,6 +159,7 @@ redef class HorizontalLayout
        redef fun add(item)
        do
                super
+               # FIXME abstract the use either homogeneous or weight to balance views size in a layout
                native.homogeneous = true
                native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
        end
@@ -116,17 +172,41 @@ redef class VerticalLayout
        do
                super
 
-               # FIXME abstract the use either homogeneous or weight to balance views size in a layout
-               native.homogeneous = true
                native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
        end
 end
 
+# On GNU/Linux, this is implemented by a `GtkListBox` inside a `GtkScrolledWindow`
 redef class ListLayout
-       redef type NATIVE: GtkListBox
-       redef var native = new GtkListBox
 
-       init do native.selection_mode = new GtkSelectionMode.none
+       redef type NATIVE: GtkScrolledWindow
+
+       redef var native = new GtkScrolledWindow
+
+       # Container inside `native`
+       var native_list_box = new GtkListBox
+
+       init do
+               native_list_box.selection_mode = new GtkSelectionMode.none
+               native.add native_list_box
+
+               # Set the size of the GtkScrolledWindow:
+               # use content width and set static height
+               native.set_policy(new GtkPolicyType.never, new GtkPolicyType.automatic)
+               native.set_size_request(gtk_window_width_request, 640)
+       end
+
+       redef fun add(item)
+       do
+               super
+               if item isa View then native_list_box.add item.native
+       end
+
+       redef fun remove(item)
+       do
+               super
+               if item isa View then native_list_box.remove item.native
+       end
 end
 
 redef class Button