ci: compile the manual
[nit.git] / lib / ios / ui / ui.nit
index 6289bea..da04397 100644 (file)
@@ -25,16 +25,16 @@ in "ObjC" `{
 @interface NitCallbackReference: NSObject
 
        // Nit object target of the callbacks from UI events
-       @property (nonatomic) Button nit_button;
+       @property (nonatomic) View nit_view;
 
        // Actual callback method
-       -(void) nitOnEvent: (UIButton*) sender;
+       -(void) nitOnEvent: (UIView*) sender;
 @end
 
 @implementation NitCallbackReference
 
-       -(void) nitOnEvent: (UIButton*) sender {
-               Button_on_click(self.nit_button);
+       -(void) nitOnEvent: (UIView*) sender {
+               View_on_ios_event(self.nit_view);
        }
 @end
 
@@ -73,9 +73,25 @@ in "ObjC" `{
                return TableView_cell_for_row_at_index_path(self.nit_list_layout, tableView, indexPath);
        }
 @end
+
+// View controller associated to an app.nit Window
+@interface NitViewController: UIViewController
+@end
+
+@implementation NitViewController
+       - (void)viewWillDisappear:(BOOL)animated {
+               [super viewWillDisappear:animated];
+
+               if (self.isMovingFromParentViewController || self.isBeingDismissed) {
+                       extern App app_nit_ios_app;
+                       App_after_window_pop(app_nit_ios_app);
+               }
+       }
+@end
 `}
 
 redef class App
+
        redef fun did_finish_launching_with_options
        do
                app_delegate.window = new UIWindow
@@ -107,11 +123,37 @@ redef class App
                native.edgesForExtendedLayout = UIRectEdgeNone;
        `}
 
-       redef fun window=(window)
+       redef fun push_window(window)
        do
                set_view_controller(app_delegate.window, window.native)
                super
        end
+
+       # Use iOS ` popViewControllerAnimated`
+       redef fun pop_window
+       do
+               manual_pop = true
+               pop_view_controller app_delegate.window
+               super
+       end
+
+       private fun pop_view_controller(window: UIWindow) in "ObjC" `{
+               UINavigationController *navController = (UINavigationController*)window.rootViewController;
+               [navController popViewControllerAnimated: YES];
+       `}
+
+       # Is the next `after_window_pop`  triggered by a call to `pop_window`?
+       #
+       # Otherwise, it's by the user via the navigation bar "Back" button.
+       private var manual_pop = false
+
+       # Callback when `window` is displayed again
+       private fun after_window_pop
+       do
+               if not manual_pop then window_stack.pop
+               manual_pop = false
+               window.on_resume
+       end
 end
 
 redef class AppDelegate
@@ -136,6 +178,8 @@ redef class View
        redef type NATIVE: UIView
 
        redef var enabled = null is lazy
+
+       private fun on_ios_event do end
 end
 
 redef class CompositeControl
@@ -150,10 +194,19 @@ redef class CompositeControl
        end
 end
 
+# View controller associated to an app.nit `Window`
+extern class NitViewController
+       super UIViewController
+
+       new import App.after_window_pop in "ObjC" `{
+               return [[NitViewController alloc] init];
+       `}
+end
+
 redef class Window
 
-       redef type NATIVE: UIViewController
-       redef var native = new UIViewController
+       redef type NATIVE: NitViewController
+       redef var native = new NitViewController
 
        # Title of this window
        fun title: String do return native.title.to_s
@@ -168,7 +221,9 @@ redef class Window
                var native_view = view.native
                assert native_view isa UIView
 
-               native.view = native_view
+               if view isa ListLayout then
+                       native.view.add_subview native_view
+               else native.view = native_view
        end
 end
 
@@ -180,10 +235,6 @@ redef class Layout
        init
        do
                native.alignment = new UIStackViewAlignment.fill
-               native.distribution = new UIStackViewDistribution.fill_equally
-
-               # TODO make customizable
-               native.spacing = 4.0
        end
 
        redef fun add(view)
@@ -197,11 +248,28 @@ redef class Layout
 end
 
 redef class HorizontalLayout
-       redef init do native.axis = new UILayoutConstraintAxis.horizontal
+       redef init
+       do
+               native.axis = new UILayoutConstraintAxis.horizontal
+               native.distribution = new UIStackViewDistribution.fill_equally
+       end
 end
 
 redef class VerticalLayout
