From 347db139a9b1b93cd07739e281631bf13031496b Mon Sep 17 00:00:00 2001 From: Kangho Hur Date: Fri, 16 Dec 2016 11:00:07 +0900 Subject: Add Tizen backend renderer - Xamarin.Forms.Platform.Tizen has been added - Xamarin.Forms.Maps.Tizen has been added - RPM build spec has been added Change-Id: I0021e0f040d97345affc87512ee0f6ce437f4e6d --- Xamarin.Forms.Platform.Tizen/Cells/CellRenderer.cs | 142 +++ .../Cells/EntryCellRenderer.cs | 163 ++++ .../Cells/ImageCellRenderer.cs | 60 ++ .../Cells/SwitchCellRenderer.cs | 74 ++ .../Cells/TextCellRenderer.cs | 72 ++ .../Cells/ViewCellRenderer.cs | 43 + Xamarin.Forms.Platform.Tizen/Deserializer.cs | 106 +++ .../ElementChangedEventArgs.cs | 38 + Xamarin.Forms.Platform.Tizen/EvasObjectWrapper.cs | 24 + .../ExportCellAttribute.cs | 12 + .../ExportImageSourceHandlerAttribute.cs | 12 + .../ExportRendererAttribute.cs | 12 + .../Extensions/ColorExtensions.cs | 39 + .../Extensions/KeyboardExtensions.cs | 42 + .../Extensions/LayoutExtensions.cs | 38 + .../Extensions/ScrollToPositionExtensions.cs | 28 + .../Extensions/TextAlignmentExtensions.cs | 26 + Xamarin.Forms.Platform.Tizen/Forms.cs | 173 ++++ Xamarin.Forms.Platform.Tizen/FormsApplication.cs | 344 +++++++ Xamarin.Forms.Platform.Tizen/GestureHandler.cs | 299 +++++++ Xamarin.Forms.Platform.Tizen/Log/ConsoleLogger.cs | 68 ++ Xamarin.Forms.Platform.Tizen/Log/DlogLogger.cs | 41 + Xamarin.Forms.Platform.Tizen/Log/ILogger.cs | 71 ++ Xamarin.Forms.Platform.Tizen/Log/Log.cs | 989 +++++++++++++++++++++ .../Log/XamarinLogListener.cs | 20 + Xamarin.Forms.Platform.Tizen/Native/Box.cs | 67 ++ Xamarin.Forms.Platform.Tizen/Native/Button.cs | 303 +++++++ Xamarin.Forms.Platform.Tizen/Native/Canvas.cs | 109 +++ Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs | 76 ++ .../Native/DateChangedEventArgs.cs | 31 + Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs | 130 +++ .../Native/DateTimePickerDialog.cs | 100 +++ Xamarin.Forms.Platform.Tizen/Native/Dialog.cs | 276 ++++++ .../Native/DisplayOrientations.cs | 36 + Xamarin.Forms.Platform.Tizen/Native/Entry.cs | 396 +++++++++ .../Native/FormattedString.cs | 128 +++ .../Native/IContainable.cs | 16 + Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs | 20 + Xamarin.Forms.Platform.Tizen/Native/ITextable.cs | 68 ++ Xamarin.Forms.Platform.Tizen/Native/Image.cs | 124 +++ Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs | 84 ++ Xamarin.Forms.Platform.Tizen/Native/Label.cs | 347 ++++++++ .../Native/LayoutEventArgs.cs | 55 ++ .../Native/LineBreakMode.cs | 43 + Xamarin.Forms.Platform.Tizen/Native/ListView.cs | 601 +++++++++++++ .../Native/MasterDetailPage.cs | 364 ++++++++ .../Native/ObservableCollection.cs | 30 + Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs | 423 +++++++++ Xamarin.Forms.Platform.Tizen/Native/Span.cs | 294 ++++++ Xamarin.Forms.Platform.Tizen/Native/TableView.cs | 67 ++ .../Native/TextAlignment.cs | 25 + Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs | 54 ++ Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs | 185 ++++ Xamarin.Forms.Platform.Tizen/Native/Window.cs | 138 +++ Xamarin.Forms.Platform.Tizen/Platform.cs | 345 +++++++ Xamarin.Forms.Platform.Tizen/PlatformEffect.cs | 11 + .../Properties/AssemblyInfo.cs | 59 ++ .../Renderers/ActivityIndicatorRenderer.cs | 57 ++ .../Renderers/BoxViewRenderer.cs | 61 ++ .../Renderers/ButtonRenderer.cs | 95 ++ .../Renderers/CarouselPageRenderer.cs | 259 ++++++ .../Renderers/ContentPageRenderer.cs | 65 ++ .../Renderers/DatePickerRenderer.cs | 77 ++ .../Renderers/EditorRenderer.cs | 88 ++ .../Renderers/EntryRenderer.cs | 127 +++ .../Renderers/EvasObjectWrapperRenderer.cs | 32 + .../Renderers/FrameRenderer.cs | 124 +++ .../Renderers/IVisualElementRenderer.cs | 39 + .../Renderers/ImageRenderer.cs | 105 +++ .../Renderers/LabelRenderer.cs | 103 +++ .../Renderers/LayoutRenderer.cs | 57 ++ .../Renderers/ListViewRenderer.cs | 437 +++++++++ .../Renderers/MasterDetailPageRenderer.cs | 96 ++ .../Renderers/NavigationPageRenderer.cs | 390 ++++++++ .../Renderers/PickerRenderer.cs | 119 +++ .../Renderers/ProgressBarRenderer.cs | 60 ++ .../Renderers/ScrollViewRenderer.cs | 130 +++ .../Renderers/SearchBarRenderer.cs | 167 ++++ .../Renderers/SliderRenderer.cs | 63 ++ .../Renderers/StepperRenderer.cs | 86 ++ .../Renderers/SwitchRenderer.cs | 50 ++ .../Renderers/TabbedPageRenderer.cs | 190 ++++ .../Renderers/TableViewRenderer.cs | 79 ++ .../Renderers/TimePickerRenderer.cs | 101 +++ .../Renderers/ViewRenderer.cs | 46 + .../Renderers/VisualElementRenderer.cs | 946 ++++++++++++++++++++ Xamarin.Forms.Platform.Tizen/ResourcePath.cs | 44 + Xamarin.Forms.Platform.Tizen/ResourcesProvider.cs | 72 ++ .../TizenIsolatedStorageFile.cs | 125 +++ .../TizenPlatformServices.cs | 232 +++++ .../TizenTitleBarVisibility.cs | 8 + .../ViewInitializedEventArgs.cs | 20 + .../VisualElementChangedEventArgs.cs | 16 + .../VisualElementRendererFlags.cs | 13 + .../Xamarin.Forms.Platform.Tizen.csproj | 163 ++++ .../Xamarin.Forms.Platform.Tizen.project.json | 13 + 96 files changed, 12896 insertions(+) create mode 100644 Xamarin.Forms.Platform.Tizen/Cells/CellRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Cells/EntryCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Cells/ImageCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Cells/SwitchCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Cells/TextCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Cells/ViewCellRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Deserializer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ElementChangedEventArgs.cs create mode 100644 Xamarin.Forms.Platform.Tizen/EvasObjectWrapper.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ExportCellAttribute.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ExportImageSourceHandlerAttribute.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ExportRendererAttribute.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Extensions/ColorExtensions.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Extensions/KeyboardExtensions.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Extensions/LayoutExtensions.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Extensions/ScrollToPositionExtensions.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Extensions/TextAlignmentExtensions.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Forms.cs create mode 100644 Xamarin.Forms.Platform.Tizen/FormsApplication.cs create mode 100644 Xamarin.Forms.Platform.Tizen/GestureHandler.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Log/ConsoleLogger.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Log/DlogLogger.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Log/ILogger.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Log/Log.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Log/XamarinLogListener.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Box.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Button.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Canvas.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs create mode 100755 Xamarin.Forms.Platform.Tizen/Native/Dialog.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Entry.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/IContainable.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/ITextable.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Image.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Label.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/ListView.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Span.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/TableView.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Native/Window.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Platform.cs create mode 100644 Xamarin.Forms.Platform.Tizen/PlatformEffect.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs create mode 100755 Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ResourcePath.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ResourcesProvider.cs create mode 100644 Xamarin.Forms.Platform.Tizen/TizenIsolatedStorageFile.cs create mode 100644 Xamarin.Forms.Platform.Tizen/TizenPlatformServices.cs create mode 100644 Xamarin.Forms.Platform.Tizen/TizenTitleBarVisibility.cs create mode 100644 Xamarin.Forms.Platform.Tizen/ViewInitializedEventArgs.cs create mode 100644 Xamarin.Forms.Platform.Tizen/VisualElementChangedEventArgs.cs create mode 100644 Xamarin.Forms.Platform.Tizen/VisualElementRendererFlags.cs create mode 100644 Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.csproj create mode 100644 Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.project.json (limited to 'Xamarin.Forms.Platform.Tizen') diff --git a/Xamarin.Forms.Platform.Tizen/Cells/CellRenderer.cs b/Xamarin.Forms.Platform.Tizen/Cells/CellRenderer.cs new file mode 100644 index 00000000..1cda74c2 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Cells/CellRenderer.cs @@ -0,0 +1,142 @@ +using ElmSharp; +using System.Collections.Generic; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public abstract class CellRenderer : IRegisterable + { + const string _heightProperty = "Height"; + protected static readonly EColor s_defaultTextColor = EColor.Black; + protected static readonly EColor s_defaultBackgroundColor = EColor.White; + readonly Dictionary> _realizedNativeViews = new Dictionary>(); + + protected CellRenderer(string style) + { + Class = new GenItemClass(style) + { + GetTextHandler = GetText, + GetContentHandler = GetContent, + DeleteHandler = ItemDeleted, + }; + } + + public GenItemClass Class + { + get; + private set; + } + + protected virtual bool OnCellPropertyChanged(Cell cell, string property, Dictionary realizedView) + { + if (property == _heightProperty) + { + return true; + } + return false; + } + + protected virtual Span OnGetText(Cell cell, string part) + { + return null; + } + protected virtual EvasObject OnGetContent(Cell cell, string part) + { + return null; + } + protected virtual void OnDeleted(Cell cell) + { + } + + protected virtual void OnUnrealizedCell(Cell cell) + { + } + + protected int FindCellContentHeight(Cell cell) + { + ViewCell viewCell = cell as ViewCell; + if (viewCell != null) + { + var parentWidth = (cell.Parent as VisualElement).Width; + var view = viewCell.View; + return (int)view.Measure(parentWidth, double.PositiveInfinity).Request.Height; + } + else + return -1; + } + + static Native.Span ToNative(Span span) + { + var nativeSpan = new Native.Span(); + nativeSpan.Text = span.Text; + nativeSpan.ForegroundColor = span.ForegroundColor.IsDefault ? s_defaultTextColor : span.ForegroundColor.ToNative(); + nativeSpan.FontAttributes = span.FontAttributes; + nativeSpan.BackgroundColor = span.BackgroundColor.IsDefault ? s_defaultBackgroundColor : span.BackgroundColor.ToNative(); + nativeSpan.FontSize = span.FontSize; + nativeSpan.FontFamily = span.FontFamily; + return nativeSpan; + } + + internal void SendCellPropertyChanged(Cell cell, GenListItem item, string property) + { + Dictionary realizedView = null; + _realizedNativeViews.TryGetValue(cell, out realizedView); + + // just to prevent null reference exception in OnCellPropertyChanged + realizedView = realizedView ?? new Dictionary(); + + if (property == Cell.IsEnabledProperty.PropertyName) + { + item.IsEnabled = cell.IsEnabled; + } + // if true was returned, item was updated + // if it's possible to update the cell property without Update(), return false + else if (OnCellPropertyChanged(cell, property, realizedView)) + { + item.Update(); + } + } + + internal void SendUnrealizedCell(Cell cell) + { + _realizedNativeViews.Remove(cell); + OnUnrealizedCell(cell); + } + + string GetText(object data, string part) + { + var span = OnGetText((data as Native.ListView.ItemContext).Cell, part); + return span != null ? ToNative(span).GetDecoratedText() : null; + } + + EvasObject GetContent(object data, string part) + { + var cell = (data as Native.ListView.ItemContext).Cell; + EvasObject nativeView = OnGetContent(cell, part); + if (part != null && nativeView != null) + { + Dictionary realizedView = null; + _realizedNativeViews.TryGetValue(cell, out realizedView); + if (realizedView == null) + { + realizedView = new Dictionary(); + _realizedNativeViews[cell] = realizedView; + } + realizedView[part] = nativeView; + + nativeView.Deleted += (sender, e) => + { + realizedView.Remove(part); + }; + } + return nativeView; + } + + void ItemDeleted(object data) + { + var cell = (data as Native.ListView.ItemContext).Cell; + _realizedNativeViews.Remove(cell); + OnDeleted(cell); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Cells/EntryCellRenderer.cs b/Xamarin.Forms.Platform.Tizen/Cells/EntryCellRenderer.cs new file mode 100644 index 00000000..c07985f8 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Cells/EntryCellRenderer.cs @@ -0,0 +1,163 @@ +using ElmSharp; +using EColor = ElmSharp.Color; +using EBox = ElmSharp.Box; +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class EntryCellRenderer : ViewCellRenderer + { + const int _defaultHeight = 120; + readonly Dictionary _realizedComponent = new Dictionary(); + + public EntryCellRenderer() + { + } + + protected override EvasObject OnGetContent(Cell cell, string part) + { + if (part == MainContentPart) + { + var entryCell = cell as EntryCell; + int height = (int)entryCell.RenderHeight; + height = height <= 0 ? FindCellContentHeight(entryCell) : height; + height = height <= 0 ? _defaultHeight : height; + + // TODO + // Need to use Forms.Core element instead of using Elmsharp. + // if we use Forms element, easily binding data to view elements + var box = new EBox(Forms.Context.MainWindow) + { + IsHorizontal = true, + MinimumHeight = height, + }; + box.SetAlignment(-1.0, -1.0); // fill + box.SetWeight(1.0, 1.0); // expand + + var label = new Native.Label(box) + { + Text = entryCell.Label, + TextColor = GetLabelColor(entryCell), + }; + label.SetAlignment(0.0, 0.5); + label.SetWeight(0.0, 1.0); + + var entry = new Native.Entry(box) + { + IsSingleLine = true, + Text = entryCell.Text, + TextColor = s_defaultTextColor, + Placeholder = entryCell.Placeholder, + PlaceholderColor = s_defaultTextColor, + Keyboard = entryCell.Keyboard.ToNative(), + HorizontalTextAlignment = entryCell.HorizontalTextAlignment.ToNative(), + }; + entry.SetAlignment(-1.0, 0.5); + entry.SetWeight(1.0, 1.0); + + box.PackEnd(label); + box.PackEnd(entry); + + label.Show(); + entry.Show(); + + entry.TextChanged += (sender, e) => + { + entryCell.Text = e.NewTextValue; + }; + entry.Activated += (sender, e) => + { + (cell as IEntryCellController).SendCompleted(); + }; + NativeEntryComponent component = new NativeEntryComponent() + { + Entry = entry, + Label = label, + }; + _realizedComponent[box] = component; + box.Deleted += (sender, e) => + { + _realizedComponent.Remove(box); + }; + return box; + } + return null; + } + + protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary realizedView) + { + if (!realizedView.ContainsKey(MainContentPart) || !_realizedComponent.ContainsKey(realizedView[MainContentPart])) + { + return base.OnCellPropertyChanged(cell, property, realizedView); + } + + NativeEntryComponent realizedCompoenet = _realizedComponent[realizedView[MainContentPart]]; + EntryCell entryCell = (EntryCell)cell; + + if (property == EntryCell.HorizontalTextAlignmentProperty.PropertyName) + { + var entry = realizedCompoenet.Entry; + if (entry != null) + { + entry.HorizontalTextAlignment = entryCell.HorizontalTextAlignment.ToNative(); + } + } + else if (property == EntryCell.KeyboardProperty.PropertyName) + { + var entry = realizedCompoenet.Entry; + if (entry != null) + { + entry.Keyboard = entryCell.Keyboard.ToNative(); + } + } + else if (property == EntryCell.PlaceholderProperty.PropertyName) + { + var entry = realizedCompoenet.Entry; + if (entry != null) + { + entry.Placeholder = entryCell.Placeholder; + } + } + else if (property == EntryCell.TextProperty.PropertyName) + { + var entry = realizedCompoenet.Entry; + if (entry != null) + { + entry.Text = entryCell.Text; + } + } + else if (property == EntryCell.LabelProperty.PropertyName) + { + var label = realizedCompoenet.Label; + if (label != null) + { + label.Text = entryCell.Label; + } + } + else if (property == EntryCell.LabelColorProperty.PropertyName) + { + var label = realizedCompoenet.Label; + if (label != null) + { + label.TextColor = GetLabelColor(entryCell); + } + } + else + { + return base.OnCellPropertyChanged(cell, property, realizedView); + } + return false; + } + + EColor GetLabelColor(EntryCell cell) + { + return cell.LabelColor.IsDefault ? s_defaultTextColor : cell.LabelColor.ToNative(); + } + + class NativeEntryComponent + { + public Native.Entry Entry { get; set; } + public Native.Label Label { get; set; } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Cells/ImageCellRenderer.cs b/Xamarin.Forms.Platform.Tizen/Cells/ImageCellRenderer.cs new file mode 100644 index 00000000..9492e7c6 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Cells/ImageCellRenderer.cs @@ -0,0 +1,60 @@ +using ElmSharp; +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ImageCellRenderer : TextCellRenderer + { + const int _defaultHeight = 100; + Dictionary _realizedViews = new Dictionary(); + + public ImageCellRenderer() : this("type1") + { + ImagePart = "elm.swallow.icon"; + } + protected ImageCellRenderer(string style) : base(style) { } + + protected string ImagePart { get; set; } + + protected override EvasObject OnGetContent(Cell cell, string part) + { + if (part == ImagePart) + { + var imgCell = cell as ImageCell; + var size = imgCell.RenderHeight; + if (size <= 0) + { + size = _defaultHeight; + } + + var image = new Native.Image(Forms.Context.MainWindow) + { + MinimumWidth = (int)size, + MinimumHeight = (int)size + }; + image.SetAlignment(-1.0, -1.0); // fill + image.SetWeight(1.0, 1.0); // expand + + image.LoadFromImageSourceAsync(imgCell.ImageSource); + return image; + } + else + { + return null; + } + } + + protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary realizedView) + { + if (property == ImageCell.ImageSourceProperty.PropertyName) + { + EvasObject image; + realizedView.TryGetValue(ImagePart, out image); + (image as Native.Image)?.LoadFromImageSourceAsync((cell as ImageCell)?.ImageSource); + return false; + } + return base.OnCellPropertyChanged(cell, property, realizedView); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Cells/SwitchCellRenderer.cs b/Xamarin.Forms.Platform.Tizen/Cells/SwitchCellRenderer.cs new file mode 100644 index 00000000..6c5a0380 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Cells/SwitchCellRenderer.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class SwitchCellRenderer : CellRenderer + { + protected SwitchCellRenderer(string style) : base(style) + { + } + public SwitchCellRenderer() : this("end_icon") + { + MainPart = "elm.text"; + SwitchPart = "elm.swallow.end"; + } + + protected string MainPart { get; set; } + protected string SwitchPart { get; set; } + + protected override Span OnGetText(Cell cell, string part) + { + if (part == MainPart) + { + return new Span() + { + Text = (cell as SwitchCell).Text + }; + } + return null; + } + + protected override EvasObject OnGetContent(Cell cell, string part) + { + if (part == SwitchPart) + { + var switchCell = cell as SwitchCell; + var checkbox = new Check(Forms.Context.MainWindow) + { + Style = "on&off", + IsChecked = switchCell.On, + }; + + checkbox.StateChanged += (sender, e) => + { + switchCell.On = e.NewState; + }; + + checkbox.SetAlignment(-1.0, -1.0); // fill + checkbox.SetWeight(1.0, 1.0); // expand + return checkbox; + } + return null; + } + + protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary realizedView) + { + if (property == SwitchCell.OnProperty.PropertyName && realizedView.ContainsKey(SwitchPart)) + { + var checkbox = realizedView[SwitchPart] as Check; + if (checkbox != null) + { + checkbox.IsChecked = (cell as SwitchCell).On; + } + return false; + } + else if (property == SwitchCell.TextProperty.PropertyName) + { + return true; + } + + return base.OnCellPropertyChanged(cell, property, realizedView); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Cells/TextCellRenderer.cs b/Xamarin.Forms.Platform.Tizen/Cells/TextCellRenderer.cs new file mode 100644 index 00000000..2bea3848 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Cells/TextCellRenderer.cs @@ -0,0 +1,72 @@ +using ElmSharp; +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class TextCellRenderer : CellRenderer + { + public TextCellRenderer() : this("double_label") { } + protected TextCellRenderer(string style) : base(style) + { + MainPart = "elm.text"; + DetailPart = "elm.text.sub"; + } + + protected string MainPart { get; set; } + protected string DetailPart { get; set; } + + protected override Span OnGetText(Cell cell, string part) + { + var textCell = (TextCell)cell; + if (part == MainPart) + { + return OnMainText(textCell); + } + if (part == DetailPart) + { + return OnDetailText(textCell); + } + return null; + } + + protected virtual Span OnMainText(TextCell cell) + { + return new Span() + { + Text = cell.Text, + ForegroundColor = cell.TextColor + }; + } + + protected virtual Span OnDetailText(TextCell cell) + { + return new Span() + { + Text = cell.Detail, + ForegroundColor = cell.DetailColor + }; + } + + protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary realizedView) + { + if (property == TextCell.TextProperty.PropertyName || + property == TextCell.TextColorProperty.PropertyName || + property == TextCell.DetailProperty.PropertyName || + property == TextCell.DetailColorProperty.PropertyName) + { + return true; + } + return base.OnCellPropertyChanged(cell, property, realizedView); + } + } + + internal class GroupCellTextRenderer : TextCellRenderer + { + + public GroupCellTextRenderer() : this("group_index") + { + DetailPart = "elm.text.end"; + } + protected GroupCellTextRenderer(string style) : base(style) { } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.Tizen/Cells/ViewCellRenderer.cs new file mode 100644 index 00000000..69065c8a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Cells/ViewCellRenderer.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ViewCellRenderer : CellRenderer + { + public ViewCellRenderer() : base("full") + { + MainContentPart = "elm.swallow.content"; + } + + protected string MainContentPart { get; set; } + + protected override EvasObject OnGetContent(Cell cell, string part) + { + if (part == MainContentPart) + { + var viewCell = cell as ViewCell; + if (viewCell != null) + { + var renderer = Platform.GetOrCreateRenderer(viewCell.View); + int height = (int)viewCell.RenderHeight; + height = height <= 0 ? FindCellContentHeight(viewCell) : height; + + renderer.NativeView.MinimumHeight = height; + return renderer.NativeView; + } + return null; + } + return null; + } + + protected override bool OnCellPropertyChanged(Cell cell, string property, Dictionary realizedView) + { + if (property == "View") + { + return true; + } + return base.OnCellPropertyChanged(cell, property, realizedView); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Deserializer.cs b/Xamarin.Forms.Platform.Tizen/Deserializer.cs new file mode 100644 index 00000000..8335840c --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Deserializer.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Runtime.Serialization; +using System.Xml; +using System.Diagnostics; +using System.IO; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal class Deserializer : IDeserializer + { + const string PropertyStoreFile = "PropertyStore.forms"; + + public Task> DeserializePropertiesAsync() + { + // Deserialize property dictionary to local storage + // Make sure to use Internal + return Task.Run(() => + { + var store = new TizenIsolatedStorageFile(); + Stream stream = null; + try + { + stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.OpenOrCreate); + if (stream.Length == 0) + { + return null; + } + using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)) + { + stream = null; + var dcs = new DataContractSerializer(typeof(Dictionary)); + return (IDictionary)dcs.ReadObject(reader); + } + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + Xamarin.Forms.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while reading Application properties: {e}"); + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + } + + return null; + }); + } + + public Task SerializePropertiesAsync(IDictionary properties) + { + properties = new Dictionary(properties); + // Serialize property dictionary to local storage + // Make sure to use Internal + return Task.Run(() => + { + var success = false; + var store = new TizenIsolatedStorageFile(); + Stream stream = null; + try + { + stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate); + using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)) + { + stream = null; + var dcs = new DataContractSerializer(typeof(Dictionary)); + dcs.WriteObject(writer, properties); + writer.Flush(); + success = true; + } + } + catch (Exception e) + { + Debug.WriteLine("Could not serialize properties: " + e.Message); + Xamarin.Forms.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}"); + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + } + + if (!success) + return; + + try + { + if (store.FileExists(PropertyStoreFile)) + store.DeleteFile(PropertyStoreFile); + store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile); + } + catch (Exception e) + { + Debug.WriteLine("Could not move new serialized property file over old: " + e.Message); + Xamarin.Forms.Log.Warning("Xamarin.Forms PropertyStore", $"Exception while writing Application properties: {e}"); + } + }); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ElementChangedEventArgs.cs b/Xamarin.Forms.Platform.Tizen/ElementChangedEventArgs.cs new file mode 100644 index 00000000..844752ec --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ElementChangedEventArgs.cs @@ -0,0 +1,38 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ElementChangedEventArgs : EventArgs where TElement : Element + { + /// + /// Holds the old element which is about to be replaced by a new element + /// + /// An TElement instance representing the old element just being replaced + public TElement OldElement + { + get; + private set; + } + + /// + /// Holds the new element which will replace the old element + /// + /// An TElement instance representing the new element to be used from now on + public TElement NewElement + { + get; + private set; + } + + /// + /// Initializes a new instance of the class. + /// + /// The old element which will be replaced by a newElement momentarily. + /// The new element, taking place of an old element. + public ElementChangedEventArgs(TElement oldElement, TElement newElement) + { + this.OldElement = oldElement; + this.NewElement = newElement; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/EvasObjectWrapper.cs b/Xamarin.Forms.Platform.Tizen/EvasObjectWrapper.cs new file mode 100644 index 00000000..97cf77f0 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/EvasObjectWrapper.cs @@ -0,0 +1,24 @@ +using ElmSharp; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen +{ + public delegate ESize? MeasureDelegate(EvasObjectWrapperRenderer renderer, int availableWidth, int availableHeight); + + public class EvasObjectWrapper : View + { + public EvasObjectWrapper(EvasObject obj, MeasureDelegate measureDelegate = null) + { + EvasObject = obj; + MeasureDelegate = measureDelegate; + } + + public EvasObject EvasObject + { + get; + private set; + } + + public MeasureDelegate MeasureDelegate { get; } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ExportCellAttribute.cs b/Xamarin.Forms.Platform.Tizen/ExportCellAttribute.cs new file mode 100644 index 00000000..e0435ad0 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ExportCellAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class ExportCellAttribute : HandlerAttribute + { + public ExportCellAttribute(Type handler, Type target) : base(handler, target) + { + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ExportImageSourceHandlerAttribute.cs b/Xamarin.Forms.Platform.Tizen/ExportImageSourceHandlerAttribute.cs new file mode 100644 index 00000000..4463db86 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ExportImageSourceHandlerAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute + { + public ExportImageSourceHandlerAttribute(Type handler, Type target) : base(handler, target) + { + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ExportRendererAttribute.cs b/Xamarin.Forms.Platform.Tizen/ExportRendererAttribute.cs new file mode 100644 index 00000000..9af8019f --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ExportRendererAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class ExportRendererAttribute : HandlerAttribute + { + public ExportRendererAttribute(Type handler, Type target) : base(handler, target) + { + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/ColorExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/ColorExtensions.cs new file mode 100644 index 00000000..553c2cba --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/ColorExtensions.cs @@ -0,0 +1,39 @@ +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class ColorExtensions + { + /// + /// Creates an instance of ElmSharp.Color class based on provided Xamarin.Forms.Color instance + /// + /// ElmSharp.Color instance representing a color which corresponds to the provided Xamarin.Forms.Color + /// The Xamarin.Forms.Color instance which will be converted to a ElmSharp.Color + public static EColor ToNative(this Color c) + { + if (c.IsDefault) + { + Log.Warn("Trying to convert the default color, this may result in black color."); + return EColor.Default; + } + else + { + return new EColor((int)(255.0 * c.R), (int)(255.0 * c.G), (int)(255.0 * c.B), (int)(255.0 * c.A)); + } + } + + /// + /// Returns a string representing the provided ElmSharp.Color instance in a hexagonal notation + /// + /// string value containing the encoded color + /// The ElmSharp.Color class instance which will be serialized + internal static string ToHex(this EColor c) + { + if (c.IsDefault) + { + Log.Warn("Trying to convert the default color to hexagonal notation, it does not works as expected."); + } + return string.Format("#{0:X2}{1:X2}{2:X2}{3:X2}", c.R, c.G, c.B, c.A); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/KeyboardExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/KeyboardExtensions.cs new file mode 100644 index 00000000..000b6bb2 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/KeyboardExtensions.cs @@ -0,0 +1,42 @@ +namespace Xamarin.Forms.Platform.Tizen +{ + public static class KeyboardExtensions + { + /// + /// Creates an instance of ElmSharp.Keyboard reflecting the provided Xamarin.Forms.Keyboard instance + /// + /// Keyboard type corresponding to the provided Xamarin.Forms.Keyboard + /// The Xamarin.Forms.Keyboard class instance to be converted to ElmSharp.Keyboard. + public static Native.Keyboard ToNative(this Keyboard keyboard) + { + if (keyboard is TextKeyboard) + { + return Native.Keyboard.Normal; + } + else if (keyboard is NumericKeyboard) + { + return Native.Keyboard.Number; + } + else if (keyboard is TelephoneKeyboard) + { + return Native.Keyboard.PhoneNumber; + } + else if (keyboard is EmailKeyboard) + { + return Native.Keyboard.Email; + } + else if (keyboard is UrlKeyboard) + { + return Native.Keyboard.Url; + } + else if (keyboard is ChatKeyboard) + { + return Native.Keyboard.Emoticon; + } + else + { + return Native.Keyboard.Normal; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/LayoutExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/LayoutExtensions.cs new file mode 100644 index 00000000..4880beb8 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/LayoutExtensions.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// + /// Extension class, provides native embedding functionalities: + /// https://developer.xamarin.com/guides/xamarin-forms/user-interface/layouts/add-platform-controls/ + /// + /// + /// This code is not used in the Xamarin.Forms.Platform.Tizen implementation, however it should not + /// be removed as it allows developers to use native controls directly. + /// + public static class LayoutExtensions + { + /// + /// Add the specified evas object to the list of children views. + /// + /// The extended class. + /// Object to be added. + /// Optional delegate which provides measurements for the added object. + public static void Add(this IList children, EvasObject obj, MeasureDelegate measureDelegate = null) + { + children.Add(obj.ToView(measureDelegate)); + } + + /// + /// Wraps the evas object into a view which can be used by Xamarin. + /// + /// The Xamarin view which wraps the evas object. + /// The extended class. + /// Optional delegate which provides measurements for the evas object. + public static View ToView(this EvasObject obj, MeasureDelegate measureDelegate = null) + { + return new EvasObjectWrapper(obj, measureDelegate); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/ScrollToPositionExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/ScrollToPositionExtensions.cs new file mode 100644 index 00000000..9d1dc255 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/ScrollToPositionExtensions.cs @@ -0,0 +1,28 @@ +using EScrollToPosition = ElmSharp.ScrollToPosition; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class ScrollToPositionExtensions + { + public static EScrollToPosition ToNative(this ScrollToPosition position) + { + switch (position) + { + case ScrollToPosition.Center: + return EScrollToPosition.Middle; + + case ScrollToPosition.End: + return EScrollToPosition.Bottom; + + case ScrollToPosition.MakeVisible: + return EScrollToPosition.In; + + case ScrollToPosition.Start: + return EScrollToPosition.Top; + + default: + return EScrollToPosition.Top; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Extensions/TextAlignmentExtensions.cs b/Xamarin.Forms.Platform.Tizen/Extensions/TextAlignmentExtensions.cs new file mode 100644 index 00000000..cf662c42 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Extensions/TextAlignmentExtensions.cs @@ -0,0 +1,26 @@ +namespace Xamarin.Forms.Platform.Tizen +{ + public static class TextAlignmentExtensions + { + public static Native.TextAlignment ToNative(this TextAlignment alignment) + { + switch (alignment) + { + case TextAlignment.Center: + return Native.TextAlignment.Center; + + case TextAlignment.Start: + return Native.TextAlignment.Start; + + case TextAlignment.End: + return Native.TextAlignment.End; + + default: + Log.Warn("Warning: unrecognized HorizontalTextAlignment value {0}. " + + "Expected: {Start|Center|End}.", alignment); + Log.Debug("Falling back to platform's default settings."); + return Native.TextAlignment.Auto; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Forms.cs b/Xamarin.Forms.Platform.Tizen/Forms.cs new file mode 100644 index 00000000..6aa0e62b --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Forms.cs @@ -0,0 +1,173 @@ +using System; +using System.Reflection; +using Tizen.Applications; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public static class Forms + { + class TizenDeviceInfo : DeviceInfo + { + readonly Size pixelScreenSize; + + readonly Size scaledScreenSize; + + readonly double scalingFactor; + + public override Size PixelScreenSize + { + get + { + return this.pixelScreenSize; + } + } + + public override Size ScaledScreenSize + { + get + { + return this.scaledScreenSize; + } + } + + public override double ScalingFactor + { + get + { + return this.scalingFactor; + } + } + + public TizenDeviceInfo(FormsApplication formsApplication) + { + // TODO: obtain screen data from device + pixelScreenSize = new Size(); + scaledScreenSize = new Size(); + scalingFactor = 0.0; + } + } + + public static event EventHandler ViewInitialized; + + public static FormsApplication Context + { + get; + internal set; + } + + public static bool IsInitialized + { + get; + private set; + } + + internal static TizenTitleBarVisibility TitleBarVisibility + { + get; + private set; + } + + internal static void SendViewInitialized(this VisualElement self, EvasObject nativeView) + { + EventHandler viewInitialized = Forms.ViewInitialized; + if (viewInitialized != null) + { + viewInitialized.Invoke(self, new ViewInitializedEventArgs + { + View = self, + NativeView = nativeView + }); + } + } + + public static void SetTitleBarVisibility(TizenTitleBarVisibility visibility) + { + TitleBarVisibility = visibility; + } + + public static void Init(FormsApplication application) + { + SetupInit(application); + } + + static void SetupInit(FormsApplication application) + { + Color.Accent = GetAccentColor(); + + Context = application; + + if (!IsInitialized) + { + Xamarin.Forms.Log.Listeners.Add(new XamarinLogListener()); + if (System.Threading.SynchronizationContext.Current == null) + { + TizenSynchronizationContext.Initialize(); + } + Elementary.Initialize(); + Elementary.ThemeOverlay(); + } + + //TO-DO: Need to change to Tizen. + Device.OS = TargetPlatform.Other; + +#if !NET45 + // In .NETCore, AppDomain feature is not supported. + // The list of assemblies returned by AppDomain.GetAssemblies() method should be registered manually. + // The assembly of the executing application and referenced assemblies of it are added into the list here. + Assembly asm = application.GetType().GetTypeInfo().Assembly; + TizenPlatformServices.AppDomain.CurrentDomain.RegisterAssembly(asm); + foreach (var refName in asm.GetReferencedAssemblies()) + { + if (!refName.Name.StartsWith("System.") && !refName.Name.StartsWith("Microsoft.")) + { + try + { + Assembly refAsm = Assembly.Load(refName); + TizenPlatformServices.AppDomain.CurrentDomain.RegisterAssembly(refAsm); + } + catch + { + Log.Warn("Reference Assembly can not be loaded. {0}", refName.FullName); + } + } + } +#endif + + Device.PlatformServices = new TizenPlatformServices(); ; + if (Device.info != null) + { + ((TizenDeviceInfo)Device.info).Dispose(); + Device.info = null; + } + + Device.Info = new Forms.TizenDeviceInfo(application); + + if (!Forms.IsInitialized) + { + Registrar.RegisterAll(new Type[] + { + typeof(ExportRendererAttribute), + typeof(ExportImageSourceHandlerAttribute), + typeof(ExportCellAttribute), + }); + } + + // FIXME: We should consider TV and Common (Desktop) profiles also. + Device.Idiom = TargetIdiom.Phone; + + IsInitialized = true; + } + + static Color GetAccentColor() + { + // On Windows Phone, this is the complementary color chosen by the user. + // Good Windows Phone applications use this as part of their styling to provide a native look and feel. + // On iOS and Android this instance is set to a contrasting color that is visible on the default + // background but is not the same as the default text color. + + // TODO: implement me + return Color.Black; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/FormsApplication.cs b/Xamarin.Forms.Platform.Tizen/FormsApplication.cs new file mode 100644 index 00000000..32ef3c38 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/FormsApplication.cs @@ -0,0 +1,344 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using Tizen.Applications; +using ElmSharp; +using EProgressBar = ElmSharp.ProgressBar; +using EColor = ElmSharp.Color; +using ELabel = ElmSharp.Label; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class FormsApplication : CoreUIApplication + { + Platform _platform; + Application _application; + bool _isInitialStart; + int _pageBusyCount; + Native.Dialog _pageBusyDialog; + Native.Window _window; + + protected FormsApplication() + { + _isInitialStart = true; + _pageBusyCount = 0; + } + + /// + /// Gets the main window or null if it's not set. + /// + /// The main window or null. + public Native.Window MainWindow + { + get + { + return _window; + } + + private set + { + _window = value; + } + } + + protected override void OnCreate() + { + base.OnCreate(); + Application.ClearCurrent(); + CreateWindow(); + } + + protected override void OnTerminate() + { + base.OnTerminate(); + MessagingCenter.Unsubscribe(this, "Xamarin.SendAlert"); + MessagingCenter.Unsubscribe(this, "Xamarin.BusySet"); + MessagingCenter.Unsubscribe(this, "Xamarin.ShowActionSheet"); + if (_platform != null) + { + _platform.Dispose(); + } + } + + protected override void OnAppControlReceived(AppControlReceivedEventArgs e) + { + base.OnAppControlReceived(e); + + if (!_isInitialStart && _application != null) + { + _application.SendResume(); + } + _isInitialStart = false; + } + + protected override void OnPause() + { + base.OnPause(); + if (_application != null) + { + _application.SendSleepAsync(); + } + } + + protected override void OnResume() + { + base.OnResume(); + if (_application != null) + { + _application.SendResume(); + } + } + + public void LoadApplication(Application application) + { + if (null == MainWindow) + { + throw new NullReferenceException("Call base OnCreate() method before loading the application."); + } + if (null == application) + { + throw new ArgumentNullException("application"); + } + _application = application; + Application.Current = application; + application.SendStart(); + application.PropertyChanged += new PropertyChangedEventHandler(this.AppOnPropertyChanged); + SetPage(_application.MainPage); + } + + void AppOnPropertyChanged(object sender, PropertyChangedEventArgs args) + { + if ("MainPage" == args.PropertyName) + { + SetPage(_application.MainPage); + } + } + + void ShowActivityIndicatorDialog(bool enabled) + { + if (null == _pageBusyDialog) + { + _pageBusyDialog = new Native.Dialog(Forms.Context.MainWindow) + { + Orientation = PopupOrientation.Top, + }; + + var activity = new EProgressBar(_pageBusyDialog) + { + Style = "process_large", + IsPulseMode = true, + }; + activity.PlayPulse(); + activity.Show(); + + _pageBusyDialog.Content = activity; + + } + _pageBusyCount = Math.Max(0, enabled ? _pageBusyCount + 1 : _pageBusyCount - 1); + if (_pageBusyCount > 0) + { + _pageBusyDialog.Show(); + } + else + { + _pageBusyDialog.Dismiss(); + _pageBusyDialog = null; + } + } + + void SetPage(Page page) + { + if (!Forms.IsInitialized) + { + throw new InvalidOperationException("Call Forms.Init (UIApplication) before this"); + } + if (_platform != null) + { + _platform.SetPage(page); + return; + } + MessagingCenter.Subscribe(this, Page.BusySetSignalName, delegate (Page sender, bool enabled) + { + ShowActivityIndicatorDialog(enabled); + }, null); + + MessagingCenter.Subscribe(this, Page.AlertSignalName, delegate (Page sender, AlertArguments arguments) + { + Native.Dialog alert = new Native.Dialog(Forms.Context.MainWindow); + + alert.Title = arguments.Title; + var label = new ELabel(alert) + { + Text = "" + arguments.Message + "<\\span>", + }; + label.Show(); + + var box = new Box(alert); + box.PackEnd(label); + box.Show(); + alert.Content = box; + + Native.Button cancel = new Native.Button(alert) { Text = arguments.Cancel }; + alert.NegativeButton = cancel; + cancel.Clicked += (s, evt) => + { + arguments.SetResult(false); + alert.Dismiss(); + }; + + if (arguments.Accept != null) + { + Native.Button ok = new Native.Button(alert) { Text = arguments.Accept }; + alert.PositiveButton = ok; + ok.Clicked += (s, evt) => + { + arguments.SetResult(true); + alert.Dismiss(); + }; + } + + alert.BackButtonPressed += (s, evt) => + { + arguments.SetResult(false); + alert.Dismiss(); + }; + + alert.Show(); + }, null); + + MessagingCenter.Subscribe(this, Page.ActionSheetSignalName, delegate (Page sender, ActionSheetArguments arguments) + { + Native.Dialog alert = new Native.Dialog(Forms.Context.MainWindow); + + alert.Title = arguments.Title; + Box box = new Box(alert); + + if (null != arguments.Destruction) + { + Native.Button destruction = new Native.Button(alert) + { + Text = arguments.Destruction, + TextColor = EColor.Red, + AlignmentX = -1 + }; + destruction.Clicked += (s, evt) => + { + arguments.SetResult(arguments.Destruction); + alert.Dismiss(); + }; + destruction.Show(); + box.PackEnd(destruction); + } + + foreach (string buttonName in arguments.Buttons) + { + Native.Button button = new Native.Button(alert) + { + Text = buttonName, + AlignmentX = -1 + }; + button.Clicked += (s, evt) => + { + arguments.SetResult(buttonName); + alert.Dismiss(); + }; + button.Show(); + box.PackEnd(button); + } + + box.Show(); + alert.Content = box; + + if (null != arguments.Cancel) + { + Native.Button cancel = new Native.Button(Forms.Context.MainWindow) { Text = arguments.Cancel }; + alert.NegativeButton = cancel; + cancel.Clicked += (s, evt) => + { + alert.Dismiss(); + }; + } + + alert.BackButtonPressed += (s, evt) => + { + alert.Dismiss(); + }; + + alert.Show(); + }, null); + + _platform = new Platform(this); + if (_application != null) + { + _application.Platform = _platform; + } + _platform.SetPage(page); + } + + void CreateWindow() + { + Debug.Assert(null == MainWindow); + + var window = new Native.Window(); + window.Closed += (s, e) => + { + Exit(); + }; + window.RotationChanged += (sender, e) => + { + switch (_window.CurrentOrientation) + { + case Native.DisplayOrientations.None: + Device.Info.CurrentOrientation = DeviceOrientation.Other; + break; + + case Native.DisplayOrientations.Portrait: + Device.Info.CurrentOrientation = DeviceOrientation.PortraitUp; + break; + + case Native.DisplayOrientations.Landscape: + Device.Info.CurrentOrientation = DeviceOrientation.LandscapeLeft; + break; + + case Native.DisplayOrientations.PortraitFlipped: + Device.Info.CurrentOrientation = DeviceOrientation.PortraitDown; + break; + + case Native.DisplayOrientations.LandscapeFlipped: + Device.Info.CurrentOrientation = DeviceOrientation.LandscapeRight; + break; + } + }; + + MainWindow = window; + } + public void Run() + { + Run(System.Environment.GetCommandLineArgs()); + } + + /// + /// Exits the application's main loop, which initiates the process of its termination + /// + public override void Exit() + { + if (_platform == null) + { + Log.Warn("Exit was already called or FormsApplication is not initialized yet."); + return; + } + // before everything is closed, inform the MainPage that it is disappearing + try + { + (_platform?.Page as IPageController)?.SendDisappearing(); + _platform = null; + } + catch (Exception e) + { + Log.Error("Exception thrown from SendDisappearing: {0}", e.Message); + } + + base.Exit(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/GestureHandler.cs b/Xamarin.Forms.Platform.Tizen/GestureHandler.cs new file mode 100644 index 00000000..209da3e3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/GestureHandler.cs @@ -0,0 +1,299 @@ +using System.Linq; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Collections.Generic; +using ElmSharp; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal class GestureHandler + { + internal readonly IVisualElementRenderer _renderer; + internal GestureLayer _gestureLayer; + View _view => _renderer.Element as View; + IPanGestureController _currentPanGestureController; + int _currentPanGestureId; + IPinchGestureController _currentPinchGestureController; + Point _currentScalePoint; + int _previousPinchRadius; + double _originalPinchScale; + Polygon _hitBox; + + public GestureHandler(IVisualElementRenderer renderer) + { + _renderer = renderer; + // Whenever a GestureRecognizer is added to the View, it will be connected to GestureLayer + (_view.GestureRecognizers as ObservableCollection).CollectionChanged += OnGestureRecognizersChanged; + // handle GestureRecognizers which were already set by the time we got here + if (_view.GestureRecognizers.Count > 0) + { + CreateGestureLayer(); + foreach (var item in _view.GestureRecognizers) + ToggleRecognizer(item, true); + } + } + + public void Clear() + { + // this will clear all callbacks in ElmSharp GestureLayer + _gestureLayer.Unrealize(); + (_view.GestureRecognizers as ObservableCollection).CollectionChanged -= OnGestureRecognizersChanged; + if (_hitBox != null) + { + _hitBox.Unrealize(); + _hitBox = null; + } + } + + public void UpdateHitBox() + { + if (_hitBox == null) + return; + // _hitBox has to be used because gestures do not work well with transformations (EvasMap) + // so we create additional object which has the same shape as tranformed target, but does not have EvasMap on it + EvasObject target = _renderer.NativeView; + _hitBox.ClearPoints(); + if (target.IsMapEnabled) + { + var map = target.EvasMap; + Point3D point; + for (var i = 0; i < 4; i++) + { + point = map.GetPointCoordinate(i); + _hitBox.AddPoint(point.X, point.Y); + } + } + else + { + var geometry = target.Geometry; + if (geometry.Width == 0 || geometry.Height == 0) + return; + _hitBox.AddPoint(geometry.Left, geometry.Top); + _hitBox.AddPoint(geometry.Right, geometry.Top); + _hitBox.AddPoint(geometry.Right, geometry.Bottom); + _hitBox.AddPoint(geometry.Left, geometry.Bottom); + } + } + + protected void ToggleRecognizer(IGestureRecognizer recognizer, bool enable) + { + TapGestureRecognizer tapRecognizer; + PanGestureRecognizer panRecognizer; + PinchGestureRecognizer pinchRecognizer; + + if ((tapRecognizer = recognizer as TapGestureRecognizer) != null) + { + ToggleTapRecognizer(tapRecognizer, enable); + } + else if ((panRecognizer = recognizer as PanGestureRecognizer) != null) + { + if (enable) + AddPanRecognizer(panRecognizer); + else + RemovePanRecognizer(panRecognizer); + } + else if ((pinchRecognizer = recognizer as PinchGestureRecognizer) != null) + { + if (enable) + AddPinchRecognizer(pinchRecognizer); + else + RemovePinchRecognizer(pinchRecognizer); + } + else + { + Log.Error("Unknown GestureRecognizer will be ignored: {0}", recognizer); + } + } + + void ToggleTapRecognizer(TapGestureRecognizer recognizer, bool enable) + { + GestureLayer.GestureType type; + switch (recognizer.NumberOfTapsRequired) + { + case 1: + type = GestureLayer.GestureType.Tap; + break; + case 2: + type = GestureLayer.GestureType.DoubleTap; + break; + default: + type = GestureLayer.GestureType.TripleTap; + break; + } + if (enable) + _gestureLayer.SetTapCallback(type, GestureLayer.GestureState.End, (data) => recognizer.SendTapped(_view)); + else + _gestureLayer.SetTapCallback(type, GestureLayer.GestureState.End, null); + } + + void AddPanRecognizer(PanGestureRecognizer recognizer) + { + if (_currentPanGestureController != null) + Log.Warn("More than one PanGestureRecognizer on {0}. Only the last one will work.", _view); + EnsureHitBoxExists(); + _currentPanGestureController = recognizer; + _gestureLayer.SetLineCallback(GestureLayer.GestureState.Start, OnPanStarted); + _gestureLayer.SetLineCallback(GestureLayer.GestureState.Move, OnPanMoved); + _gestureLayer.SetLineCallback(GestureLayer.GestureState.End, OnPanCompleted); + _gestureLayer.SetLineCallback(GestureLayer.GestureState.Abort, OnPanCancelled); + } + + void RemovePanRecognizer(PanGestureRecognizer recognizer) + { + _gestureLayer.SetLineCallback(GestureLayer.GestureState.Start, null); + _gestureLayer.SetLineCallback(GestureLayer.GestureState.Move, null); + _gestureLayer.SetLineCallback(GestureLayer.GestureState.End, null); + _gestureLayer.SetLineCallback(GestureLayer.GestureState.Abort, null); + _currentPanGestureController = null; + } + + void AddPinchRecognizer(PinchGestureRecognizer recognizer) + { + if (_currentPinchGestureController != null) + Log.Warn("More than one PinchGestureRecognizer on {0}. Only the last one will work.", _view); + EnsureHitBoxExists(); + _currentPinchGestureController = recognizer; + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.Start, OnPinchStarted); + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.Move, OnPinchMoved); + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.End, OnPinchCompleted); + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.Abort, OnPinchCancelled); + } + + void RemovePinchRecognizer(PinchGestureRecognizer recognizer) + { + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.Start, null); + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.Move, null); + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.End, null); + _gestureLayer.SetZoomCallback(GestureLayer.GestureState.Abort, null); + _currentPinchGestureController = null; + } + + void CreateGestureLayer() + { + _gestureLayer = new GestureLayer(_renderer.NativeView); + _gestureLayer.Attach(_renderer.NativeView); + } + + void EnsureHitBoxExists() + { + if (_hitBox == null) + { + Box parent = (Platform.GetRenderer(_renderer.Element.RealParent) as LayoutRenderer).Control; + _hitBox = new Polygon(parent) + { + Color = EColor.Transparent + }; + _hitBox.Show(); + UpdateHitBox(); + parent.PackAfter(_hitBox, _renderer.NativeView); + _gestureLayer.Attach(_hitBox); + } + } + + void AddAndRemoveRecognizers(IEnumerable removed, IEnumerable added) + { + if (_hitBox == null && + added != null && + added.Any(item => (item is IPanGestureController || item is IPinchGestureController))) + { + // at least one of the added recognizers requires _hitBot, which is not ready + _gestureLayer.ClearCallbacks(); + EnsureHitBoxExists(); + // as _gestureLayer was reattached, register all callbacks, not only new ones + removed = null; + added = _view.GestureRecognizers; + } + + if (removed != null) + { + foreach (var item in removed) + ToggleRecognizer(item, false); + } + if (added != null) + { + foreach (var item in added) + ToggleRecognizer(item, true); + } + } + + void OnGestureRecognizersChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + // Gestures will be registered/unregistered according to changes in the GestureRecognizers list + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (_gestureLayer == null) + CreateGestureLayer(); + AddAndRemoveRecognizers(null, e.NewItems.OfType()); + break; + + case NotifyCollectionChangedAction.Replace: + AddAndRemoveRecognizers(e.OldItems.OfType(), e.NewItems.OfType()); + break; + + case NotifyCollectionChangedAction.Remove: + AddAndRemoveRecognizers(e.OldItems.OfType(), null); + break; + + case NotifyCollectionChangedAction.Reset: + AddAndRemoveRecognizers(_view.GestureRecognizers, null); + break; + } + } + + void OnPanStarted(GestureLayer.LineData data) + { + _currentPanGestureId++; + _currentPanGestureController.SendPanStarted(_view, _currentPanGestureId); + } + + void OnPanMoved(GestureLayer.LineData data) + { + _currentPanGestureController.SendPan(_view, data.X2 - data.X1, data.Y2 - data.Y1, _currentPanGestureId); + } + + void OnPanCompleted(GestureLayer.LineData data) + { + _currentPanGestureController.SendPanCompleted(_view, _currentPanGestureId); + } + + void OnPanCancelled(GestureLayer.LineData data) + { + // don't trust ElmSharp that the gesture has been aborted, report that it is completed + _currentPanGestureController.SendPanCompleted(_view, _currentPanGestureId); + } + + void OnPinchStarted(GestureLayer.ZoomData data) + { + var geometry = _renderer.NativeView.Geometry; + _currentScalePoint = new Point((data.X - geometry.X) / (double)geometry.Width, (data.Y - geometry.Y) / (double)geometry.Height); + _originalPinchScale = _view.Scale; + _previousPinchRadius = data.Radius; + _currentPinchGestureController.SendPinchStarted(_view, _currentScalePoint); + } + + void OnPinchMoved(GestureLayer.ZoomData data) + { + if (_previousPinchRadius <= 0) + _previousPinchRadius = 1; + // functionality limitation: _currentScalePoint is not updated + _currentPinchGestureController.SendPinch(_view, + 1 + _originalPinchScale * (data.Radius - _previousPinchRadius) / _previousPinchRadius, + _currentScalePoint + ); + _previousPinchRadius = data.Radius; + } + + void OnPinchCompleted(GestureLayer.ZoomData data) + { + _currentPinchGestureController.SendPinchEnded(_view); + } + + void OnPinchCancelled(GestureLayer.ZoomData data) + { + // ElmSharp says the gesture has been aborted really too often, report completion instead + _currentPinchGestureController.SendPinchEnded(_view); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Log/ConsoleLogger.cs b/Xamarin.Forms.Platform.Tizen/Log/ConsoleLogger.cs new file mode 100644 index 00000000..19e46e9e --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Log/ConsoleLogger.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// + /// Logs a message to the console. + /// + internal class ConsoleLogger : ILogger + { + public void Debug(string tag, string message, string file, string func, int line) + { + Print("D", tag, message, file, func, line); + } + + public void Verbose(string tag, string message, string file, string func, int line) + { + Print("V", tag, message, file, func, line); + } + + public void Info(string tag, string message, string file, string func, int line) + { + Print("I", tag, message, file, func, line); + } + + public void Warn(string tag, string message, string file, string func, int line) + { + Print("W", tag, message, file, func, line); + } + + public void Error(string tag, string message, string file, string func, int line) + { + Print("E", tag, message, file, func, line); + } + + public void Fatal(string tag, string message, string file, string func, int line) + { + Print("F", tag, message, file, func, line); + } + + /// + /// Formats and prints the log information. + /// + /// Log level + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Print(string level, string tag, string message, string file, string func, int line) + { + Uri f = new Uri(file); + Console.WriteLine( + String.Format( + "\n[{6:yyyy-MM-dd HH:mm:ss.ffff} {0}/{1}]\n{2}: {3}({4}) > {5}", + level, // 0 + tag, // 1 + Path.GetFileName(f.AbsolutePath), // 2 + func, // 3 + line, // 4 + message, // 5 + DateTime.Now // 6 + ) + ); + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Log/DlogLogger.cs b/Xamarin.Forms.Platform.Tizen/Log/DlogLogger.cs new file mode 100644 index 00000000..987a000a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Log/DlogLogger.cs @@ -0,0 +1,41 @@ +using T = Tizen; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// + /// Logs a message to the dlog. + /// + internal class DlogLogger : ILogger + { + public void Debug(string tag, string message, string file, string func, int line) + { + T.Log.Debug(tag, message, file, func, line); + } + + public void Verbose(string tag, string message, string file, string func, int line) + { + T.Log.Verbose(tag, message, file, func, line); + } + + public void Info(string tag, string message, string file, string func, int line) + { + T.Log.Info(tag, message, file, func, line); + } + + public void Warn(string tag, string message, string file, string func, int line) + { + T.Log.Warn(tag, message, file, func, line); + } + + public void Error(string tag, string message, string file, string func, int line) + { + T.Log.Error(tag, message, file, func, line); + } + + public void Fatal(string tag, string message, string file, string func, int line) + { + T.Log.Fatal(tag, message, file, func, line); + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Log/ILogger.cs b/Xamarin.Forms.Platform.Tizen/Log/ILogger.cs new file mode 100644 index 00000000..6874641f --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Log/ILogger.cs @@ -0,0 +1,71 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// + /// Reports log messages with various log levels. + /// + internal interface ILogger + { + /// + /// Reports a debug log message. + /// + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Debug(string tag, string message, string file, string func, int line); + + /// + /// Reports a verbose log message. + /// + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Verbose(string tag, string message, string file, string func, int line); + + /// + /// Reports an information log message. + /// + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Info(string tag, string message, string file, string func, int line); + + /// + /// Reports a warning log message. + /// + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Warn(string tag, string message, string file, string func, int line); + + /// + /// Reports an error log message. + /// + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Error(string tag, string message, string file, string func, int line); + + /// + /// Reports a fatal error log message. + /// + /// Log tag + /// Log message + /// Full path to the file + /// Function name + /// Line number + void Fatal(string tag, string message, string file, string func, int line); + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Log/Log.cs b/Xamarin.Forms.Platform.Tizen/Log/Log.cs new file mode 100644 index 00000000..3ebeb118 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Log/Log.cs @@ -0,0 +1,989 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// + /// Provides logging functionality. + /// + internal static class Log + { + static String _tag = "Xamarin"; + + static ILogger _logger = IsTizen() ? (ILogger)new DlogLogger() : (ILogger)new ConsoleLogger(); + + /// + /// Gets or sets the log tag. + /// + public static String Tag + { + get + { + return _tag; + } + set + { + _tag = value; + } + } + + /// + /// Gets or sets the logger used to report messages. + /// It's DlogLogger on a Tizen platform, ConsoleLogger on any other. + /// + public static ILogger Logger + { + get + { + return _logger; + } + set + { + _logger = value; + } + } + + public static void Debug(string message, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + _logger.Debug(_tag, message, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3, arg4), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), _, file, func, line); + } + + public static void Debug(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + T9 arg9, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Debug(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), _, file, func, line); + } + + public static void Verbose(string message, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + _logger.Verbose(_tag, message, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3, arg4), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), _, file, func, line); + } + + public static void Verbose(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + T9 arg9, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Verbose(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), _, file, func, line); + } + + public static void Info(string message, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + _logger.Info(_tag, message, file, func, line); + } + + public static void Info(string message, + T0 arg0, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3, arg4), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), _, file, func, line); + } + + public static void Info(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + T9 arg9, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Info(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), _, file, func, line); + } + + public static void Warn(string message, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + _logger.Warn(_tag, message, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3, arg4), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), _, file, func, line); + } + + public static void Warn(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + T9 arg9, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Warn(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), _, file, func, line); + } + + public static void Error(string message, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + _logger.Error(_tag, message, file, func, line); + } + + public static void Error(string message, + T0 arg0, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3, arg4), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), _, file, func, line); + } + + public static void Error(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + T9 arg9, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Error(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), _, file, func, line); + } + + public static void Fatal(string message, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + _logger.Fatal(_tag, message, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3, arg4), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8), _, file, func, line); + } + + public static void Fatal(string message, + T0 arg0, + T1 arg1, + T2 arg2, + T3 arg3, + T4 arg4, + T5 arg5, + T6 arg6, + T7 arg7, + T8 arg8, + T9 arg9, + Guardian _ = default(Guardian), + [CallerFilePath] string file = "", + [CallerMemberName] string func = "", + [CallerLineNumber] int line = 0) + { + Fatal(String.Format(message, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9), _, file, func, line); + } + + /// + /// Determines if Xamarin is running in a Tizen environment. + /// + /// true if application is running on Tizen; otherwise, false. + static bool IsTizen() + { + return System.IO.File.Exists("/etc/tizen-release"); + } + + /// + /// A helper class, it allows to separate optional parameters from non-optional ones. + /// In case of any compilation errors, please make sure you're not using + /// explicit null value as one of the parameters. + /// + internal struct Guardian + { + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Log/XamarinLogListener.cs b/Xamarin.Forms.Platform.Tizen/Log/XamarinLogListener.cs new file mode 100644 index 00000000..02bdb0f1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Log/XamarinLogListener.cs @@ -0,0 +1,20 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal class XamarinLogListener : LogListener + { + public XamarinLogListener() + { + } + + #region implemented abstract members of LogListener + + public override void Warning(string category, string message) + { + Log.Warn("[{0}] {1}", category, message); + } + + #endregion + } +} 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 +{ + /// + /// Extends the ElmSharp.Box class with functionality useful to Xamarin.Forms renderer. + /// + /// + /// This class overrides the layout mechanism. Instead of using the native layout, + /// LayoutUpdated event is sent. + /// + public class Box : EBox + { + /// + /// The last processed geometry of the Box which was reported from the native layer. + /// + Rect _previousGeometry; + + /// + /// Initializes a new instance of the class. + /// + /// The parent EvasObject. + public Box(EvasObject parent) : base(parent) + { + Resized += (sender, e) => { NotifyOnLayout(); }; + SetLayoutCallback(() => { NotifyOnLayout(); }); + } + + /// + /// Notifies that the layout has been updated. + /// + public event EventHandler LayoutUpdated; + + /// + /// Triggers the LayoutUpdated event. + /// + /// + /// This method is called whenever there is a possibility that the size and/or position has been changed. + /// + 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 +{ + /// + /// Extends the EButton control, providing basic formatting features, + /// i.e. font color, size, additional image. + /// + public class Button : EButton, IMeasurable + { + /// + /// Holds the formatted text of the button. + /// + readonly Span _span = new Span(); + + /// + /// The internal padding of the button, helps to determine the size. + /// + readonly ESize _internalPadding; + + /// + /// Optional image, if set will be drawn on the button. + /// + Image _image; + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + public Button(EvasObject parent) : base(parent) + { + _internalPadding = GetInternalPadding(); + } + + /// + /// Gets or sets the button's text. + /// + /// The text. + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the color of the text. + /// + /// The color of the text. + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the color of the text background. + /// + /// The color of the text background. + public EColor TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font family. + /// + /// The font family. + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font attributes. + /// + /// The font attributes. + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the size of the font. + /// + /// The size of the font. + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the image to be displayed next to the button's text. + /// + /// The image displayed on the button. + public Image Image + { + get + { + return _image; + } + + set + { + if (value != _image) + { + ApplyImage(value); + } + } + } + + /// + /// Implementation of the IMeasurable.Measure() method. + /// + 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), + }; + } + } + + /// + /// Applies the button's text and its style. + /// + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + /// + /// Sets the button's internal text and its style. + /// + /// Formatted text, supports HTML tags. + /// Style applied to the formattedText. + 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"); + } + + /// + /// Gets the internal padding of the button. + /// + /// The internal padding. + 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 + }; + } + + /// + /// Applies the image to be displayed on the button. If value is null, + /// image will be removed. + /// + /// Image to be displayed or null. + void ApplyImage(Image image) + { + _image = image; + + SetInternalImage(); + } + + /// + /// Sets the internal image. If value is null, image will be removed. + /// + 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 +{ + /// + /// A Canvas provides a class which can be a container for other controls. + /// + /// + /// 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. + /// + public class Canvas : Box, IContainable + { + /// + /// The list of Views. + /// + readonly ObservableCollection _children = new ObservableCollection(); + + /// + /// Initializes a new instance of the class. + /// + /// Canvas doesn't support replacing its children, this will be ignored. + /// Parent of this instance. + 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(); + } + }; + } + + /// + /// Gets list of native elements that are placed in the canvas. + /// + public IList Children + { + get + { + return _children; + } + } + + /// + /// Provides destruction for native element and contained elements. + /// + protected override void OnUnrealize() + { + foreach (var child in _children) + { + child.Unrealize(); + } + + base.OnUnrealize(); + } + + /// + /// Adds a new child to a container. + /// + /// Native element which will be added + void OnAdd(EvasObject view) + { + PackEnd(view); + } + + /// + /// Removes a child from a container. + /// + /// Child element to be removed from canvas + void OnRemove(EvasObject view) + { + UnPack(view); + } + + /// + /// Removes all children from a canvas. + /// + 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 +{ + /// + /// A basic page which can hold a single view. + /// + public class ContentPage : Background, IContainable + { + /// + /// The name of the part to be used when setting content. + /// + public const string ContentPartName = "overlay"; + + /// + /// Exposes the Children property, mapping it to the _canvas' Children property. + /// + public IList Children => _canvas.Children; + + /// + /// The canvas, used as a container for other objects. + /// + /// + /// The canvas holds all the Views that the ContentPage is composed of. + /// + internal Canvas _canvas; + + /// + /// Initializes a new instance of the ContentPage class. + /// + public ContentPage(EvasObject parent) : base(parent) + { + _canvas = new Canvas(this); + SetPartContent(ContentPartName, _canvas); + } + + /// + /// Gets or sets the title. + /// + /// The current title.p + public string Title + { + get; + set; + } + + /// + /// Allows custom handling of events emitted when the layout has been updated. + /// + public event EventHandler LayoutUpdated + { + add + { + _canvas.LayoutUpdated += value; + } + remove + { + _canvas.LayoutUpdated -= value; + } + } + + /// + /// Handles the disposing of a ContentPage + /// + /// + /// Takes the proper care of discarding the canvas, then calls the base method. + /// + 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 +{ + /// + /// Event arguments for event. + /// + public class DateChangedEventArgs : EventArgs + { + /// + /// The date that was on the element at the time that the user selected it. + /// + public DateTime OldDate { get; private set; } + + /// + /// The date that the user entered. + /// + public DateTime NewDate { get; private set; } + + /// + /// Creates a new object that represents a change from to . + /// + /// Old date of . + /// Current date of . + 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 +{ + /// + /// Extends the ElmSharp.DateTimeSelector class with functionality useful to renderer. + /// + 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; + + /// + /// Initializes a new instance of the class. + /// + /// The parent EvasObject. + 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; + }; + } + + /// + /// Gets or sets the displayed date. + /// + public DateTime Date + { + get + { + return _date; + } + set + { + if (_date != value) + { + ApplyDate(value); + } + } + } + + /// + /// Gets of sets the highest date selectable for this . + /// + /// + /// Default value is 31st Dec, 2037. + /// + public DateTime MaximumDate + { + get + { + return _maxDate; + } + set + { + if (_maxDate != value) + { + ApplyMaxDate(value); + } + } + } + + /// + /// Gets of sets the lowest date selectable for this . + /// + /// + /// Default value is 1st Jan, 1970. + /// + public DateTime MinimumDate + { + get + { + return _minDate; + } + set + { + if (_minDate != value) + { + ApplyMinDate(value); + } + } + } + + /// + /// Sets the DateTime property according to the given . + /// + /// The date value to be applied to the date picker. + void ApplyDate(DateTime date) + { + _date = date; + DateTime = date; + } + + /// + /// Sets the MaximumDateTime property according to the given . + /// + /// The maximum date value to be applied to the date picker. + void ApplyMaxDate(DateTime maxDate) + { + _maxDate = maxDate; + MaximumDateTime = maxDate; + } + + /// + /// Sets the MinimumDateTime property according to the given . + /// + /// The minimum date value to be applied to the date picker. + 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; + + /// + /// Creates a dialog window. + /// + public DateTimePickerDialog(EvasObject parent) : base(parent) + { + _parent = parent; + Initialize(); + } + + /// + /// Occurs when the date of this dialog has changed. + /// + public event EventHandler DateTimeChanged; + + /// + /// Gets the contained in this dialog. + /// + public DateTimeSelector DateTimePicker + { + get + { + return _dateTimePicker; + } + private set + { + if (_dateTimePicker != value) + { + ApplyDateTimePicker(value); + } + } + } + + /// + /// Creates date picker in dialog window. + /// + public void InitializeDatePicker(DateTime date, DateTime minimumDate, DateTime maximumDate) + { + var datePicker = new DatePicker(this) + { + Date = date, + MinimumDate = minimumDate, + MaximumDate = maximumDate + }; + Content = DateTimePicker = datePicker; + } + + /// + /// Creates time picker in dialog window. + /// + 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 +{ + /// + /// Base class for Dialogs. + /// A dialog is a small window that prompts the user to make a decision or enter additional information. + /// + public class Dialog : Popup + { + EButton _positiveButton; + EButton _neutralButton; + EButton _negativeButton; + EvasObject _content; + string _title; + + /// + /// Creates a dialog window that uses the default dialog theme. + /// + public Dialog(EvasObject parent) : base(parent) + { + Initialize(); + } + + /// + /// Occurs when the hardware Back button is pressed. + /// + public event EventHandler BackButtonPressed; + + /// + /// Occurs whenever the dialog is first displayed. + /// + public event EventHandler Shown; + + /// + /// Enumerates the three valid positions of a dialog button. + /// + enum ButtonPosition + { + Positive, + Neutral, + Negative + } + + /// + /// Gets or sets the title of the dialog + /// + public string Title + { + get + { + return _title; + } + set + { + if (_title != value) + { + ApplyTitle(value); + } + } + } + + /// + /// Gets or sets the content to display in that dialog. + /// + public EvasObject Content + { + get + { + return _content; + } + set + { + if (_content != value) + { + ApplyContent(value); + } + } + } + + /// + /// Gets or sets the positive button used in the dialog + /// + public EButton PositiveButton + { + get + { + return _positiveButton; + } + set + { + if (_positiveButton != value) + { + ApplyButton(ButtonPosition.Positive, value); + } + } + } + + /// + /// Gets or sets the neutral button used in the dialog + /// + public EButton NeutralButton + { + get + { + return _neutralButton; + } + set + { + if (_neutralButton != value) + { + ApplyButton(ButtonPosition.Neutral, value); + } + } + } + + /// + /// Gets or sets the negative button used in the dialog + /// + public EButton NegativeButton + { + get + { + return _negativeButton; + } + set + { + if (_negativeButton != value) + { + ApplyButton(ButtonPosition.Negative, value); + } + } + } + + /// + /// Starts the dialog and displays it on screen. + /// + public new void Show() + { + base.Show(); + Shown?.Invoke(this, EventArgs.Empty); + } + + /// + /// Handles the disposing of a dialog widget. + /// + protected override void OnUnrealize() + { + _content?.Unrealize(); + + ApplyButton(ButtonPosition.Positive, null); + ApplyButton(ButtonPosition.Neutral, null); + ApplyButton(ButtonPosition.Negative, null); + ApplyContent(null); + + UngrabBackKey(); + + base.OnUnrealize(); + } + + /// + /// Called when the dialog is shown. + /// + /// When shown, the dialog will register itself for the back key press event handling. + protected virtual void OnShown() + { + GrabBackKey(); + } + + /// + /// Called when the dialog is dismissed. + /// + /// When dismissed, the dialog will unregister itself from the back key press event handling. + protected virtual void OnDismissed() + { + UngrabBackKey(); + } + + /// + /// Handles the initialization process. + /// + /// Creates handlers for vital events + 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); + }; + } + + /// + /// Changes the dialog title. + /// + /// New dialog title. + void ApplyTitle(string title) + { + _title = title; + + SetPartText("title,text", _title); + } + + /// + /// Puts the button in one of the three available slots. + /// + /// The slot to be occupied by the button expressed as a + /// The new button. + 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; + } + } + + /// + /// Updates the content of the dialog. + /// + /// New dialog content. + void ApplyContent(EvasObject content) + { + _content = content; + + SetPartContent("default", _content, true); + } + + /// + /// Registers this instance to be affected by pressing the hardware back key. + /// + void GrabBackKey() + { + KeyGrab(EvasKeyEventArgs.PlatformBackButtonName, true); + } + + /// + /// Unregisters this instance from being affected by pressing the hardware back key. + /// + 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 +{ + /// + /// Enumeration for the orientation of a rectangular screen. + /// + [Flags] + public enum DisplayOrientations + { + /// + /// No display orientation is specified. + /// + None = 0, + + /// + /// The display is oriented in a natural position. + /// + Portrait = 1, + + /// + /// The display's left side is at the top. + /// + Landscape = 2, + + /// + /// The display is upside down. + /// + PortraitFlipped = 4, + + /// + /// The display's right side is at the top. + /// + 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 +{ + /// + /// Extends the Entry control, providing basic formatting features, + /// i.e. font color, size, placeholder. + /// + public class Entry : EEntry, IMeasurable + { + /// + /// Holds the formatted text of the entry. + /// + readonly Span _span = new Span(); + + /// + /// Holds the formatted text of the placeholder. + /// + readonly Span _placeholderSpan = new Span(); + + /// + /// Helps to detect whether the text change was initiated by the user + /// or via the Text property. + /// + int _changedByUserCallbackDepth; + + /// + /// The type of the keyboard used by the entry. + /// + Keyboard _keyboard; + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + public Entry(EvasObject parent) : base(parent) + { + Scrollable = true; + + ChangedByUser += (s, e) => + { + _changedByUserCallbackDepth++; + + Text = GetInternalText(); + + _changedByUserCallbackDepth--; + }; + + ApplyKeyboard(Keyboard.Normal); + } + + /// + /// Occurs when the text has changed. + /// + public event EventHandler TextChanged; + + /// + /// Gets or sets the text. + /// + /// The text. + 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; + }); + } + } + } + + /// + /// Gets or sets the color of the text. + /// + /// The color of the text. + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font family of the text and the placeholder. + /// + /// The font family of the text and the placeholder. + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontFamily = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// + /// Gets or sets the font attributes of the text and the placeholder. + /// + /// The font attributes of the text and the placeholder. + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontAttributes = value; + ApplyPlaceholderAndStyle(); + } + } + } + + + /// + /// Gets or sets the size of the font of both text and placeholder. + /// + /// The size of the font of both text and placeholder. + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontSize = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// + /// Gets or sets the horizontal text alignment of both text and placeholder. + /// + /// The horizontal text alignment of both text and placeholder. + public TextAlignment HorizontalTextAlignment + { + get + { + return _span.HorizontalTextAlignment; + } + + set + { + if (value != _span.HorizontalTextAlignment) + { + _span.HorizontalTextAlignment = value; + ApplyTextAndStyle(); + + _placeholderSpan.HorizontalTextAlignment = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// + /// Gets or sets the keyboard type used by the entry. + /// + /// The keyboard type. + public Keyboard Keyboard + { + get + { + return _keyboard; + } + + set + { + if (value != _keyboard) + { + ApplyKeyboard(value); + } + } + } + + /// + /// Gets or sets the placeholder's text. + /// + /// The placeholder's text. + public string Placeholder + { + get + { + return _placeholderSpan.Text; + } + + set + { + if (value != _placeholderSpan.Text) + { + _placeholderSpan.Text = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// + /// Gets or sets the color of the placeholder's text. + /// + /// The color of the placeholder's text. + public EColor PlaceholderColor + { + get + { + return _placeholderSpan.ForegroundColor; + } + + set + { + if (!_placeholderSpan.ForegroundColor.Equals(value)) + { + _placeholderSpan.ForegroundColor = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// + /// Implementation of the IMeasurable.Measure() method. + /// + 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; + } + } + + /// + /// Applies entry's text and its style. + /// + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + /// + /// Sets entry's internal text and its style. + /// + /// Formatted text, supports HTML tags. + /// Style applied to the formattedText. + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + if (_changedByUserCallbackDepth == 0) + { + base.Text = formattedText; + base.TextStyle = textStyle; + } + } + + /// + /// Gets the internal text representation of the entry. + /// + /// The internal text representation. + string GetInternalText() + { + return Entry.ConvertMarkupToUtf8(base.Text); + } + + /// + /// Applies the keyboard type to be used by the entry. + /// + /// Keyboard type to be used. + void ApplyKeyboard(Keyboard keyboard) + { + SetInternalKeyboard(_keyboard = keyboard); + } + + /// + /// Configures the ElmSharp.Entry with specified keyboard type and displays + /// the keyboard automatically unless the provided type is Keyboard.None. + /// + /// Keyboard type to be used. + void SetInternalKeyboard(Keyboard keyboard) + { + if (keyboard == Keyboard.None) + { + SetInputPanelEnabled(false); + } + else + { + SetInputPanelEnabled(true); + SetInputPanelLayout((InputPanelLayout)keyboard); + } + } + + /// + /// Applies placeholders's text and its style. + /// + void ApplyPlaceholderAndStyle() + { + SetInternalPlaceholderAndStyle(_placeholderSpan.GetMarkupText()); + } + + /// + /// Sets placeholder's internal text and style. + /// + /// Markup text to be used as a placeholder. + 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 +{ + /// + /// Represents a text with attributes applied to some parts. + /// + /// + /// Formatted string consists of spans that represent text segments with various attributes applied. + /// + public class FormattedString + { + /// + /// A flag indicating whether the instance contains just a plain string without any formatting. + /// + /// + /// true if the instance contains an unformatted string. + /// + readonly bool _just_string; + + /// + /// Holds the unformatted string. + /// + /// + /// The contents of this field are accurate if and only if the _just_string flag is set. + /// + readonly string _string; + + /// + /// Holds the collection of span elements. + /// + /// + /// Span elements are basically chunks of text with uniform formatting. + /// + readonly ObservableCollection _spans; + + /// + /// Returns the collection of span elements. + /// + public IList Spans { get { return _spans; } } + + /// + /// Creates a new FormattedString instance with an empty string. + /// + public FormattedString() + { + _just_string = false; + _spans = new ObservableCollection(); + } + + /// + /// Creates a new FormattedString instance based on given str. + /// + /// + /// A string used to make a new FormattedString instance. + /// + public FormattedString(string str) + { + _just_string = true; + _string = str; + } + + /// + /// Returns the plain text of the FormattedString as an unformatted string. + /// + /// + /// The text content of the FormattedString without any format applied. + /// + public override string ToString() + { + if (_just_string) + { + return _string; + } + else + { + return string.Concat(from span in this.Spans select span.Text); + } + } + + /// + /// Returns the markup text representation of the FormattedString instance. + /// + /// The string containing a markup text. + internal string ToMarkupString() + { + if (_just_string) + { + return _string; + } + else + { + return string.Concat(from span in this.Spans select span.GetMarkupText()); + } + } + + /// + /// Casts the FormattedString to a string. + /// + /// The FormattedString instance which will be used for the conversion. + public static explicit operator string (FormattedString formatted) + { + return formatted.ToString(); + } + + /// + /// Casts the string to a FormattedString. + /// + /// The text which will be put in a new FormattedString instance. + public static implicit operator FormattedString(string text) + { + return new FormattedString(text); + } + + /// + /// Casts the Span to a FormattedString. + /// + /// The span which will be used for the conversion. + 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 +{ + /// + /// Interface defining methods for managing elements of the container. + /// + /// The type of element that can be added to the container. + public interface IContainable + { + /// + /// The children collection of an element. + /// + IList 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 +{ + /// + /// Interface of the controls which can measure their size taking into + /// account the available area. + /// + public interface IMeasurable + { + /// + /// Measures the size of the control in order to fit it into the + /// available area. + /// + /// Available width. + /// Available height. + /// Size of the control that fits the available area. + 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 +{ + /// + /// Interface defining properties of formattable text. + /// + public interface ITextable + { + /// + /// Get or sets the formatted text. + /// + FormattedString FormattedText { get; set; } + + /// + /// Gets or sets the text. + /// + string Text { get; set; } + + /// + /// Gets or sets the color for the text. + /// + EColor TextColor { get; set; } + + /// + /// Gets or sets the background color for the text. + /// + EColor TextBackgroundColor { get; set; } + + /// + /// Gets or sets the font family for the text. + /// + string FontFamily { get; set; } + + /// + /// Gets or sets the font attributes for the text. + /// See for information about FontAttributes. + /// + FontAttributes FontAttributes { get; set; } + + /// + /// Gets or sets the font size for the text. + /// + double FontSize { get; set; } + + /// + /// Gets or sets the horizontal alignment mode for the text. + /// See for information about TextAlignment. + /// + TextAlignment HorizontalTextAlignment { get; set; } + + /// + /// Gets or sets the vertical alignment mode for the text. + /// See for information about TextAlignment. + /// + TextAlignment VerticalTextAlignment { get; set; } + + /// + /// Gets or sets the value that indicates whether the text has underline. + /// + bool Underline { get; set; } + + /// + /// Gets or sets the value that indicates whether the text has strike line though it. + /// + 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 +{ + /// + /// Extends the ElmSharp.Image class with functionality useful to renderer. + /// + public class Image : EImage, IMeasurable + { + Aspect _aspect; + + /// + /// Initializes a new instance of the class. + /// + /// The parent EvasObject. + public Image(EvasObject parent) : base(parent) + { + IsScaling = true; + CanScaleUp = true; + CanScaleDown = true; + + ApplyAspect(Aspect.AspectFit); + } + + /// + /// Gets or sets the image aspect ratio preserving option. + /// + /// The aspect option. + public Aspect Aspect + { + get + { + return _aspect; + } + + set + { + if (_aspect != value) + { + ApplyAspect(value); + } + } + } + + /// + /// Loads image data from the given asynchronously. + /// + /// A task which will be completed when image data is loaded. + /// Image source specifying from where the image data has to be loaded. + public Task LoadFromImageSourceAsync(ImageSource source) + { + IImageSourceHandler handler; + if (source != null && (handler = Registrar.Registered.GetHandler(source.GetType())) != null) + { + return handler.LoadImageAsync(this, source); + } + return Task.FromResult(false); + } + + /// + /// Implements the interface. + /// + /// Available width. + /// Available height. + 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; + } + + /// + /// Sets the IsFixedAspect and CanFillOutside properties according to the given . + /// + /// The aspect setting to be applied to the image. + 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 +{ + /// + /// Keyboard layout type on entry control. + /// + public enum Keyboard + { + /// + /// Disable Keyboard + /// + None = -1, + + /// + /// Keyboard layout type default. + /// + Normal, + + /// + /// Keyboard layout type number. + /// + Number, + + /// + /// Keyboard layout type email. + /// + Email, + + /// + /// Keyboard layout type url. + /// + Url, + + /// + /// Keyboard layout type phone. + /// + PhoneNumber, + + /// + /// Keyboard layout type ip. + /// + Ip, + + /// + /// Keyboard layout type month. + /// + Month, + + /// + /// Keyboard layout type number. + /// + NumberOnly, + + /// + /// Keyboard layout type error type. Do not use it directly! + /// + Invalid, + + /// + /// Keyboard layout type hexadecimal. + /// + Hex, + + /// + /// Keyboard layout type terminal type, esc, alt, ctrl, etc. + /// + Terminal, + + /// + /// Keyboard layout type password. + /// + Password, + + /// + /// Keyboard layout type date and time. + /// + DateTime, + + /// + /// Keyboard layout type emoticons. + /// + 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 +{ + /// + /// The Label class extends ElmSharp.Label to be better suited for Xamarin renderers. + /// Mainly the formatted text support. + /// + public class Label : ELabel, ITextable, IMeasurable + { + /// + /// The _span holds the content of the label. + /// + readonly Span _span = new Span(); + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + public Label(EvasObject parent) : base(parent) + { + } + + /// + /// Get or sets the formatted text. + /// + /// Setting FormattedText changes the value of the Text property. + /// The formatted text. + public FormattedString FormattedText + { + get + { + return _span.FormattedText; + } + + set + { + if (value != _span.FormattedText) + { + _span.FormattedText = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the text. + /// + /// Setting Text overwrites the value of the FormattedText property too. + /// The content of the label. + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the color of the formatted text. + /// + /// The color of the text. + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the background color for the text. + /// + /// The color of the label's background. + public EColor TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font family for the text. + /// + /// The font family. + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font attributes. + /// + /// The font attributes. + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the font size for the text. + /// + /// The size of the font. + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the line wrap option. + /// + /// The line break mode. + public LineBreakMode LineBreakMode + { + get + { + return _span.LineBreakMode; + } + + set + { + if (value != _span.LineBreakMode) + { + _span.LineBreakMode = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the horizontal text alignment. + /// + /// The horizontal text alignment. + public TextAlignment HorizontalTextAlignment + { + get + { + return _span.HorizontalTextAlignment; + } + + set + { + if (value != _span.HorizontalTextAlignment) + { + _span.HorizontalTextAlignment = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the vertical text alignment. + /// + /// The vertical text alignment. + public TextAlignment VerticalTextAlignment + { + get + { + return _span.VerticalTextAlignment; + } + + set + { + if (value != _span.VerticalTextAlignment) + { + _span.VerticalTextAlignment = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the value that indicates whether the text is underlined. + /// + /// true if the text is underlined. + public bool Underline + { + get + { + return _span.Underline; + } + + set + { + if (value != _span.Underline) + { + _span.Underline = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Gets or sets the value that indicates whether the text is striked out. + /// + /// true if the text is striked out. + public bool Strikethrough + { + get + { + return _span.Strikethrough; + } + + set + { + if (value != _span.Strikethrough) + { + _span.Strikethrough = value; + ApplyTextAndStyle(); + } + } + } + + /// + /// Implements to provide a desired size of the label. + /// + /// Available width. + /// Available height. + /// Size of the control that fits the available area. + 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 +{ + /// + /// Holds information about size of the area which can be used for layout. + /// + public class LayoutEventArgs : EventArgs + { + /// + /// Whether or not the dimensions have changed. + /// + public bool HasChanged + { + get; + internal set; + } + + /// + /// X coordinate of the layout area, relative to the main window. + /// + public int X + { + get; + internal set; + } + + /// + /// Y coordinate of the layout area, relative to the main window. + /// + public int Y + { + get; + internal set; + } + + /// + /// Width of the layout area. + /// + public int Width + { + get; + internal set; + } + + /// + /// Height of the layout area. + /// + 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 +{ + /// + /// Enumerates values that describe options for line braking. + /// + public enum LineBreakMode + { + /// + /// Do not wrap text. + /// + NoWrap, + + /// + /// Wrap at character boundaries. + /// + CharacterWrap, + + /// + /// Wrap at word boundaries. + /// + WordWrap, + + /// + /// Tries to wrap at word boundaries, and then wrap at a character boundary if the word is too long. + /// + MixedWrap, + + /// + /// Truncate the head of text. + /// + HeadTruncation, + + /// + /// Truncate the middle of text. This may be done, for example, by replacing it with an ellipsis. + /// + MiddleTruncation, + + /// + /// Truncate the tail of text. + /// + 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 +{ + /// + /// Type alias which identifies list of cells whose data model was transformed by Xamarin. + /// + using GroupList = TemplatedItemsList, Cell>; + + /// + /// Native ListView implementation for Xamarin renderer + /// + /// + /// 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. + /// + public class ListView : GenList + { + /// + /// ItemContext helper class. This represents the association between Xamarin.Forms.Cell and + /// native elements. It also stores useful context for them. + /// + 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, Cell> ListOfSubItems; + } + + /// + /// The item context list for each added element. + /// + readonly List _itemContextList = new List(); + + /// + /// Registered cell handlers. + /// + protected readonly IDictionary _cellRendererCache = new Dictionary(); + + /// + /// Registered group handlers. + /// + protected readonly IDictionary _groupCellRendererCache = new Dictionary(); + + /// + /// The header context. + /// + ItemContext _headerContext; + + /// + /// The header element. + /// + VisualElement _headerElement; + + /// + /// The footer context. + /// + ItemContext _footerContext; + + /// + /// The footer element. + /// + VisualElement _footerElement; + + /// + /// The item class for header and footer. + /// + GenItemClass _headerFooterItemClass = null; + + /// + /// Gets or sets a value indicating whether this instance has grouping enabled. + /// + /// true if this instance has grouping enabled. + public bool IsGroupingEnabled { get; set; } + + /// + /// Constructor of ListView native control. + /// + /// ElmSharp object which is parent of particular list view + public ListView(EvasObject parent) + : base(parent) + { + ItemRealized += OnItemAppear; + ItemUnrealized += OnItemDisappear; + } + + /// + /// Gets the item context based on Cell item. + /// + /// The item context. + /// Cell for which context should be found. + internal ItemContext GetItemContext(Cell cell) + { + if (cell == null) + { + return null; + } + else + { + return _itemContextList.Find(X => X.Cell == cell); + } + } + + /// + /// Sets the HasUnevenRows property. + /// + /// If true, the list will allow uneven sizes for its rows. + public void SetHasUnevenRows(bool hasUnevenRows) + { + Homogeneous = !hasUnevenRows; + UpdateRealizedItems(); + } + + /// + /// Adds elements to the list and defines its presentation based on Cell type. + /// + /// IEnumerable on Cell collection. + /// 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. + /// + 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); + } + } + } + + /// + /// Deletes all items from a given group. + /// + /// Group of items to be deleted. + 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(); + } + } + + /// + /// Adds items to the group. + /// + /// Group to which elements will be added. + /// New list items to be added. + /// A reference to the Cell already existing in a ListView. + /// Newly added cells will be put just before this cell. + 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); + } + } + } + + /// + /// Removes the specified cells. + /// + /// Cells to be removed. + 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(); + } + } + } + + /// + /// Scrolls the list to a specified cell. + /// + /// + /// 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 position parameter. + /// + /// Cell which will be displayed after scrolling . + /// This will defines scroll to behavior based on ScrollToPosition values. + /// If true, scrolling will be animated. Otherwise the cell will be moved instantaneously. + public void ApplyScrollTo(Cell cell, ScrollToPosition position, bool animated) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + this.ScrollTo(item, position.ToNative(), animated); + } + + /// + /// Selects the specified cell. + /// + /// Cell to be selected. + public void ApplySelectedItem(Cell cell) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + item.IsSelected = true; + } + + /// + /// Sets the header. + /// + /// Header of the list. + 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); + } + } + + /// + /// Sets the footer. + /// + /// Footer of the list. + 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); + } + } + + /// + /// Removes the header. + /// + public void RemoveHeader() + { + _itemContextList.Remove(_headerContext); + _headerContext?.Item?.Delete(); + _headerContext = null; + _headerElement = null; + } + + /// + /// Removes the footer. + /// + public void RemoveFooter() + { + _itemContextList.Remove(_footerContext); + _footerContext?.Item?.Delete(); + _footerContext = null; + _footerElement = null; + } + + /// + /// Determines whether this instance has a header. + /// + /// true if the header is present. + public bool HasHeader() + { + return _headerContext != null; + } + + /// + /// Determines whether this instance has a footer. + /// + /// true if the footer is present. + public bool HasFooter() + { + return _footerContext != null; + } + + /// + /// Gets the header. + /// + /// The header. + public VisualElement GetHeader() + { + return _headerElement; + } + + /// + /// Gets the footer. + /// + /// The footer. + public VisualElement GetFooter() + { + return _footerElement; + } + + /// + /// Handles the header deleted event. + /// + /// Sender of the event. + /// Empty argument. + void HeaderDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_headerContext); + _headerContext = null; + } + + /// + /// Handles the footer deleted event. + /// + /// Sender of the event. + /// Empty argument. + void FooterDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_footerContext); + _footerContext = null; + } + + /// + /// Called every time an object gets realized. + /// + /// Sender of the event. + /// GenListItemEventArgs. + void OnItemAppear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendAppearing(); + } + } + + /// + /// Called every time an object gets unrealized. + /// + /// Sender of the event. + /// GenListItemEventArgs. + 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); + } + } + + + /// + /// A convenience shorthand method for derivate classes. + /// + /// Cell to be added. + protected void AddCell(Cell cell) + { + AddItem(cell); + } + + /// + /// Gets the cell renderer for given cell type. + /// + /// The cell handler. + /// Cell to be added. + /// If true, then group handlers will be included in the lookup as well. + 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(type); + + if (renderer == null) + { + Log.Error("Cell type is not handled: {0}", cell.GetType()); + throw new ArgumentNullException("Unsupported cell type"); + } + return cache[type] = renderer; + } + + /// + /// 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. + /// + /// Group to be added. + /// Before cell. + 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); + } + + /// + /// Adds the item. + /// + /// Cell to be added. + /// Group to which the new item should belong. + /// If the value of groupCell is not null, the new item will be put into the requested group. + /// The cell before which the new item should be placed. + /// If the value of beforeCell is not null, the new item will be placed just before the requested cell. + 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); + } + + /// + /// Handles item deleted event. + /// + /// Sender of the event. + /// Empty argument. + 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); + } + + /// + /// Invoked whenever the properties of data model change. + /// + /// Sender of the event. + /// PropertyChangedEventArgs. + /// + /// The purpose of this method is to propagate these changes to the presentation layer. + /// + 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(); + } + + /// + /// Gets the item class used for header and footer cells. + /// + /// The header and footer item class. + 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 +{ + /// + /// The native widget which provides Xamarin.MasterDetailPage features. + /// + public class MasterDetailPage : Box + { + /// + /// The portion of the screen that the MasterPage takes in Split mode. + /// + static readonly double s_splitRatio = 0.35; + + /// + /// The portion of the screen that the MasterPage takes in Popover mode. + /// + static readonly double s_popoverRatio = 0.8; + + /// + /// The default master behavior (a.k.a mode). + /// + static readonly MasterBehavior s_defaultMasterBehavior = (Device.Idiom == TargetIdiom.Phone) ? MasterBehavior.Popover : MasterBehavior.SplitOnLandscape; + + /// + /// The MasterPage native container. + /// + readonly Canvas _masterCanvas; + + /// + /// The DetailPage native container. + /// + readonly Canvas _detailCanvas; + + /// + /// The container for _masterCanvas and _detailCanvas used in split mode. + /// + readonly Panes _splitPane; + + /// + /// The container for _masterCanvas used in popover mode. + /// + readonly Panel _drawer; + + /// + /// The property value. + /// + MasterBehavior _masterBehavior = s_defaultMasterBehavior; + + /// + /// The actual MasterDetailPage mode - either split or popover. It depends on _masterBehavior and screen orientation. + /// + MasterBehavior _internalMasterBehavior = MasterBehavior.Popover; + + /// + /// The property value. + /// + EvasObject _master; + + /// + /// The property value. + /// + EvasObject _detail; + + /// + /// The main widget - either or , depending on the mode. + /// + EvasObject _mainWidget; + + /// + /// The property value. + /// + bool _isPresented; + + /// + /// The property value. + /// + bool _isGestureEnabled; + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + 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(); + }; + } + + /// + /// Occurs when the MasterPage is shown or hidden. + /// + public event EventHandler IsPresentedChanged; + + /// + /// Gets or sets the MasterDetailPage behavior. + /// + /// The behavior of the MasterDetailPage requested by the user. + public MasterBehavior MasterBehavior + { + get + { + return _masterBehavior; + } + + set + { + if (_masterBehavior != value) + { + _masterBehavior = value; + + UpdateMasterBehavior(); + } + } + } + + /// + /// Gets or sets the content of the MasterPage. + /// + /// The MasterPage. + public EvasObject Master + { + get + { + return _master; + } + + set + { + if (_master != value) + { + _master = value; + UpdatePageGeometry(_master); + _masterCanvas.Children.Clear(); + _masterCanvas.Children.Add(_master); + } + } + } + + /// + /// Gets or sets the content of the DetailPage. + /// + /// The DetailPage. + public EvasObject Detail + { + get + { + return _detail; + } + + set + { + if (_detail != value) + { + _detail = value; + UpdatePageGeometry(_detail); + _detailCanvas.Children.Clear(); + _detailCanvas.Children.Add(_detail); + } + } + } + + /// + /// Gets or sets a value indicating whether the MasterPage is shown. + /// + /// true if the MasterPage is presented. + public bool IsPresented + { + get + { + return _isPresented; + } + + set + { + if (_isPresented != value) + { + _isPresented = value; + IsPresentedChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + /// + /// Gets or sets a value indicating whether a MasterDetailPage allows showing MasterPage with swipe gesture. + /// + /// true if the MasterPage can be revealed with a gesture. + public bool IsGestureEnabled + { + get + { + return _isGestureEnabled; + } + + set + { + if (_isGestureEnabled != value) + { + _isGestureEnabled = value; + _drawer.SetScrollable(_isGestureEnabled); + } + } + } + + /// + /// Updates the geometry of the selected page. + /// + /// Master or Detail page to be updated. + 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; + } + } + } + + /// + /// Updates according to set by the user and current screen orientation. + /// + 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(); + } + } + + /// + /// Composes the structure of all the necessary widgets. + /// + 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 +{ + /// + /// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. + /// + /// The type of elements in the collection. + internal class ObservableCollection : System.Collections.ObjectModel.ObservableCollection + { + /// + /// Removes all items from the collection. + /// + /// + /// Fisrt remove all items, send CollectionChanged event with Remove Action + /// Second call ClearItems of base + /// + 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 +{ + /// + /// Provides implementation of the search bar widget. + /// + public class SearchBar : Canvas, IMeasurable + { + /// + /// The height of the background of the search bar. + /// + const int BackgroundHeight = 120; + + /// + /// The style of the cancel button. + /// + const string CancelButtonLayoutStyle = "editfield_clear"; + + /// + /// The horizontal padding of the cancel button. + /// + const int CancelButtonPaddingHorizontal = 17; + + /// + /// The size of the cancel button. + /// + const int CancelButtonSize = 80; + + /// + /// The height of the entry. + /// + const int EntryHeight = 54; + + /// + /// The horizontal padding of the entry. + /// + const int EntryPaddingHorizontal = 42; + + /// + /// The vertical padding of the entry. + /// + const int EntryPaddingVertical = 33; + + /// + /// The height of the rectangle used to draw underline effect. + /// + const int RectangleHeight = 2; + + /// + /// The bottom padding of the rectangle used to draw underline effect. + /// + const int RectanglePaddingBottom = 20; + + /// + /// The horizontal padding of the rectangle used to draw underline effect. + /// + const int RectanglePaddingHorizontal = 32; + + /// + /// The top padding of the rectangle used to draw underline effect. + /// + const int RectanglePaddingTop = 11; + + //TODO: read default platform color + + /// + /// The color of the underline rectangle. + /// + static readonly EColor s_underlineColor = EColor.Aqua; + + /// + /// The dimmed color of the underline rectangle. + /// + static readonly EColor s_underlineDimColor = EColor.Gray; + + /// + /// The cancel button. + /// + Button _cancelButton; + + /// + /// The text entry. + /// + Entry _entry; + + /// + /// The underline rectangle. + /// + ERectangle _underlineRectangle; + + /// + /// Initializes a new instance of the class. + /// + /// Parent evas object. + 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; + } + + /// + /// Occurs when the search button on the keyboard is pressed. + /// + public event EventHandler SearchButtonPressed; + + /// + /// Occurs when the entry's text has changed. + /// + public event EventHandler TextChanged; + + /// + /// Gets or sets the color of the cancel button. + /// + /// Color of the cancel button. + public EColor CancelButtonColor + { + get + { + return _cancelButton.Color; + } + + set + { + if (!_cancelButton.Color.Equals(value)) + { + _cancelButton.Color = value; + } + } + } + + /// + /// Gets or sets the font attributes of the search bar's entry. + /// + /// The font attributes. + public FontAttributes FontAttributes + { + get + { + return _entry.FontAttributes; + } + + set + { + if (value != _entry.FontAttributes) + { + _entry.FontAttributes = value; + } + } + } + + /// + /// Gets or sets the font family of the search bar's entry. + /// + /// The font family. + public string FontFamily + { + get + { + return _entry.FontFamily; + } + + set + { + if (value != _entry.FontFamily) + { + _entry.FontFamily = value; + } + } + } + + /// + /// Gets or sets the size of the font of the search bar's entry. + /// + /// The size of the font. + public double FontSize + { + get + { + return _entry.FontSize; + } + + set + { + if (value != _entry.FontSize) + { + _entry.FontSize = value; + } + } + } + + /// + /// Gets or sets the horizontal text alignment of the search bar's entry. + /// + /// The horizontal text alignment. + public TextAlignment HorizontalTextAlignment + { + get + { + return _entry.HorizontalTextAlignment; + } + + set + { + if (value != _entry.HorizontalTextAlignment) + { + _entry.HorizontalTextAlignment = value; + } + } + } + + /// + /// Gets or sets the placeholder of the search bar's entry. + /// + /// The placeholder. + public string Placeholder + { + get + { + return _entry.Placeholder; + } + + set + { + if (value != _entry.Placeholder) + { + _entry.Placeholder = value; + } + } + } + + /// + /// Gets or sets the color of the placeholder. + /// + /// The color of the placeholder. + public EColor PlaceholderColor + { + get + { + return _entry.PlaceholderColor; + } + + set + { + if (!_entry.PlaceholderColor.Equals(value)) + { + _entry.PlaceholderColor = value; + } + } + } + + /// + /// Gets or sets the text of the search bar's entry. + /// + /// The text. + public override string Text + { + get + { + return _entry.Text; + } + + set + { + if (value != _entry.Text) + { + _entry.Text = value; + } + } + } + + /// + /// Gets or sets the color of the text. + /// + /// The color of the text. + public EColor TextColor + { + get + { + return _entry.TextColor; + } + + set + { + if (!_entry.TextColor.Equals(value)) + { + _entry.TextColor = value; + } + } + } + + /// + /// Implementation of the IMeasurable.Measure() method. + /// + 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); + } + + /// + /// Handles the event triggered by the cancel button being clicked. + /// + /// Sender of the event. + /// Event arguments, ignored. + void CancelButtonClicked(object sender, EventArgs e) + { + _entry.Text = string.Empty; + _cancelButton.Hide(); + } + + /// + /// Handles the event triggered by clicking the search button on the keyboard. + /// + /// Sender of the event. + /// Event arguments, ignored. + void EntryActivated(object sender, EventArgs e) + { + SearchButtonPressed?.Invoke(this, EventArgs.Empty); + } + + /// + /// Handles the event triggered by entry gaining the focus. + /// + /// Sender of the event. + /// Event arguments, ignored. + void EntryFocused(object sender, EventArgs e) + { + _underlineRectangle.Color = s_underlineColor; + } + + /// + /// Handles the event triggered by entry's text being changed. + /// + /// Sender of the event. + /// Event arguments. + void EntryTextChanged(object sender, TextChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.NewTextValue)) + { + _cancelButton.Hide(); + } + else if (!_cancelButton.IsVisible) + { + _cancelButton.Show(); + } + TextChanged?.Invoke(this, e); + } + + /// + /// Handles the event triggered by entry losing the focus. + /// + /// Sender of the event. + /// Event arguments, ignored. + void EntryUnfocused(object sender, EventArgs e) + { + _underlineRectangle.Color = s_underlineDimColor; + } + + /// + /// Handles the event triggered by search bar's layout being changed. + /// + /// + /// Updates the geometry of the widgets comprising the search bar. + /// + /// Sender of the event. + /// Event arguments. + 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 +{ + /// + /// Represent a text with attributes applied. + /// + public class Span + { + string _text; + + /// + /// Gets or sets the formatted text. + /// + public FormattedString FormattedText { get; set; } + + /// + /// Gets or sets the text. + /// + /// + /// Setting Text to a non-null value will set the FormattedText property to null. + /// + public string Text + { + get + { + if (FormattedText != null) + { + return FormattedText.ToString(); + } + else + { + return _text; + } + } + set + { + if (value == null) + { + value = ""; + } + else + { + FormattedText = null; + } + _text = value; + } + } + + /// + /// Gets or sets the color for the text. + /// + public EColor ForegroundColor { get; set; } + + /// + /// Gets or sets the background color for the text. + /// + public EColor BackgroundColor { get; set; } + + /// + /// Gets or sets the font family for the text. + /// + public string FontFamily { get; set; } + + /// + /// Gets or sets the font attributes for the text. + /// See for information about FontAttributes. + /// + public FontAttributes FontAttributes { get; set; } + + /// + /// Gets or sets the font size for the text. + /// + public double FontSize { get; set; } + + /// + /// Gets or sets the line break mode for the text. + /// See for information about LineBreakMode. + /// + public LineBreakMode LineBreakMode { get; set; } + + /// + /// Gets or sets the horizontal alignment mode for the text. + /// See for information about TextAlignment. + /// + public TextAlignment HorizontalTextAlignment { get; set; } + + /// + /// Gets or sets the vertical alignment mode for the text. + /// See for information about TextAlignment. + /// + public TextAlignment VerticalTextAlignment { get; set; } + + /// + /// Gets or sets the value that indicates whether the text has underline. + /// + public bool Underline { get; set; } + + /// + /// Gets or sets the value that indicates whether the text has strike line though it. + /// + public bool Strikethrough { get; set; } + + /// + /// Create a new Span instance with default attributes. + /// + 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; + } + + /// + /// This method return marked up text + /// + internal string GetMarkupText() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendFormat(""); + + sb.Append(GetDecoratedText()); + + sb.Append(""); + + return sb.ToString(); + } + + /// + /// This method return text decorated with markup if FormattedText is set or plain text otherwise. + /// + 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", "
"); + } + + internal string GetStyle() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("DEFAULT='"); + + PrepareFormattingString(sb); + + sb.Append("'"); + + return sb.ToString(); + } + + /// + /// Converts string value to Span. + /// + /// The string text + 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 +{ + /// + /// Extends the ListView class to provide TableView class implementation. + /// + public class TableView : ListView + { + + static readonly SectionCellRenderer _sectionCellRenderer = new SectionCellRenderer(); + /// + /// Initializes a new instance of the TableView class. + /// + public TableView(EvasObject parent) + : base(parent) { + } + + /// + /// Sets the root of the table. + /// + /// TableRoot, which is parent to one or more TableSections. + 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); + } + + /// + /// Sets the section title. + /// + 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 +{ + /// + /// Enumerates values that describe alignemnt of text. + /// + public enum TextAlignment + { + /// + /// Aligns horizontal text according to language. Top aligned for vertical text. + /// + Auto, + /// + /// Left and top aligned for horizontal and vertical text, respectively. + /// + Start, + /// + /// Right and bottom aligned for horizontal and vertical text, respectively. + /// + End, + /// + /// Center-aligned text. + /// + 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 +{ + /// + /// The Text Helper contains functions that assist in working with text-able objects. + /// + internal static class TextHelper + { + /// + /// Gets the size of raw text block. + /// + /// The with text part. + /// Returns the size of raw text block. + public static ESize GetRawTextBlockSize(EvasObject textable) + { + return GetElmTextPart(textable).TextBlockNativeSize; + } + + /// + /// Gets the size of formatted text block. + /// + /// The with text part. + /// Returns the size of formatted text block. + public static ESize GetFormattedTextBlockSize(EvasObject textable) + { + return GetElmTextPart(textable).TextBlockFormattedSize; + } + + /// + /// Gets the ELM text part of evas object. + /// + /// The with text part. + /// Throws exception when parameter isn't text-able object or doesn't have ELM text part. + /// Requested instance. + 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 +{ + /// + /// Extends the ElmSharp.DateTimeSelector class with functionality useful to renderer. + /// + 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; + + /// + /// Initializes a new instance of the class. + /// + /// The parent EvasObject. + public TimePicker(EvasObject parent) : base(parent) + { + Style = TimeLayoutStyle; + ApplyTime(Time); + ApplyFormat(DateTimeFormat); + + DateTimeChanged += (sender, e) => + { + Time = e.NewDate.TimeOfDay; + }; + } + + /// + /// Gets or sets the displayed date time format. + /// + public string DateTimeFormat + { + get + { + return _dateTimeFormat; + } + set + { + if (_dateTimeFormat != value) + { + ApplyFormat(value); + } + } + } + + /// + /// Gets or sets the displayed time. + /// + public TimeSpan Time + { + get + { + return _time; + } + set + { + if (_time != value) + { + ApplyTime(value); + } + } + } + + /// + /// Sets the Format property according to the given . + /// + /// The format value to be applied to the time picker. + void ApplyFormat(string format) + { + _dateTimeFormat = format; + Format = ConvertToEFLFormat(_dateTimeFormat); + } + + /// + /// Sets the DateTime property according to the given . + /// + /// The time value to be applied to the time picker. + void ApplyTime(TimeSpan time) + { + _time = time; + DateTime = ConvertToDateTime(time); + } + + /// + /// Converts parameter to . + /// + /// The time value to be converted to . + /// An object representing the date 1st Jan, 1970 (minimum date of ElmSharp.DateTimeSelector) with added . + DateTime ConvertToDateTime(TimeSpan timeSpan) + { + return new DateTime(1970, 1, 1) + timeSpan; + } + + /// + /// Converts standard or custom format to EFL format. + /// + /// The format to be converted to EFL format. + /// does not contain a valid string representation of a date and time. + /// 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" + /// + 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; + + /// + /// Initializes a new instance of the Window class. + /// + public Window() : base("FormsWindow") + { + Initialize(); + } + + /// + /// Notifies that the window has been closed. + /// + public event EventHandler Closed; + + /// + /// Notifies that the back button has been pressed. + /// + public event EventHandler BackButtonPressed; + + /// + /// Gets the current orientation. + /// + public DisplayOrientations CurrentOrientation + { + get + { + if (IsRotationSupported) + { + return GetDisplayOrientation(); + } + else + { + return DisplayOrientations.None; + } + } + } + + /// + /// Gets or sets the orientation of a rectangular screen. + /// + public DisplayOrientations AvailableOrientations + { + get + { + if (IsRotationSupported) + { + return (DisplayOrientations)AvailableRotations; + } + else + { + return DisplayOrientations.None; + } + } + set + { + if (IsRotationSupported) + { + AvailableRotations = (DisplayRotation)value; + } + } + } + + /// + /// Sets the main page of Window. + /// + /// ElmSharp.EvasObject type page to be set. + 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; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Platform.cs b/Xamarin.Forms.Platform.Tizen/Platform.cs new file mode 100644 index 00000000..ab073007 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Platform.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal class Platform : BindableObject, IPlatform, INavigation, IDisposable + { + internal static readonly BindableProperty RendererProperty = BindableProperty.CreateAttached("Renderer", typeof(IVisualElementRenderer), typeof(Platform), default(IVisualElementRenderer), + propertyChanged: (bindable, oldvalue, newvalue) => + { + var ve = bindable as VisualElement; + if (ve != null) + ve.IsPlatformEnabled = newvalue != null; + }); + internal static readonly BindableProperty PageContextProperty = BindableProperty.CreateAttached("PageContext", typeof(FormsApplication), typeof(Platform), null); + + Naviframe _naviframe; + NavigationModel _navModel = new NavigationModel(); + bool _disposed; + + internal Platform(FormsApplication context) + { + Forms.Context.MainWindow.BackButtonPressed += (o, e) => + { + bool handled = false; + if (_navModel.CurrentPage != null) + { + if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted) + { + handled = true; + } + else + { + handled = _navModel.CurrentPage.SendBackButtonPressed(); + } + } + if (!handled) + context.Exit(); + }; + _naviframe = new Naviframe(Forms.Context.MainWindow) + { + PreserveContentOnPop = true, + DefaultBackButtonEnabled = false, + }; + _naviframe.SetAlignment(-1, -1); + _naviframe.SetWeight(1.0, 1.0); + _naviframe.Show(); + _naviframe.AnimationFinished += NaviAnimationFinished; + Forms.Context.MainWindow.SetMainPage(_naviframe); + } + + ~Platform() + { + Dispose(false); + } + + public Page Page + { + get; + private set; + } + + Task CurrentModalNavigationTask { get; set; } + TaskCompletionSource CurrentTaskCompletionSource { get; set; } + IPageController CurrentPageController => _navModel.CurrentPage as IPageController; + IReadOnlyList INavigation.ModalStack => _navModel.Modals.ToList(); + IReadOnlyList INavigation.NavigationStack => new List(); + + public static FormsApplication GetPageContext(BindableObject bindable) + { + return (FormsApplication)bindable.GetValue(Platform.PageContextProperty); + } + + public static void SetPageContext(BindableObject bindable, FormsApplication context) + { + bindable.SetValue(Platform.PageContextProperty, context); + } + + public static IVisualElementRenderer GetRenderer(BindableObject bindable) + { + return (IVisualElementRenderer)bindable.GetValue(Platform.RendererProperty); + } + + public static void SetRenderer(BindableObject bindable, IVisualElementRenderer value) + { + bindable.SetValue(Platform.RendererProperty, value); + } + + /// + /// Gets the renderer associated with the view. If it doesn't exist, creates a new one. + /// + /// Renderer associated with the view. + /// View for which the renderer is going to be returned. + public static IVisualElementRenderer GetOrCreateRenderer(VisualElement view) + { + return GetRenderer(view) ?? AttachRenderer(view); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void SetPage(Page newRoot) + { + if (Page != null) + { + var copyOfStack = new List(_naviframe.NavigationStack); + for (var i = 0; i < copyOfStack.Count; i++) + { + copyOfStack[i].Delete(); + } + foreach (Page page in _navModel.Roots) + { + var renderer = GetRenderer(page); + (page as IPageController)?.SendDisappearing(); + renderer?.Dispose(); + } + _navModel = new NavigationModel(); + } + + if (newRoot == null) + return; + + _navModel.Push(newRoot, null); + + Page = newRoot; + Page.Platform = this; + + Platform.SetPageContext(Page, Forms.Context); + IVisualElementRenderer pageRenderer = AttachRenderer(Page); + var naviItem = _naviframe.Push(pageRenderer.NativeView); + naviItem.TitleBarVisible = false; + ((Application)Page.RealParent).NavigationProxy.Inner = this; + + CurrentPageController?.SendAppearing(); + } + + public SizeRequest GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint) + { + widthConstraint = widthConstraint <= -1 ? double.PositiveInfinity : widthConstraint; + heightConstraint = heightConstraint <= -1 ? double.PositiveInfinity : heightConstraint; + + double width = !double.IsPositiveInfinity(widthConstraint) ? widthConstraint : Int32.MaxValue; + double height = !double.IsPositiveInfinity(heightConstraint) ? heightConstraint : Int32.MaxValue; + + return GetRenderer(view).GetDesiredSize(width, height); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + if (disposing) + { + SetPage(null); + _naviframe.Unrealize(); + } + _disposed = true; + } + + protected override void OnBindingContextChanged() + { + BindableObject.SetInheritedBindingContext(Page, base.BindingContext); + base.OnBindingContextChanged(); + } + + static IVisualElementRenderer AttachRenderer(VisualElement view) + { + IVisualElementRenderer visualElementRenderer = Registrar.Registered.GetHandler(view.GetType()); + + if (null == visualElementRenderer) + { + throw new ArgumentException(String.Format("{0} doesn't have assigned renderer!", view.GetType())); + } + + visualElementRenderer.SetElement(view); + + return visualElementRenderer; + } + + void INavigation.InsertPageBefore(Page page, Page before) + { + throw new InvalidOperationException("InsertPageBefore is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PopAsync() + { + return ((INavigation)this).PopAsync(true); + } + + Task INavigation.PopAsync(bool animated) + { + throw new InvalidOperationException("PopAsync is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PopToRootAsync() + { + return ((INavigation)this).PopToRootAsync(true); + } + + Task INavigation.PopToRootAsync(bool animated) + { + throw new InvalidOperationException("PopToRootAsync is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PushAsync(Page root) + { + return ((INavigation)this).PushAsync(root, true); + } + + Task INavigation.PushAsync(Page root, bool animated) + { + throw new InvalidOperationException("PushAsync is not supported globally on Tizen, please use a NavigationPage."); + } + + void INavigation.RemovePage(Page page) + { + throw new InvalidOperationException("RemovePage is not supported globally on Tizen, please use a NavigationPage."); + } + + Task INavigation.PushModalAsync(Page modal) + { + return ((INavigation)this).PushModalAsync(modal, true); + } + + async Task INavigation.PushModalAsync(Page modal, bool animated) + { + CurrentPageController?.SendDisappearing(); + + _navModel.PushModal(modal); + + modal.Platform = this; + + await PushModalInternal(modal, animated); + + // Verify that the modal is still on the stack + if (_navModel.CurrentPage == modal) + CurrentPageController.SendAppearing(); + } + + Task INavigation.PopModalAsync() + { + return ((INavigation)this).PopModalAsync(true); + } + + async Task INavigation.PopModalAsync(bool animated) + { + Page modal = _navModel.PopModal(); + + ((IPageController)modal).SendDisappearing(); + + IVisualElementRenderer modalRenderer = GetRenderer(modal); + if (modalRenderer != null) + { + await PopModalInternal(animated); + } + Platform.GetRenderer(modal).Dispose(); + + CurrentPageController?.SendAppearing(); + return modal; + } + + async Task PushModalInternal(Page modal, bool animated) + { + TaskCompletionSource tcs = null; + if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted) + { + var previousTask = CurrentModalNavigationTask; + tcs = new TaskCompletionSource(); + CurrentModalNavigationTask = tcs.Task; + await previousTask; + } + + var after = _naviframe.NavigationStack.LastOrDefault(); + NaviItem pushed = null; + if (animated || after == null) + { + pushed = _naviframe.Push(Platform.GetOrCreateRenderer(modal).NativeView, modal.Title); + } + else + { + pushed = _naviframe.InsertAfter(after, Platform.GetOrCreateRenderer(modal).NativeView, modal.Title); + } + pushed.TitleBarVisible = false; + + bool shouldWait = animated && after != null; + await WaitForCompletion(shouldWait, tcs); + } + + async Task PopModalInternal(bool animated) + { + TaskCompletionSource tcs = null; + if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted) + { + var previousTask = CurrentModalNavigationTask; + tcs = new TaskCompletionSource(); + CurrentModalNavigationTask = tcs.Task; + await previousTask; + } + + if (animated) + { + _naviframe.Pop(); + } + else + { + _naviframe.NavigationStack.LastOrDefault()?.Delete(); + } + + bool shouldWait = animated && (_naviframe.NavigationStack.Count != 0); + await WaitForCompletion(shouldWait, tcs); + } + + async Task WaitForCompletion(bool shouldWait, TaskCompletionSource tcs) + { + if (shouldWait) + { + tcs = tcs ?? new TaskCompletionSource(); + CurrentTaskCompletionSource = tcs; + if (CurrentModalNavigationTask == null || CurrentModalNavigationTask.IsCompleted) + { + CurrentModalNavigationTask = CurrentTaskCompletionSource.Task; + } + } + else + { + tcs?.SetResult(true); + } + + if (tcs != null) + await tcs.Task; + } + + void NaviAnimationFinished(object sender, EventArgs e) + { + var tcs = CurrentTaskCompletionSource; + CurrentTaskCompletionSource = null; + tcs?.SetResult(true); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/PlatformEffect.cs b/Xamarin.Forms.Platform.Tizen/PlatformEffect.cs new file mode 100644 index 00000000..97dec494 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/PlatformEffect.cs @@ -0,0 +1,11 @@ +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// + /// Base class for platform-specific effect classes. + /// + public abstract class PlatformEffect : PlatformEffect + { + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs b/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..37e0221a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Properties/AssemblyInfo.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Tizen; + +[assembly: AssemblyVersion("0.0.1")] +[assembly: AssemblyCompany("Samsung Electronics")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCopyright("Copyright © Samsung Electronics 2016")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyProduct("Xamarin.Forms")] +[assembly: AssemblyTitle("Xamarin.Forms.Platform.Tizen")] +[assembly: AssemblyTrademark("")] +[assembly: CompilationRelaxations(8)] +[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] +[assembly: InternalsVisibleTo("Xamarin.Forms.Platform.Tizen.Tests")] +[assembly: ComVisible(false)] + +[assembly: Xamarin.Forms.Dependency(typeof(ResourcesProvider))] +[assembly: Xamarin.Forms.Dependency(typeof(Deserializer))] + +[assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))] +[assembly: ExportRenderer(typeof(ScrollView), typeof(ScrollViewRenderer))] +[assembly: ExportRenderer(typeof(CarouselPage), typeof(CarouselPageRenderer))] +[assembly: ExportRenderer(typeof(ContentPage), typeof(ContentPageRenderer))] +[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer))] +[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer))] +[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer))] + +[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))] +[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))] +[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))] +[assembly: ExportRenderer(typeof(Slider), typeof(SliderRenderer))] +[assembly: ExportRenderer(typeof(Picker), typeof(PickerRenderer))] +[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))] +[assembly: ExportRenderer(typeof(Stepper), typeof(StepperRenderer))] +[assembly: ExportRenderer(typeof(DatePicker), typeof(DatePickerRenderer))] +[assembly: ExportRenderer(typeof(TimePicker), typeof(TimePickerRenderer))] +[assembly: ExportRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer))] +[assembly: ExportRenderer(typeof(Switch), typeof(SwitchRenderer))] +[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))] +[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))] +[assembly: ExportRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer))] +[assembly: ExportRenderer(typeof(SearchBar), typeof(SearchBarRenderer))] +[assembly: ExportRenderer(typeof(Entry), typeof(EntryRenderer))] +[assembly: ExportRenderer(typeof(Editor), typeof(EditorRenderer))] +[assembly: ExportRenderer(typeof(TableView), typeof(TableViewRenderer))] +[assembly: ExportRenderer(typeof(EvasObjectWrapper), typeof(EvasObjectWrapperRenderer))] + +[assembly: ExportImageSourceHandler(typeof(FileImageSource), typeof(FileImageSourceHandler))] +[assembly: ExportImageSourceHandler(typeof(StreamImageSource), typeof(StreamImageSourceHandler))] +[assembly: ExportImageSourceHandler(typeof(UriImageSource), typeof(UriImageSourceHandler))] + +[assembly: ExportCell(typeof(TextCell), typeof(TextCellRenderer))] +[assembly: ExportCell(typeof(ImageCell), typeof(ImageCellRenderer))] +[assembly: ExportCell(typeof(SwitchCell), typeof(SwitchCellRenderer))] +[assembly: ExportCell(typeof(EntryCell), typeof(EntryCellRenderer))] +[assembly: ExportCell(typeof(ViewCell), typeof(ViewCellRenderer))] diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs new file mode 100644 index 00000000..04a69c6a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ActivityIndicatorRenderer.cs @@ -0,0 +1,57 @@ +using EProgressBar = ElmSharp.ProgressBar; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ActivityIndicatorRenderer : ViewRenderer + { + static readonly EColor s_defaultColor = EColor.Black; + + public ActivityIndicatorRenderer() + { + RegisterPropertyHandler(ActivityIndicator.ColorProperty, UpdateColor); + RegisterPropertyHandler(ActivityIndicator.IsRunningProperty, UpdateIsRunning); + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (Control == null) + { + var ac = new EProgressBar(Forms.Context.MainWindow) + { + Style = "process_medium", + IsPulseMode = true, + }; + SetNativeControl(ac); + } + + if (e.OldElement != null) + { + } + + if (e.NewElement != null) + { + } + + base.OnElementChanged(e); + } + + void UpdateColor() + { + Control.Color = (Element.Color == Color.Default) ? s_defaultColor : Element.Color.ToNative(); + } + + void UpdateIsRunning() + { + if (Element.IsRunning) + { + Control.PlayPulse(); + } + else + { + Control.StopPulse(); + } + } + + }; +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs new file mode 100644 index 00000000..7a9f756d --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs @@ -0,0 +1,61 @@ +using System.ComponentModel; +using EColor = ElmSharp.Color; +using ERectangle = ElmSharp.Rectangle; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class BoxViewRenderer : + VisualElementRenderer + { + static readonly EColor s_defaultColor = EColor.Transparent; + + ERectangle _control; + + public BoxViewRenderer() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + if (_control == null) + { + _control = new ERectangle(Forms.Context.MainWindow); + SetNativeControl(_control); + } + + if (e.OldElement != null) + { + } + + if (e.NewElement != null) + { + UpdateColor(); + } + + base.OnElementChanged(e); + } + + void UpdateColor() + { + Color colorToSet = Element.Color; + + if (colorToSet == Color.Default) + { + colorToSet = Element.BackgroundColor; + } + + _control.Color = (colorToSet == Color.Default) ? s_defaultColor : colorToSet.ToNative(); + } + + protected override void OnElementPropertyChanged(object sender, + PropertyChangedEventArgs e) + { + if (e.PropertyName == BoxView.ColorProperty.PropertyName || + e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + { + UpdateColor(); + } + base.OnElementPropertyChanged(sender, e); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs new file mode 100644 index 00000000..a34df549 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs @@ -0,0 +1,95 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ButtonRenderer : ViewRenderer + { + static readonly EColor s_defaultTextColor = EColor.White; + + public ButtonRenderer() + { + RegisterPropertyHandler(Button.TextProperty, UpdateText); + RegisterPropertyHandler(Button.FontFamilyProperty, UpdateText); + RegisterPropertyHandler(Button.FontSizeProperty, UpdateText); + RegisterPropertyHandler(Button.FontAttributesProperty, UpdateText); + RegisterPropertyHandler(Button.TextColorProperty, UpdateTextColor); + RegisterPropertyHandler(Button.ImageProperty, UpdateBitmap); + RegisterPropertyHandler(Button.BorderColorProperty, UpdateBorder); + RegisterPropertyHandler(Button.BorderRadiusProperty, UpdateBorder); + RegisterPropertyHandler(Button.BorderWidthProperty, UpdateBorder); + } + + protected override void OnElementChanged(ElementChangedEventArgs