lib/android: intro the `ui` module
authorAlexis Laferrière <alexis.laf@xymus.net>
Tue, 25 Nov 2014 17:26:55 +0000 (12:26 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Wed, 26 Nov 2014 03:53:15 +0000 (22:53 -0500)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/android/ui.nit [new file with mode: 0644]

diff --git a/lib/android/ui.nit b/lib/android/ui.nit
new file mode 100644 (file)
index 0000000..b4ed822
--- /dev/null
@@ -0,0 +1,417 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Views and services to use the Android native user interface
+#
+# Events, such as a button click, come from the UI thread and are then
+# passed to the main thread. It is recommended to specialize one of the
+# methods of the main thread to customize the response to a given event.
+#
+# This graph shows the path of a button click:
+# ~~~
+#     UI Thread     #   Main thread
+#
+#       User
+#        |
+#        V
+# Button::click_ui --> Button::click
+#                           |
+#                           V
+#                    App::catch_event
+# ~~~
+module ui is min_api_version 14
+
+import native_app_glue
+import pthreads::concurrent_collections
+
+in "Java" `{
+       import android.app.NativeActivity;
+
+       import android.view.Gravity;
+       import android.view.MotionEvent;
+       import android.view.ViewGroup;
+       import android.view.ViewGroup.MarginLayoutParams;
+
+       import android.widget.Button;
+       import android.widget.LinearLayout;
+       import android.widget.GridLayout;
+       import android.widget.PopupWindow;
+       import android.widget.TextView;
+
+       import java.lang.*;
+       import java.util.*;
+`}
+
+# An event from the `app.nit` framework
+interface AppEvent
+       # Reaction to this event
+       fun react do end
+end
+
+# A control click event
+class ClickEvent
+       super AppEvent
+
+       # Sender of this event
+       var sender: Button
+
+       redef fun react do sender.click self
+end
+
+# Receiver of events not handled directly by the sender
+interface EventCatcher
+       fun catch_event(event: AppEvent) do end
+end
+
+redef class App
+       super EventCatcher
+
+       # Queue of events to be received by the main thread
+       var event_queue = new ConcurrentList[AppEvent]
+
+       # Call `react` on all `AppEvent` available in `event_queue`
+       protected fun loop_on_ui_callbacks
+       do
+               var queue = event_queue
+               while not queue.is_empty do
+                       var event = queue.pop
+                       event.react
+               end
+       end
+
+       redef fun run
+       do
+               loop
+                       # Process Android events
+                       poll_looper 100
+
+                       # Process app.nit events
+                       loop_on_ui_callbacks
+               end
+       end
+end
+
+redef extern class NativeActivity
+
+       # Fill this entire `NativeActivity` with `popup`
+       #
+       # This is a workaround for the use on `takeSurface` in `NativeActivity.java`
+       #
+       # TODO replace NativeActivity by our own NitActivity
+       private fun dedicate_to_popup(popup: NativePopupWindow, popup_layout: NativeViewGroup) in "Java" `{
+               final LinearLayout final_main_layout = new LinearLayout(recv);
+               final ViewGroup final_popup_layout = popup_layout;
+               final PopupWindow final_popup = popup;
+               final NativeActivity final_recv = recv;
+
+               recv.runOnUiThread(new Runnable() {
+                       @Override
+                       public void run()  {
+                               MarginLayoutParams params = new MarginLayoutParams(
+                                       LinearLayout.LayoutParams.MATCH_PARENT,
+                                       LinearLayout.LayoutParams.MATCH_PARENT);
+
+                               final_recv.setContentView(final_main_layout, params);
+
+                               final_popup.showAtLocation(final_popup_layout, Gravity.TOP, 0, 40);
+                       }
+               });
+       `}
+
+       # Set the main layout of this activity
+       fun content_view=(layout: NativeViewGroup)
+       do
+               var popup = new NativePopupWindow(self)
+               popup.content_view = layout
+               dedicate_to_popup(popup, layout)
+       end
+
+       # Set the real content view of this activity, without hack
+       #
+       # TODO bring use this instead of the hack with `dedicate_to_pupup`
+       private fun real_content_view=(layout: NativeViewGroup) in "Java" `{
+               final ViewGroup final_layout = layout;
+               final NativeActivity final_recv = recv;
+
+               recv.runOnUiThread(new Runnable() {
+                       @Override
+                       public void run()  {
+                               final_recv.setContentView(final_layout);
+
+                               final_layout.requestFocus();
+                       }
+               });
+       `}
+end
+
+# An `Object` that raises events
+abstract class Eventful
+       var event_catcher: EventCatcher = app is lazy, writable
+end
+
+#
+## Nity classes and services
+#
+
+# An Android control with text
+abstract class TextView
+       super Finalizable
+       super Eventful
+
+       # Native Java variant to this Nity class
+       type NATIVE: NativeTextView
+
+       # The native Java object encapsulated by `self`
+       var native: NATIVE is noinit
+
+       # Get the text of this view
+       fun text: String
+       do
+               var jstr = native.text
+               var str = jstr.to_s
+               jstr.delete_local_ref
+               return str
+       end
+
+       # Set the text of this view
+       fun text=(value: Text)
+       do
+               var jstr = value.to_s.to_java_string
+               native.text = jstr
+               jstr.delete_local_ref
+       end
+
+       # Get whether this view is enabled or not
+       fun enabled: Bool do return native.enabled
+
+       # Set if this view is enabled
+       fun enabled=(val: Bool) do native.enabled = val
+
+       # Set the size of the text in this view at `dpi`
+       fun text_size=(dpi: Numeric) do native.text_size = dpi.to_f
+
+       private var finalized = false
+       redef fun finalize
+       do
+               if not finalized then
+                       native.delete_global_ref
+                       finalized = true
+               end
+       end
+end
+
+# An Android button
+class Button
+       super TextView
+
+       redef type NATIVE: NativeButton
+
+       init
+       do
+               var native = new NativeButton(app.native_activity, app.event_queue, self)
+               self.native = native.new_global_ref
+       end
+
+       # Click event on the Main thread
+       #
+       # By default, this method calls `app.catch_event`. It can be specialized
+       # with custom behavior or the receiver of `catch_event` can be changed
+       # with `event_catcher=`.
+       fun click(event: AppEvent) do event_catcher.catch_event(event)
+
+       # Click event on the UI thread
+       #
+       # This method is called on the UI thread and redirects the event to `click`
+       # throught `App::event_queue`. In most cases, you should implement `click`
+       # and leave `click_ui` as is.
+       fun click_ui do app.event_queue.add(new ClickEvent(self))
+end
+
+# An Android editable text field
+class EditText
+       super TextView
+
+       redef type NATIVE: NativeEditText
+
+       init
+       do
+               var native = new NativeEditText(app.native_activity)
+               self.native = native.new_global_ref
+       end
+end
+
+#
+## Native classes
+#
+
+# A `View` for Android
+extern class NativeView in "Java" `{ android.view.View `}
+       super JavaObject
+
+       fun minimum_width=(val: Int) in "Java" `{ recv.setMinimumWidth((int)val); `}
+       fun minimum_height=(val: Int) in "Java" `{ recv.setMinimumHeight((int)val); `}
+end
+
+# A collection of `NativeView`
+extern class NativeViewGroup in "Java" `{ android.view.ViewGroup `}
+       super NativeView
+
+       fun add_view(view: NativeView) in "Java" `{ recv.addView(view); `}
+end
+
+# A `NativeViewGroup` organized in a line
+extern class NativeLinearLayout in "Java" `{ android.widget.LinearLayout `}
+       super NativeViewGroup
+
+       new(context: NativeActivity) in "Java" `{ return new LinearLayout(context); `}
+
+       fun set_vertical in "Java" `{ recv.setOrientation(LinearLayout.VERTICAL); `}
+       fun set_horizontal in "Java" `{ recv.setOrientation(LinearLayout.HORIZONTAL); `}
+
+       redef fun add_view(view) in "Java"
+       `{
+               MarginLayoutParams params = new MarginLayoutParams(
+                       LinearLayout.LayoutParams.MATCH_PARENT,
+                       LinearLayout.LayoutParams.WRAP_CONTENT);
+               recv.addView(view, params);
+       `}
+
+       fun add_view_with_weight(view: NativeView, weight: Float)
+       in "Java" `{
+               recv.addView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, (float)weight));
+       `}
+end
+
+# A `NativeViewGroup` organized as a grid
+extern class NativeGridLayout in "Java" `{ android.widget.GridLayout `}
+       super NativeViewGroup
+
+       new(context: NativeActivity) in "Java" `{ return new android.widget.GridLayout(context); `}
+
+       fun row_count=(val: Int) in "Java" `{ recv.setRowCount((int)val); `}
+
+       fun column_count=(val: Int) in "Java" `{ recv.setColumnCount((int)val); `}
+
+       redef fun add_view(view) in "Java" `{ recv.addView(view); `}
+end
+
+extern class NativePopupWindow in "Java" `{ android.widget.PopupWindow `}
+       super NativeView
+
+       new (context: NativeActivity) in "Java" `{
+               PopupWindow recv = new PopupWindow(context);
+               recv.setWindowLayoutMode(LinearLayout.LayoutParams.MATCH_PARENT,
+                       LinearLayout.LayoutParams.MATCH_PARENT);
+               recv.setClippingEnabled(false);
+               return recv;
+       `}
+
+       fun content_view=(layout: NativeViewGroup) in "Java" `{ recv.setContentView(layout); `}
+end
+
+extern class NativeTextView in "Java" `{ android.widget.TextView `}
+       super NativeView
+
+       new (context: NativeActivity) in "Java" `{ return new TextView(context); `}
+
+       fun text: JavaString in "Java" `{ return recv.getText().toString(); `}
+
+       fun text=(value: JavaString) in "Java" `{
+
+               android.util.Log.d("Nity", "1");
+               final TextView final_recv = recv;
+               final String final_value = value;
+
+               android.util.Log.d("Nity", "4");
+               ((NativeActivity)recv.getContext()).runOnUiThread(new Runnable() {
+                       @Override
+                       public void run()  {
+                               android.util.Log.d("Nity", "-5");
+                               android.util.Log.d("Nity", final_value);
+                               android.util.Log.d("Nity", "-5.5");
+                               final_recv.setText(final_value);
+                               android.util.Log.d("Nity", "-6");
+                       }
+               });
+               android.util.Log.d("Nity", "7");
+       `}
+
+       fun enabled: Bool in "Java" `{ return recv.isEnabled(); `}
+       fun enabled=(value: Bool) in "Java" `{
+               final TextView final_recv = recv;
+               final boolean final_value = value;
+
+               ((NativeActivity)recv.getContext()).runOnUiThread(new Runnable() {
+                       @Override
+                       public void run()  {
+                               final_recv.setEnabled(final_value);
+                       }
+               });
+       `}
+
+       fun gravity_center in "Java" `{
+               recv.setGravity(Gravity.CENTER);
+       `}
+
+       fun text_size=(dpi: Float) in "Java" `{
+               recv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, (float)dpi);
+       `}
+end
+
+extern class NativeEditText in "Java" `{ android.widget.EditText `}
+       super NativeTextView
+
+       redef type SELF: NativeEditText
+
+       new (context: NativeActivity) in "Java" `{ return new android.widget.EditText(context); `}
+
+       fun width=(val: Int) in "Java" `{ recv.setWidth((int)val); `}
+
+       fun input_type_text in "Java" `{ recv.setInputType(android.text.InputType.TYPE_CLASS_TEXT); `}
+
+       redef fun new_global_ref: SELF import sys, Sys.jni_env `{
+               Sys sys = NativeEditText_sys(recv);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, recv);
+       `}
+end
+
+extern class NativeButton in "Java" `{ android.widget.Button `}
+       super NativeTextView
+
+       redef type SELF: NativeButton
+
+       new (context: NativeActivity, queue: ConcurrentList[AppEvent], sender_object: Object) import Button.click_ui in "Java" `{
+               final int final_sender_object = sender_object;
+
+               return new Button(context){
+                       @Override
+                       public boolean onTouchEvent(MotionEvent event) {
+                               if(event.getAction() == MotionEvent.ACTION_DOWN) {
+                                       Button_click_ui(final_sender_object);
+                                       return true;
+                               }
+                               return false;
+                       }
+               };
+       `}
+
+       redef fun new_global_ref: SELF import sys, Sys.jni_env `{
+               Sys sys = NativeButton_sys(recv);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, recv);
+       `}
+end