This PR is the result of using Nit serialization to implement game saving/loading in WBTW.
Update the serialization to work with (or avoid) the new auto constructors.
Support serializing Sets and Maps.
The `is_same_serialized` fix a problem where two instances were wrongly serialized as being the same. Because it previously used a `HashMap` (and thus `==`), two different _empty_ arrays were serialized as one and resulted in a single object on deserialization.
Once this and #1348 are merge, I'll update the calculator example to use the serialization.
Pull-Request: #1352
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
-all:
- mkdir -p bin/
+NITC=../../bin/nitc
+NITLS=../../bin/nitls
- ../../bin/nitc --dir bin/ src/calculator_gtk.nit src/calculator_test.nit
+all: bin/calculator bin/calculator.apk bin/test
-android:
- mkdir -p bin/ res/
+bin/calculator: $(shell ${NITLS} -M src/calculator.nit ../../lib/linux/ui.nit) ${NITC}
+ mkdir -p bin
+ ${NITC} -o $@ src/calculator.nit -m ../../lib/linux/ui.nit
+
+bin/calculator.apk: $(shell ${NITLS} -M src/calculator.nit ../../lib/android/ui/) ${NITC} ../../contrib/inkscape_tools/bin/svg_to_icons
+ mkdir -p bin res
../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
- ../../bin/nitc -o bin/calculator.apk src/calculator_android.nit
+ ${NITC} -o $@ src/calculator.nit -m ../../lib/android/ui/
+
+../../contrib/inkscape_tools/bin/svg_to_icons:
+ make -C ../../contrib/inkscape_tools/
-android-install: android
+android-install: bin/calculator.apk
adb install -r bin/calculator.apk
+
+bin/test: $(shell ${NITLS} -M src/calculator_test.nit) ${NITC}
+ mkdir -p bin
+ ${NITC} -o $@ src/calculator_test.nit
+
+check: bin/test
+ bin/test
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Portable calculator UI
+module calculator is
+app_name "app.nit Calc."
+ app_version(0, 1, git_revision)
+ app_namespace "org.nitlanguage.calculator"
+
+ # Lock in portrait mode
+ android_manifest_activity """android:screenOrientation="portrait""""
+ android_api_target 15
+end
+
+import app::ui
+import app::data_store
+import android::aware
+
+import calculator_logic
+
+redef class App
+ redef fun on_create
+ do
+ # Create the main window
+ window = new CalculatorWindow
+ super
+ end
+end
+
+# The main (and only) window of this calculator
+class CalculatorWindow
+ super Window
+
+ # Calculator context, our business logic
+ private var context = new CalculatorContext
+
+ # Main window layout
+ private var layout = new VerticalLayout(parent=self)
+
+ # Main display, at the top of the screen
+ private var display = new TextInput(parent=layout)
+
+ # Maps operators as `String` to their `Button`
+ private var buttons = new HashMap[String, Button]
+
+ init
+ do
+ # All the button labels, row by row
+ var rows = [["7", "8", "9", "+"],
+ ["4", "5", "6", "-"],
+ ["1", "2", "3", "*"],
+ ["0", ".", "C", "/"],
+ ["="]]
+
+ for row in rows do
+ var row_layout = new HorizontalLayout(parent=layout)
+
+ for op in row do
+ var but = new Button(parent=row_layout, text=op)
+ but.observers.add self
+ buttons[op] = but
+ end
+ end
+ end
+
+ redef fun on_event(event)
+ do
+ if event isa ButtonPressEvent then
+
+ var op = event.sender.text
+ if op == "." then
+ event.sender.enabled = false
+ context.switch_to_decimals
+ else if op.is_numeric then
+ var n = op.to_i
+ context.push_digit n
+ else
+ buttons["."].enabled = true
+ context.push_op op.chars.first
+ end
+
+ display.text = context.display_text
+ end
+ end
+
+ redef fun on_save_state
+ do
+ app.data_store["context"] = context.to_json
+ super
+ end
+
+ redef fun on_restore_state
+ do
+ super
+
+ var save = app.data_store["context"]
+ if save == null then return
+ assert save isa String
+
+ self.context = new CalculatorContext.from_json(save)
+ display.text = context.display_text
+ end
+end
+++ /dev/null
-# 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.
-
-# Android calculator application
-module calculator_android is
- app_name "app.nit Calc."
- app_version(0, 1, git_revision)
- app_namespace "org.nitlanguage.calculator"
-
- # Lock in portrait mode
- android_manifest_activity """android:screenOrientation="portrait""""
-end
-
-# FIXME the next line should import `android` only when it uses nit_activity
-import android::log
-import android::ui
-
-import calculator_logic
-
-redef class Activity
- super EventCatcher
-
- private var context = new CalculatorContext
-
- # The main display, at the top of the screen
- private var display: EditText
-
- # Maps operators as `String` to their `Button`
- private var op2but = new HashMap[String, Button]
-
- # Has this window been initialized?
- private var inited = false
-
- redef fun on_start
- do
- super
-
- if inited then return
- inited = true
-
- # Setup UI
- var layout = new NativeLinearLayout(native)
- layout.set_vertical
-
- # Display screen
- var display = new EditText
- layout.add_view_with_weight(display.native, 1.0)
- display.text_size = 36.0
- self.display = display
-
- # Buttons; numbers and operators
- var ops = [["7", "8", "9", "+"],
- ["4", "5", "6", "-"],
- ["1", "2", "3", "*"],
- ["0", ".", "C", "/"],
- ["="]]
-
- for line in ops do
- var buts_layout = new NativeLinearLayout(native)
- buts_layout.set_horizontal
- layout.add_view_with_weight(buts_layout, 1.0)
-
- for op in line do
- var but = new Button
- but.event_catcher = self
- but.text = op
- but.text_size = 40
- buts_layout.add_view_with_weight(but.native, 1.0)
- op2but[op] = but
- end
- end
-
- native.content_view = layout
- end
-
- redef fun on_save_instance_state(state)
- do
- super
-
- var nity = new Bundle.from(state)
- nity["context"] = context.to_json
- end
-
- redef fun on_restore_instance_state(state)
- do
- super
-
- var nity = new Bundle.from(state)
- if not nity.has("context") then return
-
- var json = nity.string("context")
- if json == null then return
-
- context = new CalculatorContext.from_json(json)
- end
-
- redef fun on_resume
- do
- display.text = context.display_text
- end
-
- redef fun catch_event(event)
- do
- if event isa ClickEvent then
- var sender = event.sender
- var op = sender.text
-
- if op == "." then
- sender.enabled = false
- context.switch_to_decimals
- else if op.is_numeric then
- var n = op.to_i
- context.push_digit n
- else
- op2but["."].enabled = true
- context.push_op op.chars.first
- end
-
- display.text = context.display_text
- end
- end
-end
+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2013-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.
-
-# GTK calculator
-module calculator_gtk
-
-import calculator_logic
-
-import gtk
-
-# GTK calculator UI
-class CalculatorGui
- super GtkCallable
-
- private var win: GtkWindow is noinit
- private var container: GtkGrid is noinit
-
- private var lbl_disp: GtkLabel is noinit
- private var but_eq: GtkButton is noinit
- private var but_dot: GtkButton is noinit
-
- private var context = new CalculatorContext
-
- redef fun signal(sender, op)
- do
- if op isa Char then # is an operation
- if op == '.' then
- but_dot.sensitive = false
- context.switch_to_decimals
- else
- but_dot.sensitive = true
- context.push_op op
- end
- else if op isa Int then # is a number
- context.push_digit op
- end
-
- lbl_disp.text = context.display_text
- end
-
- init
- do
- init_gtk
-
- win = new GtkWindow(new GtkWindowType.toplevel)
- win.connect_destroy_signal_to_quit
-
- container = new GtkGrid
- win.add container
-
- lbl_disp = new GtkLabel("_")
- container.attach(lbl_disp, 0, 0, 5, 1)
-
- # Digits
- for n in [0..9] do
- var but = new GtkButton.with_label(n.to_s)
- but.request_size(64, 64)
- but.signal_connect("clicked", self, n)
- if n == 0 then
- container.attach(but, 0, 4, 1, 1)
- else container.attach(but, (n-1)%3, 3-(n-1)/3, 1, 1)
- end
-
- # Operators
- var r = 1
- for op in ['+', '-', '*', '/'] do
- var but = new GtkButton.with_label(op.to_s)
- but.request_size(64, 64)
- but.signal_connect("clicked", self, op)
- container.attach(but, 3, r, 1, 1)
- r+=1
- end
-
- # =
- but_eq = new GtkButton.with_label("=")
- but_eq.request_size(64, 64)
- but_eq.signal_connect("clicked", self, '=')
- container.attach(but_eq, 4, 3, 1, 2)
-
- # .
- but_dot = new GtkButton.with_label(".")
- but_dot.request_size(64, 64)
- but_dot.signal_connect("clicked", self, '.')
- container.attach(but_dot, 1, 4, 1, 1)
-
- # C
- var but_c = new GtkButton.with_label("C")
- but_c.request_size(64, 64)
- but_c.signal_connect("clicked", self, 'C')
- container.attach(but_c, 2, 4, 1, 1)
-
- win.show_all
- end
-end
-
-# Do not show GUI in when testing
-if "NIT_TESTING".environ == "true" then exit 0
-
-var app = new CalculatorGui
-run_gtk
context.push_digit( 2 )
context.push_op( '=' )
var r = context.result
-assert r == "30.00" else print r or else "-"
+assert r == 30 else print r or else "-"
context = new CalculatorContext
context.push_digit( 1 )
context.push_digit( 3 )
context.push_op( '=' )
r = context.result
-assert r == "42.30" else print r or else "-"
+assert r == 42.3 else print r or else "-"
context.push_op( '+' )
context.push_digit( 1 )
context.push_digit( 1 )
context.push_op( '=' )
r = context.result
-assert r == "53.30" else print r or else "-"
+assert r == 53.3 else print r or else "-"
context = new CalculatorContext
context.push_digit( 4 )
context.push_digit( 3 )
context.push_op( '=' )
r = context.result
-assert r == "14.10" else print r or else "-"
+assert r == 14.1 else print r or else "-"
#test multiple decimals
context = new CalculatorContext
context.push_digit( 1 )
context.push_op( '=' )
r = context.result
-assert r == "51.123" else print r or else "-"
+assert r == 51.123 else print r or else "-"
#test 'C' button
context = new CalculatorContext
context.push_op( '=' )
context.push_op( 'C' )
r = context.result
-assert r == "0.0" else print r or else "-"
+assert r == null else print r
# Configure your Android application
-The `app.nit` framework and this project offers some services to
-customized the generated Android application.
+The _app.nit_ framework and this project offers some services to
+customize the generated Android application.
-## Module annotations
+## Annotations
-* `app_version` specifies the version of the generated APK file.
-It takes 3 arguments: the major, minor and revision version numbers.
-The special function `git_revision` will use the prefix of the hash of the
-latest git commit. The default version is 1.0.
+* All _app.nit_ annotations are applied to Android projects:
+ `app_name`, `app_namespace` and `app_version`.
- Example: `app_version(1, 0, git_revision)`
-
-* `app_name` takes a single argument, the visible name of the Android
-application. By default, the compiler would use the name of the target
-module. This name will be used as the name of the main activity and
-as the launcher name.
-
- Example: `app_name "My App"`
-
-* `app_namespace` specifies the package used by the generated Java
-classes and the APK file. Once the application is published, this
-value should not be changed. By default, the compiler will use
-the package `org.nitlanguage.{module_name}`.
+ See: `../app/README.md`
* Custom information can be added to the Android manifest file
using the annotations `android_manifest`, `android_manifest_application`
android_manifest """<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>"""
~~~
-* The API version target can be specified with `min_api_version`,
-`max_api_version` and `target_api_version`. These take a single
+* The API version target can be specified with `android_api_min`,
+`android_api_max` and `android_api_target`. These take a single
integer as argument. They are applied in the Android manifest as
`minSdkVesion`, `targetSdkVersion` and `maxSdkVersion`.
mkdir -p bin/ res/
../../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
../../../bin/nitc --dir bin/ src/ui_test.nit
- adb install -r bin/ui_test.apk
install: android
- adb install -r bin/ui.apk
+ adb install -r bin/ui_test.apk
clean:
rm -rf bin
app_version(0, 1, git_revision)
app_namespace "org.nitlanguage.ui_test"
android_manifest_activity """android:theme="@android:style/Theme.Light""""
+ android_api_target 15
end
-import android
import android::ui
import android::toast
import android::notification
redef class App
-
- var but_notif: Button
- var but_toast: Button
-
- var notif: nullable Notification = null
-
- var inited = false
- redef fun init_window
+ redef fun on_create
do
+ self.window = new Window
super
+ end
+end
- if inited then return
- inited = true
+redef class Window
- # Setup UI
- var context = native_activity
- var layout = new NativeLinearLayout(context)
- layout.set_vertical
+ private var layout = new VerticalLayout(parent=self)
- but_notif = new Button
- but_notif.text = "Show Notification"
- layout.add_view but_notif.native
+ private var but_notif = new Button(parent=layout, text="Show Notification")
+ private var but_toast = new Button(parent=layout, text="Show Toast")
- but_toast = new Button
- but_toast.text = "Show Toast"
- layout.add_view but_toast.native
+ private var notif: nullable Notification = null
- context.content_view = layout
+ init
+ do
+ but_notif.observers.add self
+ but_toast.observers.add self
end
+ # Action when pressing `but_notif`
fun act_notif
do
var notif = self.notif
end
end
+ # Action when pressing `but_toast`
fun act_toast
do
- toast("Sample toast from app.nit at {get_time}", false)
+ app.toast("Sample toast from app.nit at {get_time}", false)
end
- redef fun catch_event(event)
+ redef fun on_event(event)
do
- if event isa ClickEvent then
+ print "on_event {event}"
+ if event isa ButtonPressEvent then
var sender = event.sender
if sender == but_notif then
act_notif
# 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
# Views and services to use the Android native user interface
module ui
+# Implementation note:
+#
+# We cannot rely on `Activity::on_restore_instance_state` to implement
+# `on_restore_state` is it only invoked if there is a bundled state,
+# and we don't use the Android bundled state.
+
import native_ui
+import log
+import nit_activity
+
+import app::ui
+private import data_store
+
+redef class Control
+ # The Android element used to implement `self`
+ fun native: NATIVE is abstract
-# An event from the `app.nit` framework
-interface AppEvent
- # Reaction to this event
- fun react do end
+ # Type of `native`
+ type NATIVE: JavaObject
end
-# A control click event
-class ClickEvent
- super AppEvent
+redef class Window
+ redef var native = app.native_activity
- # Sender of this event
- var sender: Button
+ redef type NATIVE: NativeActivity
- redef fun react do sender.click self
-end
+ redef fun add(item)
+ do
+ super
-# Receiver of events not handled directly by the sender
-interface EventCatcher
- fun catch_event(event: AppEvent) do end
+ # FIXME abstract the Android restriction where `content_view` must be a layout
+ assert item isa Layout
+ native.content_view = item.native
+ end
end
-redef class App
- super EventCatcher
-end
+redef class View
+ redef type NATIVE: NativeView
-# An `Object` that raises events
-abstract class Eventful
- var event_catcher: EventCatcher = app is lazy, writable
+ redef fun enabled=(enabled) do native.enabled = enabled or else true
+ redef fun enabled do return native.enabled
end
-#
-## Nity classes and services
-#
+redef class Layout
+ redef type NATIVE: NativeViewGroup
-# An Android control with text
-abstract class TextView
- super Finalizable
- super Eventful
-
- # Native Java variant to this Nity class
- type NATIVE: NativeTextView
+ redef fun add(item)
+ do
+ super
- # The native Java object encapsulated by `self`
- var native: NATIVE is noinit
+ assert item isa View
- # 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
+ # FIXME abstract the use either homogeneous or weight to balance views size in a layout
+ native.add_view_with_weight(item.native, 1.0)
end
+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
+redef class HorizontalLayout
+ redef var native do
+ var layout = new NativeLinearLayout(app.native_activity)
+ layout.set_horizontal
+ return layout
end
+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
+redef class VerticalLayout
+ redef var native do
+ var layout = new NativeLinearLayout(app.native_activity)
+ layout.set_vertical
+ return layout
end
end
-# An Android button
-class Button
- super TextView
+redef class TextView
+ redef type NATIVE: NativeTextView
- redef type NATIVE: NativeButton
-
- init
- do
- var native = new NativeButton(app.native_activity, self)
- self.native = native.new_global_ref
+ redef fun text do return native.text.to_s
+ redef fun text=(value) do
+ if value == null then value = ""
+ native.text = value.to_java_string
end
- # Click event
- #
- # 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)
+ # Size of the text
+ fun text_size: Float do return native.text_size
- private fun click_from_native do click(new ClickEvent(self))
+ # Size of the text
+ fun text_size=(text_size: nullable Float) do
+ if text_size != null then native.text_size = text_size
+ end
end
-# An Android editable text field
-class EditText
- super TextView
-
+redef class TextInput
redef type NATIVE: NativeEditText
+ redef var native = (new NativeEditText(app.native_activity)).new_global_ref
+end
- init
- do
- var native = new NativeEditText(app.activities.first.native)
- self.native = native.new_global_ref
- end
+redef class Button
+ super Finalizable
+
+ redef type NATIVE: NativeButton
+ redef var native = (new NativeButton(app.native_activity, self)).new_global_ref
+
+ private fun on_click do notify_observers new ButtonPressEvent(self)
+
+ redef fun finalize do native.delete_global_ref
end
redef class NativeButton
- new (context: NativeActivity, sender_object: Object)
- import Button.click_from_native in "Java" `{
+ private new (context: NativeActivity, sender_object: Button)
+ import Button.on_click in "Java" `{
final int final_sender_object = sender_object;
return new android.widget.Button(context){
@Override
public boolean onTouchEvent(android.view.MotionEvent event) {
if(event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
- Button_click_from_native(final_sender_object);
+ Button_on_click(final_sender_object);
return true;
}
return false;
--- /dev/null
+_app.nit_ is a framework to create cross-platform applications
+
+The framework provides services to manage common needs of modern mobile applications:
+
+* Life-cycle
+* User interface
+* Persistence
+* Package metadata
+* Compilation and packaging
+
+The features offered by _app.nit_ are common to all platforms, but
+may not be available on all devices.
+
+## Application Life-Cycle
+
+The _app.nit_ application life-cycle is compatible with all target platforms.
+It relies on the following sequence of events, represented here by their callback method name:
+
+1. `on_create`: The application is being created.
+ You should build the UI at this time.
+
+2. `on_start`: The app is starting or restarting, background activities may
+
+3. `on_resume`: The app enters the active state, it is in the foreground.
+
+4. `on_pause`: The app leaves the active state and the foreground.
+ It may still be visible in the background.
+ It may then go back to `on_resume` or `on_stop`.
+
+5. `on_stop`: The app is completely hidden.
+ It may then be destroyed (`on_destroy`) or go back to `on_start`.
+
+6. `on_destroy`: The app is being destroyed.
+
+Life-cycle events related to saving and restoring the application state are provided by two special callback methods:
+
+* `on_save_state`: The app may be destroyed soon, save its state for a future `on_restore_state`.
+ More on how it can be done in the `app::data_store` section.
+
+* `on_restore_state`: The app is launching, restore its state from a previous `on_save_state`.
+
+These events are synchronized to the native platforms applications
+The `App` instance is the first to be notified of these events.
+Other UI elements, from the `ui` submodule, are notified of the same events using a simple depth first visit.
+So all UI elements can react separately to live-cycle events.
+
+## User Interface
+
+The `app::ui` module defines an abstract API to build a portable graphical application.
+The API is composed of interactive `Control`s, visible `View`s and an active `Window`.
+
+Here is a subset of the most useful controls and views:
+
+* The classic pushable `Button` with text (usually rectangular).
+
+* `TextInput` is a field for the user to enter text.
+
+* `HorizontalLayout` and `VerticalLayout` organize other controls in order.
+
+Each control is notified of input events by callbacks to `on_event`.
+All controls have observers that are also notified of the events.
+So there is two ways to customize the behavior on a given event:
+
+* Create a subclass of the wanted `Control`, let's say `Button`, and specialize `on_event`.
+
+* Add an observer to a `Button` instance, and implement `on_event` in the observer.
+
+### Usage Example
+
+The calculator example (at `../../examples/calculator/src/calculator.nit`) is a concrete,
+simple and complete use of the _app.nit_ portable UI.
+
+### Platform-specific UI
+
+You can go beyond the portable UI API of _app.nit_ by using the natives services of a platform.
+
+The suggested approach is to use platform specific modules to customize the application on a precise platform.
+This module redefine `Window::on_start` to call the native language of the platform and setup a native UI.
+
+_TODO complete description and add concrete examples_
+
+## Persistent State with data\_store
+
+_app.nit_ offers the submodule `app::data_store` to easily save the application state and user preferences.
+The service is accessible by the method `App::data_store`. The `DataStore` itself defines 2 methods:
+
+* `DataStore::[]=` saves and associates any serializable instances to a `String` key.
+Pass `null` to clear the value associated to a key.
+
+* `DataStore::[]` returns the object associated to a `String` key.
+It returns `null` if nothing is associated to the key.
+
+### Usage Example
+
+~~~
+import app::data_store
+
+redef class App
+ var user_name: String
+
+ redef fun on_save_state
+ do
+ app.data_store["first run"] = false
+ app.data_store["user name"] = user_name
+
+ super # call `on_save_state` on all attached instances of `AppComponent`
+ end
+
+ redef fun on_restore_state
+ do
+ var first_run = app.data_store["first run"]
+ if first_run != true then
+ print "It's the first run of this application"
+ end
+
+ var user_name = app.data_store["user name"]
+ if user_name isa String then
+ self.user_name = user_name
+ else self.user_name = "Undefined"
+
+ super
+ end
+end
+~~~
+
+## Metadata annotations
+
+The _app.nit_ framework defines three annotations to customize the application package.
+
+* `app_name` takes a single argument, the visible name of the application.
+ This name is used for launchers and window title.
+ By default, the name of the target module.
+
+* `app_namespace` specifies the full namespace (or package name) of the application package.
+ This value usually identify the application uniquely on application stores.
+ It should not change once the application has benn published.
+ By default, the namespace is `org.nitlanguage.{module_name}`.
+
+* `app_version` specifies the version of the application package.
+ This annotation expects at least one argument, usually we use three version numbers:
+ the major, minor and revision.
+ The special function `git_revision` will use the prefix of the hash of the latest git commit.
+ By default, the version is 0.1.
+
+### Usage Example
+
+~~~
+module my_module is
+ app_name "My App"
+ app_namespace "org.example.my_app"
+ app_version(1, 0, git_revision)
+end
+~~~
+
+## Compiling and Packaging an Application
+
+The Nit compiler detects the target platform from the importations and generates the appropriate application format and package.
+
+Applications using only the portable services of _app.nit_ require some special care at compilation.
+Such an application, let's say `calculator.nit`, does not depend on a specific platform and use the portable UI.
+The target platform must be specifed to the compiler for it to produce the correct application package.
+There is two main ways to achieve this goal:
+
+* The the mixin option (`-m path`) loads an additionnal module before compiling.
+ It can be used to load platform specific implementations of the _app.nit_ portable UI.
+
+ ~~~
+ # GNU/Linux version, using GTK
+ nitc calculator.nit -m NIT_DIR/lib/linux/ui.nit
+
+ # Android version
+ nitc calculator.nit -m NIT_DIR/lib/android/ui/
+ ~~~
+
+* A common alternative for larger projects is to use platform specific modules.
+ Continuing with the `calculator.nit` example, it can be accompagnied by the module `calculator_linux.nit`.
+ This module imports both `calculator` and `linux::ui`, and can also use other GNU/Linux specific code.
+
+ ~~~
+ module calculator_linux
+
+ import calculator
+ import linux::ui
+ ~~~
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Portable UI API for the _app.nit_ framework
+module ui
+
+import app_base
+
+redef class App
+ super AppComponent
+
+ # The current `Window` of this activity
+ #
+ # This attribute must be set by refinements of `App`.
+ var window: Window is writable
+
+ redef fun on_create do window.on_create
+
+ redef fun on_start do window.on_start
+
+ redef fun on_resume do window.on_resume
+
+ redef fun on_pause do window.on_pause
+
+ redef fun on_stop do window.on_stop
+
+ redef fun on_destroy do window.on_destroy
+
+ redef fun on_restore_state do window.on_restore_state
+
+ redef fun on_save_state do window.on_save_state
+end
+
+# An event created by an `AppComponent` and sent to `AppObserver`s
+interface AppEvent
+end
+
+# Observer of `AppEvent`s raised by `AppComponent`s
+interface AppObserver
+ # Notification of `event` raised by `sender`
+ #
+ # To be implemented in subclasses as needed.
+ fun on_event(event: AppEvent) do end
+end
+
+redef class AppComponent
+ super AppObserver
+
+ # All `AppObserver` notified of events raised by `self`
+ #
+ # By default, only `self` is an observer.
+ # Any other `AppObserver` can be added to this collection.
+ var observers = new HashSet[AppObserver].from([self: AppObserver])
+
+ # Propagate `event` to all `observers` by calling `AppObserver::on_event`
+ fun notify_observers(event: AppEvent)
+ do
+ for observer in observers do
+ observer.on_event(event)
+ end
+ end
+end
+
+# A control implementing the UI
+class Control
+ super AppComponent
+
+ # Direct parent `Control` in the control tree
+ #
+ # If `null` then `self` is at the root of the tree, or not yet attached.
+ var parent: nullable CompositeControl = null is private writable(set_parent)
+
+ # Direct parent `Control` in the control tree
+ #
+ # Setting `parent` calls `remove` on the old parent and `add` on the new one.
+ fun parent=(parent: nullable CompositeControl)
+ is autoinit do
+ var old_parent = self.parent
+ if old_parent != null and old_parent != parent then
+ old_parent.remove self
+ end
+
+ if parent != null then parent.add self
+
+ set_parent parent
+ end
+end
+
+# A `Control` grouping other controls
+class CompositeControl
+ super Control
+
+ private var items = new HashSet[Control]
+
+ # Add `item` as a child of `self`
+ protected fun add(item: Control) do items.add item
+
+ # Remove `item` from `self`
+ protected fun remove(item: Control) do if has(item) then items.remove item
+
+ # Is `item` in `self`?
+ protected fun has(item: Control): Bool do return items.has(item)
+
+ redef fun on_create do for i in items do i.on_create
+
+ redef fun on_start do for i in items do i.on_start
+
+ redef fun on_resume do for i in items do i.on_resume
+
+ redef fun on_pause do for i in items do i.on_pause
+
+ redef fun on_stop do for i in items do i.on_stop
+
+ redef fun on_destroy do for i in items do i.on_destroy
+
+ redef fun on_restore_state do for i in items do i.on_restore_state
+
+ redef fun on_save_state do for i in items do i.on_save_state
+end
+
+# A window, root of the `Control` tree
+class Window
+ super CompositeControl
+end
+
+# A viewable `Control`
+abstract class View
+ super Control
+
+ # Is this control enabled so the user can interact with it?
+ #
+ # By default, or if set to `null`, the control is enabled.
+ var enabled: nullable Bool is writable #, abstract FIXME with #1311
+end
+
+# A control with some `text`
+abstract class TextView
+ super View
+
+ # Main `Text` of this control
+ #
+ # By default, or if set to `null`, no text is shown.
+ var text: nullable Text is writable #, abstract FIXME with #1311
+end
+
+# A control for the user to enter custom `text`
+class TextInput
+ super TextView
+end
+
+# A pushable button, raises `ButtonPressEvent`
+class Button
+ super TextView
+end
+
+# A `Button` press event
+class ButtonPressEvent
+ super AppEvent
+
+ # The `Button` that raised this event
+ var sender: Button
+end
+
+# A layout to visually organize `Control`s
+abstract class Layout
+ super View
+ super CompositeControl
+end
+
+# An horizontal linear organization
+class HorizontalLayout
+ super Layout
+end
+
+# A vertical linear organization
+class VerticalLayout
+ super Layout
+end
`}
# Give the children of `self` equal space in the box?
- fun omogeneous: Bool `{ return gtk_box_get_homogeneous(recv); `}
+ fun homogeneous: Bool `{ return gtk_box_get_homogeneous(recv); `}
# Give the children of `self` equal space in the box?
- fun omogeneous=(omogeneous: Bool) `{
- gtk_box_set_homogeneous(recv, omogeneous);
+ fun homogeneous=(homogeneous: Bool) `{
+ gtk_box_set_homogeneous(recv, homogeneous);
`}
# Add `child` and pack it at the start of the box
return (GtkSearchEntry *)gtk_search_entry_new();
`}
end
+
+redef extern class GtkEntry
+ # Purpose of this text field
+ #
+ # Can be used by on-screen keyboards and other input methods to adjust their behaviour.
+ fun input_purpose: GtkInputPurpose `{
+ return gtk_entry_get_input_purpose(recv);
+ `}
+
+ # Input purpose, tweaks the behavior of this widget
+ #
+ # Can be used by on-screen keyboards and other input methods to adjust their behaviour.
+ fun input_purpose=(purpose: GtkInputPurpose) `{
+ gtk_entry_set_input_purpose(recv, purpose);
+ `}
+end
+
+# Describe the purpose of an input widget
+extern class GtkInputPurpose `{ GtkInputPurpose `}
+ # Allow any character
+ new free_form `{ return GTK_INPUT_PURPOSE_FREE_FORM; `}
+
+ # Allow only alphabetic characters
+ new alpha `{ return GTK_INPUT_PURPOSE_ALPHA; `}
+
+ # Allow only digits
+ new digits `{ return GTK_INPUT_PURPOSE_DIGITS; `}
+
+ # Edited field expects numbers
+ new number `{ return GTK_INPUT_PURPOSE_NUMBER; `}
+
+ # Edited field expects phone number
+ new phone `{ return GTK_INPUT_PURPOSE_PHONE; `}
+
+ # Edited field expects URL
+ new url `{ return GTK_INPUT_PURPOSE_URL; `}
+
+ # Edited field expects email address
+ new email `{ return GTK_INPUT_PURPOSE_EMAIL; `}
+
+ # Edited field expects the name of a person
+ new name `{ return GTK_INPUT_PURPOSE_NAME; `}
+
+ # Like `free_form`, but characters are hidden
+ new password `{ return GTK_INPUT_PURPOSE_PASSWORD; `}
+
+ # Like `digits`, but characters are hidden
+ new pin `{ return GTK_INPUT_PURPOSE_PIN; `}
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Implementation of the app.nit UI module for GNU/Linux
+module ui
+
+import app::ui
+import gtk
+
+import data_store
+
+redef class App
+ redef fun setup do init_gtk
+
+ # On GNU/Linux, we go through all the callbacks once,
+ # there is no complex life-cycle.
+ redef fun run
+ do
+ app.on_create
+ app.on_restore_state
+ app.on_start
+ app.on_resume
+
+ var window = window
+ window.native.show_all
+ run_gtk
+
+ app.on_pause
+ app.on_stop
+ app.on_save_state
+ app.on_destroy
+ end
+
+ # Spacing between GTK controls, default at 2
+ var control_spacing = 2 is writable
+end
+
+redef class Control
+ super GtkCallable
+ super Finalizable
+
+ # The GTK element used to implement `self`
+ fun native: NATIVE is abstract
+
+ # Type of `native`
+ type NATIVE: GtkWidget
+
+ redef fun finalize
+ do
+ var native = native
+ if not native.address_is_null then native.destroy
+ end
+end
+
+redef class CompositeControl
+ redef type NATIVE: GtkContainer
+
+ redef fun add(item)
+ do
+ super
+ native.add item.native
+ end
+end
+
+redef class Window
+ redef type NATIVE: GtkWindow
+ redef var native do
+ var win = new GtkWindow(new GtkWindowType.toplevel)
+ win.connect_destroy_signal_to_quit
+ return win
+ end
+end
+
+redef class View
+ redef fun enabled do return native.sensitive
+ redef fun enabled=(enabled) do native.sensitive = enabled or else true
+end
+
+redef class Layout
+ redef type NATIVE: GtkBox
+end
+
+redef class HorizontalLayout
+ redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing)
+
+ redef fun add(item)
+ do
+ super
+ native.homogeneous = true
+ native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
+ end
+end
+
+redef class VerticalLayout
+ redef var native = new GtkBox(new GtkOrientation.vertical, app.control_spacing)
+
+ redef fun add(item)
+ do
+ super
+
+ # FIXME abstract the use either homogeneous or weight to balance views size in a layout
+ native.homogeneous = true
+ native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
+ end
+end
+
+redef class Button
+ redef type NATIVE: GtkButton
+ redef var native = new GtkButton
+
+ redef fun text do return native.text
+ redef fun text=(value) do native.text = (value or else "").to_s
+
+ redef fun signal(sender, data) do notify_observers new ButtonPressEvent(self)
+
+ init do native.signal_connect("clicked", self, null)
+end
+
+redef class TextInput
+ redef type NATIVE: GtkEntry
+ redef var native = new GtkEntry
+
+ redef fun text do return native.text
+ redef fun text=(value) do
+ if value == null then value = ""
+ native.text = value.to_s
+ end
+end
module doc_structure
import doc_concerns
+import modelize
# StructurePhase populates the DocPage content with section and article.
#
mentity.intro_mmodule.mgroup.mproject.booster_rank = 0
mentity.intro_mmodule.mgroup.booster_rank = 0
mentity.intro_mmodule.booster_rank = 0
+ var constructors = new ConstructorsSection(mentity)
+ var minit = mentity.root_init
+ if minit != null then
+ constructors.add_child new DefinitionArticle(minit)
+ end
+ section.add_child constructors
section.add_child new ConcernsArticle(mentity, concerns)
for mentity in concerns do
var ssection = new ConcernSection(mentity)
v.name_sorter.sort(group)
for mprop in group do
for mpropdef in mpropdefs_for(mprop, mentity) do
- ssection.add_child new DefinitionArticle(mpropdef)
+ if mpropdef isa MMethodDef and mpropdef.mproperty.is_init then
+ if mpropdef == minit then continue
+ constructors.add_child new DefinitionArticle(mpropdef)
+ else
+ ssection.add_child new DefinitionArticle(mpropdef)
+ end
end
end
end
var mentity: MEntity
end
+# A list of constructors.
+class ConstructorsSection
+ super MEntitySection
+end
+
# A Section about a Concern.
#
# Those sections are used to build the page summary.
# FIXME annotation should be handled in their own way
redef fun html_modifiers do
+ if mproperty.is_init then
+ var res = new Array[String]
+ if mproperty.visibility != public_visibility then
+ res.add mproperty.visibility.to_s
+ end
+ return res
+ end
var res = super
if is_abstract then
res.add "abstract"
else if is_intern then
res.add "intern"
end
+ res.add "fun"
+ return res
+ end
+
+ redef fun html_declaration do
if mproperty.is_init then
- res.add "init"
- else
- res.add "fun"
+ var tpl = new Template
+ tpl.add "<span>"
+ tpl.add html_modifiers.join(" ")
+ tpl.add " "
+ tpl.add html_link
+ tpl.add html_signature
+ tpl.add "</span>"
+ return tpl
end
- return res
+ return super
+ end
+
+ redef fun html_short_signature do
+ if mproperty.is_root_init and new_msignature != null then
+ return new_msignature.html_short_signature
+ end
+ return msignature.html_short_signature
end
- redef fun html_short_signature do return msignature.html_short_signature
- redef fun html_signature do return msignature.html_signature
+ redef fun html_signature do
+ if mproperty.is_root_init and new_msignature != null then
+ return new_msignature.html_signature
+ end
+ return msignature.html_signature
+ end
end
redef class MVirtualTypeProp
redef var html_subtitle is lazy do return mentity.html_declaration
end
+redef class ConstructorsSection
+ redef var html_id is lazy do return "article:{mentity.nitdoc_id}.constructors"
+ redef var html_title = "Constructors"
+ redef var html_subtitle = null
+ redef fun is_toc_hidden do return is_empty
+end
+
redef class ConcernSection
redef var html_id is lazy do return "concern:{mentity.nitdoc_id}"
redef var html_title is lazy do return "in {mentity.nitdoc_name}"
# End of this location on `line_end`
var column_end: Int
+ # Builds a location instance from its string representation.
+ #
+ # Examples:
+ #
+ # ~~~
+ # var loc = new Location.from_string("location.nit:82,2--105,8")
+ # assert loc.to_s == "location.nit:82,2--105,8"
+ #
+ # loc = new Location.from_string("location.nit")
+ # assert loc.to_s == "location.nit"
+ #
+ # loc = new Location.from_string("location.nit:82,2")
+ # assert loc.to_s == "location.nit:82,2--0,0"
+ #
+ # loc = new Location.from_string("location.nit:82--105")
+ # assert loc.to_s == "location.nit:82,0--105,0"
+ #
+ # loc = new Location.from_string("location.nit:82,2--105")
+ # assert loc.to_s == "location.nit:82,2--105,0"
+ #
+ # loc = new Location.from_string("location.nit:82--105,8")
+ # assert loc.to_s == "location.nit:82,0--105,8"
+ # ~~~
+ init from_string(string: String) do
+ self.line_start = 0
+ self.line_end = 0
+ self.column_start = 0
+ self.column_end = 0
+ # parses the location string and init position vars
+ var parts = string.split_with(":")
+ var filename = parts.shift
+ self.file = new SourceFile(filename, new FileReader.open(filename))
+ # split position
+ if parts.is_empty then return
+ var pos = parts.first.split_with("--")
+ # split start position
+ if pos.first.has(",") then
+ var pos1 = pos.first.split_with(",")
+ self.line_start = pos1[0].to_i
+ if pos1.length > 1 then
+ self.column_start = pos1[1].to_i
+ end
+ else
+ self.line_start = pos.first.to_i
+ end
+ # split end position
+ if pos.length <= 1 then return
+ if pos[1].has(",") then
+ var pos2 = pos[1].split_with(",")
+ if pos2.length > 1 then
+ self.line_end = pos2[0].to_i
+ self.column_end = pos2[1].to_i
+ else
+ self.line_end = self.line_start
+ self.column_end = pos2[0].to_i
+ end
+ else
+ self.line_end = pos[1].to_i
+ end
+ end
+
# The index in the start character in the source
fun pstart: Int do return file.line_starts[line_start-1] + column_start-1
# Get a `Location` from its string representation.
private fun to_location(loc: String): Location do
- #TODO filepath
- var parts = loc.split_with(":")
- var file = new SourceFile.from_string(parts[0], "")
- if parts.length == 1 then
- return new Location(file, 0, 0, 0, 0)
- end
- var pos = parts[1].split_with("--")
- var pos1 = pos[0].split_with(",")
- var pos2 = pos[1].split_with(",")
- var line_s = pos1[0].to_i
- var line_e = pos2[0].to_i
- var column_s = pos1[1].to_i
- var column_e = 0
- if pos2.length == 2 then pos2[1].to_i
- return new Location(file, line_s, line_e, column_s, column_e)
+ return new Location.from_string(loc)
end
# Get a `MVisibility` from its string representation.