X-Git-Url: http://nitlanguage.org diff --git a/lib/linux/ui.nit b/lib/linux/ui.nit index f3deb77..8a6cc81 100644 --- a/lib/linux/ui.nit +++ b/lib/linux/ui.nit @@ -16,34 +16,86 @@ 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 = "\%s" + else#if size > 1.0 then + pango_markup_format = "\%s" + 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