lib/android: implement back_button support using the "hardware" back key
[nit.git] / lib / android / ui / 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 # Views and services to use the Android native user interface
16 module ui
17
18 # Implementation note:
19 #
20 # We cannot rely on `Activity::on_restore_instance_state` to implement
21 # `on_restore_state` is it only invoked if there is a bundled state,
22 # and we don't use the Android bundled state.
23
24 import native_ui
25 import log
26 import nit_activity
27
28 import app::ui
29 private import data_store
30 import assets
31
32 redef class Control
33 # The Android element used to implement `self`
34 fun native: NATIVE is abstract
35
36 # Type of `native`
37 type NATIVE: JavaObject
38 end
39
40 redef class NativeActivity
41
42 private fun remove_title_bar in "Java" `{
43 self.requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
44 `}
45
46 # Insert a single layout as the root of the activity window
47 private fun insert_root_layout(root_layout_id: Int)
48 in "Java" `{
49 android.widget.FrameLayout layout = new android.widget.FrameLayout(self);
50 layout.setId((int)root_layout_id);
51 self.setContentView(layout);
52 `}
53
54 # Replace the currently visible fragment, if any, with `native_fragment`
55 private fun show_fragment(root_layout_id: Int, native_fragment: Android_app_Fragment)
56 in "Java" `{
57 android.app.FragmentTransaction transaction = self.getFragmentManager().beginTransaction();
58 transaction.replace((int)root_layout_id, native_fragment);
59 transaction.commit();
60 `}
61 end
62
63 redef class App
64 redef fun on_create
65 do
66 app.native_activity.remove_title_bar
67 native_activity.insert_root_layout(root_layout_id)
68 super
69 end
70
71 # Identifier of the container holding the fragments
72 private var root_layout_id = 0xFFFF
73
74 redef fun window=(window)
75 do
76 native_activity.show_fragment(root_layout_id, window.native)
77 super
78 end
79 end
80
81 redef class Activity
82 redef fun on_back_pressed
83 do
84 var window = app.window
85 if window.enable_back_button then
86 window.on_back_button
87 return true
88 end
89
90 return false
91 end
92 end
93
94 # On Android, a window is implemented with the fragment `native`
95 redef class Window
96 redef var native = (new Android_app_Fragment(self)).new_global_ref
97
98 redef type NATIVE: Android_app_Fragment
99
100 # Root high-level view of this window
101 var view: nullable View = null
102
103 redef fun add(item)
104 do
105 if item isa View then view = item
106 super
107 end
108
109 private fun on_create_fragment: NativeView
110 do
111 on_create
112
113 var view = view
114 assert view != null else print_error "{class_name} needs a `view` after `Window::on_create` returns"
115 return view.native
116 end
117 end
118
119 redef class View
120 redef type NATIVE: NativeView
121
122 redef fun enabled=(enabled) do native.enabled = enabled or else true
123 redef fun enabled do return native.enabled
124 end
125
126 redef class Layout
127 redef type NATIVE: NativeViewGroup
128
129 redef fun add(item)
130 do
131 super
132
133 assert item isa View
134
135 # FIXME abstract the use either homogeneous or weight to balance views size in a layout
136 native.add_view_with_weight(item.native, 1.0)
137 end
138
139 redef fun remove(item)
140 do
141 super
142 if item isa View then native.remove_view item.native
143 end
144 end
145
146 redef class HorizontalLayout
147 redef var native do
148 var layout = new NativeLinearLayout(app.native_activity)
149 layout = layout.new_global_ref
150 layout.set_horizontal
151 return layout
152 end
153 end
154
155 redef class VerticalLayout
156 redef var native do
157 var layout = new NativeLinearLayout(app.native_activity)
158 layout = layout.new_global_ref
159 layout.set_vertical
160 return layout
161 end
162 end
163
164 redef class ListLayout
165 redef type NATIVE: Android_widget_ListView
166
167 redef var native do
168 var layout = new Android_widget_ListView(app.native_activity)
169 layout = layout.new_global_ref
170 return layout
171 end
172
173 private var adapter: Android_widget_ArrayAdapter do
174 var adapter = new Android_widget_ArrayAdapter(app.native_activity,
175 android_r_layout_simple_list_item_1, self)
176 native.set_adapter adapter
177 return adapter.new_global_ref
178 end
179
180 redef fun add(item)
181 do
182 super
183 if item isa View then adapter.add item.native
184 end
185
186 private fun create_view(position: Int): NativeView
187 do
188 var ctrl = items[position]
189 assert ctrl isa View
190 return ctrl.native
191 end
192 end
193
194 redef class Android_widget_ArrayAdapter
195 private new (context: NativeContext, res: Int, sender: ListLayout)
196 import ListLayout.create_view in "Java" `{
197 final int final_sender_object = sender;
198
199 return new android.widget.ArrayAdapter(context, (int)res) {
200 @Override
201 public android.view.View getView(int position, android.view.View convertView, android.view.ViewGroup parent) {
202 return ListLayout_create_view(final_sender_object, position);
203 }
204 };
205 `}
206 end
207
208 redef class TextView
209 redef type NATIVE: NativeTextView
210
211 redef fun text do return native.text.to_s
212 redef fun text=(value) do
213 if value == null then value = ""
214 native.text = value.to_java_string
215 end
216
217 redef fun size=(size) do set_size_native(app.native_activity, native, size or else 1.0)
218
219 private fun set_size_native(context: NativeContext, view: NativeTextView, size: Float)
220 in "Java" `{
221 int s;
222 if (size == 1.0d)
223 s = android.R.style.TextAppearance_Medium;
224 else if (size < 1.0d)
225 s = android.R.style.TextAppearance_Small;
226 else // if (size > 1.0d)
227 s = android.R.style.TextAppearance_Large;
228
229 view.setTextAppearance(context, s);
230 `}
231
232 redef fun align=(align) do set_align_native(native, align or else 0.0)
233
234 private fun set_align_native(view: NativeTextView, align: Float)
235 in "Java" `{
236 int g;
237 if (align == 0.5d)
238 g = android.view.Gravity.CENTER_HORIZONTAL;
239 else if (align < 0.5d)
240 g = android.view.Gravity.LEFT;
241 else // if (align > 0.5d)
242 g = android.view.Gravity.RIGHT;
243
244 view.setGravity(g);
245 `}
246 end
247
248 redef class Label
249 redef type NATIVE: NativeTextView
250 redef var native do return (new NativeTextView(app.native_activity)).new_global_ref
251 end
252
253 redef class CheckBox
254 redef type NATIVE: Android_widget_CompoundButton
255 redef var native do return (new Android_widget_CheckBox(app.native_activity)).new_global_ref
256
257 redef fun is_checked do return native.is_checked
258 redef fun is_checked=(value) do native.set_checked(value)
259 end
260
261 redef class TextInput
262 redef type NATIVE: NativeEditText
263 redef var native = (new NativeEditText(app.native_activity)).new_global_ref
264
265 redef fun is_password=(value)
266 do
267 native.is_password = value or else false
268 super
269 end
270 end
271
272 redef class NativeEditText
273
274 # Configure this view to hide passwords
275 fun is_password=(value: Bool) in "Java" `{
276 if (value) {
277 self.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
278 self.setTransformationMethod(android.text.method.PasswordTransformationMethod.getInstance());
279 } else {
280 self.setInputType(android.text.InputType.TYPE_CLASS_TEXT);
281 self.setTransformationMethod(null);
282 }
283 `}
284 end
285
286 redef class Button
287 super Finalizable
288
289 redef type NATIVE: NativeButton
290 redef var native = (new NativeButton(app.native_activity, self)).new_global_ref
291
292 private fun on_click do notify_observers new ButtonPressEvent(self)
293
294 redef fun finalize do native.delete_global_ref
295 end
296
297 redef class NativeButton
298 private new (context: NativeActivity, sender_object: Button)
299 import Button.on_click in "Java" `{
300 final int final_sender_object = sender_object;
301 Button_incr_ref(final_sender_object);
302
303 return new android.widget.Button(context){
304 @Override
305 public boolean onTouchEvent(android.view.MotionEvent event) {
306 if(event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
307 Button_on_click(final_sender_object);
308 return true;
309 }
310 return false;
311 }
312 };
313 `}
314 end
315
316 redef class Android_app_Fragment
317 private new (nit_window: Window)
318 import Window.on_create_fragment in "Java" `{
319 final int final_nit_window = nit_window;
320
321 return new android.app.Fragment(){
322 @Override
323 public android.view.View onCreateView(android.view.LayoutInflater inflater,
324 android.view.ViewGroup container, android.os.Bundle state) {
325
326 return Window_on_create_fragment(final_nit_window);
327 }
328 };
329 `}
330 end