-       redef init do native.axis = new UILayoutConstraintAxis.vertical
+       redef init
+       do
+               native.axis = new UILayoutConstraintAxis.vertical
+               native.distribution = new UIStackViewDistribution.equal_spacing
+       end
+end
+
+redef class TextView
+       # Convert `size` from app.nit relative size to iOS font points
+       private fun ios_points(size: nullable Float): Float
+       do
+               size = size or else 1.0
+               return 8.0 + size * 5.0
+       end
 end
 
 redef class Label
@@ -211,6 +279,28 @@ redef class Label
 
        redef fun text=(text) do native.text = (text or else "").to_nsstring
        redef fun text do return native.text.to_s
+
+       redef fun size=(size) do native.size = ios_points(size)
+
+       redef fun align=(align) do native.align = align or else 0.0
+end
+
+redef class UILabel
+
+       private fun size=(points: Float)
+       in "ObjC" `{
+               self.font = [UIFont systemFontOfSize: points];
+       `}
+
+       private fun align=(align: Float)
+       in "ObjC" `{
+               if (align == 0.5)
+                       self.textAlignment = NSTextAlignmentCenter;
+               else if (align < 0.5)
+                       self.textAlignment = NSTextAlignmentLeft;
+               else//if (align > 0.5)
+                       self.textAlignment = NSTextAlignmentRight;
+       `}
 end
 
 # On iOS, check boxes are a layout composed of a label and an `UISwitch`
@@ -228,15 +318,20 @@ redef class CheckBox
        # `UISwitch` acting as the real check box
        var ui_switch: UISwitch is noautoinit
 
-       init do
+       redef fun on_ios_event do notify_observers new ToggleEvent(self)
+
+       init
+       do
                # Tweak the layout so it is centered
-               layout.native.distribution = new UIStackViewDistribution.fill_proportionally
-               layout.native.alignment = new UIStackViewAlignment.center
+               layout.native.distribution = new UIStackViewDistribution.equal_spacing
+               layout.native.alignment = new UIStackViewAlignment.fill
                layout.native.layout_margins_relative_arrangement = true
 
                var s = new UISwitch
                native.add_arranged_subview s
                ui_switch = s
+
+               ui_switch.set_callback self
        end
 
        redef fun text=(text) do lbl.text = text
@@ -246,6 +341,23 @@ redef class CheckBox
        redef fun is_checked=(value) do ui_switch.set_on_animated(value, true)
 end
 
+redef class UISwitch
+       # Register callbacks on this switch to be relayed to `sender`
+       private fun set_callback(sender: View)
+       import View.on_ios_event in "ObjC" `{
+
+               NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
+               ncr.nit_view = sender;
+
+               // Pin the objects in both Objective-C and Nit GC
+               View_incr_ref(sender);
+               ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
+
+               [self addTarget:ncr action:@selector(nitOnEvent:)
+                       forControlEvents:UIControlEventValueChanged];
+       `}
+end
+
 redef class TextInput
 
        redef type NATIVE: UITextField
@@ -259,6 +371,31 @@ redef class TextInput
                native.secure_text_entry = value or else false
                super
        end
+
+       redef fun size=(size) do native.size = ios_points(size)
+
+       redef fun align=(align) do native.align = align or else 0.0
+
+       # Set the placeholder text, shown in light gray when the field is empty
+       fun placeholder=(text: Text) do native.placeholder = text.to_nsstring
+end
+
+redef class UITextField
+
+       private fun size=(points: Float)
+       in "ObjC" `{
+               self.font = [UIFont systemFontOfSize: points];
+       `}
+
+       private fun align=(align: Float)
+       in "ObjC" `{
+               if (align == 0.5)
+                       self.textAlignment = NSTextAlignmentCenter;
+               else if (align < 0.5)
+                       self.textAlignment = NSTextAlignmentLeft;
+               else//if (align > 0.5)
+                       self.textAlignment = NSTextAlignmentRight;
+       `}
 end
 
 redef class Button
@@ -268,25 +405,29 @@ redef class Button
 
        init do native.set_callback self
 
+       redef fun on_ios_event do notify_observers new ButtonPressEvent(self)
+
        redef fun text=(text) do if text != null then native.title = text.to_nsstring
        redef fun text do return native.current_title.to_s
 
