diff options
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen/Renderers')
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; + } + } +} |