diff options
author | Kangho Hur <kangho.hur@samsung.com> | 2016-12-16 11:00:07 +0900 |
---|---|---|
committer | Kangho Hur <kangho.hur@samsung.com> | 2016-12-16 11:02:21 +0900 |
commit | 347db139a9b1b93cd07739e281631bf13031496b (patch) | |
tree | 1ab166cf542e4d0a217fa75db6b12607e2dfadc5 /Xamarin.Forms.Platform.Tizen/Native | |
parent | 8f1bb7b4b2ae8fd182c3874f208a12d549d230dd (diff) | |
download | xamarin-forms-347db139a9b1b93cd07739e281631bf13031496b.tar.gz xamarin-forms-347db139a9b1b93cd07739e281631bf13031496b.tar.bz2 xamarin-forms-347db139a9b1b93cd07739e281631bf13031496b.zip |
Add Tizen backend renderersubmit/tizen_mobile/20161216.021857accepted/tizen/mobile/20161216.084444
- Xamarin.Forms.Platform.Tizen has been added
- Xamarin.Forms.Maps.Tizen has been added
- RPM build spec has been added
Change-Id: I0021e0f040d97345affc87512ee0f6ce437f4e6d
Diffstat (limited to 'Xamarin.Forms.Platform.Tizen/Native')
29 files changed, 4590 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Tizen/Native/Box.cs b/Xamarin.Forms.Platform.Tizen/Native/Box.cs new file mode 100644 index 00000000..f22d27b5 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Box.cs @@ -0,0 +1,67 @@ +using System; +using ElmSharp; +using EBox = ElmSharp.Box; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.Box class with functionality useful to Xamarin.Forms renderer. + /// </summary> + /// <remarks> + /// This class overrides the layout mechanism. Instead of using the native layout, + /// <c>LayoutUpdated</c> event is sent. + /// </remarks> + public class Box : EBox + { + /// <summary> + /// The last processed geometry of the Box which was reported from the native layer. + /// </summary> + Rect _previousGeometry; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Box"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public Box(EvasObject parent) : base(parent) + { + Resized += (sender, e) => { NotifyOnLayout(); }; + SetLayoutCallback(() => { NotifyOnLayout(); }); + } + + /// <summary> + /// Notifies that the layout has been updated. + /// </summary> + public event EventHandler<LayoutEventArgs> LayoutUpdated; + + /// <summary> + /// Triggers the <c>LayoutUpdated</c> event. + /// </summary> + /// <remarks> + /// This method is called whenever there is a possibility that the size and/or position has been changed. + /// </remarks> + void NotifyOnLayout() + { + var g = Geometry; + + if (0 == g.Width || 0 == g.Height) + { + // ignore irrelevant dimensions + return; + } + if (null != LayoutUpdated) + { + LayoutUpdated(this, new LayoutEventArgs() + { + HasChanged = g != _previousGeometry, + X = g.X, + Y = g.Y, + Width = g.Width, + Height = g.Height, + } + ); + } + + _previousGeometry = g; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Button.cs b/Xamarin.Forms.Platform.Tizen/Native/Button.cs new file mode 100644 index 00000000..8f85da63 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Button.cs @@ -0,0 +1,303 @@ +using System; +using ElmSharp; +using EButton = ElmSharp.Button; +using ESize = ElmSharp.Size; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the EButton control, providing basic formatting features, + /// i.e. font color, size, additional image. + /// </summary> + public class Button : EButton, IMeasurable + { + /// <summary> + /// Holds the formatted text of the button. + /// </summary> + readonly Span _span = new Span(); + + /// <summary> + /// The internal padding of the button, helps to determine the size. + /// </summary> + readonly ESize _internalPadding; + + /// <summary> + /// Optional image, if set will be drawn on the button. + /// </summary> + Image _image; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Button"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public Button(EvasObject parent) : base(parent) + { + _internalPadding = GetInternalPadding(); + } + + /// <summary> + /// Gets or sets the button's text. + /// </summary> + /// <value>The text.</value> + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the text background. + /// </summary> + /// <value>The color of the text background.</value> + public EColor TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font family. + /// </summary> + /// <value>The font family.</value> + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font attributes. + /// </summary> + /// <value>The font attributes.</value> + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the size of the font. + /// </summary> + /// <value>The size of the font.</value> + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the image to be displayed next to the button's text. + /// </summary> + /// <value>The image displayed on the button.</value> + public Image Image + { + get + { + return _image; + } + + set + { + if (value != _image) + { + ApplyImage(value); + } + } + } + + /// <summary> + /// Implementation of the IMeasurable.Measure() method. + /// </summary> + public ESize Measure(int availableWidth, int availableHeight) + { + var size = Geometry; + + // resize the control using the whole available width + Resize(availableWidth, size.Height); + + // measure the button's text, use it as a hint for the size + var rawSize = Native.TextHelper.GetRawTextBlockSize(this); + var formattedSize = Native.TextHelper.GetFormattedTextBlockSize(this); + + // restore the original size + Resize(size.Width, size.Height); + + var padding = _internalPadding; + + if (rawSize.Width > availableWidth) + { + // if the raw text width is larger than the available width, use + // either formatted size or internal padding, whichever is bigger + return new ESize() + { + Width = Math.Max(padding.Width, formattedSize.Width), + Height = Math.Max(padding.Height, Math.Min(formattedSize.Height, Math.Max(rawSize.Height, availableHeight))), + }; + } + else + { + // otherwise use the formatted size along with padding + return new ESize() + { + Width = padding.Width + formattedSize.Width, + Height = Math.Max(padding.Height, formattedSize.Height), + }; + } + } + + /// <summary> + /// Applies the button's text and its style. + /// </summary> + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + /// <summary> + /// Sets the button's internal text and its style. + /// </summary> + /// <param name="formattedText">Formatted text, supports HTML tags.</param> + /// <param name="textStyle">Style applied to the formattedText.</param> + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + string emission = "elm,state,text,visible"; + + if (string.IsNullOrEmpty(formattedText)) + { + formattedText = null; + textStyle = null; + emission = "elm,state,text,hidden"; + } + + base.Text = formattedText; + + var textblock = EdjeObject["elm.text"]; + + if (textblock != null) + { + textblock.TextStyle = textStyle; + } + + EdjeObject.EmitSignal(emission, "elm"); + } + + /// <summary> + /// Gets the internal padding of the button. + /// </summary> + /// <returns>The internal padding.</returns> + ESize GetInternalPadding() + { + var edje = EdjeObject; + + return new ESize + { + Width = (edje["padding_top_left"]?.Geometry.Width ?? 64) + (edje["padding_bottom_right"]?.Geometry.Width ?? 64), + Height = edje["bg"]?.Geometry.Height ?? 64 + }; + } + + /// <summary> + /// Applies the image to be displayed on the button. If value is <c>null</c>, + /// image will be removed. + /// </summary> + /// <param name="image">Image to be displayed or null.</param> + void ApplyImage(Image image) + { + _image = image; + + SetInternalImage(); + } + + /// <summary> + /// Sets the internal image. If value is <c>null</c>, image will be removed. + /// </summary> + void SetInternalImage() + { + if (_image == null) + { + SetPartContent("icon", null); + } + else + { + SetPartContent("icon", _image); + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Canvas.cs b/Xamarin.Forms.Platform.Tizen/Native/Canvas.cs new file mode 100644 index 00000000..c345abf2 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Canvas.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// A Canvas provides a class which can be a container for other controls. + /// </summary> + /// <remarks> + /// This class is used as a container view for Layouts from Xamarin.Forms.Platform.Tizen framework. + /// It is used for implementing xamarin pages and layouts. + /// </remarks> + public class Canvas : Box, IContainable<EvasObject> + { + /// <summary> + /// The list of Views. + /// </summary> + readonly ObservableCollection<EvasObject> _children = new ObservableCollection<EvasObject>(); + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Canvas"/> class. + /// </summary> + /// <remarks>Canvas doesn't support replacing its children, this will be ignored.</remarks> + /// <param name="parent">Parent of this instance.</param> + public Canvas(EvasObject parent) : base(parent) + { + _children.CollectionChanged += (o, e) => + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (var v in e.NewItems) + { + var view = v as EvasObject; + if (null != view) + { + OnAdd(view); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (var v in e.OldItems) + { + var view = v as EvasObject; + if (null != view) + { + OnRemove(view); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + OnRemoveAll(); + } + }; + } + + /// <summary> + /// Gets list of native elements that are placed in the canvas. + /// </summary> + public IList<EvasObject> Children + { + get + { + return _children; + } + } + + /// <summary> + /// Provides destruction for native element and contained elements. + /// </summary> + protected override void OnUnrealize() + { + foreach (var child in _children) + { + child.Unrealize(); + } + + base.OnUnrealize(); + } + + /// <summary> + /// Adds a new child to a container. + /// </summary> + /// <param name="view">Native element which will be added</param> + void OnAdd(EvasObject view) + { + PackEnd(view); + } + + /// <summary> + /// Removes a child from a container. + /// </summary> + /// <param name="view">Child element to be removed from canvas</param> + void OnRemove(EvasObject view) + { + UnPack(view); + } + + /// <summary> + /// Removes all children from a canvas. + /// </summary> + void OnRemoveAll() + { + UnPackAll(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs b/Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs new file mode 100644 index 00000000..32f24a0d --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// A basic page which can hold a single view. + /// </summary> + public class ContentPage : Background, IContainable<EvasObject> + { + /// <summary> + /// The name of the part to be used when setting content. + /// </summary> + public const string ContentPartName = "overlay"; + + /// <summary> + /// Exposes the Children property, mapping it to the _canvas' Children property. + /// </summary> + public IList<EvasObject> Children => _canvas.Children; + + /// <summary> + /// The canvas, used as a container for other objects. + /// </summary> + /// <remarks> + /// The canvas holds all the Views that the ContentPage is composed of. + /// </remarks> + internal Canvas _canvas; + + /// <summary> + /// Initializes a new instance of the ContentPage class. + /// </summary> + public ContentPage(EvasObject parent) : base(parent) + { + _canvas = new Canvas(this); + SetPartContent(ContentPartName, _canvas); + } + + /// <summary> + /// Gets or sets the title. + /// </summary> + /// <value>The current title.p</value> + public string Title + { + get; + set; + } + + /// <summary> + /// Allows custom handling of events emitted when the layout has been updated. + /// </summary> + public event EventHandler<LayoutEventArgs> LayoutUpdated + { + add + { + _canvas.LayoutUpdated += value; + } + remove + { + _canvas.LayoutUpdated -= value; + } + } + + /// <summary> + /// Handles the disposing of a ContentPage + /// </summary> + /// <remarks> + /// Takes the proper care of discarding the canvas, then calls the base method. + /// </remarks> + protected override void OnUnrealize() + { + _canvas.Unrealize(); + base.OnUnrealize(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs b/Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs new file mode 100644 index 00000000..c238e9e9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs @@ -0,0 +1,31 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Event arguments for <see cref="DatePicker.DateChanged"/> event. + /// </summary> + public class DateChangedEventArgs : EventArgs + { + /// <summary> + /// The date that was on the element at the time that the user selected it. + /// </summary> + public DateTime OldDate { get; private set; } + + /// <summary> + /// The date that the user entered. + /// </summary> + public DateTime NewDate { get; private set; } + + /// <summary> + /// Creates a new <see cref="DateChangedEventArgs"/> object that represents a change from <paramref name="oldDate"/> to <paramref name="newDate"/>. + /// </summary> + /// <param name="oldDate">Old date of <see cref="DatePicker"/>.</param> + /// <param name="newDate">Current date of <see cref="DatePicker"/>.</param> + public DateChangedEventArgs(DateTime oldDate, DateTime newDate) + { + this.OldDate = oldDate; + this.NewDate = newDate; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs b/Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs new file mode 100644 index 00000000..fabd5269 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs @@ -0,0 +1,130 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.DateTimeSelector class with functionality useful to renderer. + /// </summary> + public class DatePicker : DateTimeSelector + { + const string DateLayoutStyle = "date_layout"; + const string DefaultEFLFormat = "%d/%b/%Y"; + static readonly DateTime s_defaultMaximumDate = new DateTime(2037, 12, 31); + static readonly DateTime s_defaultMinimumDate = new DateTime(1970, 1, 1); + DateTime _date; + DateTime _maxDate; + DateTime _minDate; + + /// <summary> + /// Initializes a new instance of the <see cref="DatePicker"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public DatePicker(EvasObject parent) : base(parent) + { + SetFieldVisible(DateTimeFieldType.Hour, false); + Style = DateLayoutStyle; + ApplyDate(Date); + ApplyMinDate(s_defaultMinimumDate); + ApplyMaxDate(s_defaultMaximumDate); + //TODO use date format currently set on the platform + Format = DefaultEFLFormat; + + DateTimeChanged += (sender, e) => + { + Date = e.NewDate; + }; + } + + /// <summary> + /// Gets or sets the displayed date. + /// </summary> + public DateTime Date + { + get + { + return _date; + } + set + { + if (_date != value) + { + ApplyDate(value); + } + } + } + + /// <summary> + /// Gets of sets the highest date selectable for this <see cref="DatePicker"/>. + /// </summary> + /// <remarks> + /// Default value is 31st Dec, 2037. + /// </remarks> + public DateTime MaximumDate + { + get + { + return _maxDate; + } + set + { + if (_maxDate != value) + { + ApplyMaxDate(value); + } + } + } + + /// <summary> + /// Gets of sets the lowest date selectable for this <see cref="DatePicker"/>. + /// </summary> + /// <remarks> + /// Default value is 1st Jan, 1970. + /// </remarks> + public DateTime MinimumDate + { + get + { + return _minDate; + } + set + { + if (_minDate != value) + { + ApplyMinDate(value); + } + } + } + + /// <summary> + /// Sets the <c>DateTime</c> property according to the given <paramref name="date"/>. + /// </summary> + /// <param name="date">The date value to be applied to the date picker.</param> + void ApplyDate(DateTime date) + { + _date = date; + DateTime = date; + } + + /// <summary> + /// Sets the <c>MaximumDateTime</c> property according to the given <paramref name="maxDate"/>. + /// </summary> + /// <param name="maxDate">The maximum date value to be applied to the date picker.</param> + void ApplyMaxDate(DateTime maxDate) + { + _maxDate = maxDate; + MaximumDateTime = maxDate; + } + + /// <summary> + /// Sets the <c>MinimumDateTime</c> property according to the given <paramref name="minDate"/>. + /// </summary> + /// <param name="minDate">The minimum date value to be applied to the date picker.</param> + void ApplyMinDate(DateTime minDate) + { + _minDate = minDate; + MinimumDateTime = minDate; + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs b/Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs new file mode 100644 index 00000000..64b1b3b3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs @@ -0,0 +1,100 @@ +using System; +using ElmSharp; +using EButton = ElmSharp.Button; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + public class DateTimePickerDialog : Dialog + { + DateTimeSelector _dateTimePicker; + EvasObject _parent; + + /// <summary> + /// Creates a dialog window. + /// </summary> + public DateTimePickerDialog(EvasObject parent) : base(parent) + { + _parent = parent; + Initialize(); + } + + /// <summary> + /// Occurs when the date of this dialog has changed. + /// </summary> + public event EventHandler<DateChangedEventArgs> DateTimeChanged; + + /// <summary> + /// Gets the <see cref="DateTimePicker"/> contained in this dialog. + /// </summary> + public DateTimeSelector DateTimePicker + { + get + { + return _dateTimePicker; + } + private set + { + if (_dateTimePicker != value) + { + ApplyDateTimePicker(value); + } + } + } + + /// <summary> + /// Creates date picker in dialog window. + /// </summary> + public void InitializeDatePicker(DateTime date, DateTime minimumDate, DateTime maximumDate) + { + var datePicker = new DatePicker(this) + { + Date = date, + MinimumDate = minimumDate, + MaximumDate = maximumDate + }; + Content = DateTimePicker = datePicker; + } + + /// <summary> + /// Creates time picker in dialog window. + /// </summary> + public void InitializeTimePicker(TimeSpan time, string format) + { + var timePicker = new TimePicker(this) + { + Time = time, + DateTimeFormat = format + }; + Content = DateTimePicker = timePicker; + } + + void ApplyDateTimePicker(DateTimeSelector dateTimePicker) + { + _dateTimePicker = dateTimePicker; + Content = _dateTimePicker; + } + + void Initialize() + { + //TODO need to add internationalization support + PositiveButton = new EButton(_parent) { Text = "Set" }; + PositiveButton.Clicked += (s, e) => + { + DateTime oldDate = DateTimePicker.DateTime; + DateTimeChanged?.Invoke(this, new DateChangedEventArgs(oldDate, DateTimePicker.DateTime)); + Hide(); + }; + + //TODO need to add internationalization support + NegativeButton = new EButton(_parent) { Text = "Cancel" }; + NegativeButton.Clicked += (s, e) => + { + Hide(); + }; + BackButtonPressed += (object s, EventArgs e) => + { + Hide(); + }; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Dialog.cs b/Xamarin.Forms.Platform.Tizen/Native/Dialog.cs new file mode 100755 index 00000000..e616ff5a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Dialog.cs @@ -0,0 +1,276 @@ +using System; +using ElmSharp; +using EButton = ElmSharp.Button; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Base class for Dialogs. + /// A dialog is a small window that prompts the user to make a decision or enter additional information. + /// </summary> + public class Dialog : Popup + { + EButton _positiveButton; + EButton _neutralButton; + EButton _negativeButton; + EvasObject _content; + string _title; + + /// <summary> + /// Creates a dialog window that uses the default dialog theme. + /// </summary> + public Dialog(EvasObject parent) : base(parent) + { + Initialize(); + } + + /// <summary> + /// Occurs when the hardware Back button is pressed. + /// </summary> + public event EventHandler BackButtonPressed; + + /// <summary> + /// Occurs whenever the dialog is first displayed. + /// </summary> + public event EventHandler Shown; + + /// <summary> + /// Enumerates the three valid positions of a dialog button. + /// </summary> + enum ButtonPosition + { + Positive, + Neutral, + Negative + } + + /// <summary> + /// Gets or sets the title of the dialog + /// </summary> + public string Title + { + get + { + return _title; + } + set + { + if (_title != value) + { + ApplyTitle(value); + } + } + } + + /// <summary> + /// Gets or sets the content to display in that dialog. + /// </summary> + public EvasObject Content + { + get + { + return _content; + } + set + { + if (_content != value) + { + ApplyContent(value); + } + } + } + + /// <summary> + /// Gets or sets the positive button used in the dialog + /// </summary> + public EButton PositiveButton + { + get + { + return _positiveButton; + } + set + { + if (_positiveButton != value) + { + ApplyButton(ButtonPosition.Positive, value); + } + } + } + + /// <summary> + /// Gets or sets the neutral button used in the dialog + /// </summary> + public EButton NeutralButton + { + get + { + return _neutralButton; + } + set + { + if (_neutralButton != value) + { + ApplyButton(ButtonPosition.Neutral, value); + } + } + } + + /// <summary> + /// Gets or sets the negative button used in the dialog + /// </summary> + public EButton NegativeButton + { + get + { + return _negativeButton; + } + set + { + if (_negativeButton != value) + { + ApplyButton(ButtonPosition.Negative, value); + } + } + } + + /// <summary> + /// Starts the dialog and displays it on screen. + /// </summary> + public new void Show() + { + base.Show(); + Shown?.Invoke(this, EventArgs.Empty); + } + + /// <summary> + /// Handles the disposing of a dialog widget. + /// </summary> + protected override void OnUnrealize() + { + _content?.Unrealize(); + + ApplyButton(ButtonPosition.Positive, null); + ApplyButton(ButtonPosition.Neutral, null); + ApplyButton(ButtonPosition.Negative, null); + ApplyContent(null); + + UngrabBackKey(); + + base.OnUnrealize(); + } + + /// <summary> + /// Called when the dialog is shown. + /// </summary> + /// <remarks>When shown, the dialog will register itself for the back key press event handling.</remarks> + protected virtual void OnShown() + { + GrabBackKey(); + } + + /// <summary> + /// Called when the dialog is dismissed. + /// </summary> + /// <remarks>When dismissed, the dialog will unregister itself from the back key press event handling.</remarks> + protected virtual void OnDismissed() + { + UngrabBackKey(); + } + + /// <summary> + /// Handles the initialization process. + /// </summary> + /// <remarks>Creates handlers for vital events</remarks> + void Initialize() + { + // Adds a handler for the Dismissed event. + // In effect, unregisters this instance from being affected by the hardware back key presses. + Dismissed += (s, e) => + { + OnDismissed(); + }; + + // Adds a handler for the Shown event. + // In effect, registers this instance to be affected by the hardware back key presses. + Shown += (s, e) => + { + OnShown(); + }; + + // Adds a handler for the KeyUp event. + // The handler checks whether the key just pressed is a back key + // and if that is the case, invokes the back button press handler of this instance. + KeyUp += (s, e) => + { + if (e.KeyName == EvasKeyEventArgs.PlatformBackButtonName) + BackButtonPressed?.Invoke(this, EventArgs.Empty); + }; + } + + /// <summary> + /// Changes the dialog title. + /// </summary> + /// <param name="title">New dialog title.</param> + void ApplyTitle(string title) + { + _title = title; + + SetPartText("title,text", _title); + } + + /// <summary> + /// Puts the button in one of the three available slots. + /// </summary> + /// <param name="position">The slot to be occupied by the button expressed as a <see cref="ButtonPosition"/></param> + /// <param name="button">The new button.</param> + void ApplyButton(ButtonPosition position, EButton button) + { + switch (position) + { + case ButtonPosition.Positive: + _positiveButton = button; + SetPartContent("button3", _positiveButton, true); + break; + + case ButtonPosition.Neutral: + _neutralButton = button; + SetPartContent("button2", _neutralButton, true); + break; + + case ButtonPosition.Negative: + _negativeButton = button; + SetPartContent("button1", _negativeButton, true); + break; + } + } + + /// <summary> + /// Updates the content of the dialog. + /// </summary> + /// <param name="content">New dialog content.</param> + void ApplyContent(EvasObject content) + { + _content = content; + + SetPartContent("default", _content, true); + } + + /// <summary> + /// Registers this instance to be affected by pressing the hardware back key. + /// </summary> + void GrabBackKey() + { + KeyGrab(EvasKeyEventArgs.PlatformBackButtonName, true); + } + + /// <summary> + /// Unregisters this instance from being affected by pressing the hardware back key. + /// </summary> + void UngrabBackKey() + { + KeyUngrab(EvasKeyEventArgs.PlatformBackButtonName); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs b/Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs new file mode 100644 index 00000000..efb09529 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs @@ -0,0 +1,36 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Enumeration for the orientation of a rectangular screen. + /// </summary> + [Flags] + public enum DisplayOrientations + { + /// <summary> + /// No display orientation is specified. + /// </summary> + None = 0, + + /// <summary> + /// The display is oriented in a natural position. + /// </summary> + Portrait = 1, + + /// <summary> + /// The display's left side is at the top. + /// </summary> + Landscape = 2, + + /// <summary> + /// The display is upside down. + /// </summary> + PortraitFlipped = 4, + + /// <summary> + /// The display's right side is at the top. + /// </summary> + LandscapeFlipped = 8 + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Entry.cs b/Xamarin.Forms.Platform.Tizen/Native/Entry.cs new file mode 100644 index 00000000..808155c5 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Entry.cs @@ -0,0 +1,396 @@ +using System; +using ElmSharp; +using EEntry = ElmSharp.Entry; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the Entry control, providing basic formatting features, + /// i.e. font color, size, placeholder. + /// </summary> + public class Entry : EEntry, IMeasurable + { + /// <summary> + /// Holds the formatted text of the entry. + /// </summary> + readonly Span _span = new Span(); + + /// <summary> + /// Holds the formatted text of the placeholder. + /// </summary> + readonly Span _placeholderSpan = new Span(); + + /// <summary> + /// Helps to detect whether the text change was initiated by the user + /// or via the Text property. + /// </summary> + int _changedByUserCallbackDepth; + + /// <summary> + /// The type of the keyboard used by the entry. + /// </summary> + Keyboard _keyboard; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Entry"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public Entry(EvasObject parent) : base(parent) + { + Scrollable = true; + + ChangedByUser += (s, e) => + { + _changedByUserCallbackDepth++; + + Text = GetInternalText(); + + _changedByUserCallbackDepth--; + }; + + ApplyKeyboard(Keyboard.Normal); + } + + /// <summary> + /// Occurs when the text has changed. + /// </summary> + public event EventHandler<TextChangedEventArgs> TextChanged; + + /// <summary> + /// Gets or sets the text. + /// </summary> + /// <value>The text.</value> + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + var old = _span.Text; + _span.Text = value; + ApplyTextAndStyle(); + Device.StartTimer(TimeSpan.FromTicks(1), () => + { + TextChanged?.Invoke(this, new TextChangedEventArgs(old, value)); + return false; + }); + } + } + } + + /// <summary> + /// Gets or sets the color of the text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font family of the text and the placeholder. + /// </summary> + /// <value>The font family of the text and the placeholder.</value> + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontFamily = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font attributes of the text and the placeholder. + /// </summary> + /// <value>The font attributes of the text and the placeholder.</value> + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontAttributes = value; + ApplyPlaceholderAndStyle(); + } + } + } + + + /// <summary> + /// Gets or sets the size of the font of both text and placeholder. + /// </summary> + /// <value>The size of the font of both text and placeholder.</value> + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontSize = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the horizontal text alignment of both text and placeholder. + /// </summary> + /// <value>The horizontal text alignment of both text and placeholder.</value> + public TextAlignment HorizontalTextAlignment + { + get + { + return _span.HorizontalTextAlignment; + } + + set + { + if (value != _span.HorizontalTextAlignment) + { + _span.HorizontalTextAlignment = value; + ApplyTextAndStyle(); + + _placeholderSpan.HorizontalTextAlignment = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the keyboard type used by the entry. + /// </summary> + /// <value>The keyboard type.</value> + public Keyboard Keyboard + { + get + { + return _keyboard; + } + + set + { + if (value != _keyboard) + { + ApplyKeyboard(value); + } + } + } + + /// <summary> + /// Gets or sets the placeholder's text. + /// </summary> + /// <value>The placeholder's text.</value> + public string Placeholder + { + get + { + return _placeholderSpan.Text; + } + + set + { + if (value != _placeholderSpan.Text) + { + _placeholderSpan.Text = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the placeholder's text. + /// </summary> + /// <value>The color of the placeholder's text.</value> + public EColor PlaceholderColor + { + get + { + return _placeholderSpan.ForegroundColor; + } + + set + { + if (!_placeholderSpan.ForegroundColor.Equals(value)) + { + _placeholderSpan.ForegroundColor = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Implementation of the IMeasurable.Measure() method. + /// </summary> + public ESize Measure(int availableWidth, int availableHeight) + { + var originalSize = Geometry; + // resize the control using the whole available width + Resize(availableWidth, originalSize.Height); + + ESize rawSize; + ESize formattedSize; + var edjeTextBlock = EdjeObject["elm.guide"]; + + // if there's no text, but there's a placeholder, use it for measurements + if (string.IsNullOrEmpty(Text) && !string.IsNullOrEmpty(Placeholder) && edjeTextBlock != null) + { + rawSize = edjeTextBlock.TextBlockNativeSize; + formattedSize = edjeTextBlock.TextBlockFormattedSize; + } + else + { + // there's text in the entry, use it instead + rawSize = Native.TextHelper.GetRawTextBlockSize(this); + formattedSize = Native.TextHelper.GetFormattedTextBlockSize(this); + } + + // restore the original size + Resize(originalSize.Width, originalSize.Height); + + // Set bottom padding for lower case letters that have segments below the bottom line of text (g, j, p, q, y). + var verticalPadding = (int)Math.Ceiling(0.05 * FontSize); + var horizontalPadding = (int)Math.Ceiling(0.2 * FontSize); + rawSize.Height += verticalPadding; + formattedSize.Height += verticalPadding; + formattedSize.Width += horizontalPadding; + + // if the raw text width is larger than available width, we use the available width, + // while height is set to the smallest height value + if (rawSize.Width > availableWidth) + { + return new ESize + { + Width = availableWidth, + Height = Math.Min(formattedSize.Height, Math.Max(rawSize.Height, availableHeight)), + }; + } + else + { + // width is fine, return the formatted text size + return formattedSize; + } + } + + /// <summary> + /// Applies entry's text and its style. + /// </summary> + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + /// <summary> + /// Sets entry's internal text and its style. + /// </summary> + /// <param name="formattedText">Formatted text, supports HTML tags.</param> + /// <param name="textStyle">Style applied to the formattedText.</param> + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + if (_changedByUserCallbackDepth == 0) + { + base.Text = formattedText; + base.TextStyle = textStyle; + } + } + + /// <summary> + /// Gets the internal text representation of the entry. + /// </summary> + /// <returns>The internal text representation.</returns> + string GetInternalText() + { + return Entry.ConvertMarkupToUtf8(base.Text); + } + + /// <summary> + /// Applies the keyboard type to be used by the entry. + /// </summary> + /// <param name="keyboard">Keyboard type to be used.</param> + void ApplyKeyboard(Keyboard keyboard) + { + SetInternalKeyboard(_keyboard = keyboard); + } + + /// <summary> + /// Configures the ElmSharp.Entry with specified keyboard type and displays + /// the keyboard automatically unless the provided type is Keyboard.None. + /// </summary> + /// <param name="keyboard">Keyboard type to be used.</param> + void SetInternalKeyboard(Keyboard keyboard) + { + if (keyboard == Keyboard.None) + { + SetInputPanelEnabled(false); + } + else + { + SetInputPanelEnabled(true); + SetInputPanelLayout((InputPanelLayout)keyboard); + } + } + + /// <summary> + /// Applies placeholders's text and its style. + /// </summary> + void ApplyPlaceholderAndStyle() + { + SetInternalPlaceholderAndStyle(_placeholderSpan.GetMarkupText()); + } + + /// <summary> + /// Sets placeholder's internal text and style. + /// </summary> + /// <param name="markupText">Markup text to be used as a placeholder.</param> + void SetInternalPlaceholderAndStyle(string markupText) + { + SetPartText("guide", markupText ?? ""); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs b/Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs new file mode 100644 index 00000000..f782efd1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Represents a text with attributes applied to some parts. + /// </summary> + /// <remarks> + /// Formatted string consists of spans that represent text segments with various attributes applied. + /// </remarks> + public class FormattedString + { + /// <summary> + /// A flag indicating whether the instance contains just a plain string without any formatting. + /// </summary> + /// <remarks> + /// <c>true</c> if the instance contains an unformatted string. + /// </remarks> + readonly bool _just_string; + + /// <summary> + /// Holds the unformatted string. + /// </summary> + /// <remarks> + /// The contents of this field are accurate if and only if the _just_string flag is set. + /// </remarks> + readonly string _string; + + /// <summary> + /// Holds the collection of span elements. + /// </summary> + /// <remarks> + /// Span elements are basically chunks of text with uniform formatting. + /// </remarks> + readonly ObservableCollection<Span> _spans; + + /// <summary> + /// Returns the collection of span elements. + /// </summary> + public IList<Span> Spans { get { return _spans; } } + + /// <summary> + /// Creates a new FormattedString instance with an empty string. + /// </summary> + public FormattedString() + { + _just_string = false; + _spans = new ObservableCollection<Span>(); + } + + /// <summary> + /// Creates a new FormattedString instance based on given <c>str</c>. + /// </summary> + /// <param name="str"> + /// A string used to make a new FormattedString instance. + /// </param> + public FormattedString(string str) + { + _just_string = true; + _string = str; + } + + /// <summary> + /// Returns the plain text of the FormattedString as an unformatted string. + /// </summary> + /// <returns> + /// The text content of the FormattedString without any format applied. + /// </returns> + public override string ToString() + { + if (_just_string) + { + return _string; + } + else + { + return string.Concat(from span in this.Spans select span.Text); + } + } + + /// <summary> + /// Returns the markup text representation of the FormattedString instance. + /// </summary> + /// <returns>The string containing a markup text.</returns> + internal string ToMarkupString() + { + if (_just_string) + { + return _string; + } + else + { + return string.Concat(from span in this.Spans select span.GetMarkupText()); + } + } + + /// <summary> + /// Casts the FormattedString to a string. + /// </summary> + /// <param name="formatted">The FormattedString instance which will be used for the conversion.</param> + public static explicit operator string (FormattedString formatted) + { + return formatted.ToString(); + } + + /// <summary> + /// Casts the string to a FormattedString. + /// </summary> + /// <param name="text">The text which will be put in a new FormattedString instance.</param> + public static implicit operator FormattedString(string text) + { + return new FormattedString(text); + } + + /// <summary> + /// Casts the Span to a FormattedString. + /// </summary> + /// <param name="span">The span which will be used for the conversion.</param> + public static implicit operator FormattedString(Span span) + { + return new FormattedString() + { + Spans = { span } + }; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/IContainable.cs b/Xamarin.Forms.Platform.Tizen/Native/IContainable.cs new file mode 100644 index 00000000..dfee0cbe --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/IContainable.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Interface defining methods for managing elements of the container. + /// </summary> + /// <typeparam name="T">The type of element that can be added to the container.</typeparam> + public interface IContainable<T> + { + /// <summary> + /// The children collection of an element. + /// </summary> + IList<T> Children { get; } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs b/Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs new file mode 100644 index 00000000..13ee1c12 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs @@ -0,0 +1,20 @@ +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Interface of the controls which can measure their size taking into + /// account the available area. + /// </summary> + public interface IMeasurable + { + /// <summary> + /// Measures the size of the control in order to fit it into the + /// available area. + /// </summary> + /// <param name="availableWidth">Available width.</param> + /// <param name="availableHeight">Available height.</param> + /// <returns>Size of the control that fits the available area.</returns> + ESize Measure(int availableWidth, int availableHeight); + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ITextable.cs b/Xamarin.Forms.Platform.Tizen/Native/ITextable.cs new file mode 100644 index 00000000..876646eb --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ITextable.cs @@ -0,0 +1,68 @@ +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Interface defining properties of formattable text. + /// </summary> + public interface ITextable + { + /// <summary> + /// Get or sets the formatted text. + /// </summary> + FormattedString FormattedText { get; set; } + + /// <summary> + /// Gets or sets the text. + /// </summary> + string Text { get; set; } + + /// <summary> + /// Gets or sets the color for the text. + /// </summary> + EColor TextColor { get; set; } + + /// <summary> + /// Gets or sets the background color for the text. + /// </summary> + EColor TextBackgroundColor { get; set; } + + /// <summary> + /// Gets or sets the font family for the text. + /// </summary> + string FontFamily { get; set; } + + /// <summary> + /// Gets or sets the font attributes for the text. + /// See <see cref="FontAttributes"/> for information about FontAttributes. + /// </summary> + FontAttributes FontAttributes { get; set; } + + /// <summary> + /// Gets or sets the font size for the text. + /// </summary> + double FontSize { get; set; } + + /// <summary> + /// Gets or sets the horizontal alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + TextAlignment HorizontalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the vertical alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + TextAlignment VerticalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has underline. + /// </summary> + bool Underline { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has strike line though it. + /// </summary> + bool Strikethrough { get; set; } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Image.cs b/Xamarin.Forms.Platform.Tizen/Native/Image.cs new file mode 100644 index 00000000..7321c5b1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Image.cs @@ -0,0 +1,124 @@ +using System.Threading.Tasks; +using ElmSharp; +using EImage = ElmSharp.Image; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.Image class with functionality useful to renderer. + /// </summary> + public class Image : EImage, IMeasurable + { + Aspect _aspect; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Image"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public Image(EvasObject parent) : base(parent) + { + IsScaling = true; + CanScaleUp = true; + CanScaleDown = true; + + ApplyAspect(Aspect.AspectFit); + } + + /// <summary> + /// Gets or sets the image aspect ratio preserving option. + /// </summary> + /// <value>The aspect option.</value> + public Aspect Aspect + { + get + { + return _aspect; + } + + set + { + if (_aspect != value) + { + ApplyAspect(value); + } + } + } + + /// <summary> + /// Loads image data from the given <see cref="Xamarin.Forms.ImageSource"/> asynchronously. + /// </summary> + /// <returns>A task which will be completed when image data is loaded.</returns> + /// <param name="source">Image source specifying from where the image data has to be loaded.</param> + public Task<bool> LoadFromImageSourceAsync(ImageSource source) + { + IImageSourceHandler handler; + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + return handler.LoadImageAsync(this, source); + } + return Task.FromResult<bool>(false); + } + + /// <summary> + /// Implements the <see cref="Xamarin.Forms.Platform.Tizen.Native.IMeasurable"/> interface. + /// </summary> + /// <param name="availableWidth">Available width.</param> + /// <param name="availableHeight">Available height.</param> + public ESize Measure(int availableWidth, int availableHeight) + { + var imageSize = ObjectSize; + + var size = new ESize() + { + Width = imageSize.Width, + Height = imageSize.Height, + }; + + if (0 != availableWidth && 0 != availableHeight + && (imageSize.Width > availableWidth || imageSize.Height > availableHeight)) + { + // when available size is limited and insufficient for the image ... + double imageRatio = (double)imageSize.Width / (double)imageSize.Height; + double availableRatio = (double)availableWidth / (double)availableHeight; + // depending on the relation between availableRatio and imageRatio, copy the availableWidth or availableHeight + // and calculate the size which preserves the image ratio, but does not exceed the available size + size.Width = availableRatio > imageRatio ? imageSize.Width * availableHeight / imageSize.Height : availableWidth; + size.Height = availableRatio > imageRatio ? availableHeight : imageSize.Height * availableWidth / imageSize.Width; + } + + return size; + } + + /// <summary> + /// Sets the <c>IsFixedAspect</c> and <c>CanFillOutside</c> properties according to the given <paramref name="aspect"/>. + /// </summary> + /// <param name="aspect">The aspect setting to be applied to the image.</param> + void ApplyAspect(Aspect aspect) + { + _aspect = aspect; + + switch (_aspect) + { + case Aspect.AspectFit: + IsFixedAspect = true; + CanFillOutside = false; + break; + + case Aspect.AspectFill: + IsFixedAspect = true; + CanFillOutside = true; + break; + + case Aspect.Fill: + IsFixedAspect = false; + CanFillOutside = false; + break; + + default: + Log.Warn("Invalid Aspect value: {0}", _aspect); + break; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs b/Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs new file mode 100644 index 00000000..4223ca08 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs @@ -0,0 +1,84 @@ +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Keyboard layout type on entry control. + /// </summary> + public enum Keyboard + { + /// <summary> + /// Disable Keyboard + /// </summary> + None = -1, + + /// <summary> + /// Keyboard layout type default. + /// </summary> + Normal, + + /// <summary> + /// Keyboard layout type number. + /// </summary> + Number, + + /// <summary> + /// Keyboard layout type email. + /// </summary> + Email, + + /// <summary> + /// Keyboard layout type url. + /// </summary> + Url, + + /// <summary> + /// Keyboard layout type phone. + /// </summary> + PhoneNumber, + + /// <summary> + /// Keyboard layout type ip. + /// </summary> + Ip, + + /// <summary> + /// Keyboard layout type month. + /// </summary> + Month, + + /// <summary> + /// Keyboard layout type number. + /// </summary> + NumberOnly, + + /// <summary> + /// Keyboard layout type error type. Do not use it directly! + /// </summary> + Invalid, + + /// <summary> + /// Keyboard layout type hexadecimal. + /// </summary> + Hex, + + /// <summary> + /// Keyboard layout type terminal type, esc, alt, ctrl, etc. + /// </summary> + Terminal, + + /// <summary> + /// Keyboard layout type password. + /// </summary> + Password, + + /// <summary> + /// Keyboard layout type date and time. + /// </summary> + DateTime, + + /// <summary> + /// Keyboard layout type emoticons. + /// </summary> + Emoticon + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Native/Label.cs b/Xamarin.Forms.Platform.Tizen/Native/Label.cs new file mode 100644 index 00000000..cba2ba20 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Label.cs @@ -0,0 +1,347 @@ +using System; +using ElmSharp; +using ELabel = ElmSharp.Label; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// The Label class extends <c>ElmSharp.Label</c> to be better suited for Xamarin renderers. + /// Mainly the formatted text support. + /// </summary> + public class Label : ELabel, ITextable, IMeasurable + { + /// <summary> + /// The _span holds the content of the label. + /// </summary> + readonly Span _span = new Span(); + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Label"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public Label(EvasObject parent) : base(parent) + { + } + + /// <summary> + /// Get or sets the formatted text. + /// </summary> + /// <remarks>Setting <c>FormattedText</c> changes the value of the <c>Text</c> property.</remarks> + /// <value>The formatted text.</value> + public FormattedString FormattedText + { + get + { + return _span.FormattedText; + } + + set + { + if (value != _span.FormattedText) + { + _span.FormattedText = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the text. + /// </summary> + /// <remarks>Setting <c>Text</c> overwrites the value of the <c>FormattedText</c> property too.</remarks> + /// <value>The content of the label.</value> + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the formatted text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the background color for the text. + /// </summary> + /// <value>The color of the label's background.</value> + public EColor TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font family for the text. + /// </summary> + /// <value>The font family.</value> + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font attributes. + /// </summary> + /// <value>The font attributes.</value> + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font size for the text. + /// </summary> + /// <value>The size of the font.</value> + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the line wrap option. + /// </summary> + /// <value>The line break mode.</value> + public LineBreakMode LineBreakMode + { + get + { + return _span.LineBreakMode; + } + + set + { + if (value != _span.LineBreakMode) + { + _span.LineBreakMode = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the horizontal text alignment. + /// </summary> + /// <value>The horizontal text alignment.</value> + public TextAlignment HorizontalTextAlignment + { + get + { + return _span.HorizontalTextAlignment; + } + + set + { + if (value != _span.HorizontalTextAlignment) + { + _span.HorizontalTextAlignment = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the vertical text alignment. + /// </summary> + /// <value>The vertical text alignment.</value> + public TextAlignment VerticalTextAlignment + { + get + { + return _span.VerticalTextAlignment; + } + + set + { + if (value != _span.VerticalTextAlignment) + { + _span.VerticalTextAlignment = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the value that indicates whether the text is underlined. + /// </summary> + /// <value><c>true</c> if the text is underlined.</value> + public bool Underline + { + get + { + return _span.Underline; + } + + set + { + if (value != _span.Underline) + { + _span.Underline = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the value that indicates whether the text is striked out. + /// </summary> + /// <value><c>true</c> if the text is striked out.</value> + public bool Strikethrough + { + get + { + return _span.Strikethrough; + } + + set + { + if (value != _span.Strikethrough) + { + _span.Strikethrough = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Implements <see cref="Xamarin.Forms.Platform.Tizen.Native.IMeasurable"/> to provide a desired size of the label. + /// </summary> + /// <param name="availableWidth">Available width.</param> + /// <param name="availableHeight">Available height.</param> + /// <returns>Size of the control that fits the available area.</returns> + public ESize Measure(int availableWidth, int availableHeight) + { + var size = Geometry; + + Resize(availableWidth, size.Height); + + var rawSize = Native.TextHelper.GetRawTextBlockSize(this); + var formattedSize = Native.TextHelper.GetFormattedTextBlockSize(this); + Resize(size.Width, size.Height); + + // Set bottom padding for lower case letters that have segments below the bottom line of text (g, j, p, q, y). + var verticalPadding = (int)Math.Ceiling(0.05 * FontSize); + var horizontalPadding = (int)Math.Ceiling(0.2 * FontSize); + rawSize.Height += verticalPadding; + formattedSize.Height += verticalPadding; + formattedSize.Width += horizontalPadding; + + if (rawSize.Width > availableWidth) + { + return new ESize() + { + Width = formattedSize.Width, + Height = Math.Min(formattedSize.Height, Math.Max(rawSize.Height, availableHeight)), + }; + } + else + { + return formattedSize; + } + } + + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + string emission = "elm,state,text,visible"; + + if (string.IsNullOrEmpty(formattedText)) + { + formattedText = null; + textStyle = null; + emission = "elm,state,text,hidden"; + } + + base.Text = formattedText; + + var textblock = EdjeObject["elm.text"]; + + if (textblock != null) + { + textblock.TextStyle = textStyle; + } + + EdjeObject.EmitSignal(emission, "elm"); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs b/Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs new file mode 100644 index 00000000..6668f8d7 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs @@ -0,0 +1,55 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Holds information about size of the area which can be used for layout. + /// </summary> + public class LayoutEventArgs : EventArgs + { + /// <summary> + /// Whether or not the dimensions have changed. + /// </summary> + public bool HasChanged + { + get; + internal set; + } + + /// <summary> + /// X coordinate of the layout area, relative to the main window. + /// </summary> + public int X + { + get; + internal set; + } + + /// <summary> + /// Y coordinate of the layout area, relative to the main window. + /// </summary> + public int Y + { + get; + internal set; + } + + /// <summary> + /// Width of the layout area. + /// </summary> + public int Width + { + get; + internal set; + } + + /// <summary> + /// Height of the layout area. + /// </summary> + public int Height + { + get; + internal set; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs b/Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs new file mode 100644 index 00000000..27253ad1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs @@ -0,0 +1,43 @@ +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Enumerates values that describe options for line braking. + /// </summary> + public enum LineBreakMode + { + /// <summary> + /// Do not wrap text. + /// </summary> + NoWrap, + + /// <summary> + /// Wrap at character boundaries. + /// </summary> + CharacterWrap, + + /// <summary> + /// Wrap at word boundaries. + /// </summary> + WordWrap, + + /// <summary> + /// Tries to wrap at word boundaries, and then wrap at a character boundary if the word is too long. + /// </summary> + MixedWrap, + + /// <summary> + /// Truncate the head of text. + /// </summary> + HeadTruncation, + + /// <summary> + /// Truncate the middle of text. This may be done, for example, by replacing it with an ellipsis. + /// </summary> + MiddleTruncation, + + /// <summary> + /// Truncate the tail of text. + /// </summary> + TailTruncation + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ListView.cs b/Xamarin.Forms.Platform.Tizen/Native/ListView.cs new file mode 100644 index 00000000..d5531dc4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ListView.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Type alias which identifies list of cells whose data model was transformed by Xamarin. + /// </summary> + using GroupList = TemplatedItemsList<ItemsView<Cell>, Cell>; + + /// <summary> + /// Native ListView implementation for Xamarin renderer + /// </summary> + /// <remarks> + /// This internally uses GenList class. + /// One should note that it is optimized for displaying many elements which may be + /// unavailable at first. This means that only currently visible elements will be constructed. + /// Whenever element disappears from visible space its content is destroyed for time being. + /// This is carried out by so called Cell Handlers. + /// </remarks> + public class ListView : GenList + { + /// <summary> + /// ItemContext helper class. This represents the association between Xamarin.Forms.Cell and + /// native elements. It also stores useful context for them. + /// </summary> + public class ItemContext + { + public ItemContext() + { + Item = null; + Cell = null; + Renderer = null; + ListOfSubItems = null; + } + + public GenListItem Item; + public Cell Cell; + public bool IsGroupItem; + public CellRenderer Renderer; + internal TemplatedItemsList<ItemsView<Cell>, Cell> ListOfSubItems; + } + + /// <summary> + /// The item context list for each added element. + /// </summary> + readonly List<ItemContext> _itemContextList = new List<ItemContext>(); + + /// <summary> + /// Registered cell handlers. + /// </summary> + protected readonly IDictionary<Type, CellRenderer> _cellRendererCache = new Dictionary<Type, CellRenderer>(); + + /// <summary> + /// Registered group handlers. + /// </summary> + protected readonly IDictionary<Type, CellRenderer> _groupCellRendererCache = new Dictionary<Type, CellRenderer>(); + + /// <summary> + /// The header context. + /// </summary> + ItemContext _headerContext; + + /// <summary> + /// The header element. + /// </summary> + VisualElement _headerElement; + + /// <summary> + /// The footer context. + /// </summary> + ItemContext _footerContext; + + /// <summary> + /// The footer element. + /// </summary> + VisualElement _footerElement; + + /// <summary> + /// The item class for header and footer. + /// </summary> + GenItemClass _headerFooterItemClass = null; + + /// <summary> + /// Gets or sets a value indicating whether this instance has grouping enabled. + /// </summary> + /// <value><c>true</c> if this instance has grouping enabled.</value> + public bool IsGroupingEnabled { get; set; } + + /// <summary> + /// Constructor of ListView native control. + /// </summary> + /// <param name="parent">ElmSharp object which is parent of particular list view</param> + public ListView(EvasObject parent) + : base(parent) + { + ItemRealized += OnItemAppear; + ItemUnrealized += OnItemDisappear; + } + + /// <summary> + /// Gets the item context based on Cell item. + /// </summary> + /// <returns>The item context.</returns> + /// <param name="cell">Cell for which context should be found.</param> + internal ItemContext GetItemContext(Cell cell) + { + if (cell == null) + { + return null; + } + else + { + return _itemContextList.Find(X => X.Cell == cell); + } + } + + /// <summary> + /// Sets the HasUnevenRows property. + /// </summary> + /// <param name="hasUnevenRows">If <c>true</c>, the list will allow uneven sizes for its rows.</param> + public void SetHasUnevenRows(bool hasUnevenRows) + { + Homogeneous = !hasUnevenRows; + UpdateRealizedItems(); + } + + /// <summary> + /// Adds elements to the list and defines its presentation based on Cell type. + /// </summary> + /// <param name="_source">IEnumerable on Cell collection.</param> + /// <param name="beforeCell">Cell before which new items will be placed. + /// Null value may also be passed as this parameter, which results in appending new items to the end. + /// </param> + public void AddSource(IEnumerable _source, Cell beforeCell = null) + { + foreach (var data in _source) + { + GroupList groupList = data as GroupList; + if (groupList != null) + { + AddGroupItem(groupList, beforeCell); + foreach (var item in groupList) + { + AddItem(item as Cell, groupList.HeaderContent); + } + } + else + { + AddItem(data as Cell, null, beforeCell); + } + } + } + + /// <summary> + /// Deletes all items from a given group. + /// </summary> + /// <param name="group">Group of items to be deleted.</param> + internal void ResetGroup(GroupList group) + { + var items = _itemContextList.FindAll(x => x.ListOfSubItems == group && x.Cell != group.HeaderContent); + foreach (var item in items) + { + item.Item?.Delete(); + } + } + + /// <summary> + /// Adds items to the group. + /// </summary> + /// <param name="itemGroup">Group to which elements will be added.</param> + /// <param name="newItems">New list items to be added.</param> + /// <param name="cellBefore">A reference to the Cell already existing in a ListView. + /// Newly added cells will be put just before this cell.</param> + public void AddItemsToGroup(IEnumerable itemGroup, IEnumerable newItems, Cell cellBefore = null) + { + ItemContext groupCtx = GetItemContext((itemGroup as GroupList)?.HeaderContent); + if (groupCtx != null) + { + foreach (var item in newItems) + { + AddItem(item as Cell, groupCtx.Cell, cellBefore); + } + } + } + + /// <summary> + /// Removes the specified cells. + /// </summary> + /// <param name="cells">Cells to be removed.</param> + public void Remove(IEnumerable cells) + { + foreach (var data in cells) + { + var group = data as GroupList; + if (group != null) + { + ItemContext groupCtx = GetItemContext(group.HeaderContent); + Remove(groupCtx.ListOfSubItems); + groupCtx.Item.Delete(); + } + else + { + ItemContext itemCtx = GetItemContext(data as Cell); + itemCtx?.Item?.Delete(); + } + } + } + + /// <summary> + /// Scrolls the list to a specified cell. + /// </summary> + /// <remarks> + /// Different scrolling behaviors are also possible. The element may be positioned in the center, + /// top or bottom of the visible part of the list depending on the value of the <c>position</c> parameter. + /// </remarks> + /// <param name="cell">Cell which will be displayed after scrolling .</param> + /// <param name="position">This will defines scroll to behavior based on ScrollToPosition values.</param> + /// <param name="animated">If <c>true</c>, scrolling will be animated. Otherwise the cell will be moved instantaneously.</param> + public void ApplyScrollTo(Cell cell, ScrollToPosition position, bool animated) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + this.ScrollTo(item, position.ToNative(), animated); + } + + /// <summary> + /// Selects the specified cell. + /// </summary> + /// <param name="cell">Cell to be selected.</param> + public void ApplySelectedItem(Cell cell) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + item.IsSelected = true; + } + + /// <summary> + /// Sets the header. + /// </summary> + /// <param name="header">Header of the list.</param> + public void SetHeader(VisualElement header) + { + if (header == null) + { + if (HasHeader()) + { + RemoveHeader(); + } + + return; + } + + GenItemClass headerTemplate = GetHeaderFooterItemClass(); + + _headerElement = header; + if (HasHeader()) + { + FirstItem.UpdateItemClass(headerTemplate, header); + } + else + { + _headerContext = new ItemContext(); + _headerContext.Item = _itemContextList.Count > 0 ? InsertBefore(headerTemplate, header, FirstItem) : Append(headerTemplate, header); + _headerContext.Item.SelectionMode = GenListSelectionMode.None; + _headerContext.Item.Deleted += HeaderDeletedHandler; + _itemContextList.Insert(0, _headerContext); + } + } + + /// <summary> + /// Sets the footer. + /// </summary> + /// <param name="footer">Footer of the list.</param> + public void SetFooter(VisualElement footer) + { + if (footer == null) + { + if (HasFooter()) + { + RemoveFooter(); + } + return; + } + + GenItemClass footerTemplate = GetHeaderFooterItemClass(); + + _footerElement = footer; + if (HasFooter()) + { + _footerContext.Item.UpdateItemClass(footerTemplate, footer); + } + else + { + _footerContext = new ItemContext(); + _footerContext.Item = Append(footerTemplate, footer); + _footerContext.Item.SelectionMode = GenListSelectionMode.None; + _footerContext.Item.Deleted += FooterDeletedHandler; + _itemContextList.Add(_footerContext); + } + } + + /// <summary> + /// Removes the header. + /// </summary> + public void RemoveHeader() + { + _itemContextList.Remove(_headerContext); + _headerContext?.Item?.Delete(); + _headerContext = null; + _headerElement = null; + } + + /// <summary> + /// Removes the footer. + /// </summary> + public void RemoveFooter() + { + _itemContextList.Remove(_footerContext); + _footerContext?.Item?.Delete(); + _footerContext = null; + _footerElement = null; + } + + /// <summary> + /// Determines whether this instance has a header. + /// </summary> + /// <returns><c>true</c> if the header is present.</returns> + public bool HasHeader() + { + return _headerContext != null; + } + + /// <summary> + /// Determines whether this instance has a footer. + /// </summary> + /// <returns><c>true</c> if the footer is present.</returns> + public bool HasFooter() + { + return _footerContext != null; + } + + /// <summary> + /// Gets the header. + /// </summary> + /// <returns>The header.</returns> + public VisualElement GetHeader() + { + return _headerElement; + } + + /// <summary> + /// Gets the footer. + /// </summary> + /// <returns>The footer.</returns> + public VisualElement GetFooter() + { + return _footerElement; + } + + /// <summary> + /// Handles the header deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void HeaderDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_headerContext); + _headerContext = null; + } + + /// <summary> + /// Handles the footer deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void FooterDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_footerContext); + _footerContext = null; + } + + /// <summary> + /// Called every time an object gets realized. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="evt">GenListItemEventArgs.</param> + void OnItemAppear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendAppearing(); + } + } + + /// <summary> + /// Called every time an object gets unrealized. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="evt">GenListItemEventArgs.</param> + void OnItemDisappear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendDisappearing(); + itemContext.Renderer?.SendUnrealizedCell(itemContext.Cell); + } + } + + + /// <summary> + /// A convenience shorthand method for derivate classes. + /// </summary> + /// <param name="cell">Cell to be added.</param> + protected void AddCell(Cell cell) + { + AddItem(cell); + } + + /// <summary> + /// Gets the cell renderer for given cell type. + /// </summary> + /// <returns>The cell handler.</returns> + /// <param name="cell">Cell to be added.</param> + /// <param name="isGroup">If <c>true</c>, then group handlers will be included in the lookup as well.</param> + protected virtual CellRenderer GetCellRenderer(Cell cell, bool isGroup = false) + { + Type type = cell.GetType(); + var cache = isGroup ? _groupCellRendererCache : _cellRendererCache; + if (cache.ContainsKey(type)) + return cache[type]; + + CellRenderer renderer = null; + + if (isGroup && type == typeof(TextCell)) + { + renderer = new GroupCellTextRenderer(); + } + renderer = renderer ?? Registrar.Registered.GetHandler<CellRenderer>(type); + + if (renderer == null) + { + Log.Error("Cell type is not handled: {0}", cell.GetType()); + throw new ArgumentNullException("Unsupported cell type"); + } + return cache[type] = renderer; + } + + /// <summary> + /// Adds the group item. Group item is actually of class GroupList because + /// group item has sub items (can be zero) which needs to be added. + /// If beforeCell is not null, new group will be added just before it. + /// </summary> + /// <param name="groupList">Group to be added.</param> + /// <param name="beforeCell">Before cell.</param> + void AddGroupItem(GroupList groupList, Cell beforeCell = null) + { + Cell groupCell = groupList.HeaderContent; + CellRenderer groupRenderer = GetCellRenderer(groupCell, true); + ItemContext groupItemContext = new ItemContext(); + groupItemContext.Cell = groupCell; + groupItemContext.Renderer = groupRenderer; + + if (beforeCell != null) + { + GenListItem beforeItem = GetItemContext(beforeCell)?.Item; + groupItemContext.Item = InsertBefore(groupRenderer.Class, groupItemContext, beforeItem, GenListItemType.Group); + } + else + { + groupItemContext.Item = Append(groupRenderer.Class, groupItemContext, GenListItemType.Group); + } + + groupItemContext.Item.SelectionMode = GenListSelectionMode.None; + groupItemContext.IsGroupItem = true; + + groupItemContext.ListOfSubItems = groupList; + groupItemContext.Item.Deleted += ItemDeletedHandler; + _itemContextList.Add(groupItemContext); + } + + /// <summary> + /// Adds the item. + /// </summary> + /// <param name="cell">Cell to be added.</param> + /// <param name="groupCell">Group to which the new item should belong.</param> + /// <remark>If the value of <c>groupCell</c> is not null, the new item will be put into the requested group. </remark> + /// <param name="beforeCell">The cell before which the new item should be placed.</param> + /// <remarks> If the value of <c>beforeCell</c> is not null, the new item will be placed just before the requested cell. </remarks> + void AddItem(Cell cell, Cell groupCell = null, Cell beforeCell = null) + { + CellRenderer renderer = GetCellRenderer(cell); + GenListItem parentItem = null; + + ItemContext itemContext = new ItemContext(); + itemContext.Cell = cell; + itemContext.Renderer = renderer; + + if (IsGroupingEnabled && groupCell != null) + { + var groupContext = GetItemContext(groupCell); + itemContext.ListOfSubItems = groupContext.ListOfSubItems; + parentItem = groupContext.Item; + } + + if (beforeCell != null) + { + GenListItem beforeItem = GetItemContext(beforeCell)?.Item; + itemContext.Item = InsertBefore(renderer.Class, itemContext, beforeItem, GenListItemType.Normal, parentItem); + } + else + { + itemContext.Item = Append(renderer.Class, itemContext, GenListItemType.Normal, parentItem); + } + + itemContext.Item.SelectionMode = GenListSelectionMode.Always; + + cell.PropertyChanged += OnCellPropertyChanged; + (cell as ICellController).ForceUpdateSizeRequested += OnForceUpdateSizeRequested; + itemContext.Item.Deleted += ItemDeletedHandler; + _itemContextList.Add(itemContext); + } + + /// <summary> + /// Handles item deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void ItemDeletedHandler(object sender, EventArgs e) + { + ItemContext itemContext = (sender as GenListItem).Data as ItemContext; + if (itemContext.Cell != null) + { + itemContext.Cell.PropertyChanged -= OnCellPropertyChanged; + (itemContext.Cell as ICellController).ForceUpdateSizeRequested -= OnForceUpdateSizeRequested; + } + _itemContextList.Remove(itemContext); + } + + /// <summary> + /// Invoked whenever the properties of data model change. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">PropertyChangedEventArgs.</param> + /// <remarks> + /// The purpose of this method is to propagate these changes to the presentation layer. + /// </remarks> + void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var cell = sender as Cell; + var context = GetItemContext(cell); + context.Renderer.SendCellPropertyChanged(cell, context.Item, e.PropertyName); + } + + void OnForceUpdateSizeRequested(object sender, EventArgs e) + { + var cell = sender as Cell; + var itemContext = GetItemContext(cell); + if (itemContext.Item != null) + itemContext.Item.Update(); + } + + /// <summary> + /// Gets the item class used for header and footer cells. + /// </summary> + /// <returns>The header and footer item class.</returns> + GenItemClass GetHeaderFooterItemClass() + { + if (_headerFooterItemClass == null) + { + _headerFooterItemClass = new GenItemClass("full") + { + GetContentHandler = (data, part) => + { + VisualElement element = data as VisualElement; + var renderer = Platform.GetOrCreateRenderer(element); + + if (element.MinimumHeightRequest == -1) + { + SizeRequest request = element.Measure(double.PositiveInfinity, double.PositiveInfinity); + renderer.NativeView.MinimumHeight = (int)request.Request.Height; + } + else + { + renderer.NativeView.MinimumHeight = (int)element.MinimumHeightRequest; + } + + return renderer.NativeView; + } + }; + } + return _headerFooterItemClass; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs b/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs new file mode 100644 index 00000000..5751aaf8 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs @@ -0,0 +1,364 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// The native widget which provides Xamarin.MasterDetailPage features. + /// </summary> + public class MasterDetailPage : Box + { + /// <summary> + /// The portion of the screen that the MasterPage takes in Split mode. + /// </summary> + static readonly double s_splitRatio = 0.35; + + /// <summary> + /// The portion of the screen that the MasterPage takes in Popover mode. + /// </summary> + static readonly double s_popoverRatio = 0.8; + + /// <summary> + /// The default master behavior (a.k.a mode). + /// </summary> + static readonly MasterBehavior s_defaultMasterBehavior = (Device.Idiom == TargetIdiom.Phone) ? MasterBehavior.Popover : MasterBehavior.SplitOnLandscape; + + /// <summary> + /// The MasterPage native container. + /// </summary> + readonly Canvas _masterCanvas; + + /// <summary> + /// The DetailPage native container. + /// </summary> + readonly Canvas _detailCanvas; + + /// <summary> + /// The container for <c>_masterCanvas</c> and <c>_detailCanvas</c> used in split mode. + /// </summary> + readonly Panes _splitPane; + + /// <summary> + /// The container for <c>_masterCanvas</c> used in popover mode. + /// </summary> + readonly Panel _drawer; + + /// <summary> + /// The <see cref="MasterBehavior"/> property value. + /// </summary> + MasterBehavior _masterBehavior = s_defaultMasterBehavior; + + /// <summary> + /// The actual MasterDetailPage mode - either split or popover. It depends on <c>_masterBehavior</c> and screen orientation. + /// </summary> + MasterBehavior _internalMasterBehavior = MasterBehavior.Popover; + + /// <summary> + /// The <see cref="Master"/> property value. + /// </summary> + EvasObject _master; + + /// <summary> + /// The <see cref="Detail"/> property value. + /// </summary> + EvasObject _detail; + + /// <summary> + /// The main widget - either <see cref="_splitPlane"/> or <see cref="_detailPage"/>, depending on the mode. + /// </summary> + EvasObject _mainWidget; + + /// <summary> + /// The <see cref="IsPresented"/> property value. + /// </summary> + bool _isPresented; + + /// <summary> + /// The <see cref="IsGestureEnabled"/> property value. + /// </summary> + bool _isGestureEnabled; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.MasterDetailPage"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public MasterDetailPage(EvasObject parent) : base(parent) + { + // we control the layout ourselves + Resized += (sender, e) => + { + var g = Geometry; + + // main widget should fill the area of the MasterDetailPage + if (_mainWidget != null) + { + _mainWidget.Geometry = g; + } + + g.Width = (int)((s_popoverRatio * (double)g.Width)); + _drawer.Geometry = g; + }; + + // create the controls which will hold the master and detail pages + _masterCanvas = new Canvas(this); + _masterCanvas.SetAlignment(-1.0, -1.0); // fill + _masterCanvas.SetWeight(1.0, 1.0); // expand + _masterCanvas.Resized += (sender, e) => + { + UpdatePageGeometry(_master); + }; + + _detailCanvas = new Canvas(this); + _detailCanvas.SetAlignment(-1.0, -1.0); // fill + _detailCanvas.SetWeight(1.0, 1.0); // expand + _detailCanvas.Resized += (sender, e) => + { + UpdatePageGeometry(_detail); + }; + + _splitPane = new Panes(this) + { + IsFixed = true, + IsHorizontal = false, + Proportion = s_splitRatio, + }; + + _drawer = new Panel(this) + { + Direction = PanelDirection.Left, + }; + _drawer.SetScrollable(_isGestureEnabled); + _drawer.SetScrollableArea(1.0); + _drawer.Toggled += (object sender, EventArgs e) => + { + IsPresented = _drawer.IsOpen; + }; + + IsPresentedChanged += (sender, e) => + { + _drawer.IsOpen = IsPresented; + }; + + ConfigureLayout(); + + // in case of the screen rotation we may need to update the choice between split + // and popover behaviors and reconfigure the layout + Forms.Context.MainWindow.RotationChanged += (sender, e) => + { + UpdateMasterBehavior(); + }; + } + + /// <summary> + /// Occurs when the MasterPage is shown or hidden. + /// </summary> + public event EventHandler IsPresentedChanged; + + /// <summary> + /// Gets or sets the MasterDetailPage behavior. + /// </summary> + /// <value>The behavior of the <c>MasterDetailPage</c> requested by the user.</value> + public MasterBehavior MasterBehavior + { + get + { + return _masterBehavior; + } + + set + { + if (_masterBehavior != value) + { + _masterBehavior = value; + + UpdateMasterBehavior(); + } + } + } + + /// <summary> + /// Gets or sets the content of the MasterPage. + /// </summary> + /// <value>The MasterPage.</value> + public EvasObject Master + { + get + { + return _master; + } + + set + { + if (_master != value) + { + _master = value; + UpdatePageGeometry(_master); + _masterCanvas.Children.Clear(); + _masterCanvas.Children.Add(_master); + } + } + } + + /// <summary> + /// Gets or sets the content of the DetailPage. + /// </summary> + /// <value>The DetailPage.</value> + public EvasObject Detail + { + get + { + return _detail; + } + + set + { + if (_detail != value) + { + _detail = value; + UpdatePageGeometry(_detail); + _detailCanvas.Children.Clear(); + _detailCanvas.Children.Add(_detail); + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether the MasterPage is shown. + /// </summary> + /// <value><c>true</c> if the MasterPage is presented.</value> + public bool IsPresented + { + get + { + return _isPresented; + } + + set + { + if (_isPresented != value) + { + _isPresented = value; + IsPresentedChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether a MasterDetailPage allows showing MasterPage with swipe gesture. + /// </summary> + /// <value><c>true</c> if the MasterPage can be revealed with a gesture.</value> + public bool IsGestureEnabled + { + get + { + return _isGestureEnabled; + } + + set + { + if (_isGestureEnabled != value) + { + _isGestureEnabled = value; + _drawer.SetScrollable(_isGestureEnabled); + } + } + } + + /// <summary> + /// Updates the geometry of the selected page. + /// </summary> + /// <param name="page">Master or Detail page to be updated.</param> + void UpdatePageGeometry(EvasObject page) + { + if (page != null) + { + if (_master == page) + { + // update the geometry of the master page + page.Geometry = _masterCanvas.Geometry; + } + else if (_detail == page) + { + // update the geometry of the detail page + page.Geometry = _detailCanvas.Geometry; + } + } + } + + /// <summary> + /// Updates <see cref="_internalMasterBehavior"/> according to <see cref="MasterDetailBehavior"/> set by the user and current screen orientation. + /// </summary> + void UpdateMasterBehavior() + { + var behavior = (_masterBehavior == MasterBehavior.Default) ? s_defaultMasterBehavior : _masterBehavior; + + // Screen orientation affects those 2 behaviors + if (behavior == MasterBehavior.SplitOnLandscape || + behavior == MasterBehavior.SplitOnPortrait) + { + var orientation = Forms.Context.MainWindow.CurrentOrientation; + + if (((orientation == DisplayOrientations.Landscape || orientation == DisplayOrientations.LandscapeFlipped) && behavior == MasterBehavior.SplitOnLandscape) || + ((orientation == DisplayOrientations.Portrait || orientation == DisplayOrientations.PortraitFlipped) && behavior == MasterBehavior.SplitOnPortrait)) + { + behavior = MasterBehavior.Split; + } + else + { + behavior = MasterBehavior.Popover; + } + } + + if (behavior != _internalMasterBehavior) + { + _internalMasterBehavior = behavior; + + ConfigureLayout(); + } + } + + /// <summary> + /// Composes the structure of all the necessary widgets. + /// </summary> + void ConfigureLayout() + { + _drawer.SetContent(null, true); + _drawer.Hide(); + + _splitPane.SetPartContent("left", null, true); + _splitPane.SetPartContent("right", null, true); + _splitPane.Hide(); + + UnPackAll(); + + // the structure for split mode and for popover mode looks differently + if (_internalMasterBehavior == MasterBehavior.Split) + { + _splitPane.SetPartContent("left", _masterCanvas, true); + _splitPane.SetPartContent("right", _detailCanvas, true); + PackEnd(_splitPane); + + _splitPane.Show(); + + _mainWidget = _splitPane; + + IsPresented = true; + } + else + { + _drawer.SetContent(_masterCanvas, true); + PackEnd(_detailCanvas); + PackEnd(_drawer); + + _drawer.Show(); + + _mainWidget = _detailCanvas; + + _drawer.IsOpen = IsPresented; + } + + _masterCanvas.Show(); + _detailCanvas.Show(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs b/Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs new file mode 100644 index 00000000..d33407c9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs @@ -0,0 +1,30 @@ +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. + /// </summary> + /// <typeparam name="T">The type of elements in the collection.</typeparam> + internal class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T> + { + /// <summary> + /// Removes all items from the collection. + /// </summary> + /// <remarks> + /// Fisrt remove all items, send CollectionChanged event with Remove Action + /// Second call ClearItems of base + /// </remarks> + protected override void ClearItems() + { + var oldItems = Items.ToList(); + Items.Clear(); + using (BlockReentrancy()) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems)); + } + base.ClearItems(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs b/Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs new file mode 100644 index 00000000..db597ed9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs @@ -0,0 +1,423 @@ +using System; +using ElmSharp; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; +using ERect = ElmSharp.Rect; +using ERectangle = ElmSharp.Rectangle; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Provides implementation of the search bar widget. + /// </summary> + public class SearchBar : Canvas, IMeasurable + { + /// <summary> + /// The height of the background of the search bar. + /// </summary> + const int BackgroundHeight = 120; + + /// <summary> + /// The style of the cancel button. + /// </summary> + const string CancelButtonLayoutStyle = "editfield_clear"; + + /// <summary> + /// The horizontal padding of the cancel button. + /// </summary> + const int CancelButtonPaddingHorizontal = 17; + + /// <summary> + /// The size of the cancel button. + /// </summary> + const int CancelButtonSize = 80; + + /// <summary> + /// The height of the entry. + /// </summary> + const int EntryHeight = 54; + + /// <summary> + /// The horizontal padding of the entry. + /// </summary> + const int EntryPaddingHorizontal = 42; + + /// <summary> + /// The vertical padding of the entry. + /// </summary> + const int EntryPaddingVertical = 33; + + /// <summary> + /// The height of the rectangle used to draw underline effect. + /// </summary> + const int RectangleHeight = 2; + + /// <summary> + /// The bottom padding of the rectangle used to draw underline effect. + /// </summary> + const int RectanglePaddingBottom = 20; + + /// <summary> + /// The horizontal padding of the rectangle used to draw underline effect. + /// </summary> + const int RectanglePaddingHorizontal = 32; + + /// <summary> + /// The top padding of the rectangle used to draw underline effect. + /// </summary> + const int RectanglePaddingTop = 11; + + //TODO: read default platform color + + /// <summary> + /// The color of the underline rectangle. + /// </summary> + static readonly EColor s_underlineColor = EColor.Aqua; + + /// <summary> + /// The dimmed color of the underline rectangle. + /// </summary> + static readonly EColor s_underlineDimColor = EColor.Gray; + + /// <summary> + /// The cancel button. + /// </summary> + Button _cancelButton; + + /// <summary> + /// The text entry. + /// </summary> + Entry _entry; + + /// <summary> + /// The underline rectangle. + /// </summary> + ERectangle _underlineRectangle; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.SearchBar"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public SearchBar(EvasObject parent) : base(parent) + { + _entry = new Entry(parent) + { + IsSingleLine = true, + }; + _entry.SetInputPanelReturnKeyType(InputPanelReturnKeyType.Search); + _entry.TextChanged += EntryTextChanged; + _entry.Activated += EntryActivated; + _entry.Focused += EntryFocused; + _entry.Unfocused += EntryUnfocused; + _entry.Show(); + + _cancelButton = new Button(parent); + _cancelButton.Style = CancelButtonLayoutStyle; + _cancelButton.Clicked += CancelButtonClicked; + + _underlineRectangle = new ERectangle(parent) + { + Color = IsEnabled ? s_underlineColor : s_underlineDimColor, + }; + _underlineRectangle.Show(); + + Children.Add(_entry); + Children.Add(_cancelButton); + Children.Add(_underlineRectangle); + + Show(); + + this.LayoutUpdated += SearchBarLayoutUpdated; + } + + /// <summary> + /// Occurs when the search button on the keyboard is pressed. + /// </summary> + public event EventHandler SearchButtonPressed; + + /// <summary> + /// Occurs when the entry's text has changed. + /// </summary> + public event EventHandler<TextChangedEventArgs> TextChanged; + + /// <summary> + /// Gets or sets the color of the cancel button. + /// </summary> + /// <value>Color of the cancel button.</value> + public EColor CancelButtonColor + { + get + { + return _cancelButton.Color; + } + + set + { + if (!_cancelButton.Color.Equals(value)) + { + _cancelButton.Color = value; + } + } + } + + /// <summary> + /// Gets or sets the font attributes of the search bar's entry. + /// </summary> + /// <value>The font attributes.</value> + public FontAttributes FontAttributes + { + get + { + return _entry.FontAttributes; + } + + set + { + if (value != _entry.FontAttributes) + { + _entry.FontAttributes = value; + } + } + } + + /// <summary> + /// Gets or sets the font family of the search bar's entry. + /// </summary> + /// <value>The font family.</value> + public string FontFamily + { + get + { + return _entry.FontFamily; + } + + set + { + if (value != _entry.FontFamily) + { + _entry.FontFamily = value; + } + } + } + + /// <summary> + /// Gets or sets the size of the font of the search bar's entry. + /// </summary> + /// <value>The size of the font.</value> + public double FontSize + { + get + { + return _entry.FontSize; + } + + set + { + if (value != _entry.FontSize) + { + _entry.FontSize = value; + } + } + } + + /// <summary> + /// Gets or sets the horizontal text alignment of the search bar's entry. + /// </summary> + /// <value>The horizontal text alignment.</value> + public TextAlignment HorizontalTextAlignment + { + get + { + return _entry.HorizontalTextAlignment; + } + + set + { + if (value != _entry.HorizontalTextAlignment) + { + _entry.HorizontalTextAlignment = value; + } + } + } + + /// <summary> + /// Gets or sets the placeholder of the search bar's entry. + /// </summary> + /// <value>The placeholder.</value> + public string Placeholder + { + get + { + return _entry.Placeholder; + } + + set + { + if (value != _entry.Placeholder) + { + _entry.Placeholder = value; + } + } + } + + /// <summary> + /// Gets or sets the color of the placeholder. + /// </summary> + /// <value>The color of the placeholder.</value> + public EColor PlaceholderColor + { + get + { + return _entry.PlaceholderColor; + } + + set + { + if (!_entry.PlaceholderColor.Equals(value)) + { + _entry.PlaceholderColor = value; + } + } + } + + /// <summary> + /// Gets or sets the text of the search bar's entry. + /// </summary> + /// <value>The text.</value> + public override string Text + { + get + { + return _entry.Text; + } + + set + { + if (value != _entry.Text) + { + _entry.Text = value; + } + } + } + + /// <summary> + /// Gets or sets the color of the text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _entry.TextColor; + } + + set + { + if (!_entry.TextColor.Equals(value)) + { + _entry.TextColor = value; + } + } + } + + /// <summary> + /// Implementation of the IMeasurable.Measure() method. + /// </summary> + public ESize Measure(int availableWidth, int availableHeight) + { + ESize entrySize = _entry.Measure(availableWidth, availableHeight); + int width = entrySize.Width + (CancelButtonPaddingHorizontal * 2) + CancelButtonSize; + return new ESize(width, BackgroundHeight); + } + + /// <summary> + /// Handles the event triggered by the cancel button being clicked. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void CancelButtonClicked(object sender, EventArgs e) + { + _entry.Text = string.Empty; + _cancelButton.Hide(); + } + + /// <summary> + /// Handles the event triggered by clicking the search button on the keyboard. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void EntryActivated(object sender, EventArgs e) + { + SearchButtonPressed?.Invoke(this, EventArgs.Empty); + } + + /// <summary> + /// Handles the event triggered by entry gaining the focus. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void EntryFocused(object sender, EventArgs e) + { + _underlineRectangle.Color = s_underlineColor; + } + + /// <summary> + /// Handles the event triggered by entry's text being changed. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments.</param> + void EntryTextChanged(object sender, TextChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.NewTextValue)) + { + _cancelButton.Hide(); + } + else if (!_cancelButton.IsVisible) + { + _cancelButton.Show(); + } + TextChanged?.Invoke(this, e); + } + + /// <summary> + /// Handles the event triggered by entry losing the focus. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void EntryUnfocused(object sender, EventArgs e) + { + _underlineRectangle.Color = s_underlineDimColor; + } + + /// <summary> + /// Handles the event triggered by search bar's layout being changed. + /// </summary> + /// <remarks> + /// Updates the geometry of the widgets comprising the search bar. + /// </remarks> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments.</param> + void SearchBarLayoutUpdated(object sender, LayoutEventArgs e) + { + if (!e.HasChanged) + { + return; + } + + _underlineRectangle.Geometry = new ERect(Geometry.Left + RectanglePaddingHorizontal, + Geometry.Top + EntryPaddingVertical + EntryHeight + RectanglePaddingTop, + Geometry.Width - (RectanglePaddingHorizontal * 2), + RectangleHeight); + + _entry.Geometry = new ERect(Geometry.Left + EntryPaddingHorizontal, + Geometry.Top + EntryPaddingVertical, + Geometry.Width - (EntryPaddingHorizontal + (CancelButtonPaddingHorizontal * 2) + CancelButtonSize), + EntryHeight); + + _cancelButton.Geometry = new ERect(Geometry.Right - CancelButtonSize - CancelButtonPaddingHorizontal, + Geometry.Top + RectanglePaddingBottom, + CancelButtonSize, + CancelButtonSize); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Span.cs b/Xamarin.Forms.Platform.Tizen/Native/Span.cs new file mode 100644 index 00000000..6c479e34 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Span.cs @@ -0,0 +1,294 @@ +using System.Text; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Represent a text with attributes applied. + /// </summary> + public class Span + { + string _text; + + /// <summary> + /// Gets or sets the formatted text. + /// </summary> + public FormattedString FormattedText { get; set; } + + /// <summary> + /// Gets or sets the text. + /// </summary> + /// <remarks> + /// Setting Text to a non-null value will set the FormattedText property to null. + /// </remarks> + public string Text + { + get + { + if (FormattedText != null) + { + return FormattedText.ToString(); + } + else + { + return _text; + } + } + set + { + if (value == null) + { + value = ""; + } + else + { + FormattedText = null; + } + _text = value; + } + } + + /// <summary> + /// Gets or sets the color for the text. + /// </summary> + public EColor ForegroundColor { get; set; } + + /// <summary> + /// Gets or sets the background color for the text. + /// </summary> + public EColor BackgroundColor { get; set; } + + /// <summary> + /// Gets or sets the font family for the text. + /// </summary> + public string FontFamily { get; set; } + + /// <summary> + /// Gets or sets the font attributes for the text. + /// See <see cref="FontAttributes"/> for information about FontAttributes. + /// </summary> + public FontAttributes FontAttributes { get; set; } + + /// <summary> + /// Gets or sets the font size for the text. + /// </summary> + public double FontSize { get; set; } + + /// <summary> + /// Gets or sets the line break mode for the text. + /// See <see cref="LineBreakMode"/> for information about LineBreakMode. + /// </summary> + public LineBreakMode LineBreakMode { get; set; } + + /// <summary> + /// Gets or sets the horizontal alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + public TextAlignment HorizontalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the vertical alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + public TextAlignment VerticalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has underline. + /// </summary> + public bool Underline { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has strike line though it. + /// </summary> + public bool Strikethrough { get; set; } + + /// <summary> + /// Create a new Span instance with default attributes. + /// </summary> + public Span() + { + Text = ""; + FontFamily = ""; + FontSize = -1; + FontAttributes = FontAttributes.None; + ForegroundColor = EColor.White; + BackgroundColor = EColor.Transparent; + HorizontalTextAlignment = TextAlignment.Auto; + VerticalTextAlignment = TextAlignment.Auto; + LineBreakMode = LineBreakMode.MixedWrap; + Underline = false; + Strikethrough = false; + } + + /// <summary> + /// This method return marked up text + /// </summary> + internal string GetMarkupText() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendFormat("<span "); + + sb = PrepareFormattingString(sb); + + sb.Append(">"); + + sb.Append(GetDecoratedText()); + + sb.Append("</>"); + + return sb.ToString(); + } + + /// <summary> + /// This method return text decorated with markup if FormattedText is set or plain text otherwise. + /// </summary> + internal string GetDecoratedText() + { + if (FormattedText != null) + { + return FormattedText.ToMarkupString(); + } + else + { + return ConvertTags(Text); + } + } + + StringBuilder PrepareFormattingString(StringBuilder _formattingString) + { + var foregroundColor = ForegroundColor.ToHex(); + + _formattingString.AppendFormat("color={0} ", foregroundColor); + + _formattingString.AppendFormat("backing_color={0} ", BackgroundColor.ToHex()); + _formattingString.Append("backing=on "); + + if (!string.IsNullOrEmpty(FontFamily)) + { + _formattingString.AppendFormat("font={0} ", FontFamily); + } + + if (FontSize != -1) + { + _formattingString.AppendFormat("font_size={0} ", FontSize); + } + + if ((FontAttributes & FontAttributes.Bold) != 0) + { + _formattingString.Append("font_weight=Bold "); + } + if ((FontAttributes & FontAttributes.Italic) != 0) + { + _formattingString.Append("font_style=italic "); + } + + if (Underline) + { + _formattingString.AppendFormat("underline=on underline_color={0} ", foregroundColor); + } + + if (Strikethrough) + { + _formattingString.AppendFormat("strikethrough=on strikethrough_color={0} ", foregroundColor); + } + + switch (HorizontalTextAlignment) + { + case TextAlignment.Auto: + _formattingString.Append("align=auto "); + break; + + case TextAlignment.Start: + _formattingString.Append("align=left "); + break; + + case TextAlignment.End: + _formattingString.Append("align=right "); + break; + + case TextAlignment.Center: + _formattingString.Append("align=center "); + break; + } + + switch (VerticalTextAlignment) + { + case TextAlignment.Auto: + case TextAlignment.Start: + _formattingString.Append("valign=top "); + break; + + case TextAlignment.End: + _formattingString.Append("valign=bottom "); + break; + + case TextAlignment.Center: + _formattingString.Append("valign=middle "); + break; + } + + switch (LineBreakMode) + { + case LineBreakMode.NoWrap: + _formattingString.Append("wrap=none"); + break; + + case LineBreakMode.CharacterWrap: + _formattingString.Append("wrap=char"); + break; + + case LineBreakMode.WordWrap: + _formattingString.Append("wrap=word"); + break; + + case LineBreakMode.MixedWrap: + _formattingString.Append("wrap=mixed"); + break; + + case LineBreakMode.HeadTruncation: + _formattingString.Append("ellipsis=0.0"); + break; + + case LineBreakMode.MiddleTruncation: + _formattingString.Append("ellipsis=0.5"); + break; + + case LineBreakMode.TailTruncation: + _formattingString.Append("ellipsis=1.0"); + break; + } + + return _formattingString; + } + + string ConvertTags(string text) + { + return text.Replace("<", "<") + .Replace(">", ">") + .Replace("\n", "<br>"); + } + + internal string GetStyle() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("DEFAULT='"); + + PrepareFormattingString(sb); + + sb.Append("'"); + + return sb.ToString(); + } + + /// <summary> + /// Converts string value to Span. + /// </summary> + /// <param name="text">The string text</param> + public static implicit operator Span(string text) + { + return new Span { Text = text }; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/TableView.cs b/Xamarin.Forms.Platform.Tizen/Native/TableView.cs new file mode 100644 index 00000000..73388a84 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TableView.cs @@ -0,0 +1,67 @@ +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ListView class to provide TableView class implementation. + /// </summary> + public class TableView : ListView + { + + static readonly SectionCellRenderer _sectionCellRenderer = new SectionCellRenderer(); + /// <summary> + /// Initializes a new instance of the TableView class. + /// </summary> + public TableView(EvasObject parent) + : base(parent) { + } + + /// <summary> + /// Sets the root of the table. + /// </summary> + /// <param name="root">TableRoot, which is parent to one or more TableSections.</param> + public void ApplyTableRoot(TableRoot root) + { + Clear(); + foreach (TableSection ts in root) + { + AddSectionTitle(ts.Title); + AddSource(ts); + } + } + + protected override CellRenderer GetCellRenderer(Cell cell, bool isGroup = false) + { + if (cell.GetType() == typeof(SectionCell)) + { + return _sectionCellRenderer; + } + return base.GetCellRenderer(cell, isGroup); + } + + /// <summary> + /// Sets the section title. + /// </summary> + void AddSectionTitle(string title) + { + Cell cell = new SectionCell() + { + Text = title + }; + AddCell(cell); + } + + internal class SectionCellRenderer : GroupCellTextRenderer + { + public SectionCellRenderer() + { + DetailPart = "null"; + } + protected SectionCellRenderer(string style) : base(style) { } + } + class SectionCell : TextCell + { + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs b/Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs new file mode 100644 index 00000000..c7e934bf --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs @@ -0,0 +1,25 @@ +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Enumerates values that describe alignemnt of text. + /// </summary> + public enum TextAlignment + { + /// <summary> + /// Aligns horizontal text according to language. Top aligned for vertical text. + /// </summary> + Auto, + /// <summary> + /// Left and top aligned for horizontal and vertical text, respectively. + /// </summary> + Start, + /// <summary> + /// Right and bottom aligned for horizontal and vertical text, respectively. + /// </summary> + End, + /// <summary> + /// Center-aligned text. + /// </summary> + Center, + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs b/Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs new file mode 100644 index 00000000..5109dad4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs @@ -0,0 +1,54 @@ +using System; +using ElmSharp; +using ESize = ElmSharp.Size; +using ELayout = ElmSharp.Layout; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// The Text Helper contains functions that assist in working with text-able objects. + /// </summary> + internal static class TextHelper + { + /// <summary> + /// Gets the size of raw text block. + /// </summary> + /// <param name="textable">The <see cref="EvasObject"/> with text part.</param> + /// <returns>Returns the size of raw text block.</returns> + public static ESize GetRawTextBlockSize(EvasObject textable) + { + return GetElmTextPart(textable).TextBlockNativeSize; + } + + /// <summary> + /// Gets the size of formatted text block. + /// </summary> + /// <param name="textable">The <see cref="ElmSharp.EvasObject"/> with text part.</param> + /// <returns>Returns the size of formatted text block.</returns> + public static ESize GetFormattedTextBlockSize(EvasObject textable) + { + return GetElmTextPart(textable).TextBlockFormattedSize; + } + + /// <summary> + /// Gets the ELM text part of evas object. + /// </summary> + /// <param name="textable">The <see cref="ElmSharp.EvasObject"/> with text part.</param> + /// <exception cref="ArgumentException">Throws exception when parameter <param name="textable"> isn't text-able object or doesn't have ELM text part.</exception> + /// <returns>Requested <see cref="ElmSharp.EdjeTextPartObject"/> instance.</returns> + static EdjeTextPartObject GetElmTextPart(EvasObject textable) + { + ELayout widget = textable as ELayout; + if (widget == null) + { + throw new ArgumentException("textable should be ElmSharp.Layout", "textable"); + } + EdjeTextPartObject textPart = widget.EdjeObject["elm.text"]; + if (textPart == null) + { + throw new ArgumentException("There is no elm.text part", "textable"); + } + return textPart; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs b/Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs new file mode 100644 index 00000000..6ee49ce3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.DateTimeSelector class with functionality useful to renderer. + /// </summary> + public class TimePicker : DateTimeSelector + { + const string DefaultEFLFormat = "%I:%M %p"; + //TODO need to add internationalization support + const string FormatExceptionMessage = "Input string was not in a correct format."; + const string RegexValidTimePattern = "^([h]{1,2}|[H]{1,2})[.:-]([m]{1,2})(([.:-][s]{1,2})?)(([.:-][fF]{1,7})?)(([K])?)(([z]{1,3})?)(([ ][t]{1,2})?)$"; + const string TimeLayoutStyle = "time_layout"; + string _dateTimeFormat; + TimeSpan _time; + + /// <summary> + /// Initializes a new instance of the <see cref="TimePicker"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public TimePicker(EvasObject parent) : base(parent) + { + Style = TimeLayoutStyle; + ApplyTime(Time); + ApplyFormat(DateTimeFormat); + + DateTimeChanged += (sender, e) => + { + Time = e.NewDate.TimeOfDay; + }; + } + + /// <summary> + /// Gets or sets the displayed date time format. + /// </summary> + public string DateTimeFormat + { + get + { + return _dateTimeFormat; + } + set + { + if (_dateTimeFormat != value) + { + ApplyFormat(value); + } + } + } + + /// <summary> + /// Gets or sets the displayed time. + /// </summary> + public TimeSpan Time + { + get + { + return _time; + } + set + { + if (_time != value) + { + ApplyTime(value); + } + } + } + + /// <summary> + /// Sets the <c>Format</c> property according to the given <paramref name="format"/>. + /// </summary> + /// <param name="format">The format value to be applied to the time picker.</param> + void ApplyFormat(string format) + { + _dateTimeFormat = format; + Format = ConvertToEFLFormat(_dateTimeFormat); + } + + /// <summary> + /// Sets the <c>DateTime</c> property according to the given <paramref name="time"/>. + /// </summary> + /// <param name="time">The time value to be applied to the time picker.</param> + void ApplyTime(TimeSpan time) + { + _time = time; + DateTime = ConvertToDateTime(time); + } + + /// <summary> + /// Converts parameter <paramref name="timeSpan"/> to <see cref="DateTime"/>. + /// </summary> + /// <param name="timeSpan">The time value to be converted to <see cref="DateTime"/>.</param> + /// <returns>An object representing the date 1st Jan, 1970 (minimum date of ElmSharp.DateTimeSelector) with added <paramref name="timeSpan"/>.</returns> + DateTime ConvertToDateTime(TimeSpan timeSpan) + { + return new DateTime(1970, 1, 1) + timeSpan; + } + + /// <summary> + /// Converts standard or custom <see cref="DateTime"/> format to EFL format. + /// </summary> + /// <param name="dateTimeFormat">The <see cref="DateTime"/> format to be converted to EFL format.</param> + /// <exception cref="FormatException"><param name="dateTimeFormat"> does not contain a valid string representation of a date and time.</exception> + /// <returns>An object representing the EFL time format string. + /// Example: + /// "t" or "T" returns default EFL format "%I:%M %p" + /// "HH:mm tt" returns "%H:%M %p" + /// "h:mm" returns "%l:%M" + /// </returns> + string ConvertToEFLFormat(string dateTimeFormat) + { + if (string.IsNullOrWhiteSpace(dateTimeFormat)) + { + return DefaultEFLFormat; + } + + if (dateTimeFormat.Length == 1) + { + //Standard Time Format (DateTime) + if (dateTimeFormat[0] == 't' || dateTimeFormat[0] == 'T') + { + return DefaultEFLFormat; + } + else + { + throw new FormatException(FormatExceptionMessage); + } + } + else + { + //Custom Time Format (DateTime) + Regex regex = new Regex(RegexValidTimePattern); + if (!regex.IsMatch(dateTimeFormat)) + { + throw new FormatException(FormatExceptionMessage); + } + + string format = string.Empty; + int count_h = dateTimeFormat.Count(m => m == 'h'); //12h + int count_H = dateTimeFormat.Count(m => m == 'H'); //24h + + if (count_h == 1) + { + format += "%l"; + } + else if (count_h == 2) + { + format += "%I"; + } + else if (count_H == 1) + { + format += "%k"; + } + else if (count_H == 2) + { + format += "%H"; + } + + format += ":%M"; + int count_t = dateTimeFormat.Count(m => m == 't'); + + if ((count_H > 0 && count_t > 0) || + (count_h > 0 && count_t == 0)) + { + throw new FormatException(FormatExceptionMessage); + } + + if (count_t == 1) + { + format += " %P"; + } + else if (count_t == 2) + { + format += " %p"; + } + + return format; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Window.cs b/Xamarin.Forms.Platform.Tizen/Native/Window.cs new file mode 100644 index 00000000..87a54cd3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Window.cs @@ -0,0 +1,138 @@ +using System; +using ElmSharp; +using EWindow = ElmSharp.Window; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + public class Window : EWindow + { + Conformant _conformant; + + /// <summary> + /// Initializes a new instance of the Window class. + /// </summary> + public Window() : base("FormsWindow") + { + Initialize(); + } + + /// <summary> + /// Notifies that the window has been closed. + /// </summary> + public event EventHandler Closed; + + /// <summary> + /// Notifies that the back button has been pressed. + /// </summary> + public event EventHandler BackButtonPressed; + + /// <summary> + /// Gets the current orientation. + /// </summary> + public DisplayOrientations CurrentOrientation + { + get + { + if (IsRotationSupported) + { + return GetDisplayOrientation(); + } + else + { + return DisplayOrientations.None; + } + } + } + + /// <summary> + /// Gets or sets the orientation of a rectangular screen. + /// </summary> + public DisplayOrientations AvailableOrientations + { + get + { + if (IsRotationSupported) + { + return (DisplayOrientations)AvailableRotations; + } + else + { + return DisplayOrientations.None; + } + } + set + { + if (IsRotationSupported) + { + AvailableRotations = (DisplayRotation)value; + } + } + } + + /// <summary> + /// Sets the main page of Window. + /// </summary> + /// <param name="content">ElmSharp.EvasObject type page to be set.</param> + public void SetMainPage(EvasObject content) + { + _conformant.SetContent(content); + } + + void Initialize() + { + // size + var size = ScreenSize; + Resize(size.Width, size.Height); + + // events + Deleted += (sender, e) => + { + Closed?.Invoke(this, EventArgs.Empty); + }; + CloseRequested += (sender, e) => + { + Unrealize(); + }; + + KeyGrab(EvasKeyEventArgs.PlatformBackButtonName, false); + KeyUp += (s, e) => + { + if (e.KeyName == EvasKeyEventArgs.PlatformBackButtonName) + { + BackButtonPressed?.Invoke(this, EventArgs.Empty); + } + }; + + Active(); + AutoDeletion = false; + Show(); + + _conformant = new Conformant(this); + _conformant.SetAlignment(-1.0, -1.0); // fill + _conformant.SetWeight(1.0, 1.0); // expand + _conformant.Show(); + + AvailableOrientations = DisplayOrientations.Portrait | DisplayOrientations.Landscape | DisplayOrientations.PortraitFlipped | DisplayOrientations.LandscapeFlipped; + } + DisplayOrientations GetDisplayOrientation() + { + switch (Rotation) + { + case 0: + return Native.DisplayOrientations.Portrait; + + case 90: + return Native.DisplayOrientations.Landscape; + + case 180: + return Native.DisplayOrientations.PortraitFlipped; + + case 270: + return Native.DisplayOrientations.LandscapeFlipped; + + default: + return Native.DisplayOrientations.None; + } + } + } +} |