X-Git-Url: http://nitlanguage.org diff --git a/lib/app/ui.nit b/lib/app/ui.nit index 65bcffe..42e36ce 100644 --- a/lib/app/ui.nit +++ b/lib/app/ui.nit @@ -12,27 +12,69 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Portable UI API for the _app.nit_ framework +# Portable UI controls for mobiles apps +# +# ~~~ +# import app::ui +# +# class MyWindow +# super Window +# +# var layout = new ListLayout(parent=self) +# var lbl = new Label(parent=layout, text="Hello world", align=0.5) +# var but = new Button(parent=layout, text="Press here") +# +# redef fun on_event(event) do lbl.text = "Pressed!" +# end +# +# redef fun root_window do return new MyWindow +# ~~~ module ui import app_base # Platform variations -# TODO: move on the platform once qualified names are understand in the condition import linux::ui is conditional(linux) -import android::ui is conditional(android) # FIXME it should be conditional to `android::platform` +import android::ui is conditional(android) +import ios::ui is conditional(ios) 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 + # This attribute is set by `push_window`. + var window: Window is noinit + + # Make `window` visible and push it on the top of the `window_stack` + # + # This method can be called at any times while the app is active. + fun push_window(window: Window) + do + window_stack.add window + self.window = window + window.on_create + end + + # Pop the current `window` from the stack and show the previous one + # + # Require: `window_stack.not_empty` + fun pop_window + do + assert window_stack.not_empty + window_stack.pop + window = window_stack.last + window.on_resume + end - redef fun on_create do window.on_create + # Stack of active windows + var window_stack = new Array[Window] - redef fun on_start do window.on_start + redef fun on_create + do + var window = root_window + push_window window + end redef fun on_resume do window.on_resume @@ -40,13 +82,18 @@ redef class App 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 +# Hook to create the first window shown to the user +# +# By default, a `Window` is created, which can be refined to customize it. +# However, most apps should refine this method to return a different window, +# this way the app can have more than one window. +fun root_window: Window do return new Window + # An event created by an `AppComponent` and sent to `AppObserver`s interface AppEvent end @@ -83,11 +130,17 @@ class Control # Direct parent `Control` in the control tree # + # The parents (direct and indirect) receive all events from `self`, + # like the `observers`. + # # 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 # + # The parents (direct and indirect) receive all events from `self`, + # like the `observers`. + # # Setting `parent` calls `remove` on the old parent and `add` on the new one. fun parent=(parent: nullable CompositeControl) is autoinit do @@ -100,13 +153,26 @@ class Control set_parent parent end + + # Also notify the parents (both direct and indirect) + redef fun notify_observers(event) + do + super + + var p = parent + while p != null do + p.on_event event + p = p.parent + end + end end # A `Control` grouping other controls class CompositeControl super Control - private var items = new HashSet[Control] + # Child controls composing this control + protected var items = new Array[Control] # Add `item` as a child of `self` protected fun add(item: Control) do items.add item @@ -117,9 +183,10 @@ class CompositeControl # Is `item` in `self`? fun has(item: Control): Bool do return items.has(item) - redef fun on_create do for i in items do i.on_create + # Remove all items from `self` + fun clear do for item in items.to_a do remove item - redef fun on_start do for i in items do i.on_start + redef fun on_create do for i in items do i.on_create redef fun on_resume do for i in items do i.on_resume @@ -127,19 +194,26 @@ class CompositeControl 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 +# +# Each window should hold a single control, usually a `CompositeControl`, +# which in turn holds all the displayed controls. class Window super CompositeControl + + # Should the back button be shown and used to go back to a previous window? + fun enable_back_button: Bool do return app.window_stack.length > 1 + + # The back button has been pressed, usually to open the previous window + fun on_back_button do app.pop_window end -# A viewable `Control` +# A visible `Control` abstract class View super Control @@ -149,7 +223,9 @@ abstract class View var enabled: nullable Bool is writable, abstract, autoinit end -# A control with some `text` +# A control displaying some `text` +# +# For a text-only control, see `Label`. abstract class TextView super View @@ -157,24 +233,79 @@ abstract class TextView # # By default, or if set to `null`, no text is shown. var text: nullable Text is writable, abstract, autoinit + + # Set the relative size of the text + # + # A value of 1.0, the default, use the platform default text size. + # Values under 1.0 set a smaller text size, and over 1.0 a larger size. + # + # Implementation varies per platform, and some controls may be unaffected + # depending on the customization options of each platform. + # For consistent results, it is recommended to use only on instances + # of `Label` and `size` should be either 0.5, 1.0 or 1.5. + fun size=(size: nullable Float) is autoinit do end + + # Align the text horizontally + # + # Use 0.0 to align left (the default), 0.5 to align in the center and + # 1.0 to align on the right. + # + # Implementation varies per platform, and some controls may be unaffected + # depending on the customization options of each platform. + # For consistent results, it is recommended to use only on instances + # of `Label` and `size` should be either 0.0, 0.5 or 1.0. + fun align=(align: nullable Float) is autoinit do end end # A control for the user to enter custom `text` class TextInput super TextView + + # Hide password or any content entered in this view? + var is_password: nullable Bool is writable end -# A pushable button, raises `ButtonPressEvent` +# A pressable button, raises `ButtonPressEvent` class Button super TextView end +# A simple text label +class Label + super TextView +end + +# Toggle control between two states, also displays a label +class CheckBox + super TextView + + # Is this control in the checked/on state? + var is_checked = false is writable +end + +# Event sent from a `VIEW` +class ViewEvent + super AppEvent + + # The `VIEW` that raised this event + var sender: VIEW + + # Type of the `sender` + type VIEW: View +end + # A `Button` press event class ButtonPressEvent - super AppEvent + super ViewEvent - # The `Button` that raised this event - var sender: Button + redef type VIEW: Button +end + +# The `CheckBox` `sender` has been toggled +class ToggleEvent + super ViewEvent + + redef type VIEW: CheckBox end # A layout to visually organize `Control`s @@ -192,3 +323,14 @@ end class VerticalLayout super Layout end + +# Scrollable list of views in a simple list +class ListLayout + super View + super CompositeControl +end + +redef class Text + # Open the URL `self` with the default browser + fun open_in_browser do print_error "Text::open_in_browser not implemented on this platform." +end