-       private fun on_click do notify_observers new ButtonPressEvent(self)
-
        redef fun enabled=(enabled) do native.enabled = enabled or else true
        redef fun enabled do return native.enabled
+
+       redef fun size=(size) do native.title_label.size = ios_points(size)
+
+       redef fun align=(align) do native.title_label.align = align or else 0.0
 end
 
 redef class UIButton
        # Register callbacks on this button to be relayed to `sender`
-       private fun set_callback(sender: Button)
-       import Button.on_click in "ObjC" `{
+       private fun set_callback(sender: View)
+       import View.on_ios_event in "ObjC" `{
 
                NitCallbackReference *ncr = [[NitCallbackReference alloc] init];
-               ncr.nit_button = sender;
+               ncr.nit_view = sender;
 
                // Pin the objects in both Objective-C and Nit GC
-               Button_incr_ref(sender);
+               View_incr_ref(sender);
                ncr = (__bridge NitCallbackReference*)CFBridgingRetain(ncr);
 
                [self addTarget:ncr action:@selector(nitOnEvent:)
@@ -303,25 +444,45 @@ redef class ListLayout
        # Real container of the subviews, contained within `native`
        var native_stack_view = new UIStackView
 
-       init
+       redef fun parent=(parent)
        do
+               super
+
+               var root_view
+               if parent isa Window then
+                       root_view = parent.native.view
+               else if parent isa View then
+                       root_view = parent.native
+               else return
+
+               # Setup scroll view
+               var native_scroll_view = native
+               native_scroll_view.translates_autoresizing_mask_into_constraits = false
+               native_add_constraints(root_view, native_scroll_view)
+
+               # Setup stack_view
                native_stack_view.translates_autoresizing_mask_into_constraits = false
                native_stack_view.axis = new UILayoutConstraintAxis.vertical
-               native_stack_view.alignment = new UIStackViewAlignment.fill
-               native_stack_view.distribution = new UIStackViewDistribution.fill_equally
-               native_stack_view.spacing = 4.0
-
-               native.add_subview native_stack_view
-               native_add_constraints(native, native_stack_view)
+               native_scroll_view.add_subview native_stack_view
+               native_add_constraints(native_scroll_view, native_stack_view)
+               native_lock_vertical_scroll(native_scroll_view, native_stack_view)
        end
 
-       private fun native_add_constraints(scroll_view: UIScrollView, stack_view: UIStackView) in "ObjC" `{
-               [scroll_view addConstraints:[NSLayoutConstraint
-                       constraintsWithVisualFormat: @"V:|-8-[view]-8-|"
-                       options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"view": stack_view}]];
-               [scroll_view addConstraints:[NSLayoutConstraint
-                       constraintsWithVisualFormat: @"H:|-8-[view]"
-                       options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"view": stack_view}]];
+       # Add constraints to lock the vertical and horizontal dimensions
+       private fun native_add_constraints(root_view: UIView, nested_view: UIView)
+       in "ObjC" `{
+               [root_view addConstraints:[NSLayoutConstraint
+                       constraintsWithVisualFormat: @"V:|-0-[nested_view]-0-|"
+                       options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"nested_view": nested_view}]];
+               [root_view addConstraints:[NSLayoutConstraint
+                       constraintsWithVisualFormat: @"H:|-0-[nested_view]-0-|"
+                       options: NSLayoutFormatAlignAllCenterX metrics: nil views: @{@"nested_view": nested_view}]];
+       `}
+
+       # Add a constraint to lock to the scroll vertically
+       private fun native_lock_vertical_scroll(scroll_view: UIScrollView, stack_view: UIStackView)
+       in "ObjC" `{
+               [scroll_view addConstraint: [scroll_view.widthAnchor constraintEqualToAnchor:stack_view.widthAnchor]];
        `}
 
        redef fun add(view)
@@ -454,3 +615,15 @@ redef class UITableView
                self.dataSource = objc_delegate;
        `}
 end
+
+redef class Text
+       redef fun open_in_browser do to_nsstring.native_open_in_browser
+end
+
+redef class NSString
+       private fun native_open_in_browser
+       in "ObjC" `{
+               NSURL *nsurl = [NSURL URLWithString: self];
+               [[UIApplication sharedApplication] openURL: nsurl];
+       `}
+end