Merge: app.nit: navigate between windows with the back button
[nit.git] / lib / linux / ui.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Implementation of the app.nit UI module for GNU/Linux
16 module ui
17
18 import app::ui
19 import gtk::v3_10
20
21 import data_store
22
23 # Request width of the GTK window for an app.nit application
24 #
25 # This is the minimum width of the window, it may grow bigger to fit content.
26 fun gtk_window_width_request: Int do return 480
27
28 redef class App
29 redef fun setup do gtk_init
30
31 # Single GTK window of this application
32 var native_window: GtkWindow is lazy do
33 var win = new GtkWindow(new GtkWindowType.toplevel)
34 win.connect_destroy_signal_to_quit
35 win.titlebar = native_header_bar
36 win.add native_stack
37 return win
38 end
39
40 # GTK 3 header bar
41 var native_header_bar: GtkHeaderBar is lazy do
42 var bar = new GtkHeaderBar
43 bar.title = "app.nit" # TODO offer a portable API to name windows
44 bar.show_close_button = true
45
46 bar.add back_button.native
47
48 return bar
49 end
50
51 # Root `GtkStack` used to simulate the many app.nit windows
52 var native_stack: GtkStack is lazy do
53 var stack = new GtkStack
54 stack.homogeneous = false
55 return stack
56 end
57
58 # Button on the header bar to go back
59 var back_button = new BackButton is lazy
60
61 # On GNU/Linux, we go through all the callbacks once,
62 # there is no complex life-cycle.
63 redef fun run
64 do
65 app.on_create
66 app.on_restore_state
67 app.on_start
68 app.on_resume
69
70 gtk_main
71
72 app.on_pause
73 app.on_stop
74 app.on_save_state
75 app.on_destroy
76 end
77
78 # Spacing between GTK controls, default at 2
79 var control_spacing = 2 is writable
80
81 redef fun window=(window)
82 do
83 var root_view = window.view
84 assert root_view != null
85 native_stack.add root_view.native
86 native_stack.visible_child = root_view.native
87
88 # FIXME These settings forces the GTK window to resize to its minimum
89 # size when changing app.nit windows. It is not pretty, but it could be
90 # improved with GTK 3.18 and interpolate_size.
91 native_window.resizable = false
92
93 native_window.show_all
94
95 super
96
97 if window.enable_back_button then
98 back_button.native.show
99 else back_button.native.hide
100 end
101 end
102
103 redef class Control
104 super GtkCallable
105 super Finalizable
106
107 # The GTK element used to implement `self`
108 fun native: NATIVE is abstract
109
110 # Type of `native`
111 type NATIVE: GtkWidget
112
113 redef fun finalize
114 do
115 var native = native
116 if not native.address_is_null then native.destroy
117 end
118 end
119
120 redef class CompositeControl
121 end
122
123 # On GNU/Linux, a window is implemented by placing the `view` in a `GtkStack` in the single GTK window
124 redef class Window
125
126 # Root view of this window
127 var view: nullable View = null
128
129 redef fun add(view)
130 do
131 if view isa View then
132 self.view = view
133 view.native.valign = new GtkAlign.start
134 view.native.set_size_request(gtk_window_width_request, 0)
135 end
136
137 super
138 end
139 end
140
141 redef class View
142 init do native.show
143
144 redef fun enabled do return native.sensitive
145 redef fun enabled=(enabled) do native.sensitive = enabled or else true
146 end
147
148 redef class Layout
149 redef type NATIVE: GtkBox
150
151 redef fun add(item)
152 do
153 super
154 if item isa View then native.add item.native
155 end
156
157 redef fun remove(item)
158 do
159 super
160 if item isa View then native.remove item.native
161 end
162 end
163
164 redef class HorizontalLayout
165 redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing)
166
167 redef fun add(item)
168 do
169 super
170 # FIXME abstract the use either homogeneous or weight to balance views size in a layout
171 native.homogeneous = true
172 native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
173 end
174 end
175
176 redef class VerticalLayout
177 redef var native = new GtkBox(new GtkOrientation.vertical, app.control_spacing)
178
179 redef fun add(item)
180 do
181 super
182
183 native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
184 end
185 end
186
187 # On GNU/Linux, this is implemented by a `GtkListBox` inside a `GtkScrolledWindow`
188 redef class ListLayout
189
190 redef type NATIVE: GtkScrolledWindow
191
192 redef var native = new GtkScrolledWindow
193
194 # Container inside `native`
195 var native_list_box = new GtkListBox
196
197 # `GtkListBoxRow` used to contains children `View`s
198 var native_rows = new Map[View, GtkListBoxRow]
199
200 init do
201 native_list_box.selection_mode = new GtkSelectionMode.none
202 native.add native_list_box
203
204 # Set the size of the GtkScrolledWindow:
205 # use content width and set static height
206 native.set_policy(new GtkPolicyType.never, new GtkPolicyType.automatic)
207 native.set_size_request(gtk_window_width_request, 640)
208 end
209
210 redef fun add(item)
211 do
212 super
213 if item isa View then
214 var native_row = new GtkListBoxRow
215 #native_row.activable = false # TODO with GTK 3.14
216 #native_row.selectable = false
217 native_row.add item.native
218
219 native_rows[item] = native_row
220 native_list_box.add native_row
221 native_row.show
222 end
223 end
224
225 redef fun remove(item)
226 do
227 super
228 if item isa View then
229 var native_row = native_rows.get_or_null(item)
230 if native_row == null then
231 print_error "Error: {self} does not contains {item}"
232 return
233 end
234
235 native_list_box.remove native_row
236 native_rows.keys.remove item
237 native_row.destroy
238 end
239 end
240 end
241
242 redef class Button
243 redef type NATIVE: GtkButton
244 redef var native = new GtkButton
245
246 redef fun text do return native.text
247 redef fun text=(value) do native.text = (value or else "").to_s
248
249 redef fun signal(sender, data) do notify_observers new ButtonPressEvent(self)
250
251 init do native.signal_connect("clicked", self, null)
252 end
253
254 # Button to go back between windows
255 class BackButton
256 super Button
257
258 # TODO i18n
259 redef fun text=(value) do super(value or else "Back")
260
261 redef fun signal(sender, data)
262 do
263 super
264
265 app.window.on_back_button
266 end
267 end
268
269 redef class Label
270 redef type NATIVE: GtkLabel
271 redef var native = new GtkLabel("")
272
273 redef fun text do return native.text
274
275 redef fun text=(value)
276 do
277 var cfmt = pango_markup_format.to_cstring
278 var cvalue = (value or else "").to_cstring
279 native.set_markup(cfmt, cvalue)
280 end
281
282 # Pango format string applied to the `text` attribute
283 var pango_markup_format = "\%s" is lazy
284
285 redef fun size=(size)
286 do
287 if size == null or size == 1.0 then
288 pango_markup_format = "\%s"
289 else if size < 1.0 then
290 pango_markup_format = "<span size=\"small\">\%s</span>"
291 else#if size > 1.0 then
292 pango_markup_format = "<span size=\"large\">\%s</span>"
293 end
294
295 # Force reloading `text`
296 text = text
297 end
298
299 redef fun align=(align)
300 do
301 align = align or else 0.0
302
303 # Set whole label alignement
304 native.set_alignment(align, 0.5)
305
306 # Set multiline justification
307 native.justify = if align == 0.5 then
308 new GtkJustification.center
309 else if align < 0.5 then
310 new GtkJustification.left
311 else#if align > 0.5 then
312 new GtkJustification.right
313 end
314 end
315
316 redef class CheckBox
317 redef type NATIVE: GtkCheckButton
318 redef var native = new GtkCheckButton
319
320 redef fun signal(sender, data) do notify_observers new ToggleEvent(self)
321 init do native.signal_connect("toggled", self, null)
322
323 redef fun text do return native.text
324 redef fun text=(value) do native.text = (value or else "").to_s
325
326 redef fun is_checked do return native.active
327 redef fun is_checked=(value) do native.active = value
328 end
329
330 redef class TextInput
331 redef type NATIVE: GtkEntry
332 redef var native = new GtkEntry
333
334 redef fun text do return native.text
335 redef fun text=(value) do
336 if value == null then value = ""
337 native.text = value.to_s
338 end
339
340 redef fun is_password=(value)
341 do
342 native.visibility = value != true
343 super
344 end
345 end