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