summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Tizen/Renderers
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen/Renderers')
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs57
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs61
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs95
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs259
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs65
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs77
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs88
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs127
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs32
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs124
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs39
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs105
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs103
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs57
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs437
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs96
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs390
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs119
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs60
-rwxr-xr-xXamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs130
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs167
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs63
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs86
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs50
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs190
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs79
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs101
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs46
-rw-r--r--Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs946
29 files changed, 4249 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs
new file mode 100644
index 00000000..04a69c6a
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs
@@ -0,0 +1,57 @@
+using EProgressBar = ElmSharp.ProgressBar;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class ActivityIndicatorRenderer : ViewRenderer<ActivityIndicator, EProgressBar>
+ {
+ static readonly EColor s_defaultColor = EColor.Black;
+
+ public ActivityIndicatorRenderer()
+ {
+ RegisterPropertyHandler(ActivityIndicator.ColorProperty, UpdateColor);
+ RegisterPropertyHandler(ActivityIndicator.IsRunningProperty, UpdateIsRunning);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> e)
+ {
+ if (Control == null)
+ {
+ var ac = new EProgressBar(Forms.Context.MainWindow)
+ {
+ Style = "process_medium",
+ IsPulseMode = true,
+ };
+ SetNativeControl(ac);
+ }
+
+ if (e.OldElement != null)
+ {
+ }
+
+ if (e.NewElement != null)
+ {
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void UpdateColor()
+ {
+ Control.Color = (Element.Color == Color.Default) ? s_defaultColor : Element.Color.ToNative();
+ }
+
+ void UpdateIsRunning()
+ {
+ if (Element.IsRunning)
+ {
+ Control.PlayPulse();
+ }
+ else
+ {
+ Control.StopPulse();
+ }
+ }
+
+ };
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs
new file mode 100644
index 00000000..7a9f756d
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs
@@ -0,0 +1,61 @@
+using System.ComponentModel;
+using EColor = ElmSharp.Color;
+using ERectangle = ElmSharp.Rectangle;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class BoxViewRenderer :
+ VisualElementRenderer<BoxView>
+ {
+ static readonly EColor s_defaultColor = EColor.Transparent;
+
+ ERectangle _control;
+
+ public BoxViewRenderer()
+ {
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
+ {
+ if (_control == null)
+ {
+ _control = new ERectangle(Forms.Context.MainWindow);
+ SetNativeControl(_control);
+ }
+
+ if (e.OldElement != null)
+ {
+ }
+
+ if (e.NewElement != null)
+ {
+ UpdateColor();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void UpdateColor()
+ {
+ Color colorToSet = Element.Color;
+
+ if (colorToSet == Color.Default)
+ {
+ colorToSet = Element.BackgroundColor;
+ }
+
+ _control.Color = (colorToSet == Color.Default) ? s_defaultColor : colorToSet.ToNative();
+ }
+
+ protected override void OnElementPropertyChanged(object sender,
+ PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == BoxView.ColorProperty.PropertyName ||
+ e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ {
+ UpdateColor();
+ }
+ base.OnElementPropertyChanged(sender, e);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs
new file mode 100644
index 00000000..a34df549
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs
@@ -0,0 +1,95 @@
+using System;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class ButtonRenderer : ViewRenderer<Button, Native.Button>
+ {
+ static readonly EColor s_defaultTextColor = EColor.White;
+
+ public ButtonRenderer()
+ {
+ RegisterPropertyHandler(Button.TextProperty, UpdateText);
+ RegisterPropertyHandler(Button.FontFamilyProperty, UpdateText);
+ RegisterPropertyHandler(Button.FontSizeProperty, UpdateText);
+ RegisterPropertyHandler(Button.FontAttributesProperty, UpdateText);
+ RegisterPropertyHandler(Button.TextColorProperty, UpdateTextColor);
+ RegisterPropertyHandler(Button.ImageProperty, UpdateBitmap);
+ RegisterPropertyHandler(Button.BorderColorProperty, UpdateBorder);
+ RegisterPropertyHandler(Button.BorderRadiusProperty, UpdateBorder);
+ RegisterPropertyHandler(Button.BorderWidthProperty, UpdateBorder);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
+ {
+ if (Control == null)
+ {
+ var button = new Native.Button(Forms.Context.MainWindow)
+ {
+ PropagateEvents = false,
+ };
+ SetNativeControl(button);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.Clicked -= ButtonClickedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.Clicked += ButtonClickedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override Size MinimumSize()
+ {
+ return new Size(Control.MinimumWidth, Control.MinimumHeight);
+ }
+
+ void ButtonClickedHandler(object sender, EventArgs e)
+ {
+ IButtonController btn = Element as IButtonController;
+ if (btn != null)
+ {
+ btn.SendClicked();
+ }
+ }
+
+ void UpdateText()
+ {
+ Control.Text = Element.Text ?? "";
+ Control.FontSize = Element.FontSize;
+ Control.FontAttributes = Element.FontAttributes;
+ Control.FontFamily = Element.FontFamily;
+ }
+
+ void UpdateTextColor()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ void UpdateBitmap()
+ {
+ if (!string.IsNullOrEmpty(Element.Image))
+ {
+ Control.Image = new Native.Image(Control);
+ Control.Image.LoadFromImageSourceAsync(Element.Image);
+ }
+ else
+ {
+ Control.Image = null;
+ }
+ }
+
+ void UpdateBorder()
+ {
+ /* The simpler way is to create some specialized theme for button in
+ * tizen-theme
+ */
+ // TODO: implement border handling
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs
new file mode 100644
index 00000000..812b8abb
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs
@@ -0,0 +1,259 @@
+using System;
+using ElmSharp;
+using EColor = ElmSharp.Color;
+using ERectangle = ElmSharp.Rectangle;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Renderer of a CarouselPage widget.
+ /// </summary>
+ public class CarouselPageRenderer : VisualElementRenderer<CarouselPage>, IVisualElementRenderer
+ {
+ /// <summary>
+ /// The minimum length of a swipe to be recognized as a page switching command, in screen pixels unit.
+ /// </summary>
+ public static readonly double s_minimumSwipeLengthX = 200.0;
+
+ // Different levels of "difficulty" in making a valid swipe gesture, determined by a maximum absolute value
+ // of an angle between a line formed by the swipe gesture and the horizontal axis, in arc degrees:
+ public static readonly double s_challengeEasyArcDegrees = 25.0;
+ public static readonly double s_challengeComfortableArcDegrees = 20.0;
+ public static readonly double s_challengeStandardArcDegrees = 15.0;
+ public static readonly double s_challengeHardArcDegrees = 10.0;
+
+ /// <summary>
+ /// The maximum allowed angle between a line formed by the swipe gesture and the horizontal axis, in arc degrees.
+ /// The gesture will be recognized as a page switching command if its angle does not exceed this value.
+ /// </summary>
+ public static readonly double s_thresholdSwipeArcDegrees = s_challengeComfortableArcDegrees;
+
+ /// <summary>
+ /// The tangent of a maximum allowed angle between the swipe line and the horizontal axis.
+ /// </summary>
+ public static readonly double s_thresholdSwipeTangent = Math.Tan(s_thresholdSwipeArcDegrees * (Math.PI / 180.0));
+
+ // A master container for the entire widget:
+ protected Box _box;
+
+ // Used for grabbing gestures over the entire screen, even if Page is smaller than it:
+ protected ERectangle _filler;
+
+ protected GestureLayer _gestureLayer;
+ protected EvasObject _page;
+
+ /// <summary>
+ /// The default constructor.
+ /// </summary>
+ public CarouselPageRenderer()
+ {
+ }
+
+ /// <summary>
+ /// Invoked whenever the CarouselPage element has been changed in Xamarin.
+ /// </summary>
+ /// <param name="e">Event parameters.</param>
+ protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e)
+ {
+ if (NativeView == null)
+ {
+ // Creates an overlaying box which serves as a container
+ // for both page and a gesture handling layer:
+ _box = new Box(Forms.Context.MainWindow)
+ {
+ IsHorizontal = false,
+ };
+ _box.SetAlignment(-1, -1);
+ _box.SetWeight(1, 1);
+ _box.Show();
+
+ // Disallows the Box to lay out its contents. They will be laid out manually,
+ // because the page has to overlay the conformant rectangle. By default
+ // Box will lay its contents in a stack. Applying an empty method disables it:
+ _box.SetLayoutCallback(() => {
+ ResizeContentsToFullScreen();
+ });
+
+ // Creates a Rectangle used for ensuring that the gestures will get recognized:
+ _filler = new ERectangle(Forms.Context.MainWindow)
+ {
+ Color = EColor.Transparent,
+ };
+ _filler.SetAlignment(-1, -1);
+ _filler.SetWeight(1, 1);
+ _filler.Show();
+ _box.PackEnd(_filler);
+
+ // Creates a GestureLayer used for swipe gestures recognition and attaches it to the Box:
+ _gestureLayer = new GestureLayer(_box);
+ _gestureLayer.Attach(_box);
+ AddLineGestureHandler();
+
+ SetNativeControl(_box);
+ }
+
+ if (e.OldElement != null)
+ {
+ Element.CurrentPageChanged -= OnCurrentPageChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ Element.CurrentPageChanged += OnCurrentPageChanged;
+ }
+
+ // If pages have been added to the Xamarin widget and the user has not explicitly
+ // marked one of them to be displayed, displays the first one:
+ if (_page == null && Element.Children.Count > 0)
+ {
+ DisplayPage(Element.Children[0]);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ /// <summary>
+ /// Called just before the associated element is deleted.
+ /// </summary>
+ /// <param name="disposing">True if the memory release was requested on demand.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (_box != null)
+ {
+ Element.CurrentPageChanged -= OnCurrentPageChanged;
+
+ // Unpacks the page from the box to prevent it from being disposed of prematurely:
+ _box.UnPack(_page);
+
+ _box.Unrealize();
+ _box = null;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ /// <summary>
+ /// Handles the process of switching between the displayed pages.
+ /// </summary>
+ /// <param name="sender">An object originating the request</param>
+ /// <param name="ea">Additional arguments to the event handler</param>
+ void OnCurrentPageChanged(object sender, EventArgs ea)
+ {
+ if (_page != null)
+ {
+ _page.Hide();
+ _box.UnPack(_page);
+ }
+
+ DisplayPage(Element.CurrentPage);
+ ResizeContentsToFullScreen();
+ }
+
+ /// <summary>
+ /// Gets the index of the currently displayed page in Element.Children collection.
+ /// </summary>
+ /// <returns>An int value representing the index of the page currently displayed,
+ /// or -1 if no page is being displayed currently.</returns>
+ int GetCurrentPageIndex()
+ {
+ int index = -1;
+ for (int k = 0; k < Element.Children.Count; ++k)
+ {
+ if (Element.Children[k] == Element.CurrentPage)
+ {
+ index = k;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ /// <summary>
+ /// Resizes the widget's contents to utilize all the available screen space.
+ /// </summary>
+ void ResizeContentsToFullScreen()
+ {
+ // Box's geometry should match Forms.Context.MainWindow's geometry
+ // minus the space occupied by the top toolbar.
+ // Applies Box's geometry to both displayed page and conformant rectangle:
+ _filler.Geometry = _page.Geometry = _box.Geometry;
+ }
+
+ /// <summary>
+ /// Adds the feature of recognizing swipes to the GestureLayer.
+ /// </summary>
+ void AddLineGestureHandler()
+ {
+ _gestureLayer.SetLineCallback(GestureLayer.GestureState.End, (line) => {
+ double horizontalDistance = line.X2 - line.X1;
+ double verticalDistance = line.Y2 - line.Y1;
+
+ // Determines whether the movement is long enough to be considered a swipe:
+ bool isLongEnough = (Math.Abs(horizontalDistance) >= s_minimumSwipeLengthX);
+
+ // Determines whether the movement is horizontal enough to be considered as a swipe:
+ // The swipe arc's tangent value (v/h) needs to be lesser than or equal to the threshold value.
+ // This approach allows for getting rid of computationally heavier atan2() function.
+ double angleTangent = Math.Abs(verticalDistance) / horizontalDistance;
+ bool isDirectionForward = (angleTangent < 0);
+
+ // Determines whether the movement has been recognized as a valid swipe:
+ bool isSwipeMatching = (isLongEnough && (Math.Abs(angleTangent) <= s_thresholdSwipeTangent));
+
+ if (isSwipeMatching)
+ {
+ // TODO: Unsure whether changes made via ItemsSource/ItemTemplate properties will be handled correctly this way.
+ // If not, it should be implemented in another method.
+ if (isDirectionForward)
+ {
+ // Tries to switch the page to the next one:
+ int currentPageIndex = GetCurrentPageIndex();
+ if (currentPageIndex < Element.Children.Count - 1)
+ {
+ // Sets the current page to the next one:
+ Element.CurrentPage = Element.Children[currentPageIndex + 1];
+ }
+ else
+ {
+ // Reacts to the case of forward-swiping when the last page is already being displayed:
+ Log.Debug("CarouselPage: Displaying the last page already - can not revolve further.");
+
+ // Note (TODO): Once we have a more sophisticated renderer able to e.g. display the animation
+ // of revolving Pages or at least indicate current overall position, some visual feedback
+ // should be provided here for the user who has haplessly tried to access a nonexistent page.
+ }
+ }
+ else
+ {
+ // Tries to switch the page to the previous one:
+ int currentPageIndex = GetCurrentPageIndex();
+ if (currentPageIndex > 0)
+ {
+ // Sets the current page to the previous one:
+ Element.CurrentPage = Element.Children[currentPageIndex - 1];
+ }
+ else
+ {
+ // Reacts to the case of backward-swiping when the first page is already being displayed:
+ Log.Debug("CarouselPage: The first page is already being displayed - can not revolve further.");
+
+ // Note (TODO): (The same as in case of scrolling forwards)
+ }
+ }
+ }
+ });
+ }
+
+ void DisplayPage(ContentPage p)
+ {
+ _page = Platform.GetOrCreateRenderer(p).NativeView;
+ _page.SetAlignment(-1, -1);
+ _page.SetWeight(1, 1);
+ _page.Show();
+ _box.PackEnd(_page);
+ }
+
+ }
+}
+
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs
new file mode 100644
index 00000000..c4f79653
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs
@@ -0,0 +1,65 @@
+using System;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Renderer of ContentPage.
+ /// </summary>
+ public class ContentPageRenderer : VisualElementRenderer<ContentPage>
+ {
+ /// <summary>
+ /// Native control which holds the contents.
+ /// </summary>
+ Native.ContentPage _page;
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ public ContentPageRenderer()
+ {
+ RegisterPropertyHandler(Page.BackgroundImageProperty, UpdateBackgroundImage);
+ RegisterPropertyHandler(Page.TitleProperty, UpdateTitle);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ContentPage> e)
+ {
+ if (null == _page)
+ {
+ _page = new Native.ContentPage(Forms.Context.MainWindow);
+ _page.LayoutUpdated += new EventHandler<Native.LayoutEventArgs>(OnLayoutUpdated);
+ SetNativeControl(_page);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void UpdateBackgroundColor()
+ {
+ // base.UpdateBackgroundColor() is not called on purpose, we don't want the regular background setting
+ if (Element.BackgroundColor.IsDefault || Element.BackgroundColor.A == 0)
+ _page.Color = EColor.Transparent;
+ else
+ _page.Color = Element.BackgroundColor.ToNative();
+ }
+
+ void UpdateBackgroundImage()
+ {
+ if (string.IsNullOrWhiteSpace(Element.BackgroundImage))
+ _page.File = null;
+ else
+ _page.File = ResourcePath.GetPath(Element.BackgroundImage);
+ }
+
+ void UpdateTitle()
+ {
+ _page.Title = Element.Title;
+ }
+
+ void OnLayoutUpdated(object sender, Native.LayoutEventArgs e)
+ {
+ DoLayout(e);
+ }
+ }
+}
+
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs
new file mode 100644
index 00000000..aed7b9d6
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs
@@ -0,0 +1,77 @@
+using System;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class DatePickerRenderer : ViewRenderer<DatePicker, Native.Button>
+ {
+ //TODO need to add internationalization support
+ const string DialogTitle = "Choose Date";
+ static readonly EColor s_defaultTextColor = EColor.White;
+
+ public DatePickerRenderer()
+ {
+ RegisterPropertyHandler(DatePicker.DateProperty, UpdateDate);
+ RegisterPropertyHandler(DatePicker.FormatProperty, UpdateDate);
+ RegisterPropertyHandler(DatePicker.TextColorProperty, UpdateTextColor);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
+ {
+ if (Control == null)
+ {
+ var button = new Native.Button(Forms.Context.MainWindow);
+ SetNativeControl(button);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.Clicked -= ButtonClickedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.Clicked += ButtonClickedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void ButtonClickedHandler(object sender, EventArgs e)
+ {
+ Native.DateTimePickerDialog dialog = new Native.DateTimePickerDialog(Forms.Context.MainWindow)
+ {
+ Title = DialogTitle
+ };
+
+ dialog.InitializeDatePicker(Element.Date, Element.MinimumDate, Element.MaximumDate);
+ dialog.DateTimeChanged += DialogDateTimeChangedHandler;
+ dialog.Dismissed += DialogDismissedHandler;
+ dialog.Show();
+ }
+
+ void DialogDateTimeChangedHandler(object sender, Native.DateChangedEventArgs dcea)
+ {
+ Element.Date = dcea.NewDate;
+ Control.Text = dcea.NewDate.ToString(Element.Format);
+ }
+
+ void DialogDismissedHandler(object sender, EventArgs e)
+ {
+ var dialog = sender as Native.DateTimePickerDialog;
+ dialog.DateTimeChanged -= DialogDateTimeChangedHandler;
+ dialog.Dismissed -= DialogDismissedHandler;
+ }
+
+ void UpdateDate()
+ {
+ Control.Text = Element.Date.ToString(Element.Format);
+ }
+
+ void UpdateTextColor()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs
new file mode 100644
index 00000000..0f214636
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs
@@ -0,0 +1,88 @@
+using System;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class EditorRenderer : ViewRenderer<Editor, Native.Entry>
+ {
+ static readonly EColor s_defaultTextColor = EColor.Black;
+
+ public EditorRenderer()
+ {
+ RegisterPropertyHandler(Editor.TextProperty, UpdateText);
+ RegisterPropertyHandler(Editor.TextColorProperty, UpdateTextColor);
+ RegisterPropertyHandler(Editor.FontSizeProperty, UpdateFontSize);
+ RegisterPropertyHandler(Editor.FontFamilyProperty, UpdateFontFamily);
+ RegisterPropertyHandler(Editor.FontAttributesProperty, UpdateFontAttributes);
+ RegisterPropertyHandler(Editor.KeyboardProperty, UpdateKeyboard);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
+ {
+ if (Control == null)
+ {
+ var entry = new Native.Entry(Forms.Context.MainWindow)
+ {
+ IsSingleLine = false,
+ PropagateEvents = false,
+ };
+ SetNativeControl(entry);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.TextChanged -= TextChanged;
+ Control.Unfocused -= Completed;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.TextChanged += TextChanged;
+ Control.Unfocused += Completed;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void TextChanged(object sender, EventArgs e)
+ {
+ Element.Text = ((Native.Entry)sender).Text;
+ }
+
+ void Completed(object sender, EventArgs e)
+ {
+ Element.SendCompleted();
+ }
+
+ void UpdateText()
+ {
+ Control.Text = Element.Text;
+ }
+
+ void UpdateTextColor()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ void UpdateFontSize()
+ {
+ Control.FontSize = Element.FontSize;
+ }
+
+ void UpdateFontFamily()
+ {
+ Control.FontFamily = Element.FontFamily;
+ }
+
+ void UpdateFontAttributes()
+ {
+ Control.FontAttributes = Element.FontAttributes;
+ }
+
+ void UpdateKeyboard()
+ {
+ Control.Keyboard = Element.Keyboard.ToNative();
+ }
+ }
+}
+
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs
new file mode 100644
index 00000000..95828c04
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs
@@ -0,0 +1,127 @@
+using System;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class EntryRenderer : ViewRenderer<Entry, Native.Entry>, IDisposable
+ {
+ static readonly EColor s_defaultTextColor = EColor.Black;
+
+ static readonly EColor s_defaultPlaceholderColor = EColor.Gray;
+
+ public EntryRenderer()
+ {
+ RegisterPropertyHandler(Entry.IsPasswordProperty, UpdateIsPassword);
+ RegisterPropertyHandler(Entry.TextProperty, UpdateText);
+ RegisterPropertyHandler(Entry.TextColorProperty, UpdateTextColor);
+ RegisterPropertyHandler(Entry.FontSizeProperty, UpdateFontSize);
+ RegisterPropertyHandler(Entry.FontFamilyProperty, UpdateFontFamily);
+ RegisterPropertyHandler(Entry.FontAttributesProperty, UpdateFontAttributes);
+ RegisterPropertyHandler(Entry.HorizontalTextAlignmentProperty, UpdateHorizontalTextAlignment);
+ RegisterPropertyHandler(Entry.KeyboardProperty, UpdateKeyboard);
+ RegisterPropertyHandler(Entry.PlaceholderProperty, UpdatePlaceholder);
+ RegisterPropertyHandler(Entry.PlaceholderColorProperty, UpdatePlaceholderColor);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
+ {
+ if (Control == null)
+ {
+ var entry = new Native.Entry(Forms.Context.MainWindow)
+ {
+ IsSingleLine = true,
+ PropagateEvents = false,
+ };
+ SetNativeControl(entry);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.TextChanged -= EntryChangedHandler;
+ Control.Activated -= EntryCompletedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.TextChanged += EntryChangedHandler;
+ Control.Activated += EntryCompletedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (null != Control)
+ {
+ Control.TextChanged -= EntryChangedHandler;
+ Control.Activated -= EntryCompletedHandler;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void EntryChangedHandler(object sender, EventArgs e)
+ {
+ Element.Text = Control.Text;
+ }
+
+ void EntryCompletedHandler(object sender, EventArgs e)
+ {
+ //TODO Consider if any other object should overtake focus
+ Control.SetFocus(false);
+
+ ((IEntryController)Element).SendCompleted();
+ }
+
+ void UpdateIsPassword()
+ {
+ Control.IsPassword = Element.IsPassword;
+ }
+
+ void UpdateText()
+ {
+ Control.Text = Element.Text;
+ }
+
+ void UpdateTextColor()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ void UpdateFontSize()
+ {
+ Control.FontSize = Element.FontSize;
+ }
+
+ void UpdateFontFamily()
+ {
+ Control.FontFamily = Element.FontFamily;
+ }
+
+ void UpdateFontAttributes()
+ {
+ Control.FontAttributes = Element.FontAttributes;
+ }
+
+ void UpdateHorizontalTextAlignment()
+ {
+ Control.HorizontalTextAlignment = Element.HorizontalTextAlignment.ToNative();
+ }
+
+ void UpdateKeyboard()
+ {
+ Control.Keyboard = Element.Keyboard.ToNative();
+ }
+
+ void UpdatePlaceholder()
+ {
+ Control.Placeholder = Element.Placeholder;
+ }
+
+ void UpdatePlaceholderColor()
+ {
+ Control.PlaceholderColor = Element.PlaceholderColor.IsDefault ? s_defaultPlaceholderColor : Element.PlaceholderColor.ToNative();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs
new file mode 100644
index 00000000..4e11c277
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs
@@ -0,0 +1,32 @@
+using ESize = ElmSharp.Size;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class EvasObjectWrapperRenderer : VisualElementRenderer<EvasObjectWrapper>
+ {
+ protected override void OnElementChanged(ElementChangedEventArgs<EvasObjectWrapper> e)
+ {
+ if (NativeView == null)
+ {
+ SetNativeControl(Element.EvasObject);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override ESize Measure(int availableWidth, int availableHeight)
+ {
+ if (Element?.MeasureDelegate == null)
+ {
+ return base.Measure(availableWidth, availableHeight);
+ }
+
+ // The user has specified a different implementation of MeasureDelegate
+ ESize? result = Element.MeasureDelegate(this, availableWidth, availableHeight);
+
+ // If the delegate returns a ElmSharp.Size, we use it; if it returns null,
+ // fall back to the default implementation
+ return result ?? base.Measure(availableWidth, availableHeight);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs
new file mode 100644
index 00000000..0684c4ac
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs
@@ -0,0 +1,124 @@
+using ElmSharp;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class FrameRenderer : ViewRenderer<Frame, Native.Canvas>
+ {
+ const int _thickness = 2;
+ const int _shadow_shift = 2;
+ const int _shadow_thickness = _thickness + 2;
+
+ static readonly EColor s_DefaultColor = EColor.Black;
+ static readonly EColor s_ShadowColor = EColor.FromRgba(80, 80, 80, 50);
+
+ Polygon _shadow = null;
+ Polygon _frame = null;
+
+ public FrameRenderer()
+ {
+ RegisterPropertyHandler(Frame.OutlineColorProperty, UpdateColor);
+ RegisterPropertyHandler(Frame.HasShadowProperty, UpdateShadowVisibility);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new Native.Canvas(Forms.Context.MainWindow));
+
+ _shadow = new Polygon(NativeView);
+ _shadow.Color = s_ShadowColor;
+ Control.Children.Add(_shadow);
+
+ _frame = new Polygon(NativeView);
+ _frame.Show();
+ Control.Children.Add(_frame);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.LayoutUpdated -= OnLayoutUpdated;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.LayoutUpdated += OnLayoutUpdated;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void OnLayoutUpdated(object sender, Native.LayoutEventArgs e)
+ {
+ UpdateGeometry();
+ // TODO: why is this DoLayout() required?
+ if (Element.Content != null)
+ base.DoLayout(e);
+ }
+
+ void UpdateGeometry()
+ {
+ var geometry = NativeView.Geometry;
+ DrawFrame(_frame,
+ geometry.X,
+ geometry.Y,
+ geometry.Right,
+ geometry.Bottom,
+ _thickness
+ );
+ DrawFrame(_shadow,
+ geometry.X + _shadow_shift,
+ geometry.Y + _shadow_shift,
+ geometry.Right - _thickness + _shadow_shift + _shadow_thickness,
+ geometry.Bottom - _thickness + _shadow_shift + _shadow_thickness,
+ _shadow_thickness
+ );
+ }
+
+ void DrawFrame(Polygon frame, int left, int top, int right, int bottom, int thickness)
+ {
+ frame.ClearPoints();
+ if (left + thickness >= right || top + thickness >= bottom)
+ {
+ if (left >= right || top >= bottom)
+ return;
+ // shape reduces to a rectangle
+ frame.AddPoint(left, top);
+ frame.AddPoint(right, top);
+ frame.AddPoint(right, bottom);
+ frame.AddPoint(left, bottom);
+ return;
+ }
+ // outside edge
+ frame.AddPoint(left, top);
+ frame.AddPoint(right, top);
+ frame.AddPoint(right, bottom);
+ frame.AddPoint(left, bottom);
+ frame.AddPoint(left, top + thickness);
+ // and inside edge
+ frame.AddPoint(left + thickness, top + thickness);
+ frame.AddPoint(left + thickness, bottom - thickness);
+ frame.AddPoint(right - thickness, bottom - thickness);
+ frame.AddPoint(right - thickness, top + thickness);
+ frame.AddPoint(left, top + thickness);
+ }
+
+ void UpdateColor()
+ {
+ if (Element.OutlineColor.IsDefault)
+ _frame.Color = s_DefaultColor;
+ else
+ _frame.Color = Element.OutlineColor.ToNative();
+ }
+
+ void UpdateShadowVisibility()
+ {
+ if (Element.HasShadow)
+ _shadow.Show();
+ else
+ _shadow.Hide();
+ }
+
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs
new file mode 100644
index 00000000..4702fd37
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs
@@ -0,0 +1,39 @@
+using System;
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Base interface for VisualElement renderer.
+ /// </summary>
+ public interface IVisualElementRenderer : IRegisterable, IDisposable
+ {
+ /// <summary>
+ /// Gets the VisualElement associated with this renderer.
+ /// </summary>
+ /// <value>The VisualElement.</value>
+ VisualElement Element
+ {
+ get;
+ }
+
+ /// <summary>
+ /// Gets the native view associated with this renderer.
+ /// </summary>
+ /// <value>The native view.</value>
+ EvasObject NativeView
+ {
+ get;
+ }
+
+ /// <summary>
+ /// Sets the VisualElement associated with this renderer.
+ /// </summary>
+ /// <param name="element">New element.</param>
+ void SetElement(VisualElement element);
+
+ SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint);
+
+ void UpdateNativeGeometry();
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs
new file mode 100644
index 00000000..ca06a669
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs
@@ -0,0 +1,105 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class ImageRenderer : ViewRenderer<Image, Native.Image>
+ {
+ public ImageRenderer()
+ {
+ RegisterPropertyHandler(Image.SourceProperty, UpdateSource);
+ RegisterPropertyHandler(Image.AspectProperty, UpdateAspect);
+ RegisterPropertyHandler(Image.IsOpaqueProperty, UpdateIsOpaque);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
+ {
+ if (Control == null)
+ {
+ var image = new Native.Image(Forms.Context.MainWindow);
+ SetNativeControl(image);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ async void UpdateSource()
+ {
+ ImageSource source = Element.Source;
+
+ ((IImageController)Element).SetIsLoading(true);
+
+ if (Control != null)
+ {
+ bool success = await Control.LoadFromImageSourceAsync(source);
+ if (!IsDisposed && success)
+ ((IVisualElementController)Element).NativeSizeChanged();
+ }
+
+ if (!IsDisposed)
+ ((IImageController)Element).SetIsLoading(false);
+ }
+
+ void UpdateAspect()
+ {
+ Control.Aspect = Element.Aspect;
+ }
+
+ void UpdateIsOpaque()
+ {
+ Control.IsOpaque = Element.IsOpaque;
+ }
+ }
+
+ public interface IImageSourceHandler : IRegisterable
+ {
+ Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken));
+ }
+
+ public sealed class FileImageSourceHandler : IImageSourceHandler
+ {
+ public Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken))
+ {
+ var filesource = imageSource as FileImageSource;
+ if (filesource != null)
+ {
+ string file = filesource.File;
+ if (!string.IsNullOrEmpty(file))
+ return image.LoadAsync(ResourcePath.GetPath(file), cancelationToken);
+ }
+ return Task.FromResult<bool>(false);
+ }
+ }
+
+ public sealed class StreamImageSourceHandler : IImageSourceHandler
+ {
+ public async Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken))
+ {
+ var streamsource = imageSource as StreamImageSource;
+ if (streamsource != null && streamsource.Stream != null)
+ {
+ using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken))
+ {
+ if (streamImage != null)
+ return await image.LoadAsync(streamImage, cancelationToken);
+ }
+ }
+ return false;
+ }
+ }
+
+ public sealed class UriImageSourceHandler : IImageSourceHandler
+ {
+ public Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken))
+ {
+ var urisource = imageSource as UriImageSource;
+ if (urisource != null && urisource.Uri != null)
+ {
+ return image.LoadAsync(urisource.Uri, cancelationToken);
+ }
+
+ return Task.FromResult<bool>(false);
+ }
+ }
+}
+
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs
new file mode 100644
index 00000000..5a4744fc
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs
@@ -0,0 +1,103 @@
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+
+ public class LabelRenderer : ViewRenderer<Label, Native.Label>
+ {
+ static readonly EColor s_defaultBackgroundColor = EColor.Transparent;
+ static readonly EColor s_defaultForegroundColor = EColor.Black;
+ static readonly EColor s_defaultTextColor = s_defaultForegroundColor;
+
+ public LabelRenderer()
+ {
+ RegisterPropertyHandler(Label.TextProperty, () => Control.Text = Element.Text);
+ RegisterPropertyHandler(Label.TextColorProperty, UpdateTextColor);
+ // FontProperty change is called also for FontSizeProperty, FontFamilyProperty and FontAttributesProperty change
+ RegisterPropertyHandler(Label.FontProperty, UpdateFontProperties);
+ RegisterPropertyHandler(Label.LineBreakModeProperty, UpdateLineBreakMode);
+ RegisterPropertyHandler(Label.HorizontalTextAlignmentProperty, UpdateTextAlignment);
+ RegisterPropertyHandler(Label.VerticalTextAlignmentProperty, UpdateTextAlignment);
+ RegisterPropertyHandler(Label.FormattedTextProperty, () => Control.FormattedText = ConvertFormattedText(Element.FormattedText));
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
+ {
+ if (Control == null)
+ {
+ var label = new Native.Label(Forms.Context.MainWindow);
+ base.SetNativeControl(label);
+ }
+
+ if (e.OldElement != null)
+ {
+ }
+
+ if (e.NewElement != null)
+ {
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override Size MinimumSize()
+ {
+ return new Size(Control.MinimumWidth, Control.MinimumHeight);
+ }
+
+ Native.FormattedString ConvertFormattedText(FormattedString formattedString)
+ {
+ if (formattedString == null)
+ {
+ return null;
+ }
+
+ Native.FormattedString nativeString = new Native.FormattedString();
+
+ foreach (var element in formattedString.Spans)
+ {
+ Native.Span span = new Native.Span();
+ span.FormattedText = element.Text;
+ span.FontAttributes = element.FontAttributes;
+ span.FontFamily = element.FontFamily;
+ span.FontSize = element.FontSize;
+ span.ForegroundColor = element.ForegroundColor.IsDefault ? s_defaultForegroundColor : element.ForegroundColor.ToNative();
+ span.BackgroundColor = element.BackgroundColor.IsDefault ? s_defaultBackgroundColor : element.BackgroundColor.ToNative();
+
+ nativeString.Spans.Add(span);
+ }
+
+ return nativeString;
+ }
+
+ void UpdateTextColor()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ void UpdateTextAlignment()
+ {
+ Control.HorizontalTextAlignment = Element.HorizontalTextAlignment.ToNative();
+ Control.VerticalTextAlignment = Element.VerticalTextAlignment.ToNative();
+ }
+
+ void UpdateFontProperties()
+ {
+ Control.FontSize = Element.FontSize;
+ Control.FontAttributes = Element.FontAttributes;
+ Control.FontFamily = Element.FontFamily;
+ }
+
+ void UpdateLineBreakMode()
+ {
+ if (Element.LineBreakMode == LineBreakMode.CharacterWrap)
+ Control.LineBreakMode = Native.LineBreakMode.CharacterWrap;
+ else if (Element.LineBreakMode == LineBreakMode.WordWrap)
+ Control.LineBreakMode = Native.LineBreakMode.WordWrap;
+ else if (Element.LineBreakMode == LineBreakMode.NoWrap)
+ Control.LineBreakMode = Native.LineBreakMode.NoWrap;
+ else
+ Control.LineBreakMode = Native.LineBreakMode.MixedWrap;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs
new file mode 100644
index 00000000..61421c79
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Renderer of a Layout.
+ /// </summary>
+ public class LayoutRenderer : ViewRenderer<Layout, Native.Canvas>
+ {
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ public LayoutRenderer()
+ {
+ }
+
+ protected override void UpdateLayout()
+ {
+ // in case of layouts we need to make sure that the minimum size of the native control is updated
+ // this is important in case of ScrollView, when it's content is likely to be wider/higher than the window
+ // EFL does not allow control to be larger than the window if it's minimum size is smaller than window dimensions
+ ScrollView scrollView = Element.Parent as ScrollView;
+ if (scrollView != null)
+ {
+ Size size = scrollView.ContentSize;
+ Control.MinimumWidth = ToNativeDimension(Math.Max(size.Width, scrollView.Content.Width));
+ Control.MinimumHeight = ToNativeDimension(Math.Max(size.Height, scrollView.Content.Height));
+ }
+
+ base.UpdateLayout();
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Layout> e)
+ {
+ if (null == Control)
+ {
+ var canvas = new Native.Canvas(Forms.Context.MainWindow);
+ canvas.LayoutUpdated += OnLayoutUpdated;
+ SetNativeControl(canvas);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Control.LayoutUpdated -= OnLayoutUpdated;
+
+ base.Dispose(disposing);
+ }
+
+ void OnLayoutUpdated(object sender, Native.LayoutEventArgs e)
+ {
+ DoLayout(e);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs
new file mode 100644
index 00000000..5179d7e0
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs
@@ -0,0 +1,437 @@
+using System;
+using System.Collections.Specialized;
+using ElmSharp;
+using EProgressBar = ElmSharp.ProgressBar;
+using ERect = ElmSharp.Rect;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Renderer class for Xamarin ListView class. This provides necessary logic translating
+ /// Xamarin API to Tizen Native API. This is a derivate of a ViewRenderer base class.
+ /// This is a template class with two template parameters. First one is restricted to
+ /// Xamarin.Forms.View and can be accessed via property Element. This represent actual
+ /// xamarin view which represents control logic. Second one is restricted to ElmSharp.Widget
+ /// types, and can be accessed with Control property. This represents actual native control
+ /// which is used to draw control and realize xamarin forms api.
+ /// </summary>
+ public class ListViewRenderer : ViewRenderer<ListView, Native.ListView>, IDisposable
+ {
+ /// <summary>
+ /// Event handler for ScrollToRequested.
+ /// </summary>
+ readonly EventHandler<ScrollToRequestedEventArgs> _scrollToRequested;
+
+ /// <summary>
+ /// Event handler for collection changed.
+ /// </summary>
+ readonly NotifyCollectionChangedEventHandler _collectionChanged;
+
+ /// <summary>
+ /// Event handler for grouped collection changed.
+ /// </summary>
+ readonly NotifyCollectionChangedEventHandler _groupedCollectionChanged;
+
+ /// <summary>
+ /// The _lastSelectedItem and _selectedItemChanging are used for realizing ItemTapped event. Since Xamarin
+ /// needs information only when an item has been taped, native handlers need to be agreagated
+ /// and NotifyRowTapped has to be realized with this.
+ /// </summary>
+
+ GenListItem _lastSelectedItem = null;
+ int _selectedItemChanging = 0;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.ListViewRenderer"/> class.
+ /// Note that at this stage of construction renderer dose not have required native element. This should
+ /// only be used with xamarin engine.
+ /// </summary>
+ public ListViewRenderer()
+ {
+ _scrollToRequested = OnScrollToRequested;
+ _collectionChanged = OnCollectionChanged;
+ _groupedCollectionChanged = OnGroupedCollectionChanged;
+
+ RegisterPropertyHandler(ListView.IsGroupingEnabledProperty, UpdateIsGroupingEnabled);
+ RegisterPropertyHandler(ListView.HasUnevenRowsProperty, UpdateHasUnevenRows);
+ RegisterPropertyHandler(ListView.RowHeightProperty, UpdateRowHeight);
+ RegisterPropertyHandler(ListView.HeaderProperty, UpdateHeader);
+ RegisterPropertyHandler(ListView.SelectedItemProperty, UpdateSelectedItem);
+ RegisterPropertyHandler(ListView.FooterProperty, UpdateFooter);
+ RegisterPropertyHandler(ListView.ItemsSourceProperty, UpdateSource);
+ RegisterPropertyHandler(ListView.FooterTemplateProperty, UpdateFooter);
+ RegisterPropertyHandler(ListView.HeaderTemplateProperty, UpdateHeader);
+ }
+
+ /// <summary>
+ /// Invoked on creation of new ListView renderer. Handles the creation of a native
+ /// element and initialization of the renderer.
+ /// </summary>
+ /// <param name="e"><see cref="Xamarin.Forms.Platform.Tizen.ElementChangedEventArgs"/>.</param>
+ protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(new Native.ListView(Forms.Context.MainWindow));
+ }
+
+ if (e.OldElement != null)
+ {
+ e.OldElement.ScrollToRequested -= _scrollToRequested;
+ if (Element.IsGroupingEnabled)
+ {
+ e.OldElement.TemplatedItems.GroupedCollectionChanged -= _groupedCollectionChanged;
+ }
+ e.OldElement.TemplatedItems.CollectionChanged -= _collectionChanged;
+ Control.ItemSelected -= ListViewItemSelectedHandler;
+ Control.ItemUnselected -= ListViewItemUnselectedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ e.NewElement.ScrollToRequested += _scrollToRequested;
+ Element.TemplatedItems.CollectionChanged += _collectionChanged;
+ Control.ItemSelected += ListViewItemSelectedHandler;
+ Control.ItemUnselected += ListViewItemUnselectedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ /// <summary>
+ /// Handles the disposing of an existing renderer instance. Results in event handlers
+ /// being detached and a Dispose() method from base class (VisualElementRenderer) being invoked.
+ /// </summary>
+ /// <param name="disposing">A boolean flag passed to the invocation of base class' Dispose() method.
+ /// <c>True</c> if the memory release was requested on demand.</param>
+ protected override void Dispose(bool disposing)
+ {
+ Element.ScrollToRequested -= _scrollToRequested;
+ Element.TemplatedItems.CollectionChanged -= _collectionChanged;
+ Element.TemplatedItems.GroupedCollectionChanged -= _groupedCollectionChanged;
+
+ base.Dispose(disposing);
+ }
+
+ /// <summary>
+ /// Handles item selected event. Note that it has to handle selection also for grouping mode as well.
+ /// As a result of this method, ItemTapped event should be invoked in Xamarin.
+ /// </summary>
+ /// <param name="sender">A native list instance from which the event has originated.</param>
+ /// <param name="e">Argument associated with handler, it holds native item for which event was raised</param>
+ void ListViewItemSelectedHandler(object sender, GenListItemEventArgs e)
+ {
+ GenListItem item = e.Item;
+
+ _lastSelectedItem = item;
+
+ if (_selectedItemChanging == 0)
+ {
+ if (item != null)
+ {
+ int index = -1;
+ if (Element.IsGroupingEnabled)
+ {
+ Native.ListView.ItemContext itemContext = item.Data as Native.ListView.ItemContext;
+ if (itemContext.IsGroupItem)
+ {
+ return;
+ }
+ else
+ {
+ int groupIndex = (Element.TemplatedItems as System.Collections.IList).IndexOf(itemContext.ListOfSubItems);
+ int inGroupIndex = itemContext.ListOfSubItems.IndexOf(itemContext.Cell);
+
+ ++_selectedItemChanging;
+ Element.NotifyRowTapped(groupIndex, inGroupIndex);
+ --_selectedItemChanging;
+ }
+ }
+ else
+ {
+ index = Element.TemplatedItems.IndexOf((item.Data as Native.ListView.ItemContext).Cell);
+
+ ++_selectedItemChanging;
+ Element.NotifyRowTapped(index);
+ --_selectedItemChanging;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Handles item unselected event.
+ /// </summary>
+ /// <param name="sender">A native list instance from which the event has originated.</param>
+ /// <param name="e">Argument associated with handler, it holds native item for which event was raised</param>
+ void ListViewItemUnselectedHandler(object sender, GenListItemEventArgs e)
+ {
+ if (_selectedItemChanging == 0)
+ {
+ _lastSelectedItem = null;
+ }
+ }
+
+ /// <summary>
+ /// This is method handles "scroll to" requests from xamarin events.
+ /// It allows for scrolling to specified item on list view.
+ /// </summary>
+ /// <param name="sender">A native list instance from which the event has originated.</param>
+ /// <param name="e">ScrollToRequestedEventArgs.</param>
+ void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
+ {
+ Cell cell;
+ int position;
+ var scrollArgs = (ITemplatedItemsListScrollToRequestedEventArgs)e;
+
+ var templatedItems = Element.TemplatedItems;
+ if (Element.IsGroupingEnabled)
+ {
+ var results = templatedItems.GetGroupAndIndexOfItem(scrollArgs.Group, scrollArgs.Item);
+ if (results.Item1 == -1 || results.Item2 == -1)
+ return;
+
+ var group = templatedItems.GetGroup(results.Item1);
+ cell = group[results.Item2];
+ }
+ else
+ {
+ position = templatedItems.GetGlobalIndexOfItem(scrollArgs.Item);
+ cell = templatedItems[position];
+ }
+
+ Control.ApplyScrollTo(cell, e.Position, e.ShouldAnimate);
+ }
+
+ /// <summary>
+ /// Helper class for managing proper postion of Header and Footer element.
+ /// Since both elements need to be implemented with ordinary list elements,
+ /// both header and footer are removed at first, then the list is being modified
+ /// and finally header and footer are prepended and appended to the list, respectively.
+ /// </summary>
+ class HeaderAndFooterHandler : IDisposable
+ {
+ VisualElement headerElement;
+ VisualElement footerElement;
+
+ Native.ListView Control;
+
+ public HeaderAndFooterHandler(Widget control)
+ {
+ Control = control as Native.ListView;
+
+ if (Control.HasHeader())
+ {
+ headerElement = Control.GetHeader();
+ Control.RemoveHeader();
+ }
+ if (Control.HasFooter())
+ {
+ footerElement = Control.GetFooter();
+ Control.RemoveFooter();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (headerElement != null)
+ {
+ Control.SetHeader(headerElement);
+ }
+ if (footerElement != null)
+ {
+ Control.SetFooter(footerElement);
+ }
+ }
+ }
+
+ /// <summary>
+ /// This method is called whenever something changes in list view data model.
+ /// Method will not be invoked for grouping mode, but for example event with
+ /// action reset will be handled here when switching between group and no-group mode.
+ /// </summary>
+ /// <param name="sender">TemplatedItemsList<ItemsView<Cell>, Cell>.</param>
+ /// <param name="e">NotifyCollectionChangedEventArgs.</param>
+ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ using (new HeaderAndFooterHandler(Control))
+ {
+ if (e.Action == NotifyCollectionChangedAction.Add)
+ {
+ Cell before = null;
+ if(e.NewStartingIndex + e.NewItems.Count < Element.TemplatedItems.Count)
+ {
+ before = Element.TemplatedItems[e.NewStartingIndex + e.NewItems.Count];
+ }
+ Control.AddSource(e.NewItems, before);
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ Control.Remove(e.OldItems);
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ UpdateSource();
+ }
+ }
+ }
+
+ /// <summary>
+ /// This method is called whenever something changes in list view data model.
+ /// Method will be invoked for grouping mode, but some action can be also handled
+ /// by OnCollectionChanged handler.
+ /// </summary>
+ /// <param name="sender">TemplatedItemsList<ItemsView<Cell>, Cell>.</param>
+ /// <param name="e">NotifyCollectionChangedEventArgs.</param>
+ void OnGroupedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ using (new HeaderAndFooterHandler(Control))
+ {
+ if (e.Action == NotifyCollectionChangedAction.Add)
+ {
+ TemplatedItemsList<ItemsView<Cell>,Cell> itemsGroup = sender as TemplatedItemsList<ItemsView<Cell>,Cell>;
+ Cell before = null;
+ if (e.NewStartingIndex + e.NewItems.Count < itemsGroup.Count)
+ {
+ before = itemsGroup[e.NewStartingIndex + e.NewItems.Count];
+ }
+ Control.AddItemsToGroup(itemsGroup, e.NewItems, before);
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ Control.Remove(e.OldItems);
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ Control.ResetGroup(sender as TemplatedItemsList<ItemsView<Cell>, Cell>);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Updates the source.
+ /// </summary>
+ void UpdateSource()
+ {
+ Control.Clear();
+ Control.AddSource(Element.TemplatedItems);
+ }
+
+ /// <summary>
+ /// Updates the header.
+ /// </summary>
+ void UpdateHeader()
+ {
+ if (Element.Header == null)
+ {
+ Control.SetHeader(null);
+ return;
+ }
+
+ if (((IListViewController)Element).HeaderElement == null)
+ {
+ Device.StartTimer(new TimeSpan(0), () =>
+ {
+ Control.SetHeader(((IListViewController)Element).HeaderElement as VisualElement);
+ return false;
+ });
+ }
+ else
+ {
+ Control.SetHeader(((IListViewController)Element).HeaderElement as VisualElement);
+ }
+ }
+
+ /// <summary>
+ /// Updates the footer.
+ /// </summary>
+ void UpdateFooter()
+ {
+ if (Element.Footer == null)
+ {
+ Control.SetFooter(null);
+ return;
+ }
+
+
+ if (((IListViewController)Element).FooterElement == null)
+ {
+ Device.StartTimer(new TimeSpan(0), () =>
+ {
+ Control.SetFooter(((IListViewController)Element).FooterElement as VisualElement);
+ return false;
+ });
+ }
+ else
+ {
+ Control.SetFooter(((IListViewController)Element).FooterElement as VisualElement);
+ }
+ }
+
+ /// <summary>
+ /// Updates the has uneven rows.
+ /// </summary>
+ void UpdateHasUnevenRows()
+ {
+ Control.SetHasUnevenRows(Element.HasUnevenRows);
+ }
+
+ /// <summary>
+ /// Updates the height of the row.
+ /// </summary>
+ void UpdateRowHeight()
+ {
+ Control.UpdateRealizedItems();
+ }
+
+ /// <summary>
+ /// Updates the is grouping enabled.
+ /// </summary>
+ /// <param name="initialize">If set to <c>true</c>, this method is invoked during initialization
+ /// (otherwise it will be invoked only after property changes).</param>
+ void UpdateIsGroupingEnabled(bool initialize)
+ {
+ Control.IsGroupingEnabled = Element.IsGroupingEnabled;
+ if (Element.IsGroupingEnabled)
+ {
+ Element.TemplatedItems.GroupedCollectionChanged += _groupedCollectionChanged;
+ }
+ else
+ {
+ Element.TemplatedItems.GroupedCollectionChanged -= _groupedCollectionChanged;
+ }
+ }
+
+ /// <summary>
+ /// Method is used for programaticaly selecting choosen item.
+ /// </summary>
+ void UpdateSelectedItem()
+ {
+ if (_selectedItemChanging == 0)
+ {
+ if (Element.SelectedItem == null)
+ {
+ if (_lastSelectedItem != null)
+ {
+ _lastSelectedItem.IsSelected = false;
+ _lastSelectedItem = null;
+ }
+ }
+ else
+ {
+ var templatedItems = Element.TemplatedItems;
+ var results = templatedItems.GetGroupAndIndexOfItem(Element.SelectedItem);
+ if (results.Item1 != -1 && results.Item2 != -1)
+ {
+ var itemGroup = templatedItems.GetGroup(results.Item1);
+ var cell = itemGroup[results.Item2];
+
+ ++_selectedItemChanging;
+ Control.ApplySelectedItem(cell);
+ --_selectedItemChanging;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs
new file mode 100644
index 00000000..9da812e9
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs
@@ -0,0 +1,96 @@
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class MasterDetailPageRenderer : VisualElementRenderer<MasterDetailPage>
+ {
+ Native.MasterDetailPage _mdpage;
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ public MasterDetailPageRenderer()
+ {
+ RegisterPropertyHandler("Master", UpdateMasterPage);
+ RegisterPropertyHandler("Detail", UpdateDetailPage);
+ RegisterPropertyHandler(MasterDetailPage.IsPresentedProperty,
+ UpdateIsPresented);
+ RegisterPropertyHandler(MasterDetailPage.MasterBehaviorProperty,
+ UpdateMasterBehavior);
+ RegisterPropertyHandler(MasterDetailPage.IsGestureEnabledProperty,
+ UpdateIsGestureEnabled);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<MasterDetailPage> e)
+ {
+ if (_mdpage == null)
+ {
+ _mdpage = new Native.MasterDetailPage(Forms.Context.MainWindow)
+ {
+ Master = GetNativePage(e.NewElement.Master),
+ Detail = GetNativePage(e.NewElement.Detail),
+ IsPresented = e.NewElement.IsPresented,
+ };
+
+ _mdpage.IsPresentedChanged += (sender, ev) =>
+ {
+ Element.IsPresented = _mdpage.IsPresented;
+ };
+ }
+
+ if (e.OldElement != null)
+ {
+ (e.OldElement as IMasterDetailPageController).BackButtonPressed -= BackButtonPressedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ (e.NewElement as IMasterDetailPageController).BackButtonPressed += BackButtonPressedHandler;
+ }
+
+ UpdateMasterBehavior();
+ SetNativeControl(_mdpage);
+
+ base.OnElementChanged(e);
+ }
+
+ void BackButtonPressedHandler(object sender, BackButtonPressedEventArgs e)
+ {
+ if ((Element != null) && !Element.IsPresented)
+ {
+ Element.IsPresented = true;
+ e.Handled = true;
+ }
+ }
+
+ EvasObject GetNativePage(Page page)
+ {
+ var pageRenderer = Platform.GetOrCreateRenderer(page);
+ return pageRenderer.NativeView;
+ }
+
+ void UpdateMasterBehavior() {
+ _mdpage.MasterBehavior = Element.MasterBehavior;
+ }
+
+ void UpdateMasterPage()
+ {
+ _mdpage.Master = GetNativePage(Element.Master);
+ }
+
+ void UpdateDetailPage()
+ {
+ _mdpage.Detail = GetNativePage(Element.Detail);
+ }
+
+ void UpdateIsPresented()
+ {
+ _mdpage.IsPresented = Element.IsPresented;
+ }
+
+ void UpdateIsGestureEnabled()
+ {
+ _mdpage.IsGestureEnabled = Element.IsGestureEnabled;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs
new file mode 100644
index 00000000..0d2ee2ed
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs
@@ -0,0 +1,390 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Xamarin.Forms.Internals;
+using ElmSharp;
+using EButton = ElmSharp.Button;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>, IDisposable, IVisualElementRenderer
+ {
+ Naviframe _naviFrame = null;
+ Page _previousPage = null;
+ TaskCompletionSource<bool> _currentTaskSource = null;
+ const string _partBackButton = "elm.swallow.prev_btn";
+ const string _leftToolbar = "title_left_btn";
+ const string _rightToolbar = "title_right_btn";
+ const string _defaultToolbarIcon = "naviframe/drawers";
+ const string _partTitle = "default";
+ const string _styleBackButton = "naviframe/back_btn/default";
+ readonly List<Widget> _naviItemContentPartList = new List<Widget>();
+ ToolbarTracker _toolbarTracker = null;
+
+ public NavigationPageRenderer()
+ {
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _naviFrame.AnimationFinished -= AnimationFinishedHandler;
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
+ {
+ if (_naviFrame == null)
+ {
+ _naviFrame = new Naviframe(Forms.Context.MainWindow);
+ _naviFrame.PreserveContentOnPop = true;
+ _naviFrame.DefaultBackButtonEnabled = true;
+ _naviFrame.AnimationFinished += AnimationFinishedHandler;
+
+ SetNativeControl(_naviFrame);
+ }
+
+ if (_toolbarTracker == null)
+ {
+ _toolbarTracker = new ToolbarTracker();
+ _toolbarTracker.CollectionChanged += ToolbarTrackerOnCollectionChanged;
+ }
+
+ if (e.OldElement != null)
+ {
+ var navigation = e.OldElement as INavigationPageController;
+ navigation.PopRequested -= PopRequestedHandler;
+ navigation.PopToRootRequested -= PopToRootRequestedHandler;
+ navigation.PushRequested -= PushRequestedHandler;
+ navigation.RemovePageRequested -= RemovePageRequestedHandler;
+ navigation.InsertPageBeforeRequested -= InsertPageBeforeRequestedHandler;
+
+ var pageController = e.OldElement as IPageController;
+ pageController.InternalChildren.CollectionChanged -= PageCollectionChangedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ var navigation = e.NewElement as INavigationPageController;
+ navigation.PopRequested += PopRequestedHandler;
+ navigation.PopToRootRequested += PopToRootRequestedHandler;
+ navigation.PushRequested += PushRequestedHandler;
+ navigation.RemovePageRequested += RemovePageRequestedHandler;
+ navigation.InsertPageBeforeRequested += InsertPageBeforeRequestedHandler;
+
+ var pageController = e.NewElement as IPageController;
+ pageController.InternalChildren.CollectionChanged += PageCollectionChangedHandler;
+
+ foreach (Page page in pageController.InternalChildren)
+ {
+ _naviFrame.Push(Platform.GetOrCreateRenderer(page).NativeView, SpanTitle(page.Title));
+ page.PropertyChanged += NavigationBarPropertyChangedHandler;
+
+ UpdateHasNavigationBar(page);
+ }
+
+ _toolbarTracker.Target = e.NewElement;
+ _previousPage = e.NewElement.CurrentPage;
+ }
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName)
+ {
+ (_previousPage as IPageController)?.SendDisappearing();
+ _previousPage = Element.CurrentPage;
+ (_previousPage as IPageController)?.SendAppearing();
+ }
+ }
+
+ void PageCollectionChangedHandler(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ if (e.OldItems != null)
+ foreach (Page page in e.OldItems)
+ page.PropertyChanged -= NavigationBarPropertyChangedHandler;
+ if (e.NewItems != null)
+ foreach (Page page in e.NewItems)
+ page.PropertyChanged += NavigationBarPropertyChangedHandler;
+ }
+
+ void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs)
+ {
+ UpdateToolbarItem(Element.CurrentPage);
+ }
+
+ void NavigationBarPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ // this handler is invoked only for child pages (contained on a navigation stack)
+ if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName)
+ UpdateHasNavigationBar(sender as Page);
+ else if (e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName ||
+ e.PropertyName == NavigationPage.BackButtonTitleProperty.PropertyName)
+ UpdateHasBackButton(sender as Page);
+ else if (e.PropertyName == Page.TitleProperty.PropertyName ||
+ e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName ||
+ e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName ||
+ e.PropertyName == NavigationPage.TintProperty.PropertyName)
+ UpdateTitle(sender as Page);
+ }
+
+ void UpdateHasNavigationBar(Page page)
+ {
+ NaviItem item = GetNaviItemForPage(page);
+ item.TitleBarVisible = (bool)page.GetValue(NavigationPage.HasNavigationBarProperty);
+ UpdateToolbarItem(page, item);
+ }
+
+ void UpdateToolbarItem(Page page, NaviItem item = null)
+ {
+ if (item == null)
+ item = GetNaviItemForPage(page);
+
+ if (_naviFrame.NavigationStack.Count == 0 || item == null || item != _naviFrame.NavigationStack.Last())
+ return;
+
+ item.SetPartContent(_leftToolbar, null, false);
+ item.SetPartContent(_rightToolbar, null, false);
+
+ Native.Button rightButton = GetToolbarButtonIfExists(ToolbarItemOrder.Primary);
+ item.SetPartContent(_rightToolbar, rightButton);
+
+ Native.Button leftButton = GetToolbarButtonIfExists(ToolbarItemOrder.Secondary);
+ if (leftButton == null)
+ UpdateHasBackButton(page, item);
+ else
+ item.SetPartContent(_leftToolbar, leftButton);
+ }
+
+ void UpdateHasBackButton(Page page, NaviItem item = null)
+ {
+ if (item == null)
+ item = GetNaviItemForPage(page);
+
+ EButton button = null;
+
+ if ((bool)page.GetValue(NavigationPage.HasBackButtonProperty))
+ button = CreateNavigationButton((string)page.GetValue(NavigationPage.BackButtonTitleProperty));
+ item.SetPartContent(_partBackButton, button);
+ }
+
+ void UpdateTitle(Page page)
+ {
+ NaviItem item = GetNaviItemForPage(page);
+ item.SetPartText(_partTitle, SpanTitle(page.Title));
+ }
+
+ string SpanTitle(string Title)
+ {
+ Native.Span span = new Native.Span { Text = Title };
+ if (Element.BarTextColor != Color.Default)
+ {
+ span.ForegroundColor = Element.BarTextColor.ToNative();
+ }
+ //TODO: changes only background of title not all bar
+ if (Element.BarBackgroundColor != Color.Default)
+ {
+ span.BackgroundColor = Element.BarBackgroundColor.ToNative();
+ }
+ else if (Element.Tint != Color.Default)
+ {
+ //TODO: This is only for backward compatibility
+ //- remove when Tint is no longer in Xamarin API
+ span.BackgroundColor = Element.Tint.ToNative();
+ }
+ return span.GetMarkupText();
+ }
+
+ EButton CreateNavigationButton(string text)
+ {
+ EButton button = new EButton(Forms.Context.MainWindow);
+ button.Clicked += (sender, e) =>
+ {
+ if (!Element.SendBackButtonPressed())
+ Forms.Context.Exit();
+ };
+
+ button.Style = _styleBackButton;
+ button.Text = text;
+
+ _naviItemContentPartList.Add(button);
+ button.Deleted += NaviItemPartContentDeletedHandler;
+
+ return button;
+ }
+
+ void NaviItemPartContentDeletedHandler(object sender, EventArgs e)
+ {
+ _naviItemContentPartList.Remove(sender as Widget);
+ }
+
+ NaviItem GetNaviItemForPage(Page page)
+ {
+ NaviItem item = null;
+
+ if (page != null)
+ {
+ IVisualElementRenderer renderer = Platform.GetRenderer(page);
+ if (renderer != null)
+ {
+ EvasObject content = renderer.NativeView;
+
+ for (int i = _naviFrame.NavigationStack.Count - 1; i >= 0; --i)
+ if (_naviFrame.NavigationStack[i].Content == content)
+ {
+ item = _naviFrame.NavigationStack[i];
+ break;
+ }
+ }
+ }
+ return item;
+ }
+
+ Native.Button GetToolbarButtonIfExists(ToolbarItemOrder order)
+ {
+ ToolbarItem item = _toolbarTracker.ToolbarItems.Where(
+ (i => i.Order == order ||
+ (order == ToolbarItemOrder.Primary && i.Order == ToolbarItemOrder.Default)))
+ .OrderBy(i => i.Priority)
+ .FirstOrDefault();
+
+ if (item != default(ToolbarItem))
+ {
+ return GetToolbarButton(item);
+ }
+ return null;
+ }
+
+ Native.Button GetToolbarButton(ToolbarItem item)
+ {
+ Native.Button button = new Native.Button(Forms.Context.MainWindow);
+ button.Clicked += (s, e) =>
+ {
+ IMenuItemController control = item;
+ control.Activate();
+ };
+ button.Text = item.Text;
+ button.BackgroundColor = Xamarin.Forms.Color.Transparent.ToNative();
+
+ if (String.IsNullOrEmpty(item.Icon) && String.IsNullOrEmpty(item.Text))
+ {
+ button.Style = _defaultToolbarIcon;
+ }
+ else
+ {
+ Native.Image iconImage = new Native.Image(Forms.Context.MainWindow);
+ iconImage.LoadFromImageSourceAsync(item.Icon);
+ button.Image = iconImage;
+ }
+
+ return button;
+ }
+
+ void PopRequestedHandler(object sender, NavigationRequestedEventArgs nre)
+ {
+ if ((Element as IPageController).InternalChildren.Count == _naviFrame.NavigationStack.Count)
+ {
+ if (nre.Animated)
+ {
+ _naviFrame.Pop();
+
+ _currentTaskSource = new TaskCompletionSource<bool>();
+ nre.Task = _currentTaskSource.Task;
+
+ // There is no TransitionFinished (AnimationFinished) event after Pop the last page
+ if (_naviFrame.NavigationStack.Count == 0)
+ CompleteCurrentNavigationTask();
+ }
+ else
+ _naviFrame.NavigationStack.Last().Delete();
+ }
+ }
+
+ void PopToRootRequestedHandler(object sender, NavigationRequestedEventArgs nre)
+ {
+ List<NaviItem> copyOfStack = new List<NaviItem>(_naviFrame.NavigationStack);
+ copyOfStack.Reverse();
+ NaviItem topItem = copyOfStack.FirstOrDefault();
+ NaviItem rootItem = copyOfStack.LastOrDefault();
+
+ foreach (NaviItem naviItem in copyOfStack)
+ if (naviItem != rootItem && naviItem != topItem)
+ naviItem.Delete();
+
+ if (topItem != rootItem)
+ {
+ if (nre.Animated)
+ {
+ _naviFrame.Pop();
+
+ _currentTaskSource = new TaskCompletionSource<bool>();
+ nre.Task = _currentTaskSource.Task;
+ }
+ else
+ topItem.Delete();
+ }
+ }
+
+ void PushRequestedHandler(object sender, NavigationRequestedEventArgs nre)
+ {
+ if (nre.Animated || _naviFrame.NavigationStack.Count == 0)
+ {
+ _naviFrame.Push(Platform.GetOrCreateRenderer(nre.Page).NativeView, SpanTitle(nre.Page.Title));
+ _currentTaskSource = new TaskCompletionSource<bool>();
+ nre.Task = _currentTaskSource.Task;
+
+ // There is no TransitionFinished (AnimationFinished) event after the first Push
+ if (_naviFrame.NavigationStack.Count == 1)
+ CompleteCurrentNavigationTask();
+ }
+ else
+ _naviFrame.InsertAfter(_naviFrame.NavigationStack.Last(), Platform.GetOrCreateRenderer(nre.Page).NativeView, SpanTitle(nre.Page.Title));
+
+ UpdateHasNavigationBar(nre.Page);
+ }
+
+ void RemovePageRequestedHandler(object sender, NavigationRequestedEventArgs nre)
+ {
+ GetNaviItemForPage(nre.Page).Delete();
+ }
+
+ async void InsertPageBeforeRequestedHandler(object sender, NavigationRequestedEventArgs nre)
+ {
+ TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
+ if (Element.CurrentNavigationTask != null && !Element.CurrentNavigationTask.IsCompleted)
+ {
+ await Element.CurrentNavigationTask;
+ }
+ Element.CurrentNavigationTask = tcs.Task;
+
+ Device.StartTimer(TimeSpan.FromMilliseconds(0), () =>
+ {
+ EvasObject page = Platform.GetOrCreateRenderer(nre.Page).NativeView;
+ _naviFrame.InsertBefore(GetNaviItemForPage(nre.BeforePage), page, SpanTitle(nre.Page.Title));
+ tcs.SetResult(true);
+
+ UpdateHasNavigationBar(nre.Page);
+
+ return false;
+ });
+ }
+
+ void AnimationFinishedHandler(object sender, EventArgs e)
+ {
+ CompleteCurrentNavigationTask();
+ }
+
+ void CompleteCurrentNavigationTask()
+ {
+ if (_currentTaskSource != null)
+ {
+ var tmp = _currentTaskSource;
+ _currentTaskSource = null;
+ tmp.SetResult(true);
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs
new file mode 100644
index 00000000..9abd5070
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs
@@ -0,0 +1,119 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using ElmSharp;
+using EButton = ElmSharp.Button;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class PickerRenderer : ViewRenderer<Picker, EButton>
+ {
+ internal List _list;
+ internal Native.Dialog _dialog;
+ Dictionary<ListItem, int> _itemToItemNumber = new Dictionary<ListItem, int>();
+
+ public PickerRenderer()
+ {
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
+ {
+ if (Control == null)
+ {
+ var button = new EButton(Forms.Context.MainWindow);
+ SetNativeControl (button);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.Clicked -= OnClick;
+ ((ObservableList<String>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ UpdateSelectedIndex();
+
+ Control.Clicked += OnClick;
+ ((ObservableList<String>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName)
+ {
+ UpdateSelectedIndex();
+ }
+ }
+
+ void UpdateSelectedIndex()
+ {
+ Control.Text = (Element.SelectedIndex == -1 || Element.Items == null ?
+ "" : Element.Items[Element.SelectedIndex]);
+ }
+
+ void RowsCollectionChanged(object sender, EventArgs e)
+ {
+ UpdateSelectedIndex();
+ }
+
+ void OnClick(object sender, EventArgs e)
+ {
+ int i = 0;
+ _dialog = new Native.Dialog(Forms.Context.MainWindow);
+ _list = new List(_dialog);
+ _dialog.AlignmentX = -1;
+ _dialog.AlignmentY = -1;
+
+ _dialog.Title = Element.Title;
+ _dialog.Dismissed += DialogDismissed;
+ _dialog.BackButtonPressed += (object senders, EventArgs es) =>
+ {
+ _dialog.Dismiss();
+ };
+
+ foreach (var s in Element.Items)
+ {
+ ListItem item = _list.Append(s);
+ _itemToItemNumber[item] = i;
+ i++;
+ }
+ _list.ItemSelected += ItemSelected;
+ _dialog.Content = _list;
+
+ _dialog.Show();
+ _list.Show();
+ }
+
+ void ItemSelected(object senderObject, EventArgs ev)
+ {
+ Element.SelectedIndex = _itemToItemNumber[(senderObject as List).SelectedItem];
+ _dialog.Dismiss();
+ }
+
+ void DialogDismissed(object sender, EventArgs e)
+ {
+ CleanView();
+ }
+
+ void CleanView()
+ {
+ if (null != _list)
+ {
+ _list.Unrealize();
+ _itemToItemNumber.Clear();
+ _list = null;
+ }
+ if (null != _dialog)
+ {
+ _dialog.Unrealize();
+ _dialog = null;
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs
new file mode 100644
index 00000000..3aacd3f4
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs
@@ -0,0 +1,60 @@
+using System.ComponentModel;
+using EProgressBar = ElmSharp.ProgressBar;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class ProgressBarRenderer : ViewRenderer<ProgressBar, EProgressBar>
+ {
+ public ProgressBarRenderer()
+ {
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e)
+ {
+ if (base.Control == null)
+ {
+ var progressBar = new EProgressBar(Forms.Context.MainWindow);
+ SetNativeControl(progressBar);
+ }
+
+ if (e.OldElement != null)
+ {
+ }
+
+ if (e.NewElement != null)
+ {
+ if (e.NewElement.MinimumWidthRequest == -1 &&
+ e.NewElement.MinimumHeightRequest == -1 &&
+ e.NewElement.WidthRequest == -1 &&
+ e.NewElement.HeightRequest == -1)
+ {
+ Log.Warn("Need to size request");
+ }
+
+ UpdateAll();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void UpdateAll()
+ {
+ UpdateProgress();
+ }
+
+ void UpdateProgress()
+ {
+ Control.Value = Element.Progress;
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if (e.PropertyName == ProgressBar.ProgressProperty.PropertyName)
+ {
+ UpdateProgress();
+ }
+ }
+ }
+}
+
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs
new file mode 100755
index 00000000..29f15fbe
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs
@@ -0,0 +1,130 @@
+using System;
+using System.ComponentModel;
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// This class provides a Renderer for a ScrollView widget.
+ /// </summary>
+ public class ScrollViewRenderer : ViewRenderer<ScrollView, Scroller>
+ {
+ EvasObject _content;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.ScrollViewRenderer"/> class.
+ /// </summary>
+ public ScrollViewRenderer()
+ {
+ RegisterPropertyHandler("Content", FillContent);
+ }
+
+ /// <summary>
+ /// Handles the element change event.
+ /// </summary>
+ /// <param name="e">Event arguments.</param>
+ protected override void OnElementChanged(ElementChangedEventArgs<ScrollView> e)
+ {
+ if (Control == null)
+ {
+ var scrollView = new Scroller(Forms.Context.MainWindow);
+ SetNativeControl(scrollView);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.Scrolled -= ScrollViewScrolledHandler;
+ (e.OldElement as IScrollViewController).ScrollToRequested -= ScrollRequestHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.Scrolled += ScrollViewScrolledHandler;
+ (e.NewElement as IScrollViewController).ScrollToRequested += ScrollRequestHandler;
+ }
+
+ UpdateAll();
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (null != Control)
+ {
+ (Control as IScrollViewController).ScrollToRequested -= ScrollRequestHandler;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ void FillContent()
+ {
+ if (_content != null)
+ {
+ Control.SetContent(null, true);
+ }
+
+ _content = Platform.GetOrCreateRenderer(Element.Content).NativeView;
+
+ if (_content != null)
+ {
+ Control.SetContent(_content, true);
+ }
+ }
+
+ void UpdateAll()
+ {
+ UpdateOrientation();
+ }
+
+ void UpdateOrientation()
+ {
+ switch (Element.Orientation)
+ {
+ case ScrollOrientation.Horizontal:
+ Control.ScrollBlock = ScrollBlock.Vertical;
+ Control.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible;
+ Control.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible;
+ break;
+ case ScrollOrientation.Vertical:
+ Control.ScrollBlock = ScrollBlock.Horizontal;
+ Control.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible;
+ Control.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible;
+ break;
+ default:
+ Control.ScrollBlock = ScrollBlock.None;
+ Control.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible;
+ Control.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible;
+ break;
+ }
+ }
+
+ /// <summary>
+ /// An event raised on element's property change.
+ /// </summary>
+ /// <param name="sender">Sender.</param>
+ /// <param name="e">Event arguments</param>
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (ScrollView.OrientationProperty.PropertyName == e.PropertyName)
+ {
+ UpdateOrientation();
+ }
+
+ base.OnElementPropertyChanged(sender, e);
+ }
+
+ void ScrollViewScrolledHandler(object sender, EventArgs e)
+ {
+ var region = Control.CurrentRegion;
+ ((IScrollViewController)Element).SetScrolledPosition(region.X, region.Y);
+ }
+
+ void ScrollRequestHandler(object sender, ScrollToRequestedEventArgs e)
+ {
+ Rect region = new Rect(ToNativeDimension(e.ScrollX), ToNativeDimension(e.ScrollY), ToNativeDimension(Element.Width), ToNativeDimension(Element.Height));
+ Control.ScrollTo(region, e.ShouldAnimate);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs
new file mode 100644
index 00000000..798a0497
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs
@@ -0,0 +1,167 @@
+using System;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class SearchBarRenderer : ViewRenderer<SearchBar, Native.SearchBar>
+ {
+ //TODO need to add internationalization support
+ const string DefaultPlaceholderText = "Search";
+
+ static readonly EColor s_defaultCancelButtonColor = EColor.Aqua;
+
+ //TODO: read default platform color
+ static readonly EColor s_defaultPlaceholderColor = EColor.Gray;
+ static readonly EColor s_defaultTextColor = EColor.Black;
+ /// <summary>
+ /// Creates a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.SearchBarRenderer"/> class.
+ /// Registers handlers for various properties of the SearchBar widget.
+ /// </summary>
+ public SearchBarRenderer()
+ {
+ RegisterPropertyHandler(SearchBar.CancelButtonColorProperty, CancelButtonColorPropertyHandler);
+ RegisterPropertyHandler(SearchBar.FontAttributesProperty, FontAttributesPropertyHandler);
+ RegisterPropertyHandler(SearchBar.FontFamilyProperty, FontFamilyPropertyHandler);
+ RegisterPropertyHandler(SearchBar.FontSizeProperty, FontSizePropertyHandler);
+ RegisterPropertyHandler(SearchBar.HorizontalTextAlignmentProperty, HorizontalTextAlignmentPropertyHandler);
+ RegisterPropertyHandler(SearchBar.PlaceholderProperty, PlaceholderPropertyHandler);
+ RegisterPropertyHandler(SearchBar.PlaceholderColorProperty, PlaceholderColorPropertyHandler);
+ RegisterPropertyHandler(SearchBar.TextProperty, TextPropertyHandler);
+ RegisterPropertyHandler(SearchBar.TextColorProperty, TextColorPropertyHandler);
+ }
+
+ /// <summary>
+ /// A method called whenever the associated element has changed.
+ /// </summary>
+ protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
+ {
+ if (Control == null)
+ {
+ var searchBar = new Native.SearchBar(Forms.Context.MainWindow);
+ SetNativeControl(searchBar);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.TextChanged -= SearchBarTextChangedHandler;
+ Control.SearchButtonPressed -= SearchButtonPressedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.TextChanged += SearchBarTextChangedHandler;
+ Control.SearchButtonPressed += SearchButtonPressedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override Size MinimumSize()
+ {
+ return new Size(250, 120);
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's cancel button color property.
+ /// Converts current Color to ElmSharp.Color instance and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native widget.
+ /// </summary>
+ void CancelButtonColorPropertyHandler()
+ {
+ Control.CancelButtonColor = Element.CancelButtonColor.IsDefault ? s_defaultCancelButtonColor : Element.CancelButtonColor.ToNative();
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's font attributes property.
+ /// Converts current FontAttributes to ElmSharp.FontAttributes instance
+ /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget.
+ /// </summary>
+ void FontAttributesPropertyHandler()
+ {
+ Control.FontAttributes = Element.FontAttributes;
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's font family property.
+ /// Sets current value of FontFamily property to the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget.
+ /// </summary>
+ void FontFamilyPropertyHandler()
+ {
+ Control.FontFamily = Element.FontFamily;
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's font size property.
+ /// Sets current value of FontSize property to the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget.
+ /// </summary>
+ void FontSizePropertyHandler()
+ {
+ Control.FontSize = Element.FontSize;
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's horizontal text alignment property.
+ /// Converts current HorizontalTextAlignment property's value to Xamarin.Forms.Platform.Tizen.Native.TextAlignment instance
+ /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget.
+ /// </summary>
+ void HorizontalTextAlignmentPropertyHandler()
+ {
+ Control.HorizontalTextAlignment = Element.HorizontalTextAlignment.ToNative();
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's placeholder color property.
+ /// Converts current PlaceholderColor property value to ElmSharp.Color instance
+ /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget.
+ /// </summary>
+ void PlaceholderColorPropertyHandler()
+ {
+ Control.PlaceholderColor = Element.PlaceholderColor.IsDefault ? s_defaultPlaceholderColor : Element.PlaceholderColor.ToNative();
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's placeholder text property.
+ /// </summary>
+ void PlaceholderPropertyHandler()
+ {
+ Control.Placeholder = Element.Placeholder == null ? DefaultPlaceholderText : Element.Placeholder;
+ }
+
+ /// <summary>
+ /// Called on every change of underlying SearchBar's Text property.
+ /// Rewrites current underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar's Text contents to its Xamarin counterpart.
+ /// </summary>
+ /// <param name="sender">Sender.</param>
+ void SearchBarTextChangedHandler(object sender, EventArgs e)
+ {
+ Element.Text = Control.Text;
+ }
+
+ /// <summary>
+ /// Called when the user clicks the Search button.
+ /// </summary>
+ /// <param name="sender">Sender.</param>
+ /// <param name="e">Event arguments.</param>
+ void SearchButtonPressedHandler(object sender, EventArgs e)
+ {
+ (Element as ISearchBarController).OnSearchButtonPressed();
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's text color property.
+ /// Converts current TextColor property value to ElmSharp.Color instance
+ /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget.
+ /// </summary>
+ void TextColorPropertyHandler()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ /// <summary>
+ /// Called upon changing of Xamarin widget's text property.
+ /// </summary>
+ void TextPropertyHandler()
+ {
+ Control.Text = Element.Text;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs
new file mode 100644
index 00000000..93cc6e84
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs
@@ -0,0 +1,63 @@
+using System;
+using ESlider = ElmSharp.Slider;
+using ESize = ElmSharp.Size;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class SliderRenderer : ViewRenderer<Slider, ESlider>
+ {
+
+ public SliderRenderer()
+ {
+ RegisterPropertyHandler(Slider.ValueProperty, UpdateValue);
+ RegisterPropertyHandler(Slider.MinimumProperty, UpdateMinMax);
+ RegisterPropertyHandler(Slider.MaximumProperty, UpdateMinMax);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
+ {
+ if (Control == null)
+ {
+ var slider = new ESlider(Forms.Context.MainWindow)
+ {
+ PropagateEvents = false,
+ };
+ SetNativeControl(slider);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.ValueChanged -= SliderValueChangedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.ValueChanged += SliderValueChangedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override ESize Measure(int availableWidth, int availableHeight)
+ {
+ return new ESize(Math.Min(200, availableWidth), 50);
+ }
+
+ void SliderValueChangedHandler(object sender, EventArgs e)
+ {
+ Element.Value = Control.Value;
+ }
+
+ protected void UpdateValue()
+ {
+ Control.Value = Element.Value;
+ }
+
+ protected void UpdateMinMax()
+ {
+ Control.Minimum = Element.Minimum;
+ Control.Maximum = Element.Maximum;
+ UpdateValue();
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs
new file mode 100644
index 00000000..09d1bf6b
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs
@@ -0,0 +1,86 @@
+using System;
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class StepperRenderer : ViewRenderer<Stepper, Spinner>
+ {
+
+ public StepperRenderer()
+ {
+ RegisterPropertyHandler(Stepper.ValueProperty, UpdateValue);
+ RegisterPropertyHandler(Stepper.MinimumProperty, UpdateMinMax);
+ RegisterPropertyHandler(Stepper.MaximumProperty, UpdateMinMax);
+ RegisterPropertyHandler(Stepper.IncrementProperty, UpdateStep);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
+ {
+ if (Control == null)
+ {
+ var stepper = new Spinner(Forms.Context.MainWindow)
+ {
+ IsEditable = false,
+ };
+
+ SetNativeControl(stepper);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.ValueChanged -= StepperValueChangedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.ValueChanged += StepperValueChangedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void StepperValueChangedHandler(object sender, EventArgs e)
+ {
+ double newValue = Control.Value;
+ ((IElementController)Element).SetValueFromRenderer(Stepper.ValueProperty, newValue);
+
+ // Determines how many decimal places are there in current Stepper's value.
+ // The 15 pound characters below correspond to the maximum precision of Double type.
+ var decimalValue = Decimal.Parse(newValue.ToString("0.###############"));
+
+ // GetBits() method returns an array of four 32-bit integer values.
+ // The third (0-indexing) element of an array contains the following information:
+ // bits 00-15: unused, required to be 0
+ // bits 16-23: an exponent between 0 and 28 indicating the power of 10 to divide the integer number passed as a parameter.
+ // Conversely this is the number of decimal digits in the number as well.
+ // bits 24-30: unused, required to be 0
+ // bit 31: indicates the sign. 0 means positive number, 1 is for negative numbers.
+ //
+ // The precision information needs to be extracted from bits 16-23 of third element of an array
+ // returned by GetBits() call. Right-shifting by 16 bits followed by zeroing anything else results
+ // in a nice conversion of this data to integer variable.
+ var precision = (Decimal.GetBits(decimalValue)[3] >> 16) & 0x000000FF;
+
+ // Sets Stepper's inner label decimal format to use exactly as many decimal places as needed:
+ Control.LabelFormat = string.Format("%.{0}f", precision);
+ }
+
+ protected void UpdateValue()
+ {
+ Control.Value = Element.Value;
+ }
+
+ protected void UpdateMinMax()
+ {
+ Control.Minimum = Element.Minimum;
+ Control.Maximum = Element.Maximum;
+ UpdateValue();
+ }
+
+ void UpdateStep()
+ {
+ Control.Step = Element.Increment;
+ }
+ }
+}
+
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs
new file mode 100644
index 00000000..b3539e6c
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs
@@ -0,0 +1,50 @@
+using System;
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class SwitchRenderer : ViewRenderer<Switch, Check>
+ {
+ public SwitchRenderer()
+ {
+ RegisterPropertyHandler(Switch.IsToggledProperty, HandleToggled);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
+ {
+ if (Control == null)
+ {
+ var _switch = new Check(Forms.Context.MainWindow)
+ {
+ PropagateEvents = false,
+ };
+ SetNativeControl(_switch);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.StateChanged -= CheckChangedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ Control.Style = "toggle";
+
+ Control.StateChanged += CheckChangedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void CheckChangedHandler(object sender, EventArgs e)
+ {
+ Element.SetValue(Switch.IsToggledProperty, Control.IsChecked);
+ }
+
+ void HandleToggled()
+ {
+ Control.IsChecked = Element.IsToggled;
+ }
+
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs
new file mode 100644
index 00000000..db97b339
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs
@@ -0,0 +1,190 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using ElmSharp;
+using EToolbarItem = ElmSharp.ToolbarItem;
+using EToolbarItemEventArgs = ElmSharp.ToolbarItemEventArgs;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, IVisualElementRenderer
+ {
+ Box _box;
+ Toolbar _tpage;
+ EvasObject _tcontent;
+ Dictionary<EToolbarItem, Page> _itemToItemPage = new Dictionary<EToolbarItem, Page>();
+
+ public TabbedPageRenderer ()
+ {
+ //Register for title change property
+ RegisterPropertyHandler(TabbedPage.TitleProperty, UpdateTitle);
+ //Register for current page change property
+ RegisterPropertyHandler("CurrentPage", CurrentPageChanged);
+ //TODO renderer should add item on EFL toolbar when new Page is added to TabbedPage
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
+ {
+ if (_tpage == null)
+ {
+ //Create box that holds toolbar and selected content
+ _box = new Box(Forms.Context.MainWindow)
+ {
+ AlignmentX = -1,
+ AlignmentY = -1,
+ WeightX = 1,
+ WeightY = 1,
+ IsHorizontal = false,
+ };
+ _box.Show();
+
+ //Create toolbar that is placed inside the _box
+ _tpage = new Toolbar(Forms.Context.MainWindow)
+ {
+ AlignmentX = -1,
+ WeightX = 1,
+ ShrinkMode = ToolbarShrinkMode.Expand,
+ SelectionMode = ToolbarSelectionMode.Always,
+ };
+ _tpage.Show();
+ //Add callback for item selection
+ _tpage.Selected += OnCurrentPageChanged;
+ _box.PackEnd(_tpage);
+
+ SetNativeControl(_box);
+ UpdateTitle();
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_box != null)
+ {
+ _box.Unrealize();
+ _box = null;
+ }
+ if (_tpage != null)
+ {
+ _tpage.Selected -= OnCurrentPageChanged;
+
+ _tpage.Unrealize();
+ _tpage = null;
+ }
+ base.Dispose(disposing);
+ }
+
+ protected override void OnElementReady()
+ {
+ FillToolbar();
+ base.OnElementReady();
+ }
+
+ void UpdateTitle()
+ {
+ _tpage.Text = Element.Title;
+ }
+
+ void UpdateTitle(Page page)
+ {
+ if (_itemToItemPage.ContainsValue(page))
+ {
+ var pair = _itemToItemPage.FirstOrDefault(x => x.Value == page);
+ pair.Key.SetPartText(null, pair.Value.Title);
+ }
+ }
+
+ void OnPageTitleChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == Page.TitleProperty.PropertyName)
+ {
+ UpdateTitle(sender as Page);
+ }
+ }
+
+ void FillToolbar()
+ {
+ var logicalChildren = (Element as IElementController).LogicalChildren;
+ bool hasIcon = false;
+
+ //add items to toolbar
+ foreach (Page child in logicalChildren)
+ {
+ var childRenderer = Platform.GetRenderer(child);
+ if (childRenderer != null)
+ {
+ childRenderer.NativeView.Hide();
+ }
+
+ EToolbarItem toolbarItem;
+ if (string.IsNullOrEmpty(child.Icon))
+ {
+ toolbarItem = _tpage.Append(child.Title);
+ }
+ else
+ {
+ //elm_toobar style and size hint must be changed at least once before adding the toolbar item having icon.
+ if (!hasIcon)
+ {
+ int windowHeight = Forms.Context.MainWindow.Geometry.Height;
+ //This value is from efl-theme-tizen-mobile theme. (NAVIFRAME_TABBAR_HEIGHT_WITH_TITLE_INC 80)
+ double requiredToolbarHeight = 80.0;
+ double toolBarWeight = requiredToolbarHeight/windowHeight;
+ _tpage.Style="tabbar";
+ _tpage.TransverseExpansion = true;
+ _tpage.SetAlignment(-1,-1);
+ _tpage.SetWeight(1, toolBarWeight);
+ _box.SetAlignment(-1,-1);
+ _box.SetWeight(1, 1- toolBarWeight);
+ hasIcon = true;
+ }
+ toolbarItem = _tpage.Append(child.Title, ResourcePath.GetPath(child.Icon));
+ }
+ _itemToItemPage.Add(toolbarItem, child);
+ if (Element.CurrentPage == child)
+ {
+ //select item on the toolbar and fill content
+ toolbarItem.IsSelected = true;
+ OnCurrentPageChanged(null, null);
+ }
+ child.PropertyChanged += OnPageTitleChanged;
+ }
+ }
+
+ void OnCurrentPageChanged(object sender, EToolbarItemEventArgs e)
+ {
+ if (_tpage.SelectedItem == null)
+ return;
+ Element.CurrentPage = _itemToItemPage[_tpage.SelectedItem];
+
+ //detach content from view without EvasObject changes
+ if (_tcontent != null)
+ {
+ //hide content that should not be visible
+ _tcontent.Hide();
+ //unpack content that is hiden an prepare for new content
+ _box.UnPack(_tcontent);
+ }
+ //create EvasObject using renderer and remember to not destroy
+ //it for better performance (creat once)
+ _tcontent = Platform.GetOrCreateRenderer(Element.CurrentPage).NativeView;
+ _tcontent.SetAlignment(-1, -1);
+ _tcontent.SetWeight(1, 1);
+ _tcontent.Show();
+ _box.PackEnd(_tcontent);
+ }
+
+ void CurrentPageChanged()
+ {
+ foreach (KeyValuePair<EToolbarItem, Page> pair in _itemToItemPage)
+ {
+ if (pair.Value == Element.CurrentPage)
+ {
+ pair.Key.IsSelected = true;
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs
new file mode 100644
index 00000000..59123549
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs
@@ -0,0 +1,79 @@
+using System;
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class TableViewRenderer : ViewRenderer<TableView, Native.TableView>, IVisualElementRenderer
+ {
+ internal static BindableProperty PresentationProperty = BindableProperty.Create("Presentation", typeof(View), typeof(TableSectionBase), null, BindingMode.OneWay, null, null, null, null, null as BindableProperty.CreateDefaultValueDelegate);
+
+ public TableViewRenderer()
+ {
+ RegisterPropertyHandler(TableView.HasUnevenRowsProperty, UpdateHasUnevenRows);
+ RegisterPropertyHandler(TableView.RowHeightProperty, UpdateRowHeight);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
+ {
+ if (Control == null)
+ {
+ var _tableView = new Native.TableView(Forms.Context.MainWindow);
+ SetNativeControl(_tableView);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.ItemSelected -= ListViewItemSelectedHandler;
+ e.OldElement.ModelChanged -= OnRootPropertyChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ e.NewElement.ModelChanged += OnRootPropertyChanged;
+ Control.ItemSelected += ListViewItemSelectedHandler;
+ Control.ApplyTableRoot(e.NewElement.Root);
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Element.ModelChanged -= OnRootPropertyChanged;
+ base.Dispose(disposing);
+ }
+
+ void ListViewItemSelectedHandler(object sender, GenListItemEventArgs e)
+ {
+ var item = e.Item as GenListItem;
+
+ if (item != null)
+ {
+ var clickedCell = item.Data as Native.ListView.ItemContext;
+ if (null != clickedCell)
+ {
+ Element.Model.RowSelected(clickedCell.Cell);
+ }
+ }
+ }
+
+ void OnRootPropertyChanged(object sender, EventArgs e)
+ {
+ if (Element != null)
+ {
+ Control.ApplyTableRoot(Element.Root);
+ }
+ }
+
+ void UpdateHasUnevenRows()
+ {
+ //TODO Implement UnevenRows in ListView
+ }
+
+ void UpdateRowHeight()
+ {
+ Control.UpdateRealizedItems();
+ }
+
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs
new file mode 100644
index 00000000..9f964c18
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Globalization;
+using EColor = ElmSharp.Color;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ public class TimePickerRenderer : ViewRenderer<TimePicker, Native.Button>
+ {
+ //TODO need to add internationalization support
+ const string DialogTitle = "Choose Time";
+
+ static readonly string s_defaultFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
+
+ static readonly EColor s_defaultTextColor = EColor.White;
+
+ string _format;
+
+ TimeSpan _time;
+
+ public TimePickerRenderer()
+ {
+ RegisterPropertyHandler(TimePicker.FormatProperty, UpdateFormat);
+ RegisterPropertyHandler(TimePicker.TimeProperty, UpdateTime);
+ RegisterPropertyHandler(TimePicker.TextColorProperty, UpdateTextColor);
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
+ {
+ if (Control == null)
+ {
+ var button = new Native.Button(Forms.Context.MainWindow);
+ SetNativeControl(button);
+ }
+
+ if (e.OldElement != null)
+ {
+ Control.Clicked -= ButtonClickedHandler;
+ }
+
+ if (e.NewElement != null)
+ {
+ _time = DateTime.Now.TimeOfDay;
+ _format = s_defaultFormat;
+ UpdateTimeAndFormat();
+
+ Control.Clicked += ButtonClickedHandler;
+ }
+
+ base.OnElementChanged(e);
+ }
+
+ void ButtonClickedHandler(object o, EventArgs e)
+ {
+ Native.DateTimePickerDialog dialog = new Native.DateTimePickerDialog(Forms.Context.MainWindow)
+ {
+ Title = DialogTitle
+ };
+
+ dialog.InitializeTimePicker(_time, _format);
+ dialog.DateTimeChanged += DialogDateTimeChangedHandler;
+ dialog.Dismissed += DialogDismissedHandler;
+ dialog.Show();
+ }
+
+ void DialogDateTimeChangedHandler(object sender, Native.DateChangedEventArgs dcea)
+ {
+ Element.Time = dcea.NewDate.TimeOfDay;
+ UpdateTime();
+ }
+
+ void DialogDismissedHandler(object sender, EventArgs e)
+ {
+ var dialog = sender as Native.DateTimePickerDialog;
+ dialog.DateTimeChanged -= DialogDateTimeChangedHandler;
+ dialog.Dismissed -= DialogDismissedHandler;
+ }
+
+ void UpdateFormat()
+ {
+ _format = Element.Format ?? s_defaultFormat;
+ UpdateTimeAndFormat();
+ }
+
+ void UpdateTextColor()
+ {
+ Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative();
+ }
+
+ void UpdateTime()
+ {
+ _time = Element.Time;
+ UpdateTimeAndFormat();
+ }
+
+ void UpdateTimeAndFormat()
+ {
+ // Xamarin using DateTime formatting (https://developer.xamarin.com/api/property/Xamarin.Forms.TimePicker.Format/)
+ Control.Text = new DateTime(_time.Ticks).ToString(_format);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs
new file mode 100644
index 00000000..24debc8b
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs
@@ -0,0 +1,46 @@
+using ElmSharp;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Base class for view renderers.
+ /// </summary>
+ public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>
+ where TView : View
+ where TNativeView : Widget
+ {
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ protected ViewRenderer()
+ {
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs<TView> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.OldElement != null)
+ {
+ _gestureHandler.Clear();
+ _gestureHandler = null;
+ }
+
+ if (e.NewElement != null)
+ {
+ _gestureHandler = new GestureHandler(this);
+ }
+ }
+
+ /// <summary>
+ /// Native control associated with this renderer.
+ /// </summary>
+ public TNativeView Control
+ {
+ get
+ {
+ return (TNativeView)NativeView;
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs
new file mode 100644
index 00000000..8c5c0be0
--- /dev/null
+++ b/Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs
@@ -0,0 +1,946 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.ComponentModel;
+using ElmSharp;
+using ELayout = ElmSharp.Layout;
+using EColor = ElmSharp.Color;
+using ESize = ElmSharp.Size;
+using ERect = ElmSharp.Rect;
+using ERectangle = ElmSharp.Rectangle;
+using EBox = ElmSharp.Box;
+
+namespace Xamarin.Forms.Platform.Tizen
+{
+ /// <summary>
+ /// Base class for rendering of a Xamarin element.
+ /// </summary>
+ public abstract class VisualElementRenderer<TElement> : IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement
+ {
+ /// <summary>
+ /// Holds registered element changed handlers.
+ /// </summary>
+ readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>();
+
+ /// <summary>
+ /// Handler for property changed events.
+ /// </summary>
+ PropertyChangedEventHandler _propertyChangedHandler;
+
+ EventHandler<EventArg<VisualElement>> _batchCommittedHandler;
+
+ /// <summary>
+ /// Flags which control status of renderer.
+ /// </summary>
+ VisualElementRendererFlags _flags = VisualElementRendererFlags.None;
+
+ /// <summary>
+ /// Holds the native view.
+ /// </summary>
+ EvasObject _view;
+
+ Dictionary<string, Action<bool>> _propertyHandlersWithInit = new Dictionary<string, Action<bool>>();
+
+ Dictionary<string, Action> _propertyHandlers = new Dictionary<string, Action>();
+
+ HashSet<string> _batchedProperties = new HashSet<string>();
+
+ ERectangle _opacityLayer;
+
+ internal GestureHandler _gestureHandler;
+
+ /// <summary>
+ /// Default constructor.
+ /// </summary>
+ protected VisualElementRenderer()
+ {
+ RegisterPropertyHandler(VisualElement.IsVisibleProperty, UpdateIsVisible);
+ RegisterPropertyHandler(VisualElement.OpacityProperty, UpdateOpacity);
+ RegisterPropertyHandler(VisualElement.IsEnabledProperty, UpdateIsEnabled);
+ RegisterPropertyHandler(VisualElement.InputTransparentProperty, UpdateInputTransparent);
+ RegisterPropertyHandler(VisualElement.BackgroundColorProperty, UpdateBackgroundColor);
+
+ RegisterPropertyHandler(VisualElement.AnchorXProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.AnchorYProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.ScaleProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.RotationProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.RotationXProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.RotationYProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.TranslationXProperty, ApplyTransformation);
+ RegisterPropertyHandler(VisualElement.TranslationYProperty, ApplyTransformation);
+ }
+
+ ~VisualElementRenderer()
+ {
+ Dispose(false);
+ }
+
+ event EventHandler<VisualElementChangedEventArgs> ElementChanged
+ {
+ add
+ {
+ _elementChangedHandlers.Add(value);
+ }
+ remove
+ {
+ _elementChangedHandlers.Remove(value);
+ }
+ }
+
+ /// <summary>
+ /// Gets the Xamarin element associated with this renderer.
+ /// </summary>
+ public TElement Element
+ {
+ get;
+ private set;
+ }
+
+ VisualElement IVisualElementRenderer.Element
+ {
+ get
+ {
+ return this.Element;
+ }
+ }
+
+ public EvasObject NativeView
+ {
+ get
+ {
+ return _view;
+ }
+ }
+
+ protected bool IsDisposed => (_flags == VisualElementRendererFlags.Disposed);
+
+ /// <summary>
+ /// Releases all resource used by the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> object.
+ /// </summary>
+ /// <remarks>Call <see cref="Dispose"/> when you are finished using the
+ /// <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/>. The <see cref="Dispose"/> method
+ /// leaves the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> in an unusable state.
+ /// After calling <see cref="Dispose"/>, you must release all references to the
+ /// <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> so the garbage collector can reclaim
+ /// the memory that the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> was occupying.</remarks>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ if (null == NativeView)
+ {
+ return new SizeRequest(new Size(0, 0));
+ }
+ else
+ {
+ int availableWidth = ToNativeDimension(widthConstraint);
+ int availableHeight = ToNativeDimension(heightConstraint);
+ ESize measured;
+
+ var nativeViewMeasurable = NativeView as Native.IMeasurable;
+ if (nativeViewMeasurable != null)
+ {
+ measured = nativeViewMeasurable.Measure(availableWidth, availableHeight);
+ }
+ else
+ {
+ measured = Measure(availableWidth, availableHeight);
+ }
+
+ return new SizeRequest(new Size(measured.Width, measured.Height), MinimumSize());
+ }
+ }
+
+ /// <summary>
+ /// Sets the element associated with this renderer.
+ /// </summary>
+ public void SetElement(TElement newElement)
+ {
+ if (newElement == null)
+ {
+ throw new ArgumentNullException("newElement");
+ }
+
+ TElement oldElement = Element;
+ if (oldElement != null)
+ {
+ throw new InvalidOperationException("oldElement");
+ }
+
+ Element = newElement;
+ if (_propertyChangedHandler == null)
+ {
+ _propertyChangedHandler = new PropertyChangedEventHandler(OnElementPropertyChanged);
+ }
+
+ if (_batchCommittedHandler == null)
+ {
+ _batchCommittedHandler = OnBatchCommitted;
+ }
+
+ // send notification
+ OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, newElement));
+
+ // store renderer for the new element
+ Platform.SetRenderer(newElement, this);
+
+ // add children
+ var logicalChildren = (newElement as IElementController).LogicalChildren;
+ foreach (Element child in logicalChildren)
+ {
+ AddChild(child);
+ }
+
+ OnElementReady();
+ }
+
+ public void UpdateNativeGeometry()
+ {
+ var x = ComputeAbsoluteX(Element);
+ var y = ComputeAbsoluteY(Element);
+ NativeView.Geometry = new ERect(ToNativeDimension(x), ToNativeDimension(y), ToNativeDimension(Element.Width), ToNativeDimension(Element.Height));
+ ApplyTransformation();
+ UpdateOpacityLayer();
+ }
+
+ void IVisualElementRenderer.SetElement(VisualElement element)
+ {
+ TElement tElement = element as TElement;
+ if (tElement == null)
+ {
+ throw new ArgumentException("Element is not of type " + typeof(TElement), "Element");
+ }
+ SetElement(tElement);
+ }
+
+ /// <summary>
+ /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform.
+ /// </summary>
+ /// <param name="effect">The effect to register.</param>
+ void IEffectControlProvider.RegisterEffect(Effect effect)
+ {
+ RegisterEffect(effect);
+ }
+
+ /// <summary>
+ /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform.
+ /// </summary>
+ /// <param name="effect">The effect to register.</param>
+ protected void RegisterEffect(Effect effect)
+ {
+ var platformEffect = effect as PlatformEffect;
+ if (platformEffect != null)
+ {
+ OnRegisterEffect(platformEffect);
+ }
+ }
+
+
+ protected virtual void UpdateLayout()
+ {
+ // we're updating the coordinates of native control only if they were modified
+ // via Xamarin (Settings.IgnoreBatchCommitted is set to false);
+ // otherwise native control is already in the right place
+ if (!Settings.IgnoreBatchCommitted && null != NativeView)
+ {
+ UpdateNativeGeometry();
+ }
+
+ // we're updating just immediate children
+ var logicalChildren = (Element as IElementController).LogicalChildren;
+ foreach (var child in logicalChildren)
+ {
+ Platform.GetRenderer(child)?.UpdateNativeGeometry();
+ }
+ }
+
+ /// <summary>
+ /// Disposes of underlying resources.
+ /// </summary>
+ /// <param name="disposing">True if the memory release was requested on demand.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if ((_flags & VisualElementRendererFlags.Disposed) != 0)
+ {
+ return;
+ }
+
+ _flags |= VisualElementRendererFlags.Disposed;
+
+ if (disposing)
+ {
+ if (Element != null)
+ {
+ Element.PropertyChanged -= _propertyChangedHandler;
+ Element.BatchCommitted -= _batchCommittedHandler;
+
+ Element.ChildAdded -= OnChildAdded;
+ Element.ChildRemoved -= OnChildRemoved;
+ Element.ChildrenReordered -= OnChildrenReordered;
+
+ Element.FocusChangeRequested -= OnFocusChangeRequested;
+
+ Settings.StartIgnoringBatchCommitted();
+ Element.Layout(new Rectangle(0, 0, -1, -1));
+ Settings.StopIgnoringBatchCommitted();
+
+ var logicalChildren = (Element as IElementController).LogicalChildren;
+ foreach (var child in logicalChildren)
+ {
+ Platform.GetRenderer(child)?.Dispose();
+ }
+
+ if (Platform.GetRenderer(Element) == this)
+ {
+ Platform.SetRenderer(Element, (IVisualElementRenderer)null);
+ }
+ Element = default(TElement);
+ }
+
+ if (NativeView != null)
+ {
+ NativeView.Deleted -= NativeViewDeleted;
+ EnsureOpacityLayerIsDestroyed();
+ NativeView.Unrealize();
+ SetNativeView(null);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Notification that the associated element has changed.
+ /// </summary>
+ /// <param name="e">Event parameters.</param>
+ protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e)
+ {
+ if (null != e.OldElement)
+ {
+ e.OldElement.PropertyChanged -= _propertyChangedHandler;
+ e.OldElement.BatchCommitted -= _batchCommittedHandler;
+
+ e.OldElement.ChildAdded -= OnChildAdded;
+ e.OldElement.ChildRemoved -= OnChildRemoved;
+ e.OldElement.ChildrenReordered -= OnChildrenReordered;
+
+ e.OldElement.FocusChangeRequested -= OnFocusChangeRequested;
+
+ Settings.StartIgnoringBatchCommitted();
+ Element.Layout(new Rectangle(0, 0, -1, -1));
+ Settings.StopIgnoringBatchCommitted();
+
+ var controller = e.OldElement as IElementController;
+ if (controller != null && controller.EffectControlProvider == this)
+ {
+ controller.EffectControlProvider = null;
+ }
+ }
+
+ if (null != e.NewElement)
+ {
+ e.NewElement.PropertyChanged += _propertyChangedHandler;
+ e.NewElement.BatchCommitted += _batchCommittedHandler;
+
+ e.NewElement.ChildAdded += OnChildAdded;
+ e.NewElement.ChildRemoved += OnChildRemoved;
+ e.NewElement.ChildrenReordered += OnChildrenReordered;
+
+ e.NewElement.FocusChangeRequested += OnFocusChangeRequested;
+
+ UpdateAllProperties(true);
+
+ var controller = e.NewElement as IElementController;
+ if (controller != null)
+ {
+ controller.EffectControlProvider = this;
+ }
+ }
+
+ // TODO: handle the event
+ }
+
+ /// <summary>
+ /// Notification that the property of the associated element has changed.
+ /// </summary>
+ /// <param name="sender">Object which sent the notification.</param>
+ /// <param name="e">Event parameters.</param>
+ protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (Element.Batched)
+ {
+ if (e.PropertyName == VisualElement.XProperty.PropertyName ||
+ e.PropertyName == VisualElement.YProperty.PropertyName ||
+ e.PropertyName == VisualElement.WidthProperty.PropertyName ||
+ e.PropertyName == VisualElement.HeightProperty.PropertyName)
+ {
+ _flags |= VisualElementRendererFlags.NeedsLayout;
+ }
+ else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName ||
+ e.PropertyName == VisualElement.TranslationYProperty.PropertyName ||
+ e.PropertyName == VisualElement.RotationProperty.PropertyName ||
+ e.PropertyName == VisualElement.RotationXProperty.PropertyName ||
+ e.PropertyName == VisualElement.RotationYProperty.PropertyName ||
+ e.PropertyName == VisualElement.ScaleProperty.PropertyName ||
+ e.PropertyName == VisualElement.AnchorXProperty.PropertyName ||
+ e.PropertyName == VisualElement.AnchorYProperty.PropertyName)
+ {
+ _flags |= VisualElementRendererFlags.NeedsTransformation;
+ }
+ else
+ {
+ _batchedProperties.Add(e.PropertyName);
+ }
+ return;
+ }
+
+ Action<bool> init;
+ if (_propertyHandlersWithInit.TryGetValue(e.PropertyName, out init))
+ {
+ init(false);
+ }
+ else
+ {
+ Action handler;
+ if (_propertyHandlers.TryGetValue(e.PropertyName, out handler))
+ {
+ handler();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Updates the attached event handlers, sets the native control.
+ /// </summary>
+ protected void SetNativeControl(EvasObject control)
+ {
+ if (NativeView != null)
+ {
+ NativeView.Moved -= OnMoved;
+ NativeView.Deleted -= NativeViewDeleted;
+ EnsureOpacityLayerIsDestroyed();
+ }
+
+ Widget widget = NativeView as Widget;
+ if (widget != null)
+ {
+ widget.Focused -= OnFocused;
+ widget.Unfocused -= OnUnfocused;
+ }
+
+ SetNativeView(control);
+
+ if (NativeView != null)
+ {
+ NativeView.Deleted += NativeViewDeleted;
+ NativeView.Moved += OnMoved;
+ }
+
+ widget = NativeView as Widget;
+ if (widget != null)
+ {
+ widget.Focused += OnFocused;
+ widget.Unfocused += OnUnfocused;
+ }
+ }
+
+ void NativeViewDeleted(object sender, EventArgs e)
+ {
+ Dispose();
+ }
+
+ void OnBatchCommitted(object sender, EventArg<VisualElement> e)
+ {
+ if (_flags.HasFlag(VisualElementRendererFlags.NeedsLayout))
+ {
+ if (!Settings.IgnoreBatchCommitted)
+ {
+ UpdateLayout();
+ // UpdateLayout already updates transformation, clear NeedsTranformation flag then
+ _flags &= ~VisualElementRendererFlags.NeedsTransformation;
+ }
+ _flags ^= VisualElementRendererFlags.NeedsLayout;
+ }
+ if (_flags.HasFlag(VisualElementRendererFlags.NeedsTransformation))
+ {
+ ApplyTransformation();
+ _flags ^= VisualElementRendererFlags.NeedsTransformation;
+ }
+
+ foreach (string property in _batchedProperties)
+ {
+ OnElementPropertyChanged(this, new PropertyChangedEventArgs(property));
+ }
+ _batchedProperties.Clear();
+ }
+
+ /// <summary>
+ /// Registers a handler which is executed when specified property changes.
+ /// </summary>
+ /// <param name="property">Handled property.</param>
+ /// <param name="handler">Action to be executed when property changes.</param>
+ protected void RegisterPropertyHandler(BindableProperty property, Action<bool> handler)
+ {
+ RegisterPropertyHandler(property.PropertyName, handler);
+ }
+
+ /// <summary>
+ /// Registers a handler which is executed when specified property changes.
+ /// </summary>
+ /// <param name="name">Name of the handled property.</param>
+ /// <param name="handler">Action to be executed when property changes.</param>
+ protected void RegisterPropertyHandler(string name, Action<bool> handler)
+ {
+ _propertyHandlersWithInit.Add(name, handler);
+ }
+
+ /// <summary>
+ /// Registers a handler which is executed when specified property changes.
+ /// </summary>
+ /// <param name="property">Handled property.</param>
+ /// <param name="handler">Action to be executed when property changes.</param>
+ protected void RegisterPropertyHandler(BindableProperty property, Action handler)
+ {
+ RegisterPropertyHandler(property.PropertyName, handler);
+ }
+
+ /// <summary>
+ /// Registers a handler which is executed when specified property changes.
+ /// </summary>
+ /// <param name="name">Name of the handled property.</param>
+ /// <param name="handler">Action to be executed when property changes.</param>
+ protected void RegisterPropertyHandler(string name, Action handler)
+ {
+ _propertyHandlers.Add(name, handler);
+ }
+
+ /// <summary>
+ /// Updates all registered properties.
+ /// </summary>
+ /// <param name="initialization">If set to <c>true</c> the method is called for an uninitialized object.</param>
+ protected void UpdateAllProperties(bool initialization)
+ {
+ foreach (KeyValuePair<string, Action<bool>> kvp in _propertyHandlersWithInit)
+ {
+ kvp.Value(initialization);
+ }
+
+ foreach (KeyValuePair<string, Action> kvp in _propertyHandlers)
+ {
+ kvp.Value();
+ }
+ }
+
+ /// <summary>
+ /// Called when Element has been set and its native counterpart
+ /// is properly initialized.
+ /// </summary>
+ protected virtual void OnElementReady()
+ {
+ }
+
+ protected void DoLayout(Native.LayoutEventArgs e)
+ {
+ Settings.StartIgnoringBatchCommitted();
+ Element.Layout(new Rectangle(Element.X, Element.Y, e.Width, e.Height));
+ if (e.HasChanged)
+ {
+ UpdateLayout();
+ }
+ Settings.StopIgnoringBatchCommitted();
+ }
+
+ protected virtual Size MinimumSize()
+ {
+ return new Size();
+ }
+
+ /// <summary>
+ /// Calculates how much space this element should take, given how much room there is.
+ /// </summary>
+ /// <returns>a desired dimensions of the element</returns>
+ protected virtual ESize Measure(int availableWidth, int availableHeight)
+ {
+ return new ESize(NativeView.MinimumWidth, NativeView.MinimumHeight);
+ }
+
+ protected virtual void UpdateBackgroundColor()
+ {
+ if (NativeView is Widget)
+ {
+ (NativeView as Widget).BackgroundColor = Element.BackgroundColor.ToNative();
+ }
+ else
+ {
+ Log.Warn("{0} uses {1} which does not support background color", this, NativeView);
+ }
+ }
+
+ /// <summary>
+ /// Converts provided value to native dimension.
+ /// </summary>
+ /// <param name="v">value to be converted.</param>
+ /// <returns>converted value</returns>
+ protected static int ToNativeDimension(double v)
+ {
+ return (int)Math.Round(v);
+ }
+
+ static double ComputeAbsoluteX(VisualElement e)
+ {
+ return e.X + (e.RealParent is VisualElement ? Platform.GetRenderer(e.RealParent).NativeView.Geometry.X : 0.0);
+ }
+
+ static double ComputeAbsoluteY(VisualElement e)
+ {
+ return e.Y + (e.RealParent is VisualElement ? Platform.GetRenderer(e.RealParent).NativeView.Geometry.Y : 0.0);
+ }
+
+ /// <summary>
+ /// Handles focus events.
+ /// </summary>
+ void OnFocused(object sender, EventArgs e)
+ {
+ if (null != Element)
+ {
+ Element.SetValue(VisualElement.IsFocusedPropertyKey, true);
+ }
+ }
+
+ /// <summary>
+ /// Handles unfocus events.
+ /// </summary>
+ void OnUnfocused(object sender, EventArgs e)
+ {
+ if (null != Element)
+ {
+ Element.SetValue(VisualElement.IsFocusedPropertyKey, false);
+ }
+ }
+
+ /// <summary>
+ /// Sets the native control, updates the control's properties.
+ /// </summary>
+ void SetNativeView(EvasObject control)
+ {
+ _view = control;
+ }
+
+ /// <summary>
+ /// Adds a new child if it's derived from the VisualElement class. Otherwise this method does nothing.
+ /// </summary>
+ /// <param name="child">Child to be added.</param>
+ void AddChild(Element child)
+ {
+ VisualElement vElement = child as VisualElement;
+ if (vElement != null)
+ {
+ var childRenderer = Platform.GetOrCreateRenderer(vElement);
+
+ // if the native view can have children, attach the new child
+ if (NativeView is Native.IContainable<EvasObject>)
+ {
+ (NativeView as Native.IContainable<EvasObject>).Children.Add(childRenderer.NativeView);
+ }
+ }
+ }
+
+ void RemoveChild(VisualElement view)
+ {
+ var renderer = Platform.GetRenderer(view);
+ var containerObject = NativeView as Native.IContainable<EvasObject>;
+ if (containerObject != null)
+ {
+ containerObject.Children.Remove(renderer.NativeView);
+ }
+
+ renderer.Dispose();
+ }
+
+ void OnChildAdded(object sender, ElementEventArgs e)
+ {
+ var view = e.Element as VisualElement;
+ if (view != null)
+ {
+ AddChild(view);
+ }
+
+ // changing the order makes sense only in case of Layouts
+ if (Element is Layout)
+ {
+ IElementController controller = Element as IElementController;
+ if (controller.LogicalChildren[controller.LogicalChildren.Count - 1] != view)
+ {
+ EnsureChildOrder();
+ }
+ }
+ }
+
+ void OnChildRemoved(object sender, ElementEventArgs e)
+ {
+ var view = e.Element as VisualElement;
+ if (view != null)
+ {
+ RemoveChild(view);
+ }
+ }
+
+ void OnChildrenReordered(object sender, EventArgs e)
+ {
+ EnsureChildOrder();
+ Layout layout = Element as Layout;
+ if (layout != null)
+ {
+ layout.InvalidateMeasureInternal(Internals.InvalidationTrigger.MeasureChanged);
+ layout.ForceLayout();
+ }
+ }
+
+ void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
+ {
+ Widget widget = NativeView as Widget;
+ if (widget == null)
+ {
+ Log.Warn("{0} is not a widget, it cannot receive focus", NativeView);
+ return;
+ }
+
+ widget.SetFocus(e.Focus);
+ e.Result = true;
+ }
+
+ /// <summary>
+ /// On register the effect
+ /// </summary>
+ /// <param name="effect">The effect to register.</param>
+ void OnRegisterEffect(PlatformEffect effect)
+ {
+ effect.Container = Element.Parent == null ? null : Platform.GetRenderer(Element.Parent).NativeView;
+ effect.Control = NativeView;
+ }
+
+ void OnMoved(object sender, EventArgs e)
+ {
+ UpdateOpacityLayer();
+ ApplyTransformation();
+ _gestureHandler?.UpdateHitBox();
+ }
+
+ void EnsureChildOrder()
+ {
+ var logicalChildren = (Element as IElementController).LogicalChildren;
+ for (var i = logicalChildren.Count - 1; i >= 0; --i)
+ {
+ var element = logicalChildren[i] as VisualElement;
+ if (element != null)
+ {
+ Platform.GetRenderer(element).NativeView?.Lower();
+ }
+ }
+ }
+
+ void UpdateIsVisible()
+ {
+ if (null != NativeView)
+ {
+ if (Element.IsVisible)
+ {
+ NativeView.Show();
+ }
+ else
+ {
+ NativeView.Hide();
+ }
+ }
+ }
+
+ void UpdateOpacity()
+ {
+ if (null != NativeView)
+ {
+ if (Element.Opacity < 1.0)
+ {
+ EnsureOpacityLayerExists();
+
+ var alpha = (int)(Element.Opacity * 255.0);
+ _opacityLayer.Color = new EColor(255, 255, 255, alpha);
+ }
+ else
+ {
+ EnsureOpacityLayerIsDestroyed();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Updates the IsEnabled property.
+ /// </summary>
+ void UpdateIsEnabled()
+ {
+ Widget widget = NativeView as Widget;
+ if (widget != null)
+ {
+ widget.IsEnabled = Element.IsEnabled;
+ }
+ }
+
+ /// <summary>
+ /// Updates the InputTransparent property.
+ /// </summary>
+ void UpdateInputTransparent()
+ {
+ NativeView.PassEvents = Element.InputTransparent;
+ }
+
+ void ApplyRotation(EvasMap map, ERect geometry, ref bool changed)
+ {
+ var rotationX = Element.RotationX;
+ var rotationY = Element.RotationY;
+ var rotationZ = Element.Rotation;
+ var anchorX = Element.AnchorX;
+ var anchorY = Element.AnchorY;
+
+ // apply rotations
+ if (rotationX != 0 || rotationY != 0 || rotationZ != 0)
+ {
+ map.Rotate3D(rotationX, rotationY, rotationZ, (int)(geometry.X + geometry.Width * anchorX),
+ (int)(geometry.Y + geometry.Height * anchorY), 0);
+ changed = true;
+ }
+ }
+
+ void ApplyScale(EvasMap map, ERect geometry, ref bool changed)
+ {
+ var scale = Element.Scale;
+
+ // apply scale factor
+ if (scale != 1.0)
+ {
+ map.Zoom(scale, scale,
+ geometry.X + (int)(geometry.Width * Element.AnchorX),
+ geometry.Y + (int)(geometry.Height * Element.AnchorY));
+ changed = true;
+ }
+ }
+
+ void ApplyTranslation(EvasMap map, ERect geometry, ref bool changed)
+ {
+ var shiftX = ToNativeDimension(Element.TranslationX);
+ var shiftY = ToNativeDimension(Element.TranslationY);
+
+ // apply translation, i.e. move/shift the object a little
+ if (shiftX != 0 || shiftY != 0)
+ {
+ if (changed)
+ {
+ // special care is taken to apply the translation last
+ Point3D p;
+ for (int i = 0; i < 4; i++)
+ {
+ p = map.GetPointCoordinate(i);
+ p.X += shiftX;
+ p.Y += shiftY;
+ map.SetPointCoordinate(i, p);
+ }
+ }
+ else
+ {
+ // in case when we only need translation, then construct the map in a simpler way
+ geometry.X += shiftX;
+ geometry.Y += shiftY;
+ map.PopulatePoints(geometry, 0);
+
+ changed = true;
+ }
+ }
+ }
+
+ public void ApplyTransformation()
+ {
+ if (null == NativeView)
+ {
+ Log.Error("Trying to apply transformation to the non-existent native control");
+ return;
+ }
+
+ // prepare the EFL effect structure
+ ERect geometry = NativeView.Geometry;
+ EvasMap map = new EvasMap(4);
+ map.PopulatePoints(geometry, 0);
+
+ bool changed = false;
+ ApplyRotation(map, geometry, ref changed);
+ ApplyScale(map, geometry, ref changed);
+ ApplyTranslation(map, geometry, ref changed);
+
+ NativeView.IsMapEnabled = changed;
+
+ if (changed)
+ {
+ NativeView.EvasMap = map;
+
+ if (_opacityLayer != null)
+ {
+ _opacityLayer.IsMapEnabled = true;
+ _opacityLayer.EvasMap = map;
+ }
+ }
+ _gestureHandler?.UpdateHitBox();
+ }
+
+ protected virtual void UpdateOpacityLayer()
+ {
+ if (_opacityLayer != null)
+ {
+ _opacityLayer.Geometry = NativeView.Geometry;
+ NativeView.SetClip(_opacityLayer);
+ }
+ }
+
+ void EnsureOpacityLayerExists()
+ {
+ if (_opacityLayer == null)
+ {
+ _opacityLayer = new ERectangle(NativeView);
+ UpdateOpacityLayer();
+ _opacityLayer.Show();
+ }
+ }
+
+ void EnsureOpacityLayerIsDestroyed()
+ {
+ if (_opacityLayer != null)
+ {
+ NativeView.SetClip(null);
+ _opacityLayer.Unrealize();
+ _opacityLayer = null;
+ }
+ }
+ }
+
+ internal static class Settings
+ {
+ static int s_ignoreCount = 0;
+
+ public static bool IgnoreBatchCommitted
+ {
+ get
+ {
+ return s_ignoreCount != 0;
+ }
+ }
+
+ public static void StartIgnoringBatchCommitted()
+ {
+ ++s_ignoreCount;
+ }
+
+ public static void StopIgnoringBatchCommitted()
+ {
+ Debug.Assert(s_ignoreCount > 0);
+ --s_ignoreCount;
+ }
+ }
+}