lib/core/stream: LineIterator use CachedIterator
[nit.git] / lib / linux / ui.nit
index f3deb77..8a6cc81 100644 (file)
 module ui
 
 import app::ui
-import gtk
+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
+
+               bar.add back_button.native
+
+               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
+
+       # Button on the header bar to go back
+       var back_button = new BackButton is lazy
+
        # On GNU/Linux, we go through all the callbacks once,
        # there is no complex life-cycle.
        redef fun run
        do
                app.on_create
                app.on_restore_state
-               app.on_start
                app.on_resume
 
-               var window = window
-               window.native.show_all
                gtk_main
 
                app.on_pause
                app.on_stop
                app.on_save_state
-               app.on_destroy
        end
 
        # 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
+
+               native_window.show_all
+
+               super
+
+               if window.enable_back_button then
+                       back_button.native.show
+               else back_button.native.hide
+       end
 end
 
 redef class Control
@@ -64,31 +116,47 @@ 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
 
 redef class View
+       init do native.show
+
        redef fun enabled do return native.sensitive
        redef fun enabled=(enabled) do native.sensitive = enabled or else true
 end
 
 redef class Layout
        redef type NATIVE: GtkBox
+
+       redef fun add(item)
+       do
+               super
+               if item isa View then native.add item.native
+       end
+
+       redef fun remove(item)
+       do
+               super
+               if item isa View then native.remove item.native
+       end
 end
 
 redef class HorizontalLayout
@@ -97,6 +165,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
@@ -109,12 +178,65 @@ 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: GtkScrolledWindow
+
+       redef var native = new GtkScrolledWindow
+
+       # Container inside `native`
+       var native_list_box = new GtkListBox
+
+       # `GtkListBoxRow` used to contains children `View`s
+       var native_rows = new Map[View, GtkListBoxRow]
+
+       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
+                       var native_row = new GtkListBoxRow
+                       #native_row.activable = false # TODO with GTK 3.14
+                       #native_row.selectable = false
+                       native_row.add item.native
+
+                       native_rows[item] = native_row
+                       native_list_box.add native_row
+                       native_row.show
+               end
+       end
+
+       redef fun remove(item)
+       do
+               super
+               if item isa View then
+                       var native_row = native_rows.get_or_null(item)
+                       if native_row == null then
+                               print_error "Error: {self} does not contains {item}"
+                               return
+                       end
+
+                       native_list_box.remove native_row
+                       native_rows.keys.remove item
+                       native_row.destroy
+               end
+       end
+end
+
 redef class Button
        redef type NATIVE: GtkButton
        redef var native = new GtkButton
@@ -127,6 +249,82 @@ redef class Button
        init do native.signal_connect("clicked", self, null)
 end
 
+# Button to go back between windows
+class BackButton
+       super Button
+
+       # TODO i18n
+       redef fun text=(value) do super(value or else "Back")
+
+       redef fun signal(sender, data)
+       do
+               super
+
+               app.window.on_back_button
+       end
+end
+
+redef class Label
+       redef type NATIVE: GtkLabel
+       redef var native = new GtkLabel("")
+
+       redef fun text do return native.text
+
+       redef fun text=(value)
+       do
+               var cfmt = pango_markup_format.to_cstring
+               var cvalue = (value or else "").to_cstring
+               native.set_markup(cfmt, cvalue)
+       end
+
+       # Pango format string applied to the `text` attribute
+       var pango_markup_format = "\%s" is lazy
+
+       redef fun size=(size)
+       do
+               if size == null or size == 1.0 then
+                       pango_markup_format = "\%s"
+               else if size < 1.0 then
+                       pango_markup_format = "<span size=\"small\">\%s</span>"
+               else#if size > 1.0 then
+                       pango_markup_format = "<span size=\"large\">\%s</span>"
+               end
+
+               # Force reloading `text`
+               text = text
+       end
+
+       redef fun align=(align)
+       do
+               align = align or else 0.0
+
+               # Set whole label alignement
+               native.set_alignment(align, 0.5)
+
+               # Set multiline justification
+               native.justify = if align == 0.5 then
+                       new GtkJustification.center
+               else if align < 0.5 then
+                       new GtkJustification.left
+               else#if align > 0.5 then
+                       new GtkJustification.right
+       end
+end
+
+redef class CheckBox
+       redef type NATIVE: GtkCheckButton
+       redef var native = new GtkCheckButton
+
+       redef fun signal(sender, data) do notify_observers new ToggleEvent(self)
+       init do native.signal_connect("toggled", self, null)
+
+       redef fun text do return native.text
+       redef fun text=(value) do native.text = (value or else "").to_s
+
+       redef fun is_checked do return native.active
+       redef fun is_checked=(value) do native.active = value
+end
+
 redef class TextInput
        redef type NATIVE: GtkEntry
        redef var native = new GtkEntry
@@ -136,4 +334,14 @@ redef class TextInput
                if value == null then value = ""
                native.text = value.to_s
        end
+
+       redef fun is_password=(value)
+       do
+               native.visibility = value != true
+               super
+       end
+end
+
+redef class Text
+       redef fun open_in_browser do system("xdg-open '{self.escape_to_sh}' &")
 end