parser: regenerate with lambda
[nit.git] / lib / app / 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 # Portable UI controls for mobiles apps
16 #
17 # ~~~
18 # import app::ui
19 #
20 # class MyWindow
21 # super Window
22 #
23 # var layout = new ListLayout(parent=self)
24 # var lbl = new Label(parent=layout, text="Hello world", align=0.5)
25 # var but = new Button(parent=layout, text="Press here")
26 #
27 # redef fun on_event(event) do lbl.text = "Pressed!"
28 # end
29 #
30 # redef fun root_window do return new MyWindow
31 # ~~~
32 module ui
33
34 import app_base
35
36 # Platform variations
37 import linux::ui is conditional(linux)
38 import android::ui is conditional(android)
39 import ios::ui is conditional(ios)
40
41 redef class App
42 super AppComponent
43
44 # The current `Window` of this activity
45 #
46 # This attribute is set by `push_window`.
47 var window: Window is noinit
48
49 # Make `window` visible and push it on the top of the `window_stack`
50 #
51 # This method can be called at any times while the app is active.
52 fun push_window(window: Window)
53 do
54 window_stack.add window
55 self.window = window
56 window.on_create
57 end
58
59 # Pop the current `window` from the stack and show the previous one
60 #
61 # Require: `window_stack.not_empty`
62 fun pop_window
63 do
64 assert window_stack.not_empty
65 window_stack.pop
66 window = window_stack.last
67 window.on_resume
68 end
69
70 # Stack of active windows
71 var window_stack = new Array[Window]
72
73 redef fun on_create
74 do
75 var window = root_window
76 push_window window
77 end
78
79 redef fun on_resume do window.on_resume
80
81 redef fun on_pause do window.on_pause
82
83 redef fun on_stop do window.on_stop
84
85 redef fun on_restore_state do window.on_restore_state
86
87 redef fun on_save_state do window.on_save_state
88 end
89
90 # Hook to create the first window shown to the user
91 #
92 # By default, a `Window` is created, which can be refined to customize it.
93 # However, most apps should refine this method to return a different window,
94 # this way the app can have more than one window.
95 fun root_window: Window do return new Window
96
97 # An event created by an `AppComponent` and sent to `AppObserver`s
98 interface AppEvent
99 end
100
101 # Observer of `AppEvent`s raised by `AppComponent`s
102 interface AppObserver
103 # Notification of `event` raised by `sender`
104 #
105 # To be implemented in subclasses as needed.
106 fun on_event(event: AppEvent) do end
107 end
108
109 redef class AppComponent
110 super AppObserver
111
112 # All `AppObserver` notified of events raised by `self`
113 #
114 # By default, only `self` is an observer.
115 # Any other `AppObserver` can be added to this collection.
116 var observers = new HashSet[AppObserver].from([self: AppObserver])
117
118 # Propagate `event` to all `observers` by calling `AppObserver::on_event`
119 fun notify_observers(event: AppEvent)
120 do
121 for observer in observers do
122 observer.on_event(event)
123 end
124 end
125 end
126
127 # A control implementing the UI
128 class Control
129 super AppComponent
130
131 # Direct parent `Control` in the control tree
132 #
133 # The parents (direct and indirect) receive all events from `self`,
134 # like the `observers`.
135 #
136 # If `null` then `self` is at the root of the tree, or not yet attached.
137 var parent: nullable CompositeControl = null is private writable(set_parent)
138
139 # Direct parent `Control` in the control tree
140 #
141 # The parents (direct and indirect) receive all events from `self`,
142 # like the `observers`.
143 #
144 # Setting `parent` calls `remove` on the old parent and `add` on the new one.
145 fun parent=(parent: nullable CompositeControl)
146 is autoinit do
147 var old_parent = self.parent
148 if old_parent != null and old_parent != parent then
149 old_parent.remove self
150 end
151
152 if parent != null then parent.add self
153
154 set_parent parent
155 end
156
157 # Also notify the parents (both direct and indirect)
158 redef fun notify_observers(event)
159 do
160 super
161
162 var p = parent
163 while p != null do
164 p.on_event event
165 p = p.parent
166 end
167 end
168 end
169
170 # A `Control` grouping other controls
171 class CompositeControl
172 super Control
173
174 # Child controls composing this control
175 protected var items = new Array[Control]
176
177 # Add `item` as a child of `self`
178 protected fun add(item: Control) do items.add item
179
180 # Remove `item` from `self`
181 fun remove(item: Control) do if has(item) then items.remove item
182
183 # Is `item` in `self`?
184 fun has(item: Control): Bool do return items.has(item)
185
186 # Remove all items from `self`
187 fun clear do for item in items.to_a do remove item
188
189 redef fun on_create do for i in items do i.on_create
190
191 redef fun on_resume do for i in items do i.on_resume
192
193 redef fun on_pause do for i in items do i.on_pause
194
195 redef fun on_stop do for i in items do i.on_stop
196
197 redef fun on_restore_state do for i in items do i.on_restore_state
198
199 redef fun on_save_state do for i in items do i.on_save_state
200 end
201
202 # A window, root of the `Control` tree
203 #
204 # Each window should hold a single control, usually a `CompositeControl`,
205 # which in turn holds all the displayed controls.
206 class Window
207 super CompositeControl
208
209 # Should the back button be shown and used to go back to a previous window?
210 fun enable_back_button: Bool do return app.window_stack.length > 1
211
212 # The back button has been pressed, usually to open the previous window
213 fun on_back_button do app.pop_window
214 end
215
216 # A visible `Control`
217 abstract class View
218 super Control
219
220 # Is this control enabled so the user can interact with it?
221 #
222 # By default, or if set to `null`, the control is enabled.
223 var enabled: nullable Bool is writable, abstract, autoinit
224 end
225
226 # A control displaying some `text`
227 #
228 # For a text-only control, see `Label`.
229 abstract class TextView
230 super View
231
232 # Main `Text` of this control
233 #
234 # By default, or if set to `null`, no text is shown.
235 var text: nullable Text is writable, abstract, autoinit
236
237 # Set the relative size of the text
238 #
239 # A value of 1.0, the default, use the platform default text size.
240 # Values under 1.0 set a smaller text size, and over 1.0 a larger size.
241 #
242 # Implementation varies per platform, and some controls may be unaffected
243 # depending on the customization options of each platform.
244 # For consistent results, it is recommended to use only on instances
245 # of `Label` and `size` should be either 0.5, 1.0 or 1.5.
246 fun size=(size: nullable Float) is autoinit do end
247
248 # Align the text horizontally
249 #
250 # Use 0.0 to align left (the default), 0.5 to align in the center and
251 # 1.0 to align on the right.
252 #
253 # Implementation varies per platform, and some controls may be unaffected
254 # depending on the customization options of each platform.
255 # For consistent results, it is recommended to use only on instances
256 # of `Label` and `size` should be either 0.0, 0.5 or 1.0.
257 fun align=(align: nullable Float) is autoinit do end
258 end
259
260 # A control for the user to enter custom `text`
261 class TextInput
262 super TextView
263
264 # Hide password or any content entered in this view?
265 var is_password: nullable Bool is writable
266 end
267
268 # A pressable button, raises `ButtonPressEvent`
269 class Button
270 super TextView
271 end
272
273 # A simple text label
274 class Label
275 super TextView
276 end
277
278 # Toggle control between two states, also displays a label
279 class CheckBox
280 super TextView
281
282 # Is this control in the checked/on state?
283 var is_checked = false is writable
284 end
285
286 # Event sent from a `VIEW`
287 class ViewEvent
288 super AppEvent
289
290 # The `VIEW` that raised this event
291 var sender: VIEW
292
293 # Type of the `sender`
294 type VIEW: View
295 end
296
297 # A `Button` press event
298 class ButtonPressEvent
299 super ViewEvent
300
301 redef type VIEW: Button
302 end
303
304 # The `CheckBox` `sender` has been toggled
305 class ToggleEvent
306 super ViewEvent
307
308 redef type VIEW: CheckBox
309 end
310
311 # A layout to visually organize `Control`s
312 abstract class Layout
313 super View
314 super CompositeControl
315 end
316
317 # An horizontal linear organization
318 class HorizontalLayout
319 super Layout
320 end
321
322 # A vertical linear organization
323 class VerticalLayout
324 super Layout
325 end
326
327 # Scrollable list of views in a simple list
328 class ListLayout
329 super View
330 super CompositeControl
331 end
332
333 redef class Text
334 # Open the URL `self` with the default browser
335 fun open_in_browser do print_error "Text::open_in_browser not implemented on this platform."
336 end