lib/android: update `ui` to use NitActivity and be on the UI thread
[nit.git] / lib / android / ui.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Views and services to use the Android native user interface
18 #
19 # Events, such as a button click, come from the UI thread and are then
20 # passed to the main thread. It is recommended to specialize one of the
21 # methods of the main thread to customize the response to a given event.
22 #
23 # This graph shows the path of a button click:
24 # ~~~raw
25 # UI Thread # Main thread
26 #
27 # User
28 # |
29 # V
30 # Button::click_ui --> Button::click
31 # |
32 # V
33 # App::catch_event
34 # ~~~
35 module ui is min_api_version 14
36
37 import nit_activity
38 import pthreads::concurrent_collections
39
40 in "Java" `{
41 import android.app.Activity;
42
43 import android.view.Gravity;
44 import android.view.MotionEvent;
45 import android.view.ViewGroup;
46 import android.view.ViewGroup.MarginLayoutParams;
47
48 import android.widget.Button;
49 import android.widget.LinearLayout;
50 import android.widget.GridLayout;
51 import android.widget.PopupWindow;
52 import android.widget.TextView;
53
54 import java.lang.*;
55 import java.util.*;
56 `}
57
58 # An event from the `app.nit` framework
59 interface AppEvent
60 # Reaction to this event
61 fun react do end
62 end
63
64 # A control click event
65 class ClickEvent
66 super AppEvent
67
68 # Sender of this event
69 var sender: Button
70
71 redef fun react do sender.click self
72 end
73
74 # Receiver of events not handled directly by the sender
75 interface EventCatcher
76 fun catch_event(event: AppEvent) do end
77 end
78
79 redef class App
80 super EventCatcher
81 end
82
83 redef extern class NativeActivity
84
85 # Set the main layout of this activity
86 fun content_view=(layout: NativeViewGroup) in "Java" `{
87 final ViewGroup final_layout = layout;
88 final Activity final_recv = recv;
89
90 recv.runOnUiThread(new Runnable() {
91 @Override
92 public void run() {
93 final_recv.setContentView(final_layout);
94
95 final_layout.requestFocus();
96 }
97 });
98 `}
99 end
100
101 # An `Object` that raises events
102 abstract class Eventful
103 var event_catcher: EventCatcher = app is lazy, writable
104 end
105
106 #
107 ## Nity classes and services
108 #
109
110 # An Android control with text
111 abstract class TextView
112 super Finalizable
113 super Eventful
114
115 # Native Java variant to this Nity class
116 type NATIVE: NativeTextView
117
118 # The native Java object encapsulated by `self`
119 var native: NATIVE is noinit
120
121 # Get the text of this view
122 fun text: String
123 do
124 var jstr = native.text
125 var str = jstr.to_s
126 jstr.delete_local_ref
127 return str
128 end
129
130 # Set the text of this view
131 fun text=(value: Text)
132 do
133 var jstr = value.to_s.to_java_string
134 native.text = jstr
135 jstr.delete_local_ref
136 end
137
138 # Get whether this view is enabled or not
139 fun enabled: Bool do return native.enabled
140
141 # Set if this view is enabled
142 fun enabled=(val: Bool) do native.enabled = val
143
144 # Set the size of the text in this view at `dpi`
145 fun text_size=(dpi: Numeric) do native.text_size = dpi.to_f
146
147 private var finalized = false
148 redef fun finalize
149 do
150 if not finalized then
151 native.delete_global_ref
152 finalized = true
153 end
154 end
155 end
156
157 # An Android button
158 class Button
159 super TextView
160
161 redef type NATIVE: NativeButton
162
163 init
164 do
165 var native = new NativeButton(app.native_activity, self)
166 self.native = native.new_global_ref
167 end
168
169 # Click event
170 #
171 # By default, this method calls `app.catch_event`. It can be specialized
172 # with custom behavior or the receiver of `catch_event` can be changed
173 # with `event_catcher=`.
174 fun click(event: AppEvent) do event_catcher.catch_event(event)
175
176 private fun click_from_native do click(new ClickEvent(self))
177 end
178
179 # An Android editable text field
180 class EditText
181 super TextView
182
183 redef type NATIVE: NativeEditText
184
185 init
186 do
187 var native = new NativeEditText(app.activities.first.native)
188 self.native = native.new_global_ref
189 end
190 end
191
192 #
193 ## Native classes
194 #
195
196 # A `View` for Android
197 extern class NativeView in "Java" `{ android.view.View `}
198 super JavaObject
199
200 fun minimum_width=(val: Int) in "Java" `{ recv.setMinimumWidth((int)val); `}
201 fun minimum_height=(val: Int) in "Java" `{ recv.setMinimumHeight((int)val); `}
202 end
203
204 # A collection of `NativeView`
205 extern class NativeViewGroup in "Java" `{ android.view.ViewGroup `}
206 super NativeView
207
208 fun add_view(view: NativeView) in "Java" `{ recv.addView(view); `}
209 end
210
211 # A `NativeViewGroup` organized in a line
212 extern class NativeLinearLayout in "Java" `{ android.widget.LinearLayout `}
213 super NativeViewGroup
214
215 new(context: NativeActivity) in "Java" `{ return new LinearLayout(context); `}
216
217 fun set_vertical in "Java" `{ recv.setOrientation(LinearLayout.VERTICAL); `}
218 fun set_horizontal in "Java" `{ recv.setOrientation(LinearLayout.HORIZONTAL); `}
219
220 redef fun add_view(view) in "Java"
221 `{
222 MarginLayoutParams params = new MarginLayoutParams(
223 LinearLayout.LayoutParams.MATCH_PARENT,
224 LinearLayout.LayoutParams.WRAP_CONTENT);
225 recv.addView(view, params);
226 `}
227
228 fun add_view_with_weight(view: NativeView, weight: Float)
229 in "Java" `{
230 recv.addView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, (float)weight));
231 `}
232 end
233
234 # A `NativeViewGroup` organized as a grid
235 extern class NativeGridLayout in "Java" `{ android.widget.GridLayout `}
236 super NativeViewGroup
237
238 new(context: NativeActivity) in "Java" `{ return new android.widget.GridLayout(context); `}
239
240 fun row_count=(val: Int) in "Java" `{ recv.setRowCount((int)val); `}
241
242 fun column_count=(val: Int) in "Java" `{ recv.setColumnCount((int)val); `}
243
244 redef fun add_view(view) in "Java" `{ recv.addView(view); `}
245 end
246
247 extern class NativePopupWindow in "Java" `{ android.widget.PopupWindow `}
248 super NativeView
249
250 new (context: NativeActivity) in "Java" `{
251 PopupWindow recv = new PopupWindow(context);
252 recv.setWindowLayoutMode(LinearLayout.LayoutParams.MATCH_PARENT,
253 LinearLayout.LayoutParams.MATCH_PARENT);
254 recv.setClippingEnabled(false);
255 return recv;
256 `}
257
258 fun content_view=(layout: NativeViewGroup) in "Java" `{ recv.setContentView(layout); `}
259 end
260
261 extern class NativeTextView in "Java" `{ android.widget.TextView `}
262 super NativeView
263
264 new (context: NativeActivity) in "Java" `{ return new TextView(context); `}
265
266 fun text: JavaString in "Java" `{ return recv.getText().toString(); `}
267
268 fun text=(value: JavaString) in "Java" `{
269
270 final TextView final_recv = recv;
271 final String final_value = value;
272
273 ((Activity)recv.getContext()).runOnUiThread(new Runnable() {
274 @Override
275 public void run() {
276 final_recv.setText(final_value);
277 }
278 });
279 `}
280
281 fun enabled: Bool in "Java" `{ return recv.isEnabled(); `}
282 fun enabled=(value: Bool) in "Java" `{
283 final TextView final_recv = recv;
284 final boolean final_value = value;
285
286 ((Activity)recv.getContext()).runOnUiThread(new Runnable() {
287 @Override
288 public void run() {
289 final_recv.setEnabled(final_value);
290 }
291 });
292 `}
293
294 fun gravity_center in "Java" `{
295 recv.setGravity(Gravity.CENTER);
296 `}
297
298 fun text_size=(dpi: Float) in "Java" `{
299 recv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, (float)dpi);
300 `}
301 end
302
303 extern class NativeEditText in "Java" `{ android.widget.EditText `}
304 super NativeTextView
305
306 redef type SELF: NativeEditText
307
308 new (context: NativeActivity) in "Java" `{ return new android.widget.EditText(context); `}
309
310 fun width=(val: Int) in "Java" `{ recv.setWidth((int)val); `}
311
312 fun input_type_text in "Java" `{ recv.setInputType(android.text.InputType.TYPE_CLASS_TEXT); `}
313
314 redef fun new_global_ref: SELF import sys, Sys.jni_env `{
315 Sys sys = NativeEditText_sys(recv);
316 JNIEnv *env = Sys_jni_env(sys);
317 return (*env)->NewGlobalRef(env, recv);
318 `}
319 end
320
321 extern class NativeButton in "Java" `{ android.widget.Button `}
322 super NativeTextView
323
324 redef type SELF: NativeButton
325
326 new (context: NativeActivity, sender_object: Object)
327 import Button.click_from_native in "Java" `{
328 final int final_sender_object = sender_object;
329
330 return new Button(context){
331 @Override
332 public boolean onTouchEvent(MotionEvent event) {
333 if(event.getAction() == MotionEvent.ACTION_DOWN) {
334 Button_click_from_native(final_sender_object);
335 return true;
336 }
337 return false;
338 }
339 };
340 `}
341
342 redef fun new_global_ref: SELF import sys, Sys.jni_env `{
343 Sys sys = NativeButton_sys(recv);
344 JNIEnv *env = Sys_jni_env(sys);
345 return (*env)->NewGlobalRef(env, recv);
346 `}
347 end