diff options
author | Kangho Hur <kangho.hur@samsung.com> | 2016-12-16 11:00:07 +0900 |
---|---|---|
committer | Kangho Hur <kangho.hur@samsung.com> | 2017-07-10 11:11:14 +0900 |
commit | 79bf87f2bc00d823cf8b25ed7d0d3650cf819b4c (patch) | |
tree | 99d3412413a92c057cb8ad8429ddb0c7d4cb8c14 | |
parent | b7297c8ac01d6ce2d5f038d3df8f4bc9e74a8162 (diff) | |
download | xamarin-forms-79bf87f2bc00d823cf8b25ed7d0d3650cf819b4c.tar.gz xamarin-forms-79bf87f2bc00d823cf8b25ed7d0d3650cf819b4c.tar.bz2 xamarin-forms-79bf87f2bc00d823cf8b25ed7d0d3650cf819b4c.zip |
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
111 files changed, 13346 insertions, 1 deletions
diff --git a/.nuspec/Xamarin.Forms.Maps.Tizen.nuspec b/.nuspec/Xamarin.Forms.Maps.Tizen.nuspec new file mode 100644 index 00000000..b951d4aa --- /dev/null +++ b/.nuspec/Xamarin.Forms.Maps.Tizen.nuspec @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<package> + <metadata> + <id>Xamarin.Forms.Maps.Tizen</id> + <version>$version$</version> + <authors>Tizen Developers</authors> + <description>Xamarin.Forms.Maps Renderer for Tizen.Net</description> + <dependencies> + <dependency id="Xamarin.Forms.Maps" version="2.3.3.0" /> + </dependencies> + </metadata> + <files> + <file src="../Xamarin.Forms.Maps.Tizen/bin/$Configuration$/Xamarin.Forms.Maps.Tizen.dll" target="lib/netcoreapp1.0"/> + <file src="../Xamarin.Forms.Maps.Tizen/bin/$Configuration$/Xamarin.Forms.Maps.Tizen.*pdb" target="lib/netcoreapp1.0"/> + </files> +</package> diff --git a/.nuspec/Xamarin.Forms.Platform.Tizen.nuspec b/.nuspec/Xamarin.Forms.Platform.Tizen.nuspec new file mode 100644 index 00000000..3bcf80be --- /dev/null +++ b/.nuspec/Xamarin.Forms.Platform.Tizen.nuspec @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<package> + <metadata> + <id>Xamarin.Forms.Platform.Tizen</id> + <version>$version$</version> + <authors>tizen</authors> + <tags>xamarin forms xamarin.forms tizen</tags> + <projectUrl>https://www.tizen.org/</projectUrl> + <iconUrl>https://developer.tizen.org/sites/default/files/images/tizen-pinwheel-on-light-rgb_64_64.png</iconUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description>Xamarin Forms Renderer to build native UIs for Tizen.Net</description> + <copyright>Copyright 2016</copyright> + <dependencies> + <dependency id="Xamarin.Forms" version="2.3.3.175" /> + </dependencies> + </metadata> + <files> + <file src="../Xamarin.Forms.Platform.Tizen/bin/$Configuration$/Xamarin.Forms.Platform.Tizen.dll" target="lib/netcoreapp1.0"/> + <file src="../Xamarin.Forms.Platform.Tizen/bin/$Configuration$/Xamarin.Forms.Platform.Tizen.*pdb" target="lib/netcoreapp1.0"/> + </files> +</package> diff --git a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs index 429aca99..84b9f313 100644 --- a/Xamarin.Forms.Core/Properties/AssemblyInfo.cs +++ b/Xamarin.Forms.Core/Properties/AssemblyInfo.cs @@ -40,6 +40,7 @@ using Xamarin.Forms.Internals; [assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS")] [assembly: InternalsVisibleTo("Xamarin.Forms.Maps.iOS.Classic")] [assembly: InternalsVisibleTo("Xamarin.Forms.Maps.Android")] +[assembly: InternalsVisibleTo("Xamarin.Forms.Maps.Tizen")] [assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.UnitTests")] [assembly: InternalsVisibleTo("Xamarin.Forms.UITests")] [assembly: InternalsVisibleTo("Xamarin.Forms.FlexLayout.UnitTests")] @@ -59,4 +60,4 @@ using Xamarin.Forms.Internals; [assembly: InternalsVisibleTo("Xamarin.Forms.CarouselView")] [assembly: Preserve] -[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms", "Xamarin.Forms")]
\ No newline at end of file +[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms", "Xamarin.Forms")] diff --git a/Xamarin.Forms.Maps.Tizen/FormsMaps.cs b/Xamarin.Forms.Maps.Tizen/FormsMaps.cs new file mode 100755 index 00000000..b41b50d8 --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/FormsMaps.cs @@ -0,0 +1,19 @@ +using Xamarin.Forms.Maps.Tizen; + +namespace Xamarin +{ + public static class FormsMaps + { + public static bool IsInitialized { get; private set; } + + public static void Init() + { + if (IsInitialized) + return; + + IsInitialized = true; + + GeocoderBackend.Register(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Maps.Tizen/GeocoderBackend.cs b/Xamarin.Forms.Maps.Tizen/GeocoderBackend.cs new file mode 100644 index 00000000..a18990d4 --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/GeocoderBackend.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System; + +namespace Xamarin.Forms.Maps.Tizen +{ + public class Position {}; + + internal class GeocoderBackend + { + public static void Register() + { + } + + public static async Task<IEnumerable<Position>> GetPositionsForAddressAsync(string address) + { + return new Position[]{}; + } + + public static async Task<IEnumerable<string>> GetAddressesForPositionAsync(Position position) + { + return new String[]{"Not supported"}; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Maps.Tizen/MapControl.cs b/Xamarin.Forms.Maps.Tizen/MapControl.cs new file mode 100755 index 00000000..a10bb804 --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/MapControl.cs @@ -0,0 +1,14 @@ +using TLabel = Xamarin.Forms.Platform.Tizen.Native.Label; + +namespace Xamarin.Forms.Maps.Tizen +{ + public class MapControl : TLabel + { + public MapControl(ElmSharp.EvasObject parent) : base(parent) + { + Text = "Can not supported Maps"; + TextColor = ElmSharp.Color.Red; + } + } +} + diff --git a/Xamarin.Forms.Maps.Tizen/MapRenderer.cs b/Xamarin.Forms.Maps.Tizen/MapRenderer.cs new file mode 100755 index 00000000..b7278dcc --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/MapRenderer.cs @@ -0,0 +1,47 @@ +using Xamarin.Forms.Platform.Tizen; +using TForms = Xamarin.Forms.Platform.Tizen.Forms; + +namespace Xamarin.Forms.Maps.Tizen +{ + public class MapRenderer : ViewRenderer<Map, MapControl> + { + public MapRenderer() + { + RegisterPropertyHandler(Map.MapTypeProperty, UpdateMapType); + RegisterPropertyHandler(Map.IsShowingUserProperty, UpdateIsShowingUser); + RegisterPropertyHandler(Map.HasScrollEnabledProperty, UpdateHasScrollEnabled); + RegisterPropertyHandler(Map.HasZoomEnabledProperty, UpdateHasZoomEnabled); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Map> e) + { + base.OnElementChanged(e); + + if (Control == null) + { + var mapControl = new MapControl(TForms.Context.MainWindow); + SetNativeControl(mapControl); + } + } + + void UpdateMapType() + { + // TODO + } + + void UpdateIsShowingUser() + { + // TODO + } + + void UpdateHasScrollEnabled() + { + // TODO + } + + void UpdateHasZoomEnabled() + { + // TODO + } + } +} diff --git a/Xamarin.Forms.Maps.Tizen/Properties/AssemblyInfo.cs b/Xamarin.Forms.Maps.Tizen/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ec69a71a --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. +using Xamarin.Forms; +using Xamarin.Forms.Maps; +using Xamarin.Forms.Maps.Tizen; +using Xamarin.Forms.Platform.Tizen; + +[assembly: AssemblyTitle("Xamarin.Forms.Maps.Tizen")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("k.lipner")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +[assembly: ExportRenderer(typeof (Map), typeof (MapRenderer))] +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/Xamarin.Forms.Maps.Tizen/Xamarin.Forms.Maps.Tizen.csproj b/Xamarin.Forms.Maps.Tizen/Xamarin.Forms.Maps.Tizen.csproj new file mode 100644 index 00000000..9b163767 --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/Xamarin.Forms.Maps.Tizen.csproj @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{D29D5750-9A39-4E92-A19E-62567D660B7D}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Xamarin.Forms.Maps.Tizen</RootNamespace> + <AssemblyName>Xamarin.Forms.Maps.Tizen</AssemblyName> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup> + <TargetFrameworkIdentifier>.NETStandard</TargetFrameworkIdentifier> + <TargetFrameworkVersion>v1.6</TargetFrameworkVersion> + <NuGetTargetMoniker>.NETStandard,Version=v1.6</NuGetTargetMoniker> + <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences> + <NoStdLib>true</NoStdLib> + <NoWarn>$(NoWarn);1701;1702</NoWarn> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="FormsMaps.cs" /> + <Compile Include="GeocoderBackend.cs" /> + <Compile Include="MapRenderer.cs" /> + <Compile Include="MapControl.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Xamarin.Forms.Maps.Tizen.project.json" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Maps\Xamarin.Forms.Maps.csproj"> + <Project>{7D13BAC2-C6A4-416A-B07E-C169B199E52B}</Project> + <Name>Xamarin.Forms.Maps</Name> + </ProjectReference> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + <ProjectReference Include="..\Xamarin.Forms.Platform.Tizen\Xamarin.Forms.Platform.Tizen.csproj"> + <Project>{227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}</Project> + <Name>Xamarin.Forms.Platform.Tizen</Name> + </ProjectReference> + </ItemGroup> + <PropertyGroup> + <!-- https://github.com/dotnet/corefxlab/tree/master/samples/NetCoreSample and + https://docs.microsoft.com/en-us/dotnet/articles/core/tutorials/target-dotnetcore-with-msbuild + --> + <!-- We don't use any of MSBuild's resolution logic for resolving the framework, so just set these two + properties to any folder that exists to skip the GetReferenceAssemblyPaths task (not target) and + to prevent it from outputting a warning (MSB3644). + --> + <_TargetFrameworkDirectories>$(MSBuildThisFileDirectory)</_TargetFrameworkDirectories> + <_FullFrameworkReferenceAssemblyPaths>$(MSBuildThisFileDirectory)</_FullFrameworkReferenceAssemblyPaths> + <AutoUnifyAssemblyReferences>true</AutoUnifyAssemblyReferences> + </PropertyGroup> +</Project>
\ No newline at end of file diff --git a/Xamarin.Forms.Maps.Tizen/Xamarin.Forms.Maps.Tizen.project.json b/Xamarin.Forms.Maps.Tizen/Xamarin.Forms.Maps.Tizen.project.json new file mode 100755 index 00000000..f4511fe7 --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/Xamarin.Forms.Maps.Tizen.project.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "ElmSharp": "1.1.0-*", + "NETStandard.Library": "1.6.0", + "Tizen.Applications": "1.0.2" + }, + "frameworks": { + "netstandard1.6": { + "imports": "portable-net45+win8+wpa81+wp8" + } + } +} diff --git a/Xamarin.Forms.Maps.Tizen/packages.config b/Xamarin.Forms.Maps.Tizen/packages.config new file mode 100644 index 00000000..433f1b92 --- /dev/null +++ b/Xamarin.Forms.Maps.Tizen/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="ElmSharp" version="1.0.3" targetFramework="net45" /> +</packages>
\ No newline at end of file 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<Cell, Dictionary<string, EvasObject>> _realizedNativeViews = new Dictionary<Cell, Dictionary<string, EvasObject>>(); + + 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<string, EvasObject> 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<string, EvasObject> realizedView = null; + _realizedNativeViews.TryGetValue(cell, out realizedView); + + // just to prevent null reference exception in OnCellPropertyChanged + realizedView = realizedView ?? new Dictionary<string, EvasObject>(); + + 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<string, EvasObject> realizedView = null; + _realizedNativeViews.TryGetValue(cell, out realizedView); + if (realizedView == null) + { + realizedView = new Dictionary<string, EvasObject>(); + _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<EvasObject, NativeEntryComponent> _realizedComponent = new Dictionary<EvasObject, NativeEntryComponent>(); + + 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<string, EvasObject> 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<EvasObject, Native.Image> _realizedViews = new Dictionary<EvasObject, Native.Image>(); + + 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<string, EvasObject> 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<string, EvasObject> 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<string, EvasObject> 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<string, EvasObject> 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<IDictionary<string, object>> 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<string, object>)); + return (IDictionary<string, object>)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<string, object> properties) + { + properties = new Dictionary<string, object>(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<string, object>)); + 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<TElement> : EventArgs where TElement : Element + { + /// <summary> + /// Holds the old element which is about to be replaced by a new element + /// </summary> + /// <value>An TElement instance representing the old element just being replaced</value> + public TElement OldElement + { + get; + private set; + } + + /// <summary> + /// Holds the new element which will replace the old element + /// </summary> + /// <value>An TElement instance representing the new element to be used from now on</value> + public TElement NewElement + { + get; + private set; + } + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.ElementChangedEventArgs`1"/> class. + /// </summary> + /// <param name="oldElement">The old element which will be replaced by a newElement momentarily.</param> + /// <param name="newElement">The new element, taking place of an old element.</param> + 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 + { + /// <summary> + /// Creates an instance of ElmSharp.Color class based on provided Xamarin.Forms.Color instance + /// </summary> + /// <returns>ElmSharp.Color instance representing a color which corresponds to the provided Xamarin.Forms.Color</returns> + /// <param name="c">The Xamarin.Forms.Color instance which will be converted to a ElmSharp.Color</param> + 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)); + } + } + + /// <summary> + /// Returns a string representing the provided ElmSharp.Color instance in a hexagonal notation + /// </summary> + /// <returns>string value containing the encoded color</returns> + /// <param name="c">The ElmSharp.Color class instance which will be serialized</param> + 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 + { + /// <summary> + /// Creates an instance of ElmSharp.Keyboard reflecting the provided Xamarin.Forms.Keyboard instance + /// </summary> + /// <returns>Keyboard type corresponding to the provided Xamarin.Forms.Keyboard</returns> + /// <param name="keyboard">The Xamarin.Forms.Keyboard class instance to be converted to ElmSharp.Keyboard.</param> + 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 +{ + /// <summary> + /// Extension class, provides native embedding functionalities: + /// https://developer.xamarin.com/guides/xamarin-forms/user-interface/layouts/add-platform-controls/ + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + public static class LayoutExtensions + { + /// <summary> + /// Add the specified evas object to the list of children views. + /// </summary> + /// <param name="children">The extended class.</param> + /// <param name="obj">Object to be added.</param> + /// <param name="measureDelegate">Optional delegate which provides measurements for the added object.</param> + public static void Add(this IList<View> children, EvasObject obj, MeasureDelegate measureDelegate = null) + { + children.Add(obj.ToView(measureDelegate)); + } + + /// <summary> + /// Wraps the evas object into a view which can be used by Xamarin. + /// </summary> + /// <returns>The Xamarin view which wraps the evas object.</returns> + /// <param name="obj">The extended class.</param> + /// <param name="measureDelegate">Optional delegate which provides measurements for the evas object.</param> + 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<ViewInitializedEventArgs> 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<ViewInitializedEventArgs> 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; + } + + /// <summary> + /// Gets the main window or <c>null</c> if it's not set. + /// </summary> + /// <value>The main window or <c>null</c>.</value> + 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<Page, AlertArguments>(this, "Xamarin.SendAlert"); + MessagingCenter.Unsubscribe<Page, bool>(this, "Xamarin.BusySet"); + MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(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<Page, bool>(this, Page.BusySetSignalName, delegate (Page sender, bool enabled) + { + ShowActivityIndicatorDialog(enabled); + }, null); + + MessagingCenter.Subscribe<Page, AlertArguments>(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 = "<span font_size=30 >" + 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<Page, ActionSheetArguments>(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()); + } + + /// <summary> + /// Exits the application's main loop, which initiates the process of its termination + /// </summary> + 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<IGestureRecognizer>).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<IGestureRecognizer>).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<IGestureRecognizer> removed, IEnumerable<IGestureRecognizer> 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<IGestureRecognizer>()); + break; + + case NotifyCollectionChangedAction.Replace: + AddAndRemoveRecognizers(e.OldItems.OfType<IGestureRecognizer>(), e.NewItems.OfType<IGestureRecognizer>()); + break; + + case NotifyCollectionChangedAction.Remove: + AddAndRemoveRecognizers(e.OldItems.OfType<IGestureRecognizer>(), 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 +{ + /// <summary> + /// Logs a message to the console. + /// </summary> + 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); + } + + /// <summary> + /// Formats and prints the log information. + /// </summary> + /// <param name="level">Log level</param> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + 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 +{ + /// <summary> + /// Logs a message to the dlog. + /// </summary> + 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 +{ + /// <summary> + /// Reports log messages with various log levels. + /// </summary> + internal interface ILogger + { + /// <summary> + /// Reports a debug log message. + /// </summary> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + void Debug(string tag, string message, string file, string func, int line); + + /// <summary> + /// Reports a verbose log message. + /// </summary> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + void Verbose(string tag, string message, string file, string func, int line); + + /// <summary> + /// Reports an information log message. + /// </summary> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + void Info(string tag, string message, string file, string func, int line); + + /// <summary> + /// Reports a warning log message. + /// </summary> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + void Warn(string tag, string message, string file, string func, int line); + + /// <summary> + /// Reports an error log message. + /// </summary> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + void Error(string tag, string message, string file, string func, int line); + + /// <summary> + /// Reports a fatal error log message. + /// </summary> + /// <param name="tag">Log tag</param> + /// <param name="message">Log message</param> + /// <param name="file">Full path to the file</param> + /// <param name="func">Function name</param> + /// <param name="line">Line number</param> + 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 +{ + /// <summary> + /// Provides logging functionality. + /// </summary> + internal static class Log + { + static String _tag = "Xamarin"; + + static ILogger _logger = IsTizen() ? (ILogger)new DlogLogger() : (ILogger)new ConsoleLogger(); + + /// <summary> + /// Gets or sets the log tag. + /// </summary> + public static String Tag + { + get + { + return _tag; + } + set + { + _tag = value; + } + } + + /// <summary> + /// Gets or sets the logger used to report messages. + /// It's DlogLogger on a Tizen platform, ConsoleLogger on any other. + /// </summary> + 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<T0>(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<T0, T1>(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<T0, T1, T2>(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<T0, T1, T2, T3>(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<T0, T1, T2, T3, T4>(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<T0, T1, T2, T3, T4, T5>(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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(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<T0>(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<T0, T1>(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<T0, T1, T2>(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<T0, T1, T2, T3>(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<T0, T1, T2, T3, T4>(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<T0, T1, T2, T3, T4, T5>(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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(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<T0>(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<T0, T1>(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<T0, T1, T2>(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<T0, T1, T2, T3>(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<T0, T1, T2, T3, T4>(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<T0, T1, T2, T3, T4, T5>(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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(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<T0>(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<T0, T1>(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<T0, T1, T2>(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<T0, T1, T2, T3>(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<T0, T1, T2, T3, T4>(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<T0, T1, T2, T3, T4, T5>(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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(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<T0>(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<T0, T1>(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<T0, T1, T2>(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<T0, T1, T2, T3>(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<T0, T1, T2, T3, T4>(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<T0, T1, T2, T3, T4, T5>(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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(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<T0>(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<T0, T1>(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<T0, T1, T2>(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<T0, T1, T2, T3>(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<T0, T1, T2, T3, T4>(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<T0, T1, T2, T3, T4, T5>(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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8>(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<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(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); + } + + /// <summary> + /// Determines if Xamarin is running in a Tizen environment. + /// </summary> + /// <returns><c>true</c> if application is running on Tizen; otherwise, <c>false</c>.</returns> + static bool IsTizen() + { + return System.IO.File.Exists("/etc/tizen-release"); + } + + /// <summary> + /// 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 <c>null</c> value as one of the parameters. + /// </summary> + 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 +{ + /// <summary> + /// Extends the ElmSharp.Box class with functionality useful to Xamarin.Forms renderer. + /// </summary> + /// <remarks> + /// This class overrides the layout mechanism. Instead of using the native layout, + /// <c>LayoutUpdated</c> event is sent. + /// </remarks> + public class Box : EBox + { + /// <summary> + /// The last processed geometry of the Box which was reported from the native layer. + /// </summary> + Rect _previousGeometry; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Box"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public Box(EvasObject parent) : base(parent) + { + Resized += (sender, e) => { NotifyOnLayout(); }; + SetLayoutCallback(() => { NotifyOnLayout(); }); + } + + /// <summary> + /// Notifies that the layout has been updated. + /// </summary> + public event EventHandler<LayoutEventArgs> LayoutUpdated; + + /// <summary> + /// Triggers the <c>LayoutUpdated</c> event. + /// </summary> + /// <remarks> + /// This method is called whenever there is a possibility that the size and/or position has been changed. + /// </remarks> + void NotifyOnLayout() + { + var g = Geometry; + + if (0 == g.Width || 0 == g.Height) + { + // ignore irrelevant dimensions + return; + } + if (null != LayoutUpdated) + { + LayoutUpdated(this, new LayoutEventArgs() + { + HasChanged = g != _previousGeometry, + X = g.X, + Y = g.Y, + Width = g.Width, + Height = g.Height, + } + ); + } + + _previousGeometry = g; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Button.cs b/Xamarin.Forms.Platform.Tizen/Native/Button.cs new file mode 100644 index 00000000..8f85da63 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Button.cs @@ -0,0 +1,303 @@ +using System; +using ElmSharp; +using EButton = ElmSharp.Button; +using ESize = ElmSharp.Size; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the EButton control, providing basic formatting features, + /// i.e. font color, size, additional image. + /// </summary> + public class Button : EButton, IMeasurable + { + /// <summary> + /// Holds the formatted text of the button. + /// </summary> + readonly Span _span = new Span(); + + /// <summary> + /// The internal padding of the button, helps to determine the size. + /// </summary> + readonly ESize _internalPadding; + + /// <summary> + /// Optional image, if set will be drawn on the button. + /// </summary> + Image _image; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Button"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public Button(EvasObject parent) : base(parent) + { + _internalPadding = GetInternalPadding(); + } + + /// <summary> + /// Gets or sets the button's text. + /// </summary> + /// <value>The text.</value> + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the text background. + /// </summary> + /// <value>The color of the text background.</value> + public EColor TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font family. + /// </summary> + /// <value>The font family.</value> + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font attributes. + /// </summary> + /// <value>The font attributes.</value> + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the size of the font. + /// </summary> + /// <value>The size of the font.</value> + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the image to be displayed next to the button's text. + /// </summary> + /// <value>The image displayed on the button.</value> + public Image Image + { + get + { + return _image; + } + + set + { + if (value != _image) + { + ApplyImage(value); + } + } + } + + /// <summary> + /// Implementation of the IMeasurable.Measure() method. + /// </summary> + public ESize Measure(int availableWidth, int availableHeight) + { + var size = Geometry; + + // resize the control using the whole available width + Resize(availableWidth, size.Height); + + // measure the button's text, use it as a hint for the size + var rawSize = Native.TextHelper.GetRawTextBlockSize(this); + var formattedSize = Native.TextHelper.GetFormattedTextBlockSize(this); + + // restore the original size + Resize(size.Width, size.Height); + + var padding = _internalPadding; + + if (rawSize.Width > availableWidth) + { + // if the raw text width is larger than the available width, use + // either formatted size or internal padding, whichever is bigger + return new ESize() + { + Width = Math.Max(padding.Width, formattedSize.Width), + Height = Math.Max(padding.Height, Math.Min(formattedSize.Height, Math.Max(rawSize.Height, availableHeight))), + }; + } + else + { + // otherwise use the formatted size along with padding + return new ESize() + { + Width = padding.Width + formattedSize.Width, + Height = Math.Max(padding.Height, formattedSize.Height), + }; + } + } + + /// <summary> + /// Applies the button's text and its style. + /// </summary> + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + /// <summary> + /// Sets the button's internal text and its style. + /// </summary> + /// <param name="formattedText">Formatted text, supports HTML tags.</param> + /// <param name="textStyle">Style applied to the formattedText.</param> + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + string emission = "elm,state,text,visible"; + + if (string.IsNullOrEmpty(formattedText)) + { + formattedText = null; + textStyle = null; + emission = "elm,state,text,hidden"; + } + + base.Text = formattedText; + + var textblock = EdjeObject["elm.text"]; + + if (textblock != null) + { + textblock.TextStyle = textStyle; + } + + EdjeObject.EmitSignal(emission, "elm"); + } + + /// <summary> + /// Gets the internal padding of the button. + /// </summary> + /// <returns>The internal padding.</returns> + ESize GetInternalPadding() + { + var edje = EdjeObject; + + return new ESize + { + Width = (edje["padding_top_left"]?.Geometry.Width ?? 64) + (edje["padding_bottom_right"]?.Geometry.Width ?? 64), + Height = edje["bg"]?.Geometry.Height ?? 64 + }; + } + + /// <summary> + /// Applies the image to be displayed on the button. If value is <c>null</c>, + /// image will be removed. + /// </summary> + /// <param name="image">Image to be displayed or null.</param> + void ApplyImage(Image image) + { + _image = image; + + SetInternalImage(); + } + + /// <summary> + /// Sets the internal image. If value is <c>null</c>, image will be removed. + /// </summary> + void SetInternalImage() + { + if (_image == null) + { + SetPartContent("icon", null); + } + else + { + SetPartContent("icon", _image); + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Canvas.cs b/Xamarin.Forms.Platform.Tizen/Native/Canvas.cs new file mode 100644 index 00000000..c345abf2 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Canvas.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// A Canvas provides a class which can be a container for other controls. + /// </summary> + /// <remarks> + /// This class is used as a container view for Layouts from Xamarin.Forms.Platform.Tizen framework. + /// It is used for implementing xamarin pages and layouts. + /// </remarks> + public class Canvas : Box, IContainable<EvasObject> + { + /// <summary> + /// The list of Views. + /// </summary> + readonly ObservableCollection<EvasObject> _children = new ObservableCollection<EvasObject>(); + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Canvas"/> class. + /// </summary> + /// <remarks>Canvas doesn't support replacing its children, this will be ignored.</remarks> + /// <param name="parent">Parent of this instance.</param> + public Canvas(EvasObject parent) : base(parent) + { + _children.CollectionChanged += (o, e) => + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (var v in e.NewItems) + { + var view = v as EvasObject; + if (null != view) + { + OnAdd(view); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (var v in e.OldItems) + { + var view = v as EvasObject; + if (null != view) + { + OnRemove(view); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + OnRemoveAll(); + } + }; + } + + /// <summary> + /// Gets list of native elements that are placed in the canvas. + /// </summary> + public IList<EvasObject> Children + { + get + { + return _children; + } + } + + /// <summary> + /// Provides destruction for native element and contained elements. + /// </summary> + protected override void OnUnrealize() + { + foreach (var child in _children) + { + child.Unrealize(); + } + + base.OnUnrealize(); + } + + /// <summary> + /// Adds a new child to a container. + /// </summary> + /// <param name="view">Native element which will be added</param> + void OnAdd(EvasObject view) + { + PackEnd(view); + } + + /// <summary> + /// Removes a child from a container. + /// </summary> + /// <param name="view">Child element to be removed from canvas</param> + void OnRemove(EvasObject view) + { + UnPack(view); + } + + /// <summary> + /// Removes all children from a canvas. + /// </summary> + void OnRemoveAll() + { + UnPackAll(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs b/Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs new file mode 100644 index 00000000..32f24a0d --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ContentPage.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// A basic page which can hold a single view. + /// </summary> + public class ContentPage : Background, IContainable<EvasObject> + { + /// <summary> + /// The name of the part to be used when setting content. + /// </summary> + public const string ContentPartName = "overlay"; + + /// <summary> + /// Exposes the Children property, mapping it to the _canvas' Children property. + /// </summary> + public IList<EvasObject> Children => _canvas.Children; + + /// <summary> + /// The canvas, used as a container for other objects. + /// </summary> + /// <remarks> + /// The canvas holds all the Views that the ContentPage is composed of. + /// </remarks> + internal Canvas _canvas; + + /// <summary> + /// Initializes a new instance of the ContentPage class. + /// </summary> + public ContentPage(EvasObject parent) : base(parent) + { + _canvas = new Canvas(this); + SetPartContent(ContentPartName, _canvas); + } + + /// <summary> + /// Gets or sets the title. + /// </summary> + /// <value>The current title.p</value> + public string Title + { + get; + set; + } + + /// <summary> + /// Allows custom handling of events emitted when the layout has been updated. + /// </summary> + public event EventHandler<LayoutEventArgs> LayoutUpdated + { + add + { + _canvas.LayoutUpdated += value; + } + remove + { + _canvas.LayoutUpdated -= value; + } + } + + /// <summary> + /// Handles the disposing of a ContentPage + /// </summary> + /// <remarks> + /// Takes the proper care of discarding the canvas, then calls the base method. + /// </remarks> + protected override void OnUnrealize() + { + _canvas.Unrealize(); + base.OnUnrealize(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs b/Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs new file mode 100644 index 00000000..c238e9e9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DateChangedEventArgs.cs @@ -0,0 +1,31 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Event arguments for <see cref="DatePicker.DateChanged"/> event. + /// </summary> + public class DateChangedEventArgs : EventArgs + { + /// <summary> + /// The date that was on the element at the time that the user selected it. + /// </summary> + public DateTime OldDate { get; private set; } + + /// <summary> + /// The date that the user entered. + /// </summary> + public DateTime NewDate { get; private set; } + + /// <summary> + /// Creates a new <see cref="DateChangedEventArgs"/> object that represents a change from <paramref name="oldDate"/> to <paramref name="newDate"/>. + /// </summary> + /// <param name="oldDate">Old date of <see cref="DatePicker"/>.</param> + /// <param name="newDate">Current date of <see cref="DatePicker"/>.</param> + public DateChangedEventArgs(DateTime oldDate, DateTime newDate) + { + this.OldDate = oldDate; + this.NewDate = newDate; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs b/Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs new file mode 100644 index 00000000..fabd5269 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DatePicker.cs @@ -0,0 +1,130 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.DateTimeSelector class with functionality useful to renderer. + /// </summary> + public class DatePicker : DateTimeSelector + { + const string DateLayoutStyle = "date_layout"; + const string DefaultEFLFormat = "%d/%b/%Y"; + static readonly DateTime s_defaultMaximumDate = new DateTime(2037, 12, 31); + static readonly DateTime s_defaultMinimumDate = new DateTime(1970, 1, 1); + DateTime _date; + DateTime _maxDate; + DateTime _minDate; + + /// <summary> + /// Initializes a new instance of the <see cref="DatePicker"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public DatePicker(EvasObject parent) : base(parent) + { + SetFieldVisible(DateTimeFieldType.Hour, false); + Style = DateLayoutStyle; + ApplyDate(Date); + ApplyMinDate(s_defaultMinimumDate); + ApplyMaxDate(s_defaultMaximumDate); + //TODO use date format currently set on the platform + Format = DefaultEFLFormat; + + DateTimeChanged += (sender, e) => + { + Date = e.NewDate; + }; + } + + /// <summary> + /// Gets or sets the displayed date. + /// </summary> + public DateTime Date + { + get + { + return _date; + } + set + { + if (_date != value) + { + ApplyDate(value); + } + } + } + + /// <summary> + /// Gets of sets the highest date selectable for this <see cref="DatePicker"/>. + /// </summary> + /// <remarks> + /// Default value is 31st Dec, 2037. + /// </remarks> + public DateTime MaximumDate + { + get + { + return _maxDate; + } + set + { + if (_maxDate != value) + { + ApplyMaxDate(value); + } + } + } + + /// <summary> + /// Gets of sets the lowest date selectable for this <see cref="DatePicker"/>. + /// </summary> + /// <remarks> + /// Default value is 1st Jan, 1970. + /// </remarks> + public DateTime MinimumDate + { + get + { + return _minDate; + } + set + { + if (_minDate != value) + { + ApplyMinDate(value); + } + } + } + + /// <summary> + /// Sets the <c>DateTime</c> property according to the given <paramref name="date"/>. + /// </summary> + /// <param name="date">The date value to be applied to the date picker.</param> + void ApplyDate(DateTime date) + { + _date = date; + DateTime = date; + } + + /// <summary> + /// Sets the <c>MaximumDateTime</c> property according to the given <paramref name="maxDate"/>. + /// </summary> + /// <param name="maxDate">The maximum date value to be applied to the date picker.</param> + void ApplyMaxDate(DateTime maxDate) + { + _maxDate = maxDate; + MaximumDateTime = maxDate; + } + + /// <summary> + /// Sets the <c>MinimumDateTime</c> property according to the given <paramref name="minDate"/>. + /// </summary> + /// <param name="minDate">The minimum date value to be applied to the date picker.</param> + void ApplyMinDate(DateTime minDate) + { + _minDate = minDate; + MinimumDateTime = minDate; + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs b/Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs new file mode 100644 index 00000000..64b1b3b3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DateTimePickerDialog.cs @@ -0,0 +1,100 @@ +using System; +using ElmSharp; +using EButton = ElmSharp.Button; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + public class DateTimePickerDialog : Dialog + { + DateTimeSelector _dateTimePicker; + EvasObject _parent; + + /// <summary> + /// Creates a dialog window. + /// </summary> + public DateTimePickerDialog(EvasObject parent) : base(parent) + { + _parent = parent; + Initialize(); + } + + /// <summary> + /// Occurs when the date of this dialog has changed. + /// </summary> + public event EventHandler<DateChangedEventArgs> DateTimeChanged; + + /// <summary> + /// Gets the <see cref="DateTimePicker"/> contained in this dialog. + /// </summary> + public DateTimeSelector DateTimePicker + { + get + { + return _dateTimePicker; + } + private set + { + if (_dateTimePicker != value) + { + ApplyDateTimePicker(value); + } + } + } + + /// <summary> + /// Creates date picker in dialog window. + /// </summary> + public void InitializeDatePicker(DateTime date, DateTime minimumDate, DateTime maximumDate) + { + var datePicker = new DatePicker(this) + { + Date = date, + MinimumDate = minimumDate, + MaximumDate = maximumDate + }; + Content = DateTimePicker = datePicker; + } + + /// <summary> + /// Creates time picker in dialog window. + /// </summary> + public void InitializeTimePicker(TimeSpan time, string format) + { + var timePicker = new TimePicker(this) + { + Time = time, + DateTimeFormat = format + }; + Content = DateTimePicker = timePicker; + } + + void ApplyDateTimePicker(DateTimeSelector dateTimePicker) + { + _dateTimePicker = dateTimePicker; + Content = _dateTimePicker; + } + + void Initialize() + { + //TODO need to add internationalization support + PositiveButton = new EButton(_parent) { Text = "Set" }; + PositiveButton.Clicked += (s, e) => + { + DateTime oldDate = DateTimePicker.DateTime; + DateTimeChanged?.Invoke(this, new DateChangedEventArgs(oldDate, DateTimePicker.DateTime)); + Hide(); + }; + + //TODO need to add internationalization support + NegativeButton = new EButton(_parent) { Text = "Cancel" }; + NegativeButton.Clicked += (s, e) => + { + Hide(); + }; + BackButtonPressed += (object s, EventArgs e) => + { + Hide(); + }; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Dialog.cs b/Xamarin.Forms.Platform.Tizen/Native/Dialog.cs new file mode 100755 index 00000000..e616ff5a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Dialog.cs @@ -0,0 +1,276 @@ +using System; +using ElmSharp; +using EButton = ElmSharp.Button; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Base class for Dialogs. + /// A dialog is a small window that prompts the user to make a decision or enter additional information. + /// </summary> + public class Dialog : Popup + { + EButton _positiveButton; + EButton _neutralButton; + EButton _negativeButton; + EvasObject _content; + string _title; + + /// <summary> + /// Creates a dialog window that uses the default dialog theme. + /// </summary> + public Dialog(EvasObject parent) : base(parent) + { + Initialize(); + } + + /// <summary> + /// Occurs when the hardware Back button is pressed. + /// </summary> + public event EventHandler BackButtonPressed; + + /// <summary> + /// Occurs whenever the dialog is first displayed. + /// </summary> + public event EventHandler Shown; + + /// <summary> + /// Enumerates the three valid positions of a dialog button. + /// </summary> + enum ButtonPosition + { + Positive, + Neutral, + Negative + } + + /// <summary> + /// Gets or sets the title of the dialog + /// </summary> + public string Title + { + get + { + return _title; + } + set + { + if (_title != value) + { + ApplyTitle(value); + } + } + } + + /// <summary> + /// Gets or sets the content to display in that dialog. + /// </summary> + public EvasObject Content + { + get + { + return _content; + } + set + { + if (_content != value) + { + ApplyContent(value); + } + } + } + + /// <summary> + /// Gets or sets the positive button used in the dialog + /// </summary> + public EButton PositiveButton + { + get + { + return _positiveButton; + } + set + { + if (_positiveButton != value) + { + ApplyButton(ButtonPosition.Positive, value); + } + } + } + + /// <summary> + /// Gets or sets the neutral button used in the dialog + /// </summary> + public EButton NeutralButton + { + get + { + return _neutralButton; + } + set + { + if (_neutralButton != value) + { + ApplyButton(ButtonPosition.Neutral, value); + } + } + } + + /// <summary> + /// Gets or sets the negative button used in the dialog + /// </summary> + public EButton NegativeButton + { + get + { + return _negativeButton; + } + set + { + if (_negativeButton != value) + { + ApplyButton(ButtonPosition.Negative, value); + } + } + } + + /// <summary> + /// Starts the dialog and displays it on screen. + /// </summary> + public new void Show() + { + base.Show(); + Shown?.Invoke(this, EventArgs.Empty); + } + + /// <summary> + /// Handles the disposing of a dialog widget. + /// </summary> + protected override void OnUnrealize() + { + _content?.Unrealize(); + + ApplyButton(ButtonPosition.Positive, null); + ApplyButton(ButtonPosition.Neutral, null); + ApplyButton(ButtonPosition.Negative, null); + ApplyContent(null); + + UngrabBackKey(); + + base.OnUnrealize(); + } + + /// <summary> + /// Called when the dialog is shown. + /// </summary> + /// <remarks>When shown, the dialog will register itself for the back key press event handling.</remarks> + protected virtual void OnShown() + { + GrabBackKey(); + } + + /// <summary> + /// Called when the dialog is dismissed. + /// </summary> + /// <remarks>When dismissed, the dialog will unregister itself from the back key press event handling.</remarks> + protected virtual void OnDismissed() + { + UngrabBackKey(); + } + + /// <summary> + /// Handles the initialization process. + /// </summary> + /// <remarks>Creates handlers for vital events</remarks> + void Initialize() + { + // Adds a handler for the Dismissed event. + // In effect, unregisters this instance from being affected by the hardware back key presses. + Dismissed += (s, e) => + { + OnDismissed(); + }; + + // Adds a handler for the Shown event. + // In effect, registers this instance to be affected by the hardware back key presses. + Shown += (s, e) => + { + OnShown(); + }; + + // Adds a handler for the KeyUp event. + // The handler checks whether the key just pressed is a back key + // and if that is the case, invokes the back button press handler of this instance. + KeyUp += (s, e) => + { + if (e.KeyName == EvasKeyEventArgs.PlatformBackButtonName) + BackButtonPressed?.Invoke(this, EventArgs.Empty); + }; + } + + /// <summary> + /// Changes the dialog title. + /// </summary> + /// <param name="title">New dialog title.</param> + void ApplyTitle(string title) + { + _title = title; + + SetPartText("title,text", _title); + } + + /// <summary> + /// Puts the button in one of the three available slots. + /// </summary> + /// <param name="position">The slot to be occupied by the button expressed as a <see cref="ButtonPosition"/></param> + /// <param name="button">The new button.</param> + void ApplyButton(ButtonPosition position, EButton button) + { + switch (position) + { + case ButtonPosition.Positive: + _positiveButton = button; + SetPartContent("button3", _positiveButton, true); + break; + + case ButtonPosition.Neutral: + _neutralButton = button; + SetPartContent("button2", _neutralButton, true); + break; + + case ButtonPosition.Negative: + _negativeButton = button; + SetPartContent("button1", _negativeButton, true); + break; + } + } + + /// <summary> + /// Updates the content of the dialog. + /// </summary> + /// <param name="content">New dialog content.</param> + void ApplyContent(EvasObject content) + { + _content = content; + + SetPartContent("default", _content, true); + } + + /// <summary> + /// Registers this instance to be affected by pressing the hardware back key. + /// </summary> + void GrabBackKey() + { + KeyGrab(EvasKeyEventArgs.PlatformBackButtonName, true); + } + + /// <summary> + /// Unregisters this instance from being affected by pressing the hardware back key. + /// </summary> + void UngrabBackKey() + { + KeyUngrab(EvasKeyEventArgs.PlatformBackButtonName); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs b/Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs new file mode 100644 index 00000000..efb09529 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/DisplayOrientations.cs @@ -0,0 +1,36 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Enumeration for the orientation of a rectangular screen. + /// </summary> + [Flags] + public enum DisplayOrientations + { + /// <summary> + /// No display orientation is specified. + /// </summary> + None = 0, + + /// <summary> + /// The display is oriented in a natural position. + /// </summary> + Portrait = 1, + + /// <summary> + /// The display's left side is at the top. + /// </summary> + Landscape = 2, + + /// <summary> + /// The display is upside down. + /// </summary> + PortraitFlipped = 4, + + /// <summary> + /// The display's right side is at the top. + /// </summary> + LandscapeFlipped = 8 + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Entry.cs b/Xamarin.Forms.Platform.Tizen/Native/Entry.cs new file mode 100644 index 00000000..808155c5 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Entry.cs @@ -0,0 +1,396 @@ +using System; +using ElmSharp; +using EEntry = ElmSharp.Entry; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the Entry control, providing basic formatting features, + /// i.e. font color, size, placeholder. + /// </summary> + public class Entry : EEntry, IMeasurable + { + /// <summary> + /// Holds the formatted text of the entry. + /// </summary> + readonly Span _span = new Span(); + + /// <summary> + /// Holds the formatted text of the placeholder. + /// </summary> + readonly Span _placeholderSpan = new Span(); + + /// <summary> + /// Helps to detect whether the text change was initiated by the user + /// or via the Text property. + /// </summary> + int _changedByUserCallbackDepth; + + /// <summary> + /// The type of the keyboard used by the entry. + /// </summary> + Keyboard _keyboard; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Entry"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public Entry(EvasObject parent) : base(parent) + { + Scrollable = true; + + ChangedByUser += (s, e) => + { + _changedByUserCallbackDepth++; + + Text = GetInternalText(); + + _changedByUserCallbackDepth--; + }; + + ApplyKeyboard(Keyboard.Normal); + } + + /// <summary> + /// Occurs when the text has changed. + /// </summary> + public event EventHandler<TextChangedEventArgs> TextChanged; + + /// <summary> + /// Gets or sets the text. + /// </summary> + /// <value>The text.</value> + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + var old = _span.Text; + _span.Text = value; + ApplyTextAndStyle(); + Device.StartTimer(TimeSpan.FromTicks(1), () => + { + TextChanged?.Invoke(this, new TextChangedEventArgs(old, value)); + return false; + }); + } + } + } + + /// <summary> + /// Gets or sets the color of the text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font family of the text and the placeholder. + /// </summary> + /// <value>The font family of the text and the placeholder.</value> + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontFamily = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font attributes of the text and the placeholder. + /// </summary> + /// <value>The font attributes of the text and the placeholder.</value> + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontAttributes = value; + ApplyPlaceholderAndStyle(); + } + } + } + + + /// <summary> + /// Gets or sets the size of the font of both text and placeholder. + /// </summary> + /// <value>The size of the font of both text and placeholder.</value> + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + + _placeholderSpan.FontSize = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the horizontal text alignment of both text and placeholder. + /// </summary> + /// <value>The horizontal text alignment of both text and placeholder.</value> + public TextAlignment HorizontalTextAlignment + { + get + { + return _span.HorizontalTextAlignment; + } + + set + { + if (value != _span.HorizontalTextAlignment) + { + _span.HorizontalTextAlignment = value; + ApplyTextAndStyle(); + + _placeholderSpan.HorizontalTextAlignment = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the keyboard type used by the entry. + /// </summary> + /// <value>The keyboard type.</value> + public Keyboard Keyboard + { + get + { + return _keyboard; + } + + set + { + if (value != _keyboard) + { + ApplyKeyboard(value); + } + } + } + + /// <summary> + /// Gets or sets the placeholder's text. + /// </summary> + /// <value>The placeholder's text.</value> + public string Placeholder + { + get + { + return _placeholderSpan.Text; + } + + set + { + if (value != _placeholderSpan.Text) + { + _placeholderSpan.Text = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the placeholder's text. + /// </summary> + /// <value>The color of the placeholder's text.</value> + public EColor PlaceholderColor + { + get + { + return _placeholderSpan.ForegroundColor; + } + + set + { + if (!_placeholderSpan.ForegroundColor.Equals(value)) + { + _placeholderSpan.ForegroundColor = value; + ApplyPlaceholderAndStyle(); + } + } + } + + /// <summary> + /// Implementation of the IMeasurable.Measure() method. + /// </summary> + public ESize Measure(int availableWidth, int availableHeight) + { + var originalSize = Geometry; + // resize the control using the whole available width + Resize(availableWidth, originalSize.Height); + + ESize rawSize; + ESize formattedSize; + var edjeTextBlock = EdjeObject["elm.guide"]; + + // if there's no text, but there's a placeholder, use it for measurements + if (string.IsNullOrEmpty(Text) && !string.IsNullOrEmpty(Placeholder) && edjeTextBlock != null) + { + rawSize = edjeTextBlock.TextBlockNativeSize; + formattedSize = edjeTextBlock.TextBlockFormattedSize; + } + else + { + // there's text in the entry, use it instead + rawSize = Native.TextHelper.GetRawTextBlockSize(this); + formattedSize = Native.TextHelper.GetFormattedTextBlockSize(this); + } + + // restore the original size + Resize(originalSize.Width, originalSize.Height); + + // Set bottom padding for lower case letters that have segments below the bottom line of text (g, j, p, q, y). + var verticalPadding = (int)Math.Ceiling(0.05 * FontSize); + var horizontalPadding = (int)Math.Ceiling(0.2 * FontSize); + rawSize.Height += verticalPadding; + formattedSize.Height += verticalPadding; + formattedSize.Width += horizontalPadding; + + // if the raw text width is larger than available width, we use the available width, + // while height is set to the smallest height value + if (rawSize.Width > availableWidth) + { + return new ESize + { + Width = availableWidth, + Height = Math.Min(formattedSize.Height, Math.Max(rawSize.Height, availableHeight)), + }; + } + else + { + // width is fine, return the formatted text size + return formattedSize; + } + } + + /// <summary> + /// Applies entry's text and its style. + /// </summary> + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + /// <summary> + /// Sets entry's internal text and its style. + /// </summary> + /// <param name="formattedText">Formatted text, supports HTML tags.</param> + /// <param name="textStyle">Style applied to the formattedText.</param> + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + if (_changedByUserCallbackDepth == 0) + { + base.Text = formattedText; + base.TextStyle = textStyle; + } + } + + /// <summary> + /// Gets the internal text representation of the entry. + /// </summary> + /// <returns>The internal text representation.</returns> + string GetInternalText() + { + return Entry.ConvertMarkupToUtf8(base.Text); + } + + /// <summary> + /// Applies the keyboard type to be used by the entry. + /// </summary> + /// <param name="keyboard">Keyboard type to be used.</param> + void ApplyKeyboard(Keyboard keyboard) + { + SetInternalKeyboard(_keyboard = keyboard); + } + + /// <summary> + /// Configures the ElmSharp.Entry with specified keyboard type and displays + /// the keyboard automatically unless the provided type is Keyboard.None. + /// </summary> + /// <param name="keyboard">Keyboard type to be used.</param> + void SetInternalKeyboard(Keyboard keyboard) + { + if (keyboard == Keyboard.None) + { + SetInputPanelEnabled(false); + } + else + { + SetInputPanelEnabled(true); + SetInputPanelLayout((InputPanelLayout)keyboard); + } + } + + /// <summary> + /// Applies placeholders's text and its style. + /// </summary> + void ApplyPlaceholderAndStyle() + { + SetInternalPlaceholderAndStyle(_placeholderSpan.GetMarkupText()); + } + + /// <summary> + /// Sets placeholder's internal text and style. + /// </summary> + /// <param name="markupText">Markup text to be used as a placeholder.</param> + void SetInternalPlaceholderAndStyle(string markupText) + { + SetPartText("guide", markupText ?? ""); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs b/Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs new file mode 100644 index 00000000..f782efd1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/FormattedString.cs @@ -0,0 +1,128 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Represents a text with attributes applied to some parts. + /// </summary> + /// <remarks> + /// Formatted string consists of spans that represent text segments with various attributes applied. + /// </remarks> + public class FormattedString + { + /// <summary> + /// A flag indicating whether the instance contains just a plain string without any formatting. + /// </summary> + /// <remarks> + /// <c>true</c> if the instance contains an unformatted string. + /// </remarks> + readonly bool _just_string; + + /// <summary> + /// Holds the unformatted string. + /// </summary> + /// <remarks> + /// The contents of this field are accurate if and only if the _just_string flag is set. + /// </remarks> + readonly string _string; + + /// <summary> + /// Holds the collection of span elements. + /// </summary> + /// <remarks> + /// Span elements are basically chunks of text with uniform formatting. + /// </remarks> + readonly ObservableCollection<Span> _spans; + + /// <summary> + /// Returns the collection of span elements. + /// </summary> + public IList<Span> Spans { get { return _spans; } } + + /// <summary> + /// Creates a new FormattedString instance with an empty string. + /// </summary> + public FormattedString() + { + _just_string = false; + _spans = new ObservableCollection<Span>(); + } + + /// <summary> + /// Creates a new FormattedString instance based on given <c>str</c>. + /// </summary> + /// <param name="str"> + /// A string used to make a new FormattedString instance. + /// </param> + public FormattedString(string str) + { + _just_string = true; + _string = str; + } + + /// <summary> + /// Returns the plain text of the FormattedString as an unformatted string. + /// </summary> + /// <returns> + /// The text content of the FormattedString without any format applied. + /// </returns> + public override string ToString() + { + if (_just_string) + { + return _string; + } + else + { + return string.Concat(from span in this.Spans select span.Text); + } + } + + /// <summary> + /// Returns the markup text representation of the FormattedString instance. + /// </summary> + /// <returns>The string containing a markup text.</returns> + internal string ToMarkupString() + { + if (_just_string) + { + return _string; + } + else + { + return string.Concat(from span in this.Spans select span.GetMarkupText()); + } + } + + /// <summary> + /// Casts the FormattedString to a string. + /// </summary> + /// <param name="formatted">The FormattedString instance which will be used for the conversion.</param> + public static explicit operator string (FormattedString formatted) + { + return formatted.ToString(); + } + + /// <summary> + /// Casts the string to a FormattedString. + /// </summary> + /// <param name="text">The text which will be put in a new FormattedString instance.</param> + public static implicit operator FormattedString(string text) + { + return new FormattedString(text); + } + + /// <summary> + /// Casts the Span to a FormattedString. + /// </summary> + /// <param name="span">The span which will be used for the conversion.</param> + public static implicit operator FormattedString(Span span) + { + return new FormattedString() + { + Spans = { span } + }; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/IContainable.cs b/Xamarin.Forms.Platform.Tizen/Native/IContainable.cs new file mode 100644 index 00000000..dfee0cbe --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/IContainable.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Interface defining methods for managing elements of the container. + /// </summary> + /// <typeparam name="T">The type of element that can be added to the container.</typeparam> + public interface IContainable<T> + { + /// <summary> + /// The children collection of an element. + /// </summary> + IList<T> Children { get; } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs b/Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs new file mode 100644 index 00000000..13ee1c12 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/IMeasurable.cs @@ -0,0 +1,20 @@ +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Interface of the controls which can measure their size taking into + /// account the available area. + /// </summary> + public interface IMeasurable + { + /// <summary> + /// Measures the size of the control in order to fit it into the + /// available area. + /// </summary> + /// <param name="availableWidth">Available width.</param> + /// <param name="availableHeight">Available height.</param> + /// <returns>Size of the control that fits the available area.</returns> + ESize Measure(int availableWidth, int availableHeight); + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ITextable.cs b/Xamarin.Forms.Platform.Tizen/Native/ITextable.cs new file mode 100644 index 00000000..876646eb --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ITextable.cs @@ -0,0 +1,68 @@ +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Interface defining properties of formattable text. + /// </summary> + public interface ITextable + { + /// <summary> + /// Get or sets the formatted text. + /// </summary> + FormattedString FormattedText { get; set; } + + /// <summary> + /// Gets or sets the text. + /// </summary> + string Text { get; set; } + + /// <summary> + /// Gets or sets the color for the text. + /// </summary> + EColor TextColor { get; set; } + + /// <summary> + /// Gets or sets the background color for the text. + /// </summary> + EColor TextBackgroundColor { get; set; } + + /// <summary> + /// Gets or sets the font family for the text. + /// </summary> + string FontFamily { get; set; } + + /// <summary> + /// Gets or sets the font attributes for the text. + /// See <see cref="FontAttributes"/> for information about FontAttributes. + /// </summary> + FontAttributes FontAttributes { get; set; } + + /// <summary> + /// Gets or sets the font size for the text. + /// </summary> + double FontSize { get; set; } + + /// <summary> + /// Gets or sets the horizontal alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + TextAlignment HorizontalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the vertical alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + TextAlignment VerticalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has underline. + /// </summary> + bool Underline { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has strike line though it. + /// </summary> + bool Strikethrough { get; set; } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Image.cs b/Xamarin.Forms.Platform.Tizen/Native/Image.cs new file mode 100644 index 00000000..7321c5b1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Image.cs @@ -0,0 +1,124 @@ +using System.Threading.Tasks; +using ElmSharp; +using EImage = ElmSharp.Image; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.Image class with functionality useful to renderer. + /// </summary> + public class Image : EImage, IMeasurable + { + Aspect _aspect; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Image"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public Image(EvasObject parent) : base(parent) + { + IsScaling = true; + CanScaleUp = true; + CanScaleDown = true; + + ApplyAspect(Aspect.AspectFit); + } + + /// <summary> + /// Gets or sets the image aspect ratio preserving option. + /// </summary> + /// <value>The aspect option.</value> + public Aspect Aspect + { + get + { + return _aspect; + } + + set + { + if (_aspect != value) + { + ApplyAspect(value); + } + } + } + + /// <summary> + /// Loads image data from the given <see cref="Xamarin.Forms.ImageSource"/> asynchronously. + /// </summary> + /// <returns>A task which will be completed when image data is loaded.</returns> + /// <param name="source">Image source specifying from where the image data has to be loaded.</param> + public Task<bool> LoadFromImageSourceAsync(ImageSource source) + { + IImageSourceHandler handler; + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + return handler.LoadImageAsync(this, source); + } + return Task.FromResult<bool>(false); + } + + /// <summary> + /// Implements the <see cref="Xamarin.Forms.Platform.Tizen.Native.IMeasurable"/> interface. + /// </summary> + /// <param name="availableWidth">Available width.</param> + /// <param name="availableHeight">Available height.</param> + public ESize Measure(int availableWidth, int availableHeight) + { + var imageSize = ObjectSize; + + var size = new ESize() + { + Width = imageSize.Width, + Height = imageSize.Height, + }; + + if (0 != availableWidth && 0 != availableHeight + && (imageSize.Width > availableWidth || imageSize.Height > availableHeight)) + { + // when available size is limited and insufficient for the image ... + double imageRatio = (double)imageSize.Width / (double)imageSize.Height; + double availableRatio = (double)availableWidth / (double)availableHeight; + // depending on the relation between availableRatio and imageRatio, copy the availableWidth or availableHeight + // and calculate the size which preserves the image ratio, but does not exceed the available size + size.Width = availableRatio > imageRatio ? imageSize.Width * availableHeight / imageSize.Height : availableWidth; + size.Height = availableRatio > imageRatio ? availableHeight : imageSize.Height * availableWidth / imageSize.Width; + } + + return size; + } + + /// <summary> + /// Sets the <c>IsFixedAspect</c> and <c>CanFillOutside</c> properties according to the given <paramref name="aspect"/>. + /// </summary> + /// <param name="aspect">The aspect setting to be applied to the image.</param> + void ApplyAspect(Aspect aspect) + { + _aspect = aspect; + + switch (_aspect) + { + case Aspect.AspectFit: + IsFixedAspect = true; + CanFillOutside = false; + break; + + case Aspect.AspectFill: + IsFixedAspect = true; + CanFillOutside = true; + break; + + case Aspect.Fill: + IsFixedAspect = false; + CanFillOutside = false; + break; + + default: + Log.Warn("Invalid Aspect value: {0}", _aspect); + break; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs b/Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs new file mode 100644 index 00000000..4223ca08 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Keyboard.cs @@ -0,0 +1,84 @@ +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Keyboard layout type on entry control. + /// </summary> + public enum Keyboard + { + /// <summary> + /// Disable Keyboard + /// </summary> + None = -1, + + /// <summary> + /// Keyboard layout type default. + /// </summary> + Normal, + + /// <summary> + /// Keyboard layout type number. + /// </summary> + Number, + + /// <summary> + /// Keyboard layout type email. + /// </summary> + Email, + + /// <summary> + /// Keyboard layout type url. + /// </summary> + Url, + + /// <summary> + /// Keyboard layout type phone. + /// </summary> + PhoneNumber, + + /// <summary> + /// Keyboard layout type ip. + /// </summary> + Ip, + + /// <summary> + /// Keyboard layout type month. + /// </summary> + Month, + + /// <summary> + /// Keyboard layout type number. + /// </summary> + NumberOnly, + + /// <summary> + /// Keyboard layout type error type. Do not use it directly! + /// </summary> + Invalid, + + /// <summary> + /// Keyboard layout type hexadecimal. + /// </summary> + Hex, + + /// <summary> + /// Keyboard layout type terminal type, esc, alt, ctrl, etc. + /// </summary> + Terminal, + + /// <summary> + /// Keyboard layout type password. + /// </summary> + Password, + + /// <summary> + /// Keyboard layout type date and time. + /// </summary> + DateTime, + + /// <summary> + /// Keyboard layout type emoticons. + /// </summary> + Emoticon + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Native/Label.cs b/Xamarin.Forms.Platform.Tizen/Native/Label.cs new file mode 100644 index 00000000..cba2ba20 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Label.cs @@ -0,0 +1,347 @@ +using System; +using ElmSharp; +using ELabel = ElmSharp.Label; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// The Label class extends <c>ElmSharp.Label</c> to be better suited for Xamarin renderers. + /// Mainly the formatted text support. + /// </summary> + public class Label : ELabel, ITextable, IMeasurable + { + /// <summary> + /// The _span holds the content of the label. + /// </summary> + readonly Span _span = new Span(); + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.Label"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public Label(EvasObject parent) : base(parent) + { + } + + /// <summary> + /// Get or sets the formatted text. + /// </summary> + /// <remarks>Setting <c>FormattedText</c> changes the value of the <c>Text</c> property.</remarks> + /// <value>The formatted text.</value> + public FormattedString FormattedText + { + get + { + return _span.FormattedText; + } + + set + { + if (value != _span.FormattedText) + { + _span.FormattedText = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the text. + /// </summary> + /// <remarks>Setting <c>Text</c> overwrites the value of the <c>FormattedText</c> property too.</remarks> + /// <value>The content of the label.</value> + public override string Text + { + get + { + return _span.Text; + } + + set + { + if (value != _span.Text) + { + _span.Text = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the color of the formatted text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _span.ForegroundColor; + } + + set + { + if (!_span.ForegroundColor.Equals(value)) + { + _span.ForegroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the background color for the text. + /// </summary> + /// <value>The color of the label's background.</value> + public EColor TextBackgroundColor + { + get + { + return _span.BackgroundColor; + } + + set + { + if (!_span.BackgroundColor.Equals(value)) + { + _span.BackgroundColor = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font family for the text. + /// </summary> + /// <value>The font family.</value> + public string FontFamily + { + get + { + return _span.FontFamily; + } + + set + { + if (value != _span.FontFamily) + { + _span.FontFamily = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font attributes. + /// </summary> + /// <value>The font attributes.</value> + public FontAttributes FontAttributes + { + get + { + return _span.FontAttributes; + } + + set + { + if (value != _span.FontAttributes) + { + _span.FontAttributes = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the font size for the text. + /// </summary> + /// <value>The size of the font.</value> + public double FontSize + { + get + { + return _span.FontSize; + } + + set + { + if (value != _span.FontSize) + { + _span.FontSize = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the line wrap option. + /// </summary> + /// <value>The line break mode.</value> + public LineBreakMode LineBreakMode + { + get + { + return _span.LineBreakMode; + } + + set + { + if (value != _span.LineBreakMode) + { + _span.LineBreakMode = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the horizontal text alignment. + /// </summary> + /// <value>The horizontal text alignment.</value> + public TextAlignment HorizontalTextAlignment + { + get + { + return _span.HorizontalTextAlignment; + } + + set + { + if (value != _span.HorizontalTextAlignment) + { + _span.HorizontalTextAlignment = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the vertical text alignment. + /// </summary> + /// <value>The vertical text alignment.</value> + public TextAlignment VerticalTextAlignment + { + get + { + return _span.VerticalTextAlignment; + } + + set + { + if (value != _span.VerticalTextAlignment) + { + _span.VerticalTextAlignment = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the value that indicates whether the text is underlined. + /// </summary> + /// <value><c>true</c> if the text is underlined.</value> + public bool Underline + { + get + { + return _span.Underline; + } + + set + { + if (value != _span.Underline) + { + _span.Underline = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Gets or sets the value that indicates whether the text is striked out. + /// </summary> + /// <value><c>true</c> if the text is striked out.</value> + public bool Strikethrough + { + get + { + return _span.Strikethrough; + } + + set + { + if (value != _span.Strikethrough) + { + _span.Strikethrough = value; + ApplyTextAndStyle(); + } + } + } + + /// <summary> + /// Implements <see cref="Xamarin.Forms.Platform.Tizen.Native.IMeasurable"/> to provide a desired size of the label. + /// </summary> + /// <param name="availableWidth">Available width.</param> + /// <param name="availableHeight">Available height.</param> + /// <returns>Size of the control that fits the available area.</returns> + public ESize Measure(int availableWidth, int availableHeight) + { + var size = Geometry; + + Resize(availableWidth, size.Height); + + var rawSize = Native.TextHelper.GetRawTextBlockSize(this); + var formattedSize = Native.TextHelper.GetFormattedTextBlockSize(this); + Resize(size.Width, size.Height); + + // Set bottom padding for lower case letters that have segments below the bottom line of text (g, j, p, q, y). + var verticalPadding = (int)Math.Ceiling(0.05 * FontSize); + var horizontalPadding = (int)Math.Ceiling(0.2 * FontSize); + rawSize.Height += verticalPadding; + formattedSize.Height += verticalPadding; + formattedSize.Width += horizontalPadding; + + if (rawSize.Width > availableWidth) + { + return new ESize() + { + Width = formattedSize.Width, + Height = Math.Min(formattedSize.Height, Math.Max(rawSize.Height, availableHeight)), + }; + } + else + { + return formattedSize; + } + } + + void ApplyTextAndStyle() + { + SetInternalTextAndStyle(_span.GetDecoratedText(), _span.GetStyle()); + } + + void SetInternalTextAndStyle(string formattedText, string textStyle) + { + string emission = "elm,state,text,visible"; + + if (string.IsNullOrEmpty(formattedText)) + { + formattedText = null; + textStyle = null; + emission = "elm,state,text,hidden"; + } + + base.Text = formattedText; + + var textblock = EdjeObject["elm.text"]; + + if (textblock != null) + { + textblock.TextStyle = textStyle; + } + + EdjeObject.EmitSignal(emission, "elm"); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs b/Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs new file mode 100644 index 00000000..6668f8d7 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/LayoutEventArgs.cs @@ -0,0 +1,55 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Holds information about size of the area which can be used for layout. + /// </summary> + public class LayoutEventArgs : EventArgs + { + /// <summary> + /// Whether or not the dimensions have changed. + /// </summary> + public bool HasChanged + { + get; + internal set; + } + + /// <summary> + /// X coordinate of the layout area, relative to the main window. + /// </summary> + public int X + { + get; + internal set; + } + + /// <summary> + /// Y coordinate of the layout area, relative to the main window. + /// </summary> + public int Y + { + get; + internal set; + } + + /// <summary> + /// Width of the layout area. + /// </summary> + public int Width + { + get; + internal set; + } + + /// <summary> + /// Height of the layout area. + /// </summary> + public int Height + { + get; + internal set; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs b/Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs new file mode 100644 index 00000000..27253ad1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/LineBreakMode.cs @@ -0,0 +1,43 @@ +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Enumerates values that describe options for line braking. + /// </summary> + public enum LineBreakMode + { + /// <summary> + /// Do not wrap text. + /// </summary> + NoWrap, + + /// <summary> + /// Wrap at character boundaries. + /// </summary> + CharacterWrap, + + /// <summary> + /// Wrap at word boundaries. + /// </summary> + WordWrap, + + /// <summary> + /// Tries to wrap at word boundaries, and then wrap at a character boundary if the word is too long. + /// </summary> + MixedWrap, + + /// <summary> + /// Truncate the head of text. + /// </summary> + HeadTruncation, + + /// <summary> + /// Truncate the middle of text. This may be done, for example, by replacing it with an ellipsis. + /// </summary> + MiddleTruncation, + + /// <summary> + /// Truncate the tail of text. + /// </summary> + TailTruncation + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ListView.cs b/Xamarin.Forms.Platform.Tizen/Native/ListView.cs new file mode 100644 index 00000000..d5531dc4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ListView.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Type alias which identifies list of cells whose data model was transformed by Xamarin. + /// </summary> + using GroupList = TemplatedItemsList<ItemsView<Cell>, Cell>; + + /// <summary> + /// Native ListView implementation for Xamarin renderer + /// </summary> + /// <remarks> + /// This internally uses GenList class. + /// One should note that it is optimized for displaying many elements which may be + /// unavailable at first. This means that only currently visible elements will be constructed. + /// Whenever element disappears from visible space its content is destroyed for time being. + /// This is carried out by so called Cell Handlers. + /// </remarks> + public class ListView : GenList + { + /// <summary> + /// ItemContext helper class. This represents the association between Xamarin.Forms.Cell and + /// native elements. It also stores useful context for them. + /// </summary> + public class ItemContext + { + public ItemContext() + { + Item = null; + Cell = null; + Renderer = null; + ListOfSubItems = null; + } + + public GenListItem Item; + public Cell Cell; + public bool IsGroupItem; + public CellRenderer Renderer; + internal TemplatedItemsList<ItemsView<Cell>, Cell> ListOfSubItems; + } + + /// <summary> + /// The item context list for each added element. + /// </summary> + readonly List<ItemContext> _itemContextList = new List<ItemContext>(); + + /// <summary> + /// Registered cell handlers. + /// </summary> + protected readonly IDictionary<Type, CellRenderer> _cellRendererCache = new Dictionary<Type, CellRenderer>(); + + /// <summary> + /// Registered group handlers. + /// </summary> + protected readonly IDictionary<Type, CellRenderer> _groupCellRendererCache = new Dictionary<Type, CellRenderer>(); + + /// <summary> + /// The header context. + /// </summary> + ItemContext _headerContext; + + /// <summary> + /// The header element. + /// </summary> + VisualElement _headerElement; + + /// <summary> + /// The footer context. + /// </summary> + ItemContext _footerContext; + + /// <summary> + /// The footer element. + /// </summary> + VisualElement _footerElement; + + /// <summary> + /// The item class for header and footer. + /// </summary> + GenItemClass _headerFooterItemClass = null; + + /// <summary> + /// Gets or sets a value indicating whether this instance has grouping enabled. + /// </summary> + /// <value><c>true</c> if this instance has grouping enabled.</value> + public bool IsGroupingEnabled { get; set; } + + /// <summary> + /// Constructor of ListView native control. + /// </summary> + /// <param name="parent">ElmSharp object which is parent of particular list view</param> + public ListView(EvasObject parent) + : base(parent) + { + ItemRealized += OnItemAppear; + ItemUnrealized += OnItemDisappear; + } + + /// <summary> + /// Gets the item context based on Cell item. + /// </summary> + /// <returns>The item context.</returns> + /// <param name="cell">Cell for which context should be found.</param> + internal ItemContext GetItemContext(Cell cell) + { + if (cell == null) + { + return null; + } + else + { + return _itemContextList.Find(X => X.Cell == cell); + } + } + + /// <summary> + /// Sets the HasUnevenRows property. + /// </summary> + /// <param name="hasUnevenRows">If <c>true</c>, the list will allow uneven sizes for its rows.</param> + public void SetHasUnevenRows(bool hasUnevenRows) + { + Homogeneous = !hasUnevenRows; + UpdateRealizedItems(); + } + + /// <summary> + /// Adds elements to the list and defines its presentation based on Cell type. + /// </summary> + /// <param name="_source">IEnumerable on Cell collection.</param> + /// <param name="beforeCell">Cell before which new items will be placed. + /// Null value may also be passed as this parameter, which results in appending new items to the end. + /// </param> + public void AddSource(IEnumerable _source, Cell beforeCell = null) + { + foreach (var data in _source) + { + GroupList groupList = data as GroupList; + if (groupList != null) + { + AddGroupItem(groupList, beforeCell); + foreach (var item in groupList) + { + AddItem(item as Cell, groupList.HeaderContent); + } + } + else + { + AddItem(data as Cell, null, beforeCell); + } + } + } + + /// <summary> + /// Deletes all items from a given group. + /// </summary> + /// <param name="group">Group of items to be deleted.</param> + internal void ResetGroup(GroupList group) + { + var items = _itemContextList.FindAll(x => x.ListOfSubItems == group && x.Cell != group.HeaderContent); + foreach (var item in items) + { + item.Item?.Delete(); + } + } + + /// <summary> + /// Adds items to the group. + /// </summary> + /// <param name="itemGroup">Group to which elements will be added.</param> + /// <param name="newItems">New list items to be added.</param> + /// <param name="cellBefore">A reference to the Cell already existing in a ListView. + /// Newly added cells will be put just before this cell.</param> + public void AddItemsToGroup(IEnumerable itemGroup, IEnumerable newItems, Cell cellBefore = null) + { + ItemContext groupCtx = GetItemContext((itemGroup as GroupList)?.HeaderContent); + if (groupCtx != null) + { + foreach (var item in newItems) + { + AddItem(item as Cell, groupCtx.Cell, cellBefore); + } + } + } + + /// <summary> + /// Removes the specified cells. + /// </summary> + /// <param name="cells">Cells to be removed.</param> + public void Remove(IEnumerable cells) + { + foreach (var data in cells) + { + var group = data as GroupList; + if (group != null) + { + ItemContext groupCtx = GetItemContext(group.HeaderContent); + Remove(groupCtx.ListOfSubItems); + groupCtx.Item.Delete(); + } + else + { + ItemContext itemCtx = GetItemContext(data as Cell); + itemCtx?.Item?.Delete(); + } + } + } + + /// <summary> + /// Scrolls the list to a specified cell. + /// </summary> + /// <remarks> + /// Different scrolling behaviors are also possible. The element may be positioned in the center, + /// top or bottom of the visible part of the list depending on the value of the <c>position</c> parameter. + /// </remarks> + /// <param name="cell">Cell which will be displayed after scrolling .</param> + /// <param name="position">This will defines scroll to behavior based on ScrollToPosition values.</param> + /// <param name="animated">If <c>true</c>, scrolling will be animated. Otherwise the cell will be moved instantaneously.</param> + public void ApplyScrollTo(Cell cell, ScrollToPosition position, bool animated) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + this.ScrollTo(item, position.ToNative(), animated); + } + + /// <summary> + /// Selects the specified cell. + /// </summary> + /// <param name="cell">Cell to be selected.</param> + public void ApplySelectedItem(Cell cell) + { + GenListItem item = GetItemContext(cell)?.Item; + if (item != null) + item.IsSelected = true; + } + + /// <summary> + /// Sets the header. + /// </summary> + /// <param name="header">Header of the list.</param> + public void SetHeader(VisualElement header) + { + if (header == null) + { + if (HasHeader()) + { + RemoveHeader(); + } + + return; + } + + GenItemClass headerTemplate = GetHeaderFooterItemClass(); + + _headerElement = header; + if (HasHeader()) + { + FirstItem.UpdateItemClass(headerTemplate, header); + } + else + { + _headerContext = new ItemContext(); + _headerContext.Item = _itemContextList.Count > 0 ? InsertBefore(headerTemplate, header, FirstItem) : Append(headerTemplate, header); + _headerContext.Item.SelectionMode = GenListSelectionMode.None; + _headerContext.Item.Deleted += HeaderDeletedHandler; + _itemContextList.Insert(0, _headerContext); + } + } + + /// <summary> + /// Sets the footer. + /// </summary> + /// <param name="footer">Footer of the list.</param> + public void SetFooter(VisualElement footer) + { + if (footer == null) + { + if (HasFooter()) + { + RemoveFooter(); + } + return; + } + + GenItemClass footerTemplate = GetHeaderFooterItemClass(); + + _footerElement = footer; + if (HasFooter()) + { + _footerContext.Item.UpdateItemClass(footerTemplate, footer); + } + else + { + _footerContext = new ItemContext(); + _footerContext.Item = Append(footerTemplate, footer); + _footerContext.Item.SelectionMode = GenListSelectionMode.None; + _footerContext.Item.Deleted += FooterDeletedHandler; + _itemContextList.Add(_footerContext); + } + } + + /// <summary> + /// Removes the header. + /// </summary> + public void RemoveHeader() + { + _itemContextList.Remove(_headerContext); + _headerContext?.Item?.Delete(); + _headerContext = null; + _headerElement = null; + } + + /// <summary> + /// Removes the footer. + /// </summary> + public void RemoveFooter() + { + _itemContextList.Remove(_footerContext); + _footerContext?.Item?.Delete(); + _footerContext = null; + _footerElement = null; + } + + /// <summary> + /// Determines whether this instance has a header. + /// </summary> + /// <returns><c>true</c> if the header is present.</returns> + public bool HasHeader() + { + return _headerContext != null; + } + + /// <summary> + /// Determines whether this instance has a footer. + /// </summary> + /// <returns><c>true</c> if the footer is present.</returns> + public bool HasFooter() + { + return _footerContext != null; + } + + /// <summary> + /// Gets the header. + /// </summary> + /// <returns>The header.</returns> + public VisualElement GetHeader() + { + return _headerElement; + } + + /// <summary> + /// Gets the footer. + /// </summary> + /// <returns>The footer.</returns> + public VisualElement GetFooter() + { + return _footerElement; + } + + /// <summary> + /// Handles the header deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void HeaderDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_headerContext); + _headerContext = null; + } + + /// <summary> + /// Handles the footer deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void FooterDeletedHandler(object sender, EventArgs e) + { + _itemContextList.Remove(_footerContext); + _footerContext = null; + } + + /// <summary> + /// Called every time an object gets realized. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="evt">GenListItemEventArgs.</param> + void OnItemAppear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendAppearing(); + } + } + + /// <summary> + /// Called every time an object gets unrealized. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="evt">GenListItemEventArgs.</param> + void OnItemDisappear(object sender, GenListItemEventArgs evt) + { + ItemContext itemContext = (evt.Item.Data as ItemContext); + if (itemContext != null && itemContext.Cell != null) + { + (itemContext.Cell as ICellController).SendDisappearing(); + itemContext.Renderer?.SendUnrealizedCell(itemContext.Cell); + } + } + + + /// <summary> + /// A convenience shorthand method for derivate classes. + /// </summary> + /// <param name="cell">Cell to be added.</param> + protected void AddCell(Cell cell) + { + AddItem(cell); + } + + /// <summary> + /// Gets the cell renderer for given cell type. + /// </summary> + /// <returns>The cell handler.</returns> + /// <param name="cell">Cell to be added.</param> + /// <param name="isGroup">If <c>true</c>, then group handlers will be included in the lookup as well.</param> + protected virtual CellRenderer GetCellRenderer(Cell cell, bool isGroup = false) + { + Type type = cell.GetType(); + var cache = isGroup ? _groupCellRendererCache : _cellRendererCache; + if (cache.ContainsKey(type)) + return cache[type]; + + CellRenderer renderer = null; + + if (isGroup && type == typeof(TextCell)) + { + renderer = new GroupCellTextRenderer(); + } + renderer = renderer ?? Registrar.Registered.GetHandler<CellRenderer>(type); + + if (renderer == null) + { + Log.Error("Cell type is not handled: {0}", cell.GetType()); + throw new ArgumentNullException("Unsupported cell type"); + } + return cache[type] = renderer; + } + + /// <summary> + /// Adds the group item. Group item is actually of class GroupList because + /// group item has sub items (can be zero) which needs to be added. + /// If beforeCell is not null, new group will be added just before it. + /// </summary> + /// <param name="groupList">Group to be added.</param> + /// <param name="beforeCell">Before cell.</param> + void AddGroupItem(GroupList groupList, Cell beforeCell = null) + { + Cell groupCell = groupList.HeaderContent; + CellRenderer groupRenderer = GetCellRenderer(groupCell, true); + ItemContext groupItemContext = new ItemContext(); + groupItemContext.Cell = groupCell; + groupItemContext.Renderer = groupRenderer; + + if (beforeCell != null) + { + GenListItem beforeItem = GetItemContext(beforeCell)?.Item; + groupItemContext.Item = InsertBefore(groupRenderer.Class, groupItemContext, beforeItem, GenListItemType.Group); + } + else + { + groupItemContext.Item = Append(groupRenderer.Class, groupItemContext, GenListItemType.Group); + } + + groupItemContext.Item.SelectionMode = GenListSelectionMode.None; + groupItemContext.IsGroupItem = true; + + groupItemContext.ListOfSubItems = groupList; + groupItemContext.Item.Deleted += ItemDeletedHandler; + _itemContextList.Add(groupItemContext); + } + + /// <summary> + /// Adds the item. + /// </summary> + /// <param name="cell">Cell to be added.</param> + /// <param name="groupCell">Group to which the new item should belong.</param> + /// <remark>If the value of <c>groupCell</c> is not null, the new item will be put into the requested group. </remark> + /// <param name="beforeCell">The cell before which the new item should be placed.</param> + /// <remarks> If the value of <c>beforeCell</c> is not null, the new item will be placed just before the requested cell. </remarks> + void AddItem(Cell cell, Cell groupCell = null, Cell beforeCell = null) + { + CellRenderer renderer = GetCellRenderer(cell); + GenListItem parentItem = null; + + ItemContext itemContext = new ItemContext(); + itemContext.Cell = cell; + itemContext.Renderer = renderer; + + if (IsGroupingEnabled && groupCell != null) + { + var groupContext = GetItemContext(groupCell); + itemContext.ListOfSubItems = groupContext.ListOfSubItems; + parentItem = groupContext.Item; + } + + if (beforeCell != null) + { + GenListItem beforeItem = GetItemContext(beforeCell)?.Item; + itemContext.Item = InsertBefore(renderer.Class, itemContext, beforeItem, GenListItemType.Normal, parentItem); + } + else + { + itemContext.Item = Append(renderer.Class, itemContext, GenListItemType.Normal, parentItem); + } + + itemContext.Item.SelectionMode = GenListSelectionMode.Always; + + cell.PropertyChanged += OnCellPropertyChanged; + (cell as ICellController).ForceUpdateSizeRequested += OnForceUpdateSizeRequested; + itemContext.Item.Deleted += ItemDeletedHandler; + _itemContextList.Add(itemContext); + } + + /// <summary> + /// Handles item deleted event. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Empty argument.</param> + void ItemDeletedHandler(object sender, EventArgs e) + { + ItemContext itemContext = (sender as GenListItem).Data as ItemContext; + if (itemContext.Cell != null) + { + itemContext.Cell.PropertyChanged -= OnCellPropertyChanged; + (itemContext.Cell as ICellController).ForceUpdateSizeRequested -= OnForceUpdateSizeRequested; + } + _itemContextList.Remove(itemContext); + } + + /// <summary> + /// Invoked whenever the properties of data model change. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">PropertyChangedEventArgs.</param> + /// <remarks> + /// The purpose of this method is to propagate these changes to the presentation layer. + /// </remarks> + void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var cell = sender as Cell; + var context = GetItemContext(cell); + context.Renderer.SendCellPropertyChanged(cell, context.Item, e.PropertyName); + } + + void OnForceUpdateSizeRequested(object sender, EventArgs e) + { + var cell = sender as Cell; + var itemContext = GetItemContext(cell); + if (itemContext.Item != null) + itemContext.Item.Update(); + } + + /// <summary> + /// Gets the item class used for header and footer cells. + /// </summary> + /// <returns>The header and footer item class.</returns> + GenItemClass GetHeaderFooterItemClass() + { + if (_headerFooterItemClass == null) + { + _headerFooterItemClass = new GenItemClass("full") + { + GetContentHandler = (data, part) => + { + VisualElement element = data as VisualElement; + var renderer = Platform.GetOrCreateRenderer(element); + + if (element.MinimumHeightRequest == -1) + { + SizeRequest request = element.Measure(double.PositiveInfinity, double.PositiveInfinity); + renderer.NativeView.MinimumHeight = (int)request.Request.Height; + } + else + { + renderer.NativeView.MinimumHeight = (int)element.MinimumHeightRequest; + } + + return renderer.NativeView; + } + }; + } + return _headerFooterItemClass; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs b/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs new file mode 100644 index 00000000..5751aaf8 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/MasterDetailPage.cs @@ -0,0 +1,364 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// The native widget which provides Xamarin.MasterDetailPage features. + /// </summary> + public class MasterDetailPage : Box + { + /// <summary> + /// The portion of the screen that the MasterPage takes in Split mode. + /// </summary> + static readonly double s_splitRatio = 0.35; + + /// <summary> + /// The portion of the screen that the MasterPage takes in Popover mode. + /// </summary> + static readonly double s_popoverRatio = 0.8; + + /// <summary> + /// The default master behavior (a.k.a mode). + /// </summary> + static readonly MasterBehavior s_defaultMasterBehavior = (Device.Idiom == TargetIdiom.Phone) ? MasterBehavior.Popover : MasterBehavior.SplitOnLandscape; + + /// <summary> + /// The MasterPage native container. + /// </summary> + readonly Canvas _masterCanvas; + + /// <summary> + /// The DetailPage native container. + /// </summary> + readonly Canvas _detailCanvas; + + /// <summary> + /// The container for <c>_masterCanvas</c> and <c>_detailCanvas</c> used in split mode. + /// </summary> + readonly Panes _splitPane; + + /// <summary> + /// The container for <c>_masterCanvas</c> used in popover mode. + /// </summary> + readonly Panel _drawer; + + /// <summary> + /// The <see cref="MasterBehavior"/> property value. + /// </summary> + MasterBehavior _masterBehavior = s_defaultMasterBehavior; + + /// <summary> + /// The actual MasterDetailPage mode - either split or popover. It depends on <c>_masterBehavior</c> and screen orientation. + /// </summary> + MasterBehavior _internalMasterBehavior = MasterBehavior.Popover; + + /// <summary> + /// The <see cref="Master"/> property value. + /// </summary> + EvasObject _master; + + /// <summary> + /// The <see cref="Detail"/> property value. + /// </summary> + EvasObject _detail; + + /// <summary> + /// The main widget - either <see cref="_splitPlane"/> or <see cref="_detailPage"/>, depending on the mode. + /// </summary> + EvasObject _mainWidget; + + /// <summary> + /// The <see cref="IsPresented"/> property value. + /// </summary> + bool _isPresented; + + /// <summary> + /// The <see cref="IsGestureEnabled"/> property value. + /// </summary> + bool _isGestureEnabled; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.MasterDetailPage"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public MasterDetailPage(EvasObject parent) : base(parent) + { + // we control the layout ourselves + Resized += (sender, e) => + { + var g = Geometry; + + // main widget should fill the area of the MasterDetailPage + if (_mainWidget != null) + { + _mainWidget.Geometry = g; + } + + g.Width = (int)((s_popoverRatio * (double)g.Width)); + _drawer.Geometry = g; + }; + + // create the controls which will hold the master and detail pages + _masterCanvas = new Canvas(this); + _masterCanvas.SetAlignment(-1.0, -1.0); // fill + _masterCanvas.SetWeight(1.0, 1.0); // expand + _masterCanvas.Resized += (sender, e) => + { + UpdatePageGeometry(_master); + }; + + _detailCanvas = new Canvas(this); + _detailCanvas.SetAlignment(-1.0, -1.0); // fill + _detailCanvas.SetWeight(1.0, 1.0); // expand + _detailCanvas.Resized += (sender, e) => + { + UpdatePageGeometry(_detail); + }; + + _splitPane = new Panes(this) + { + IsFixed = true, + IsHorizontal = false, + Proportion = s_splitRatio, + }; + + _drawer = new Panel(this) + { + Direction = PanelDirection.Left, + }; + _drawer.SetScrollable(_isGestureEnabled); + _drawer.SetScrollableArea(1.0); + _drawer.Toggled += (object sender, EventArgs e) => + { + IsPresented = _drawer.IsOpen; + }; + + IsPresentedChanged += (sender, e) => + { + _drawer.IsOpen = IsPresented; + }; + + ConfigureLayout(); + + // in case of the screen rotation we may need to update the choice between split + // and popover behaviors and reconfigure the layout + Forms.Context.MainWindow.RotationChanged += (sender, e) => + { + UpdateMasterBehavior(); + }; + } + + /// <summary> + /// Occurs when the MasterPage is shown or hidden. + /// </summary> + public event EventHandler IsPresentedChanged; + + /// <summary> + /// Gets or sets the MasterDetailPage behavior. + /// </summary> + /// <value>The behavior of the <c>MasterDetailPage</c> requested by the user.</value> + public MasterBehavior MasterBehavior + { + get + { + return _masterBehavior; + } + + set + { + if (_masterBehavior != value) + { + _masterBehavior = value; + + UpdateMasterBehavior(); + } + } + } + + /// <summary> + /// Gets or sets the content of the MasterPage. + /// </summary> + /// <value>The MasterPage.</value> + public EvasObject Master + { + get + { + return _master; + } + + set + { + if (_master != value) + { + _master = value; + UpdatePageGeometry(_master); + _masterCanvas.Children.Clear(); + _masterCanvas.Children.Add(_master); + } + } + } + + /// <summary> + /// Gets or sets the content of the DetailPage. + /// </summary> + /// <value>The DetailPage.</value> + public EvasObject Detail + { + get + { + return _detail; + } + + set + { + if (_detail != value) + { + _detail = value; + UpdatePageGeometry(_detail); + _detailCanvas.Children.Clear(); + _detailCanvas.Children.Add(_detail); + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether the MasterPage is shown. + /// </summary> + /// <value><c>true</c> if the MasterPage is presented.</value> + public bool IsPresented + { + get + { + return _isPresented; + } + + set + { + if (_isPresented != value) + { + _isPresented = value; + IsPresentedChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether a MasterDetailPage allows showing MasterPage with swipe gesture. + /// </summary> + /// <value><c>true</c> if the MasterPage can be revealed with a gesture.</value> + public bool IsGestureEnabled + { + get + { + return _isGestureEnabled; + } + + set + { + if (_isGestureEnabled != value) + { + _isGestureEnabled = value; + _drawer.SetScrollable(_isGestureEnabled); + } + } + } + + /// <summary> + /// Updates the geometry of the selected page. + /// </summary> + /// <param name="page">Master or Detail page to be updated.</param> + void UpdatePageGeometry(EvasObject page) + { + if (page != null) + { + if (_master == page) + { + // update the geometry of the master page + page.Geometry = _masterCanvas.Geometry; + } + else if (_detail == page) + { + // update the geometry of the detail page + page.Geometry = _detailCanvas.Geometry; + } + } + } + + /// <summary> + /// Updates <see cref="_internalMasterBehavior"/> according to <see cref="MasterDetailBehavior"/> set by the user and current screen orientation. + /// </summary> + void UpdateMasterBehavior() + { + var behavior = (_masterBehavior == MasterBehavior.Default) ? s_defaultMasterBehavior : _masterBehavior; + + // Screen orientation affects those 2 behaviors + if (behavior == MasterBehavior.SplitOnLandscape || + behavior == MasterBehavior.SplitOnPortrait) + { + var orientation = Forms.Context.MainWindow.CurrentOrientation; + + if (((orientation == DisplayOrientations.Landscape || orientation == DisplayOrientations.LandscapeFlipped) && behavior == MasterBehavior.SplitOnLandscape) || + ((orientation == DisplayOrientations.Portrait || orientation == DisplayOrientations.PortraitFlipped) && behavior == MasterBehavior.SplitOnPortrait)) + { + behavior = MasterBehavior.Split; + } + else + { + behavior = MasterBehavior.Popover; + } + } + + if (behavior != _internalMasterBehavior) + { + _internalMasterBehavior = behavior; + + ConfigureLayout(); + } + } + + /// <summary> + /// Composes the structure of all the necessary widgets. + /// </summary> + void ConfigureLayout() + { + _drawer.SetContent(null, true); + _drawer.Hide(); + + _splitPane.SetPartContent("left", null, true); + _splitPane.SetPartContent("right", null, true); + _splitPane.Hide(); + + UnPackAll(); + + // the structure for split mode and for popover mode looks differently + if (_internalMasterBehavior == MasterBehavior.Split) + { + _splitPane.SetPartContent("left", _masterCanvas, true); + _splitPane.SetPartContent("right", _detailCanvas, true); + PackEnd(_splitPane); + + _splitPane.Show(); + + _mainWidget = _splitPane; + + IsPresented = true; + } + else + { + _drawer.SetContent(_masterCanvas, true); + PackEnd(_detailCanvas); + PackEnd(_drawer); + + _drawer.Show(); + + _mainWidget = _detailCanvas; + + _drawer.IsOpen = IsPresented; + } + + _masterCanvas.Show(); + _detailCanvas.Show(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs b/Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs new file mode 100644 index 00000000..d33407c9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/ObservableCollection.cs @@ -0,0 +1,30 @@ +using System.Collections.Specialized; +using System.Linq; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. + /// </summary> + /// <typeparam name="T">The type of elements in the collection.</typeparam> + internal class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T> + { + /// <summary> + /// Removes all items from the collection. + /// </summary> + /// <remarks> + /// Fisrt remove all items, send CollectionChanged event with Remove Action + /// Second call ClearItems of base + /// </remarks> + protected override void ClearItems() + { + var oldItems = Items.ToList(); + Items.Clear(); + using (BlockReentrancy()) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems)); + } + base.ClearItems(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs b/Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs new file mode 100644 index 00000000..db597ed9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/SearchBar.cs @@ -0,0 +1,423 @@ +using System; +using ElmSharp; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; +using ERect = ElmSharp.Rect; +using ERectangle = ElmSharp.Rectangle; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Provides implementation of the search bar widget. + /// </summary> + public class SearchBar : Canvas, IMeasurable + { + /// <summary> + /// The height of the background of the search bar. + /// </summary> + const int BackgroundHeight = 120; + + /// <summary> + /// The style of the cancel button. + /// </summary> + const string CancelButtonLayoutStyle = "editfield_clear"; + + /// <summary> + /// The horizontal padding of the cancel button. + /// </summary> + const int CancelButtonPaddingHorizontal = 17; + + /// <summary> + /// The size of the cancel button. + /// </summary> + const int CancelButtonSize = 80; + + /// <summary> + /// The height of the entry. + /// </summary> + const int EntryHeight = 54; + + /// <summary> + /// The horizontal padding of the entry. + /// </summary> + const int EntryPaddingHorizontal = 42; + + /// <summary> + /// The vertical padding of the entry. + /// </summary> + const int EntryPaddingVertical = 33; + + /// <summary> + /// The height of the rectangle used to draw underline effect. + /// </summary> + const int RectangleHeight = 2; + + /// <summary> + /// The bottom padding of the rectangle used to draw underline effect. + /// </summary> + const int RectanglePaddingBottom = 20; + + /// <summary> + /// The horizontal padding of the rectangle used to draw underline effect. + /// </summary> + const int RectanglePaddingHorizontal = 32; + + /// <summary> + /// The top padding of the rectangle used to draw underline effect. + /// </summary> + const int RectanglePaddingTop = 11; + + //TODO: read default platform color + + /// <summary> + /// The color of the underline rectangle. + /// </summary> + static readonly EColor s_underlineColor = EColor.Aqua; + + /// <summary> + /// The dimmed color of the underline rectangle. + /// </summary> + static readonly EColor s_underlineDimColor = EColor.Gray; + + /// <summary> + /// The cancel button. + /// </summary> + Button _cancelButton; + + /// <summary> + /// The text entry. + /// </summary> + Entry _entry; + + /// <summary> + /// The underline rectangle. + /// </summary> + ERectangle _underlineRectangle; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.Native.SearchBar"/> class. + /// </summary> + /// <param name="parent">Parent evas object.</param> + public SearchBar(EvasObject parent) : base(parent) + { + _entry = new Entry(parent) + { + IsSingleLine = true, + }; + _entry.SetInputPanelReturnKeyType(InputPanelReturnKeyType.Search); + _entry.TextChanged += EntryTextChanged; + _entry.Activated += EntryActivated; + _entry.Focused += EntryFocused; + _entry.Unfocused += EntryUnfocused; + _entry.Show(); + + _cancelButton = new Button(parent); + _cancelButton.Style = CancelButtonLayoutStyle; + _cancelButton.Clicked += CancelButtonClicked; + + _underlineRectangle = new ERectangle(parent) + { + Color = IsEnabled ? s_underlineColor : s_underlineDimColor, + }; + _underlineRectangle.Show(); + + Children.Add(_entry); + Children.Add(_cancelButton); + Children.Add(_underlineRectangle); + + Show(); + + this.LayoutUpdated += SearchBarLayoutUpdated; + } + + /// <summary> + /// Occurs when the search button on the keyboard is pressed. + /// </summary> + public event EventHandler SearchButtonPressed; + + /// <summary> + /// Occurs when the entry's text has changed. + /// </summary> + public event EventHandler<TextChangedEventArgs> TextChanged; + + /// <summary> + /// Gets or sets the color of the cancel button. + /// </summary> + /// <value>Color of the cancel button.</value> + public EColor CancelButtonColor + { + get + { + return _cancelButton.Color; + } + + set + { + if (!_cancelButton.Color.Equals(value)) + { + _cancelButton.Color = value; + } + } + } + + /// <summary> + /// Gets or sets the font attributes of the search bar's entry. + /// </summary> + /// <value>The font attributes.</value> + public FontAttributes FontAttributes + { + get + { + return _entry.FontAttributes; + } + + set + { + if (value != _entry.FontAttributes) + { + _entry.FontAttributes = value; + } + } + } + + /// <summary> + /// Gets or sets the font family of the search bar's entry. + /// </summary> + /// <value>The font family.</value> + public string FontFamily + { + get + { + return _entry.FontFamily; + } + + set + { + if (value != _entry.FontFamily) + { + _entry.FontFamily = value; + } + } + } + + /// <summary> + /// Gets or sets the size of the font of the search bar's entry. + /// </summary> + /// <value>The size of the font.</value> + public double FontSize + { + get + { + return _entry.FontSize; + } + + set + { + if (value != _entry.FontSize) + { + _entry.FontSize = value; + } + } + } + + /// <summary> + /// Gets or sets the horizontal text alignment of the search bar's entry. + /// </summary> + /// <value>The horizontal text alignment.</value> + public TextAlignment HorizontalTextAlignment + { + get + { + return _entry.HorizontalTextAlignment; + } + + set + { + if (value != _entry.HorizontalTextAlignment) + { + _entry.HorizontalTextAlignment = value; + } + } + } + + /// <summary> + /// Gets or sets the placeholder of the search bar's entry. + /// </summary> + /// <value>The placeholder.</value> + public string Placeholder + { + get + { + return _entry.Placeholder; + } + + set + { + if (value != _entry.Placeholder) + { + _entry.Placeholder = value; + } + } + } + + /// <summary> + /// Gets or sets the color of the placeholder. + /// </summary> + /// <value>The color of the placeholder.</value> + public EColor PlaceholderColor + { + get + { + return _entry.PlaceholderColor; + } + + set + { + if (!_entry.PlaceholderColor.Equals(value)) + { + _entry.PlaceholderColor = value; + } + } + } + + /// <summary> + /// Gets or sets the text of the search bar's entry. + /// </summary> + /// <value>The text.</value> + public override string Text + { + get + { + return _entry.Text; + } + + set + { + if (value != _entry.Text) + { + _entry.Text = value; + } + } + } + + /// <summary> + /// Gets or sets the color of the text. + /// </summary> + /// <value>The color of the text.</value> + public EColor TextColor + { + get + { + return _entry.TextColor; + } + + set + { + if (!_entry.TextColor.Equals(value)) + { + _entry.TextColor = value; + } + } + } + + /// <summary> + /// Implementation of the IMeasurable.Measure() method. + /// </summary> + public ESize Measure(int availableWidth, int availableHeight) + { + ESize entrySize = _entry.Measure(availableWidth, availableHeight); + int width = entrySize.Width + (CancelButtonPaddingHorizontal * 2) + CancelButtonSize; + return new ESize(width, BackgroundHeight); + } + + /// <summary> + /// Handles the event triggered by the cancel button being clicked. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void CancelButtonClicked(object sender, EventArgs e) + { + _entry.Text = string.Empty; + _cancelButton.Hide(); + } + + /// <summary> + /// Handles the event triggered by clicking the search button on the keyboard. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void EntryActivated(object sender, EventArgs e) + { + SearchButtonPressed?.Invoke(this, EventArgs.Empty); + } + + /// <summary> + /// Handles the event triggered by entry gaining the focus. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void EntryFocused(object sender, EventArgs e) + { + _underlineRectangle.Color = s_underlineColor; + } + + /// <summary> + /// Handles the event triggered by entry's text being changed. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments.</param> + void EntryTextChanged(object sender, TextChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.NewTextValue)) + { + _cancelButton.Hide(); + } + else if (!_cancelButton.IsVisible) + { + _cancelButton.Show(); + } + TextChanged?.Invoke(this, e); + } + + /// <summary> + /// Handles the event triggered by entry losing the focus. + /// </summary> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments, ignored.</param> + void EntryUnfocused(object sender, EventArgs e) + { + _underlineRectangle.Color = s_underlineDimColor; + } + + /// <summary> + /// Handles the event triggered by search bar's layout being changed. + /// </summary> + /// <remarks> + /// Updates the geometry of the widgets comprising the search bar. + /// </remarks> + /// <param name="sender">Sender of the event.</param> + /// <param name="e">Event arguments.</param> + void SearchBarLayoutUpdated(object sender, LayoutEventArgs e) + { + if (!e.HasChanged) + { + return; + } + + _underlineRectangle.Geometry = new ERect(Geometry.Left + RectanglePaddingHorizontal, + Geometry.Top + EntryPaddingVertical + EntryHeight + RectanglePaddingTop, + Geometry.Width - (RectanglePaddingHorizontal * 2), + RectangleHeight); + + _entry.Geometry = new ERect(Geometry.Left + EntryPaddingHorizontal, + Geometry.Top + EntryPaddingVertical, + Geometry.Width - (EntryPaddingHorizontal + (CancelButtonPaddingHorizontal * 2) + CancelButtonSize), + EntryHeight); + + _cancelButton.Geometry = new ERect(Geometry.Right - CancelButtonSize - CancelButtonPaddingHorizontal, + Geometry.Top + RectanglePaddingBottom, + CancelButtonSize, + CancelButtonSize); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Span.cs b/Xamarin.Forms.Platform.Tizen/Native/Span.cs new file mode 100644 index 00000000..6c479e34 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Span.cs @@ -0,0 +1,294 @@ +using System.Text; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Represent a text with attributes applied. + /// </summary> + public class Span + { + string _text; + + /// <summary> + /// Gets or sets the formatted text. + /// </summary> + public FormattedString FormattedText { get; set; } + + /// <summary> + /// Gets or sets the text. + /// </summary> + /// <remarks> + /// Setting Text to a non-null value will set the FormattedText property to null. + /// </remarks> + public string Text + { + get + { + if (FormattedText != null) + { + return FormattedText.ToString(); + } + else + { + return _text; + } + } + set + { + if (value == null) + { + value = ""; + } + else + { + FormattedText = null; + } + _text = value; + } + } + + /// <summary> + /// Gets or sets the color for the text. + /// </summary> + public EColor ForegroundColor { get; set; } + + /// <summary> + /// Gets or sets the background color for the text. + /// </summary> + public EColor BackgroundColor { get; set; } + + /// <summary> + /// Gets or sets the font family for the text. + /// </summary> + public string FontFamily { get; set; } + + /// <summary> + /// Gets or sets the font attributes for the text. + /// See <see cref="FontAttributes"/> for information about FontAttributes. + /// </summary> + public FontAttributes FontAttributes { get; set; } + + /// <summary> + /// Gets or sets the font size for the text. + /// </summary> + public double FontSize { get; set; } + + /// <summary> + /// Gets or sets the line break mode for the text. + /// See <see cref="LineBreakMode"/> for information about LineBreakMode. + /// </summary> + public LineBreakMode LineBreakMode { get; set; } + + /// <summary> + /// Gets or sets the horizontal alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + public TextAlignment HorizontalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the vertical alignment mode for the text. + /// See <see cref="TextAlignment"/> for information about TextAlignment. + /// </summary> + public TextAlignment VerticalTextAlignment { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has underline. + /// </summary> + public bool Underline { get; set; } + + /// <summary> + /// Gets or sets the value that indicates whether the text has strike line though it. + /// </summary> + public bool Strikethrough { get; set; } + + /// <summary> + /// Create a new Span instance with default attributes. + /// </summary> + public Span() + { + Text = ""; + FontFamily = ""; + FontSize = -1; + FontAttributes = FontAttributes.None; + ForegroundColor = EColor.White; + BackgroundColor = EColor.Transparent; + HorizontalTextAlignment = TextAlignment.Auto; + VerticalTextAlignment = TextAlignment.Auto; + LineBreakMode = LineBreakMode.MixedWrap; + Underline = false; + Strikethrough = false; + } + + /// <summary> + /// This method return marked up text + /// </summary> + internal string GetMarkupText() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendFormat("<span "); + + sb = PrepareFormattingString(sb); + + sb.Append(">"); + + sb.Append(GetDecoratedText()); + + sb.Append("</>"); + + return sb.ToString(); + } + + /// <summary> + /// This method return text decorated with markup if FormattedText is set or plain text otherwise. + /// </summary> + internal string GetDecoratedText() + { + if (FormattedText != null) + { + return FormattedText.ToMarkupString(); + } + else + { + return ConvertTags(Text); + } + } + + StringBuilder PrepareFormattingString(StringBuilder _formattingString) + { + var foregroundColor = ForegroundColor.ToHex(); + + _formattingString.AppendFormat("color={0} ", foregroundColor); + + _formattingString.AppendFormat("backing_color={0} ", BackgroundColor.ToHex()); + _formattingString.Append("backing=on "); + + if (!string.IsNullOrEmpty(FontFamily)) + { + _formattingString.AppendFormat("font={0} ", FontFamily); + } + + if (FontSize != -1) + { + _formattingString.AppendFormat("font_size={0} ", FontSize); + } + + if ((FontAttributes & FontAttributes.Bold) != 0) + { + _formattingString.Append("font_weight=Bold "); + } + if ((FontAttributes & FontAttributes.Italic) != 0) + { + _formattingString.Append("font_style=italic "); + } + + if (Underline) + { + _formattingString.AppendFormat("underline=on underline_color={0} ", foregroundColor); + } + + if (Strikethrough) + { + _formattingString.AppendFormat("strikethrough=on strikethrough_color={0} ", foregroundColor); + } + + switch (HorizontalTextAlignment) + { + case TextAlignment.Auto: + _formattingString.Append("align=auto "); + break; + + case TextAlignment.Start: + _formattingString.Append("align=left "); + break; + + case TextAlignment.End: + _formattingString.Append("align=right "); + break; + + case TextAlignment.Center: + _formattingString.Append("align=center "); + break; + } + + switch (VerticalTextAlignment) + { + case TextAlignment.Auto: + case TextAlignment.Start: + _formattingString.Append("valign=top "); + break; + + case TextAlignment.End: + _formattingString.Append("valign=bottom "); + break; + + case TextAlignment.Center: + _formattingString.Append("valign=middle "); + break; + } + + switch (LineBreakMode) + { + case LineBreakMode.NoWrap: + _formattingString.Append("wrap=none"); + break; + + case LineBreakMode.CharacterWrap: + _formattingString.Append("wrap=char"); + break; + + case LineBreakMode.WordWrap: + _formattingString.Append("wrap=word"); + break; + + case LineBreakMode.MixedWrap: + _formattingString.Append("wrap=mixed"); + break; + + case LineBreakMode.HeadTruncation: + _formattingString.Append("ellipsis=0.0"); + break; + + case LineBreakMode.MiddleTruncation: + _formattingString.Append("ellipsis=0.5"); + break; + + case LineBreakMode.TailTruncation: + _formattingString.Append("ellipsis=1.0"); + break; + } + + return _formattingString; + } + + string ConvertTags(string text) + { + return text.Replace("<", "<") + .Replace(">", ">") + .Replace("\n", "<br>"); + } + + internal string GetStyle() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("DEFAULT='"); + + PrepareFormattingString(sb); + + sb.Append("'"); + + return sb.ToString(); + } + + /// <summary> + /// Converts string value to Span. + /// </summary> + /// <param name="text">The string text</param> + public static implicit operator Span(string text) + { + return new Span { Text = text }; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/TableView.cs b/Xamarin.Forms.Platform.Tizen/Native/TableView.cs new file mode 100644 index 00000000..73388a84 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TableView.cs @@ -0,0 +1,67 @@ +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ListView class to provide TableView class implementation. + /// </summary> + public class TableView : ListView + { + + static readonly SectionCellRenderer _sectionCellRenderer = new SectionCellRenderer(); + /// <summary> + /// Initializes a new instance of the TableView class. + /// </summary> + public TableView(EvasObject parent) + : base(parent) { + } + + /// <summary> + /// Sets the root of the table. + /// </summary> + /// <param name="root">TableRoot, which is parent to one or more TableSections.</param> + public void ApplyTableRoot(TableRoot root) + { + Clear(); + foreach (TableSection ts in root) + { + AddSectionTitle(ts.Title); + AddSource(ts); + } + } + + protected override CellRenderer GetCellRenderer(Cell cell, bool isGroup = false) + { + if (cell.GetType() == typeof(SectionCell)) + { + return _sectionCellRenderer; + } + return base.GetCellRenderer(cell, isGroup); + } + + /// <summary> + /// Sets the section title. + /// </summary> + void AddSectionTitle(string title) + { + Cell cell = new SectionCell() + { + Text = title + }; + AddCell(cell); + } + + internal class SectionCellRenderer : GroupCellTextRenderer + { + public SectionCellRenderer() + { + DetailPart = "null"; + } + protected SectionCellRenderer(string style) : base(style) { } + } + class SectionCell : TextCell + { + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs b/Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs new file mode 100644 index 00000000..c7e934bf --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TextAlignment.cs @@ -0,0 +1,25 @@ +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Enumerates values that describe alignemnt of text. + /// </summary> + public enum TextAlignment + { + /// <summary> + /// Aligns horizontal text according to language. Top aligned for vertical text. + /// </summary> + Auto, + /// <summary> + /// Left and top aligned for horizontal and vertical text, respectively. + /// </summary> + Start, + /// <summary> + /// Right and bottom aligned for horizontal and vertical text, respectively. + /// </summary> + End, + /// <summary> + /// Center-aligned text. + /// </summary> + Center, + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs b/Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs new file mode 100644 index 00000000..5109dad4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TextHelper.cs @@ -0,0 +1,54 @@ +using System; +using ElmSharp; +using ESize = ElmSharp.Size; +using ELayout = ElmSharp.Layout; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// The Text Helper contains functions that assist in working with text-able objects. + /// </summary> + internal static class TextHelper + { + /// <summary> + /// Gets the size of raw text block. + /// </summary> + /// <param name="textable">The <see cref="EvasObject"/> with text part.</param> + /// <returns>Returns the size of raw text block.</returns> + public static ESize GetRawTextBlockSize(EvasObject textable) + { + return GetElmTextPart(textable).TextBlockNativeSize; + } + + /// <summary> + /// Gets the size of formatted text block. + /// </summary> + /// <param name="textable">The <see cref="ElmSharp.EvasObject"/> with text part.</param> + /// <returns>Returns the size of formatted text block.</returns> + public static ESize GetFormattedTextBlockSize(EvasObject textable) + { + return GetElmTextPart(textable).TextBlockFormattedSize; + } + + /// <summary> + /// Gets the ELM text part of evas object. + /// </summary> + /// <param name="textable">The <see cref="ElmSharp.EvasObject"/> with text part.</param> + /// <exception cref="ArgumentException">Throws exception when parameter <param name="textable"> isn't text-able object or doesn't have ELM text part.</exception> + /// <returns>Requested <see cref="ElmSharp.EdjeTextPartObject"/> instance.</returns> + static EdjeTextPartObject GetElmTextPart(EvasObject textable) + { + ELayout widget = textable as ELayout; + if (widget == null) + { + throw new ArgumentException("textable should be ElmSharp.Layout", "textable"); + } + EdjeTextPartObject textPart = widget.EdjeObject["elm.text"]; + if (textPart == null) + { + throw new ArgumentException("There is no elm.text part", "textable"); + } + return textPart; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs b/Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs new file mode 100644 index 00000000..6ee49ce3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/TimePicker.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + /// <summary> + /// Extends the ElmSharp.DateTimeSelector class with functionality useful to renderer. + /// </summary> + public class TimePicker : DateTimeSelector + { + const string DefaultEFLFormat = "%I:%M %p"; + //TODO need to add internationalization support + const string FormatExceptionMessage = "Input string was not in a correct format."; + const string RegexValidTimePattern = "^([h]{1,2}|[H]{1,2})[.:-]([m]{1,2})(([.:-][s]{1,2})?)(([.:-][fF]{1,7})?)(([K])?)(([z]{1,3})?)(([ ][t]{1,2})?)$"; + const string TimeLayoutStyle = "time_layout"; + string _dateTimeFormat; + TimeSpan _time; + + /// <summary> + /// Initializes a new instance of the <see cref="TimePicker"/> class. + /// </summary> + /// <param name="parent">The parent EvasObject.</param> + public TimePicker(EvasObject parent) : base(parent) + { + Style = TimeLayoutStyle; + ApplyTime(Time); + ApplyFormat(DateTimeFormat); + + DateTimeChanged += (sender, e) => + { + Time = e.NewDate.TimeOfDay; + }; + } + + /// <summary> + /// Gets or sets the displayed date time format. + /// </summary> + public string DateTimeFormat + { + get + { + return _dateTimeFormat; + } + set + { + if (_dateTimeFormat != value) + { + ApplyFormat(value); + } + } + } + + /// <summary> + /// Gets or sets the displayed time. + /// </summary> + public TimeSpan Time + { + get + { + return _time; + } + set + { + if (_time != value) + { + ApplyTime(value); + } + } + } + + /// <summary> + /// Sets the <c>Format</c> property according to the given <paramref name="format"/>. + /// </summary> + /// <param name="format">The format value to be applied to the time picker.</param> + void ApplyFormat(string format) + { + _dateTimeFormat = format; + Format = ConvertToEFLFormat(_dateTimeFormat); + } + + /// <summary> + /// Sets the <c>DateTime</c> property according to the given <paramref name="time"/>. + /// </summary> + /// <param name="time">The time value to be applied to the time picker.</param> + void ApplyTime(TimeSpan time) + { + _time = time; + DateTime = ConvertToDateTime(time); + } + + /// <summary> + /// Converts parameter <paramref name="timeSpan"/> to <see cref="DateTime"/>. + /// </summary> + /// <param name="timeSpan">The time value to be converted to <see cref="DateTime"/>.</param> + /// <returns>An object representing the date 1st Jan, 1970 (minimum date of ElmSharp.DateTimeSelector) with added <paramref name="timeSpan"/>.</returns> + DateTime ConvertToDateTime(TimeSpan timeSpan) + { + return new DateTime(1970, 1, 1) + timeSpan; + } + + /// <summary> + /// Converts standard or custom <see cref="DateTime"/> format to EFL format. + /// </summary> + /// <param name="dateTimeFormat">The <see cref="DateTime"/> format to be converted to EFL format.</param> + /// <exception cref="FormatException"><param name="dateTimeFormat"> does not contain a valid string representation of a date and time.</exception> + /// <returns>An object representing the EFL time format string. + /// Example: + /// "t" or "T" returns default EFL format "%I:%M %p" + /// "HH:mm tt" returns "%H:%M %p" + /// "h:mm" returns "%l:%M" + /// </returns> + string ConvertToEFLFormat(string dateTimeFormat) + { + if (string.IsNullOrWhiteSpace(dateTimeFormat)) + { + return DefaultEFLFormat; + } + + if (dateTimeFormat.Length == 1) + { + //Standard Time Format (DateTime) + if (dateTimeFormat[0] == 't' || dateTimeFormat[0] == 'T') + { + return DefaultEFLFormat; + } + else + { + throw new FormatException(FormatExceptionMessage); + } + } + else + { + //Custom Time Format (DateTime) + Regex regex = new Regex(RegexValidTimePattern); + if (!regex.IsMatch(dateTimeFormat)) + { + throw new FormatException(FormatExceptionMessage); + } + + string format = string.Empty; + int count_h = dateTimeFormat.Count(m => m == 'h'); //12h + int count_H = dateTimeFormat.Count(m => m == 'H'); //24h + + if (count_h == 1) + { + format += "%l"; + } + else if (count_h == 2) + { + format += "%I"; + } + else if (count_H == 1) + { + format += "%k"; + } + else if (count_H == 2) + { + format += "%H"; + } + + format += ":%M"; + int count_t = dateTimeFormat.Count(m => m == 't'); + + if ((count_H > 0 && count_t > 0) || + (count_h > 0 && count_t == 0)) + { + throw new FormatException(FormatExceptionMessage); + } + + if (count_t == 1) + { + format += " %P"; + } + else if (count_t == 2) + { + format += " %p"; + } + + return format; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Native/Window.cs b/Xamarin.Forms.Platform.Tizen/Native/Window.cs new file mode 100644 index 00000000..87a54cd3 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Native/Window.cs @@ -0,0 +1,138 @@ +using System; +using ElmSharp; +using EWindow = ElmSharp.Window; + +namespace Xamarin.Forms.Platform.Tizen.Native +{ + public class Window : EWindow + { + Conformant _conformant; + + /// <summary> + /// Initializes a new instance of the Window class. + /// </summary> + public Window() : base("FormsWindow") + { + Initialize(); + } + + /// <summary> + /// Notifies that the window has been closed. + /// </summary> + public event EventHandler Closed; + + /// <summary> + /// Notifies that the back button has been pressed. + /// </summary> + public event EventHandler BackButtonPressed; + + /// <summary> + /// Gets the current orientation. + /// </summary> + public DisplayOrientations CurrentOrientation + { + get + { + if (IsRotationSupported) + { + return GetDisplayOrientation(); + } + else + { + return DisplayOrientations.None; + } + } + } + + /// <summary> + /// Gets or sets the orientation of a rectangular screen. + /// </summary> + public DisplayOrientations AvailableOrientations + { + get + { + if (IsRotationSupported) + { + return (DisplayOrientations)AvailableRotations; + } + else + { + return DisplayOrientations.None; + } + } + set + { + if (IsRotationSupported) + { + AvailableRotations = (DisplayRotation)value; + } + } + } + + /// <summary> + /// Sets the main page of Window. + /// </summary> + /// <param name="content">ElmSharp.EvasObject type page to be set.</param> + public void SetMainPage(EvasObject content) + { + _conformant.SetContent(content); + } + + void Initialize() + { + // size + var size = ScreenSize; + Resize(size.Width, size.Height); + + // events + Deleted += (sender, e) => + { + Closed?.Invoke(this, EventArgs.Empty); + }; + CloseRequested += (sender, e) => + { + Unrealize(); + }; + + KeyGrab(EvasKeyEventArgs.PlatformBackButtonName, false); + KeyUp += (s, e) => + { + if (e.KeyName == EvasKeyEventArgs.PlatformBackButtonName) + { + BackButtonPressed?.Invoke(this, EventArgs.Empty); + } + }; + + Active(); + AutoDeletion = false; + Show(); + + _conformant = new Conformant(this); + _conformant.SetAlignment(-1.0, -1.0); // fill + _conformant.SetWeight(1.0, 1.0); // expand + _conformant.Show(); + + AvailableOrientations = DisplayOrientations.Portrait | DisplayOrientations.Landscape | DisplayOrientations.PortraitFlipped | DisplayOrientations.LandscapeFlipped; + } + DisplayOrientations GetDisplayOrientation() + { + switch (Rotation) + { + case 0: + return Native.DisplayOrientations.Portrait; + + case 90: + return Native.DisplayOrientations.Landscape; + + case 180: + return Native.DisplayOrientations.PortraitFlipped; + + case 270: + return Native.DisplayOrientations.LandscapeFlipped; + + default: + return Native.DisplayOrientations.None; + } + } + } +} 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<bool> CurrentTaskCompletionSource { get; set; } + IPageController CurrentPageController => _navModel.CurrentPage as IPageController; + IReadOnlyList<Page> INavigation.ModalStack => _navModel.Modals.ToList(); + IReadOnlyList<Page> INavigation.NavigationStack => new List<Page>(); + + 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); + } + + /// <summary> + /// Gets the renderer associated with the <c>view</c>. If it doesn't exist, creates a new one. + /// </summary> + /// <returns>Renderer associated with the <c>view</c>.</returns> + /// <param name="view">View for which the renderer is going to be returned.</param> + 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<NaviItem>(_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<IVisualElementRenderer>(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<Page> INavigation.PopAsync() + { + return ((INavigation)this).PopAsync(true); + } + + Task<Page> 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<Page> INavigation.PopModalAsync() + { + return ((INavigation)this).PopModalAsync(true); + } + + async Task<Page> 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<bool> tcs = null; + if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted) + { + var previousTask = CurrentModalNavigationTask; + tcs = new TaskCompletionSource<bool>(); + 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<bool> tcs = null; + if (CurrentModalNavigationTask != null && !CurrentModalNavigationTask.IsCompleted) + { + var previousTask = CurrentModalNavigationTask; + tcs = new TaskCompletionSource<bool>(); + 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<bool> tcs) + { + if (shouldWait) + { + tcs = tcs ?? new TaskCompletionSource<bool>(); + 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 +{ + /// <summary> + /// Base class for platform-specific effect classes. + /// </summary> + public abstract class PlatformEffect : PlatformEffect<EvasObject, EvasObject> + { + } +} 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<ActivityIndicator, EProgressBar> + { + static readonly EColor s_defaultColor = EColor.Black; + + public ActivityIndicatorRenderer() + { + RegisterPropertyHandler(ActivityIndicator.ColorProperty, UpdateColor); + RegisterPropertyHandler(ActivityIndicator.IsRunningProperty, UpdateIsRunning); + } + + protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> e) + { + if (Control == null) + { + var ac = new EProgressBar(Forms.Context.MainWindow) + { + Style = "process_medium", + IsPulseMode = true, + }; + SetNativeControl(ac); + } + + if (e.OldElement != null) + { + } + + if (e.NewElement != null) + { + } + + base.OnElementChanged(e); + } + + void UpdateColor() + { + Control.Color = (Element.Color == Color.Default) ? s_defaultColor : Element.Color.ToNative(); + } + + void UpdateIsRunning() + { + if (Element.IsRunning) + { + Control.PlayPulse(); + } + else + { + Control.StopPulse(); + } + } + + }; +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs new file mode 100644 index 00000000..7a9f756d --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/BoxViewRenderer.cs @@ -0,0 +1,61 @@ +using System.ComponentModel; +using EColor = ElmSharp.Color; +using ERectangle = ElmSharp.Rectangle; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class BoxViewRenderer : + VisualElementRenderer<BoxView> + { + static readonly EColor s_defaultColor = EColor.Transparent; + + ERectangle _control; + + public BoxViewRenderer() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e) + { + if (_control == null) + { + _control = new ERectangle(Forms.Context.MainWindow); + SetNativeControl(_control); + } + + if (e.OldElement != null) + { + } + + if (e.NewElement != null) + { + UpdateColor(); + } + + base.OnElementChanged(e); + } + + void UpdateColor() + { + Color colorToSet = Element.Color; + + if (colorToSet == Color.Default) + { + colorToSet = Element.BackgroundColor; + } + + _control.Color = (colorToSet == Color.Default) ? s_defaultColor : colorToSet.ToNative(); + } + + protected override void OnElementPropertyChanged(object sender, + PropertyChangedEventArgs e) + { + if (e.PropertyName == BoxView.ColorProperty.PropertyName || + e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + { + UpdateColor(); + } + base.OnElementPropertyChanged(sender, e); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs new file mode 100644 index 00000000..a34df549 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ButtonRenderer.cs @@ -0,0 +1,95 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ButtonRenderer : ViewRenderer<Button, Native.Button> + { + static readonly EColor s_defaultTextColor = EColor.White; + + public ButtonRenderer() + { + RegisterPropertyHandler(Button.TextProperty, UpdateText); + RegisterPropertyHandler(Button.FontFamilyProperty, UpdateText); + RegisterPropertyHandler(Button.FontSizeProperty, UpdateText); + RegisterPropertyHandler(Button.FontAttributesProperty, UpdateText); + RegisterPropertyHandler(Button.TextColorProperty, UpdateTextColor); + RegisterPropertyHandler(Button.ImageProperty, UpdateBitmap); + RegisterPropertyHandler(Button.BorderColorProperty, UpdateBorder); + RegisterPropertyHandler(Button.BorderRadiusProperty, UpdateBorder); + RegisterPropertyHandler(Button.BorderWidthProperty, UpdateBorder); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Button> e) + { + if (Control == null) + { + var button = new Native.Button(Forms.Context.MainWindow) + { + PropagateEvents = false, + }; + SetNativeControl(button); + } + + if (e.OldElement != null) + { + Control.Clicked -= ButtonClickedHandler; + } + + if (e.NewElement != null) + { + Control.Clicked += ButtonClickedHandler; + } + + base.OnElementChanged(e); + } + + protected override Size MinimumSize() + { + return new Size(Control.MinimumWidth, Control.MinimumHeight); + } + + void ButtonClickedHandler(object sender, EventArgs e) + { + IButtonController btn = Element as IButtonController; + if (btn != null) + { + btn.SendClicked(); + } + } + + void UpdateText() + { + Control.Text = Element.Text ?? ""; + Control.FontSize = Element.FontSize; + Control.FontAttributes = Element.FontAttributes; + Control.FontFamily = Element.FontFamily; + } + + void UpdateTextColor() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + void UpdateBitmap() + { + if (!string.IsNullOrEmpty(Element.Image)) + { + Control.Image = new Native.Image(Control); + Control.Image.LoadFromImageSourceAsync(Element.Image); + } + else + { + Control.Image = null; + } + } + + void UpdateBorder() + { + /* The simpler way is to create some specialized theme for button in + * tizen-theme + */ + // TODO: implement border handling + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs new file mode 100644 index 00000000..812b8abb --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/CarouselPageRenderer.cs @@ -0,0 +1,259 @@ +using System; +using ElmSharp; +using EColor = ElmSharp.Color; +using ERectangle = ElmSharp.Rectangle; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Renderer of a CarouselPage widget. + /// </summary> + public class CarouselPageRenderer : VisualElementRenderer<CarouselPage>, IVisualElementRenderer + { + /// <summary> + /// The minimum length of a swipe to be recognized as a page switching command, in screen pixels unit. + /// </summary> + public static readonly double s_minimumSwipeLengthX = 200.0; + + // Different levels of "difficulty" in making a valid swipe gesture, determined by a maximum absolute value + // of an angle between a line formed by the swipe gesture and the horizontal axis, in arc degrees: + public static readonly double s_challengeEasyArcDegrees = 25.0; + public static readonly double s_challengeComfortableArcDegrees = 20.0; + public static readonly double s_challengeStandardArcDegrees = 15.0; + public static readonly double s_challengeHardArcDegrees = 10.0; + + /// <summary> + /// The maximum allowed angle between a line formed by the swipe gesture and the horizontal axis, in arc degrees. + /// The gesture will be recognized as a page switching command if its angle does not exceed this value. + /// </summary> + public static readonly double s_thresholdSwipeArcDegrees = s_challengeComfortableArcDegrees; + + /// <summary> + /// The tangent of a maximum allowed angle between the swipe line and the horizontal axis. + /// </summary> + public static readonly double s_thresholdSwipeTangent = Math.Tan(s_thresholdSwipeArcDegrees * (Math.PI / 180.0)); + + // A master container for the entire widget: + protected Box _box; + + // Used for grabbing gestures over the entire screen, even if Page is smaller than it: + protected ERectangle _filler; + + protected GestureLayer _gestureLayer; + protected EvasObject _page; + + /// <summary> + /// The default constructor. + /// </summary> + public CarouselPageRenderer() + { + } + + /// <summary> + /// Invoked whenever the CarouselPage element has been changed in Xamarin. + /// </summary> + /// <param name="e">Event parameters.</param> + protected override void OnElementChanged(ElementChangedEventArgs<CarouselPage> e) + { + if (NativeView == null) + { + // Creates an overlaying box which serves as a container + // for both page and a gesture handling layer: + _box = new Box(Forms.Context.MainWindow) + { + IsHorizontal = false, + }; + _box.SetAlignment(-1, -1); + _box.SetWeight(1, 1); + _box.Show(); + + // Disallows the Box to lay out its contents. They will be laid out manually, + // because the page has to overlay the conformant rectangle. By default + // Box will lay its contents in a stack. Applying an empty method disables it: + _box.SetLayoutCallback(() => { + ResizeContentsToFullScreen(); + }); + + // Creates a Rectangle used for ensuring that the gestures will get recognized: + _filler = new ERectangle(Forms.Context.MainWindow) + { + Color = EColor.Transparent, + }; + _filler.SetAlignment(-1, -1); + _filler.SetWeight(1, 1); + _filler.Show(); + _box.PackEnd(_filler); + + // Creates a GestureLayer used for swipe gestures recognition and attaches it to the Box: + _gestureLayer = new GestureLayer(_box); + _gestureLayer.Attach(_box); + AddLineGestureHandler(); + + SetNativeControl(_box); + } + + if (e.OldElement != null) + { + Element.CurrentPageChanged -= OnCurrentPageChanged; + } + + if (e.NewElement != null) + { + Element.CurrentPageChanged += OnCurrentPageChanged; + } + + // If pages have been added to the Xamarin widget and the user has not explicitly + // marked one of them to be displayed, displays the first one: + if (_page == null && Element.Children.Count > 0) + { + DisplayPage(Element.Children[0]); + } + + base.OnElementChanged(e); + } + + /// <summary> + /// Called just before the associated element is deleted. + /// </summary> + /// <param name="disposing">True if the memory release was requested on demand.</param> + protected override void Dispose(bool disposing) + { + if (_box != null) + { + Element.CurrentPageChanged -= OnCurrentPageChanged; + + // Unpacks the page from the box to prevent it from being disposed of prematurely: + _box.UnPack(_page); + + _box.Unrealize(); + _box = null; + } + + base.Dispose(disposing); + } + + /// <summary> + /// Handles the process of switching between the displayed pages. + /// </summary> + /// <param name="sender">An object originating the request</param> + /// <param name="ea">Additional arguments to the event handler</param> + void OnCurrentPageChanged(object sender, EventArgs ea) + { + if (_page != null) + { + _page.Hide(); + _box.UnPack(_page); + } + + DisplayPage(Element.CurrentPage); + ResizeContentsToFullScreen(); + } + + /// <summary> + /// Gets the index of the currently displayed page in Element.Children collection. + /// </summary> + /// <returns>An int value representing the index of the page currently displayed, + /// or -1 if no page is being displayed currently.</returns> + int GetCurrentPageIndex() + { + int index = -1; + for (int k = 0; k < Element.Children.Count; ++k) + { + if (Element.Children[k] == Element.CurrentPage) + { + index = k; + break; + } + } + + return index; + } + + /// <summary> + /// Resizes the widget's contents to utilize all the available screen space. + /// </summary> + void ResizeContentsToFullScreen() + { + // Box's geometry should match Forms.Context.MainWindow's geometry + // minus the space occupied by the top toolbar. + // Applies Box's geometry to both displayed page and conformant rectangle: + _filler.Geometry = _page.Geometry = _box.Geometry; + } + + /// <summary> + /// Adds the feature of recognizing swipes to the GestureLayer. + /// </summary> + void AddLineGestureHandler() + { + _gestureLayer.SetLineCallback(GestureLayer.GestureState.End, (line) => { + double horizontalDistance = line.X2 - line.X1; + double verticalDistance = line.Y2 - line.Y1; + + // Determines whether the movement is long enough to be considered a swipe: + bool isLongEnough = (Math.Abs(horizontalDistance) >= s_minimumSwipeLengthX); + + // Determines whether the movement is horizontal enough to be considered as a swipe: + // The swipe arc's tangent value (v/h) needs to be lesser than or equal to the threshold value. + // This approach allows for getting rid of computationally heavier atan2() function. + double angleTangent = Math.Abs(verticalDistance) / horizontalDistance; + bool isDirectionForward = (angleTangent < 0); + + // Determines whether the movement has been recognized as a valid swipe: + bool isSwipeMatching = (isLongEnough && (Math.Abs(angleTangent) <= s_thresholdSwipeTangent)); + + if (isSwipeMatching) + { + // TODO: Unsure whether changes made via ItemsSource/ItemTemplate properties will be handled correctly this way. + // If not, it should be implemented in another method. + if (isDirectionForward) + { + // Tries to switch the page to the next one: + int currentPageIndex = GetCurrentPageIndex(); + if (currentPageIndex < Element.Children.Count - 1) + { + // Sets the current page to the next one: + Element.CurrentPage = Element.Children[currentPageIndex + 1]; + } + else + { + // Reacts to the case of forward-swiping when the last page is already being displayed: + Log.Debug("CarouselPage: Displaying the last page already - can not revolve further."); + + // Note (TODO): Once we have a more sophisticated renderer able to e.g. display the animation + // of revolving Pages or at least indicate current overall position, some visual feedback + // should be provided here for the user who has haplessly tried to access a nonexistent page. + } + } + else + { + // Tries to switch the page to the previous one: + int currentPageIndex = GetCurrentPageIndex(); + if (currentPageIndex > 0) + { + // Sets the current page to the previous one: + Element.CurrentPage = Element.Children[currentPageIndex - 1]; + } + else + { + // Reacts to the case of backward-swiping when the first page is already being displayed: + Log.Debug("CarouselPage: The first page is already being displayed - can not revolve further."); + + // Note (TODO): (The same as in case of scrolling forwards) + } + } + } + }); + } + + void DisplayPage(ContentPage p) + { + _page = Platform.GetOrCreateRenderer(p).NativeView; + _page.SetAlignment(-1, -1); + _page.SetWeight(1, 1); + _page.Show(); + _box.PackEnd(_page); + } + + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs new file mode 100644 index 00000000..c4f79653 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ContentPageRenderer.cs @@ -0,0 +1,65 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Renderer of ContentPage. + /// </summary> + public class ContentPageRenderer : VisualElementRenderer<ContentPage> + { + /// <summary> + /// Native control which holds the contents. + /// </summary> + Native.ContentPage _page; + + /// <summary> + /// Default constructor. + /// </summary> + public ContentPageRenderer() + { + RegisterPropertyHandler(Page.BackgroundImageProperty, UpdateBackgroundImage); + RegisterPropertyHandler(Page.TitleProperty, UpdateTitle); + } + + protected override void OnElementChanged(ElementChangedEventArgs<ContentPage> e) + { + if (null == _page) + { + _page = new Native.ContentPage(Forms.Context.MainWindow); + _page.LayoutUpdated += new EventHandler<Native.LayoutEventArgs>(OnLayoutUpdated); + SetNativeControl(_page); + } + + base.OnElementChanged(e); + } + + protected override void UpdateBackgroundColor() + { + // base.UpdateBackgroundColor() is not called on purpose, we don't want the regular background setting + if (Element.BackgroundColor.IsDefault || Element.BackgroundColor.A == 0) + _page.Color = EColor.Transparent; + else + _page.Color = Element.BackgroundColor.ToNative(); + } + + void UpdateBackgroundImage() + { + if (string.IsNullOrWhiteSpace(Element.BackgroundImage)) + _page.File = null; + else + _page.File = ResourcePath.GetPath(Element.BackgroundImage); + } + + void UpdateTitle() + { + _page.Title = Element.Title; + } + + void OnLayoutUpdated(object sender, Native.LayoutEventArgs e) + { + DoLayout(e); + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs new file mode 100644 index 00000000..aed7b9d6 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/DatePickerRenderer.cs @@ -0,0 +1,77 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class DatePickerRenderer : ViewRenderer<DatePicker, Native.Button> + { + //TODO need to add internationalization support + const string DialogTitle = "Choose Date"; + static readonly EColor s_defaultTextColor = EColor.White; + + public DatePickerRenderer() + { + RegisterPropertyHandler(DatePicker.DateProperty, UpdateDate); + RegisterPropertyHandler(DatePicker.FormatProperty, UpdateDate); + RegisterPropertyHandler(DatePicker.TextColorProperty, UpdateTextColor); + } + + protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e) + { + if (Control == null) + { + var button = new Native.Button(Forms.Context.MainWindow); + SetNativeControl(button); + } + + if (e.OldElement != null) + { + Control.Clicked -= ButtonClickedHandler; + } + + if (e.NewElement != null) + { + Control.Clicked += ButtonClickedHandler; + } + + base.OnElementChanged(e); + } + + void ButtonClickedHandler(object sender, EventArgs e) + { + Native.DateTimePickerDialog dialog = new Native.DateTimePickerDialog(Forms.Context.MainWindow) + { + Title = DialogTitle + }; + + dialog.InitializeDatePicker(Element.Date, Element.MinimumDate, Element.MaximumDate); + dialog.DateTimeChanged += DialogDateTimeChangedHandler; + dialog.Dismissed += DialogDismissedHandler; + dialog.Show(); + } + + void DialogDateTimeChangedHandler(object sender, Native.DateChangedEventArgs dcea) + { + Element.Date = dcea.NewDate; + Control.Text = dcea.NewDate.ToString(Element.Format); + } + + void DialogDismissedHandler(object sender, EventArgs e) + { + var dialog = sender as Native.DateTimePickerDialog; + dialog.DateTimeChanged -= DialogDateTimeChangedHandler; + dialog.Dismissed -= DialogDismissedHandler; + } + + void UpdateDate() + { + Control.Text = Element.Date.ToString(Element.Format); + } + + void UpdateTextColor() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs new file mode 100644 index 00000000..0f214636 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/EditorRenderer.cs @@ -0,0 +1,88 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class EditorRenderer : ViewRenderer<Editor, Native.Entry> + { + static readonly EColor s_defaultTextColor = EColor.Black; + + public EditorRenderer() + { + RegisterPropertyHandler(Editor.TextProperty, UpdateText); + RegisterPropertyHandler(Editor.TextColorProperty, UpdateTextColor); + RegisterPropertyHandler(Editor.FontSizeProperty, UpdateFontSize); + RegisterPropertyHandler(Editor.FontFamilyProperty, UpdateFontFamily); + RegisterPropertyHandler(Editor.FontAttributesProperty, UpdateFontAttributes); + RegisterPropertyHandler(Editor.KeyboardProperty, UpdateKeyboard); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Editor> e) + { + if (Control == null) + { + var entry = new Native.Entry(Forms.Context.MainWindow) + { + IsSingleLine = false, + PropagateEvents = false, + }; + SetNativeControl(entry); + } + + if (e.OldElement != null) + { + Control.TextChanged -= TextChanged; + Control.Unfocused -= Completed; + } + + if (e.NewElement != null) + { + Control.TextChanged += TextChanged; + Control.Unfocused += Completed; + } + + base.OnElementChanged(e); + } + + void TextChanged(object sender, EventArgs e) + { + Element.Text = ((Native.Entry)sender).Text; + } + + void Completed(object sender, EventArgs e) + { + Element.SendCompleted(); + } + + void UpdateText() + { + Control.Text = Element.Text; + } + + void UpdateTextColor() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + void UpdateFontSize() + { + Control.FontSize = Element.FontSize; + } + + void UpdateFontFamily() + { + Control.FontFamily = Element.FontFamily; + } + + void UpdateFontAttributes() + { + Control.FontAttributes = Element.FontAttributes; + } + + void UpdateKeyboard() + { + Control.Keyboard = Element.Keyboard.ToNative(); + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs new file mode 100644 index 00000000..95828c04 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/EntryRenderer.cs @@ -0,0 +1,127 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class EntryRenderer : ViewRenderer<Entry, Native.Entry>, IDisposable + { + static readonly EColor s_defaultTextColor = EColor.Black; + + static readonly EColor s_defaultPlaceholderColor = EColor.Gray; + + public EntryRenderer() + { + RegisterPropertyHandler(Entry.IsPasswordProperty, UpdateIsPassword); + RegisterPropertyHandler(Entry.TextProperty, UpdateText); + RegisterPropertyHandler(Entry.TextColorProperty, UpdateTextColor); + RegisterPropertyHandler(Entry.FontSizeProperty, UpdateFontSize); + RegisterPropertyHandler(Entry.FontFamilyProperty, UpdateFontFamily); + RegisterPropertyHandler(Entry.FontAttributesProperty, UpdateFontAttributes); + RegisterPropertyHandler(Entry.HorizontalTextAlignmentProperty, UpdateHorizontalTextAlignment); + RegisterPropertyHandler(Entry.KeyboardProperty, UpdateKeyboard); + RegisterPropertyHandler(Entry.PlaceholderProperty, UpdatePlaceholder); + RegisterPropertyHandler(Entry.PlaceholderColorProperty, UpdatePlaceholderColor); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) + { + if (Control == null) + { + var entry = new Native.Entry(Forms.Context.MainWindow) + { + IsSingleLine = true, + PropagateEvents = false, + }; + SetNativeControl(entry); + } + + if (e.OldElement != null) + { + Control.TextChanged -= EntryChangedHandler; + Control.Activated -= EntryCompletedHandler; + } + + if (e.NewElement != null) + { + Control.TextChanged += EntryChangedHandler; + Control.Activated += EntryCompletedHandler; + } + + base.OnElementChanged(e); + } + + protected override void Dispose(bool disposing) + { + if (null != Control) + { + Control.TextChanged -= EntryChangedHandler; + Control.Activated -= EntryCompletedHandler; + } + + base.Dispose(disposing); + } + + void EntryChangedHandler(object sender, EventArgs e) + { + Element.Text = Control.Text; + } + + void EntryCompletedHandler(object sender, EventArgs e) + { + //TODO Consider if any other object should overtake focus + Control.SetFocus(false); + + ((IEntryController)Element).SendCompleted(); + } + + void UpdateIsPassword() + { + Control.IsPassword = Element.IsPassword; + } + + void UpdateText() + { + Control.Text = Element.Text; + } + + void UpdateTextColor() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + void UpdateFontSize() + { + Control.FontSize = Element.FontSize; + } + + void UpdateFontFamily() + { + Control.FontFamily = Element.FontFamily; + } + + void UpdateFontAttributes() + { + Control.FontAttributes = Element.FontAttributes; + } + + void UpdateHorizontalTextAlignment() + { + Control.HorizontalTextAlignment = Element.HorizontalTextAlignment.ToNative(); + } + + void UpdateKeyboard() + { + Control.Keyboard = Element.Keyboard.ToNative(); + } + + void UpdatePlaceholder() + { + Control.Placeholder = Element.Placeholder; + } + + void UpdatePlaceholderColor() + { + Control.PlaceholderColor = Element.PlaceholderColor.IsDefault ? s_defaultPlaceholderColor : Element.PlaceholderColor.ToNative(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs new file mode 100644 index 00000000..4e11c277 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/EvasObjectWrapperRenderer.cs @@ -0,0 +1,32 @@ +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class EvasObjectWrapperRenderer : VisualElementRenderer<EvasObjectWrapper> + { + protected override void OnElementChanged(ElementChangedEventArgs<EvasObjectWrapper> e) + { + if (NativeView == null) + { + SetNativeControl(Element.EvasObject); + } + + base.OnElementChanged(e); + } + + protected override ESize Measure(int availableWidth, int availableHeight) + { + if (Element?.MeasureDelegate == null) + { + return base.Measure(availableWidth, availableHeight); + } + + // The user has specified a different implementation of MeasureDelegate + ESize? result = Element.MeasureDelegate(this, availableWidth, availableHeight); + + // If the delegate returns a ElmSharp.Size, we use it; if it returns null, + // fall back to the default implementation + return result ?? base.Measure(availableWidth, availableHeight); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs new file mode 100644 index 00000000..0684c4ac --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/FrameRenderer.cs @@ -0,0 +1,124 @@ +using ElmSharp; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class FrameRenderer : ViewRenderer<Frame, Native.Canvas> + { + const int _thickness = 2; + const int _shadow_shift = 2; + const int _shadow_thickness = _thickness + 2; + + static readonly EColor s_DefaultColor = EColor.Black; + static readonly EColor s_ShadowColor = EColor.FromRgba(80, 80, 80, 50); + + Polygon _shadow = null; + Polygon _frame = null; + + public FrameRenderer() + { + RegisterPropertyHandler(Frame.OutlineColorProperty, UpdateColor); + RegisterPropertyHandler(Frame.HasShadowProperty, UpdateShadowVisibility); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Frame> e) + { + if (Control == null) + { + SetNativeControl(new Native.Canvas(Forms.Context.MainWindow)); + + _shadow = new Polygon(NativeView); + _shadow.Color = s_ShadowColor; + Control.Children.Add(_shadow); + + _frame = new Polygon(NativeView); + _frame.Show(); + Control.Children.Add(_frame); + } + + if (e.OldElement != null) + { + Control.LayoutUpdated -= OnLayoutUpdated; + } + + if (e.NewElement != null) + { + Control.LayoutUpdated += OnLayoutUpdated; + } + + base.OnElementChanged(e); + } + + void OnLayoutUpdated(object sender, Native.LayoutEventArgs e) + { + UpdateGeometry(); + // TODO: why is this DoLayout() required? + if (Element.Content != null) + base.DoLayout(e); + } + + void UpdateGeometry() + { + var geometry = NativeView.Geometry; + DrawFrame(_frame, + geometry.X, + geometry.Y, + geometry.Right, + geometry.Bottom, + _thickness + ); + DrawFrame(_shadow, + geometry.X + _shadow_shift, + geometry.Y + _shadow_shift, + geometry.Right - _thickness + _shadow_shift + _shadow_thickness, + geometry.Bottom - _thickness + _shadow_shift + _shadow_thickness, + _shadow_thickness + ); + } + + void DrawFrame(Polygon frame, int left, int top, int right, int bottom, int thickness) + { + frame.ClearPoints(); + if (left + thickness >= right || top + thickness >= bottom) + { + if (left >= right || top >= bottom) + return; + // shape reduces to a rectangle + frame.AddPoint(left, top); + frame.AddPoint(right, top); + frame.AddPoint(right, bottom); + frame.AddPoint(left, bottom); + return; + } + // outside edge + frame.AddPoint(left, top); + frame.AddPoint(right, top); + frame.AddPoint(right, bottom); + frame.AddPoint(left, bottom); + frame.AddPoint(left, top + thickness); + // and inside edge + frame.AddPoint(left + thickness, top + thickness); + frame.AddPoint(left + thickness, bottom - thickness); + frame.AddPoint(right - thickness, bottom - thickness); + frame.AddPoint(right - thickness, top + thickness); + frame.AddPoint(left, top + thickness); + } + + void UpdateColor() + { + if (Element.OutlineColor.IsDefault) + _frame.Color = s_DefaultColor; + else + _frame.Color = Element.OutlineColor.ToNative(); + } + + void UpdateShadowVisibility() + { + if (Element.HasShadow) + _shadow.Show(); + else + _shadow.Hide(); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs new file mode 100644 index 00000000..4702fd37 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/IVisualElementRenderer.cs @@ -0,0 +1,39 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Base interface for VisualElement renderer. + /// </summary> + public interface IVisualElementRenderer : IRegisterable, IDisposable + { + /// <summary> + /// Gets the VisualElement associated with this renderer. + /// </summary> + /// <value>The VisualElement.</value> + VisualElement Element + { + get; + } + + /// <summary> + /// Gets the native view associated with this renderer. + /// </summary> + /// <value>The native view.</value> + EvasObject NativeView + { + get; + } + + /// <summary> + /// Sets the VisualElement associated with this renderer. + /// </summary> + /// <param name="element">New element.</param> + void SetElement(VisualElement element); + + SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint); + + void UpdateNativeGeometry(); + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs new file mode 100644 index 00000000..ca06a669 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ImageRenderer.cs @@ -0,0 +1,105 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ImageRenderer : ViewRenderer<Image, Native.Image> + { + public ImageRenderer() + { + RegisterPropertyHandler(Image.SourceProperty, UpdateSource); + RegisterPropertyHandler(Image.AspectProperty, UpdateAspect); + RegisterPropertyHandler(Image.IsOpaqueProperty, UpdateIsOpaque); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Image> e) + { + if (Control == null) + { + var image = new Native.Image(Forms.Context.MainWindow); + SetNativeControl(image); + } + + base.OnElementChanged(e); + } + + async void UpdateSource() + { + ImageSource source = Element.Source; + + ((IImageController)Element).SetIsLoading(true); + + if (Control != null) + { + bool success = await Control.LoadFromImageSourceAsync(source); + if (!IsDisposed && success) + ((IVisualElementController)Element).NativeSizeChanged(); + } + + if (!IsDisposed) + ((IImageController)Element).SetIsLoading(false); + } + + void UpdateAspect() + { + Control.Aspect = Element.Aspect; + } + + void UpdateIsOpaque() + { + Control.IsOpaque = Element.IsOpaque; + } + } + + public interface IImageSourceHandler : IRegisterable + { + Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)); + } + + public sealed class FileImageSourceHandler : IImageSourceHandler + { + public Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) + { + var filesource = imageSource as FileImageSource; + if (filesource != null) + { + string file = filesource.File; + if (!string.IsNullOrEmpty(file)) + return image.LoadAsync(ResourcePath.GetPath(file), cancelationToken); + } + return Task.FromResult<bool>(false); + } + } + + public sealed class StreamImageSourceHandler : IImageSourceHandler + { + public async Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) + { + var streamsource = imageSource as StreamImageSource; + if (streamsource != null && streamsource.Stream != null) + { + using (var streamImage = await ((IStreamImageSource)streamsource).GetStreamAsync(cancelationToken)) + { + if (streamImage != null) + return await image.LoadAsync(streamImage, cancelationToken); + } + } + return false; + } + } + + public sealed class UriImageSourceHandler : IImageSourceHandler + { + public Task<bool> LoadImageAsync(Native.Image image, ImageSource imageSource, CancellationToken cancelationToken = default(CancellationToken)) + { + var urisource = imageSource as UriImageSource; + if (urisource != null && urisource.Uri != null) + { + return image.LoadAsync(urisource.Uri, cancelationToken); + } + + return Task.FromResult<bool>(false); + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs new file mode 100644 index 00000000..5a4744fc --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/LabelRenderer.cs @@ -0,0 +1,103 @@ +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + + public class LabelRenderer : ViewRenderer<Label, Native.Label> + { + static readonly EColor s_defaultBackgroundColor = EColor.Transparent; + static readonly EColor s_defaultForegroundColor = EColor.Black; + static readonly EColor s_defaultTextColor = s_defaultForegroundColor; + + public LabelRenderer() + { + RegisterPropertyHandler(Label.TextProperty, () => Control.Text = Element.Text); + RegisterPropertyHandler(Label.TextColorProperty, UpdateTextColor); + // FontProperty change is called also for FontSizeProperty, FontFamilyProperty and FontAttributesProperty change + RegisterPropertyHandler(Label.FontProperty, UpdateFontProperties); + RegisterPropertyHandler(Label.LineBreakModeProperty, UpdateLineBreakMode); + RegisterPropertyHandler(Label.HorizontalTextAlignmentProperty, UpdateTextAlignment); + RegisterPropertyHandler(Label.VerticalTextAlignmentProperty, UpdateTextAlignment); + RegisterPropertyHandler(Label.FormattedTextProperty, () => Control.FormattedText = ConvertFormattedText(Element.FormattedText)); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Label> e) + { + if (Control == null) + { + var label = new Native.Label(Forms.Context.MainWindow); + base.SetNativeControl(label); + } + + if (e.OldElement != null) + { + } + + if (e.NewElement != null) + { + } + + base.OnElementChanged(e); + } + + protected override Size MinimumSize() + { + return new Size(Control.MinimumWidth, Control.MinimumHeight); + } + + Native.FormattedString ConvertFormattedText(FormattedString formattedString) + { + if (formattedString == null) + { + return null; + } + + Native.FormattedString nativeString = new Native.FormattedString(); + + foreach (var element in formattedString.Spans) + { + Native.Span span = new Native.Span(); + span.FormattedText = element.Text; + span.FontAttributes = element.FontAttributes; + span.FontFamily = element.FontFamily; + span.FontSize = element.FontSize; + span.ForegroundColor = element.ForegroundColor.IsDefault ? s_defaultForegroundColor : element.ForegroundColor.ToNative(); + span.BackgroundColor = element.BackgroundColor.IsDefault ? s_defaultBackgroundColor : element.BackgroundColor.ToNative(); + + nativeString.Spans.Add(span); + } + + return nativeString; + } + + void UpdateTextColor() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + void UpdateTextAlignment() + { + Control.HorizontalTextAlignment = Element.HorizontalTextAlignment.ToNative(); + Control.VerticalTextAlignment = Element.VerticalTextAlignment.ToNative(); + } + + void UpdateFontProperties() + { + Control.FontSize = Element.FontSize; + Control.FontAttributes = Element.FontAttributes; + Control.FontFamily = Element.FontFamily; + } + + void UpdateLineBreakMode() + { + if (Element.LineBreakMode == LineBreakMode.CharacterWrap) + Control.LineBreakMode = Native.LineBreakMode.CharacterWrap; + else if (Element.LineBreakMode == LineBreakMode.WordWrap) + Control.LineBreakMode = Native.LineBreakMode.WordWrap; + else if (Element.LineBreakMode == LineBreakMode.NoWrap) + Control.LineBreakMode = Native.LineBreakMode.NoWrap; + else + Control.LineBreakMode = Native.LineBreakMode.MixedWrap; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs new file mode 100644 index 00000000..61421c79 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/LayoutRenderer.cs @@ -0,0 +1,57 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Renderer of a Layout. + /// </summary> + public class LayoutRenderer : ViewRenderer<Layout, Native.Canvas> + { + /// <summary> + /// Default constructor. + /// </summary> + public LayoutRenderer() + { + } + + protected override void UpdateLayout() + { + // in case of layouts we need to make sure that the minimum size of the native control is updated + // this is important in case of ScrollView, when it's content is likely to be wider/higher than the window + // EFL does not allow control to be larger than the window if it's minimum size is smaller than window dimensions + ScrollView scrollView = Element.Parent as ScrollView; + if (scrollView != null) + { + Size size = scrollView.ContentSize; + Control.MinimumWidth = ToNativeDimension(Math.Max(size.Width, scrollView.Content.Width)); + Control.MinimumHeight = ToNativeDimension(Math.Max(size.Height, scrollView.Content.Height)); + } + + base.UpdateLayout(); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Layout> e) + { + if (null == Control) + { + var canvas = new Native.Canvas(Forms.Context.MainWindow); + canvas.LayoutUpdated += OnLayoutUpdated; + SetNativeControl(canvas); + } + + base.OnElementChanged(e); + } + + protected override void Dispose(bool disposing) + { + Control.LayoutUpdated -= OnLayoutUpdated; + + base.Dispose(disposing); + } + + void OnLayoutUpdated(object sender, Native.LayoutEventArgs e) + { + DoLayout(e); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs new file mode 100644 index 00000000..5179d7e0 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ListViewRenderer.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Specialized; +using ElmSharp; +using EProgressBar = ElmSharp.ProgressBar; +using ERect = ElmSharp.Rect; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Renderer class for Xamarin ListView class. This provides necessary logic translating + /// Xamarin API to Tizen Native API. This is a derivate of a ViewRenderer base class. + /// This is a template class with two template parameters. First one is restricted to + /// Xamarin.Forms.View and can be accessed via property Element. This represent actual + /// xamarin view which represents control logic. Second one is restricted to ElmSharp.Widget + /// types, and can be accessed with Control property. This represents actual native control + /// which is used to draw control and realize xamarin forms api. + /// </summary> + public class ListViewRenderer : ViewRenderer<ListView, Native.ListView>, IDisposable + { + /// <summary> + /// Event handler for ScrollToRequested. + /// </summary> + readonly EventHandler<ScrollToRequestedEventArgs> _scrollToRequested; + + /// <summary> + /// Event handler for collection changed. + /// </summary> + readonly NotifyCollectionChangedEventHandler _collectionChanged; + + /// <summary> + /// Event handler for grouped collection changed. + /// </summary> + readonly NotifyCollectionChangedEventHandler _groupedCollectionChanged; + + /// <summary> + /// The _lastSelectedItem and _selectedItemChanging are used for realizing ItemTapped event. Since Xamarin + /// needs information only when an item has been taped, native handlers need to be agreagated + /// and NotifyRowTapped has to be realized with this. + /// </summary> + + GenListItem _lastSelectedItem = null; + int _selectedItemChanging = 0; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.ListViewRenderer"/> class. + /// Note that at this stage of construction renderer dose not have required native element. This should + /// only be used with xamarin engine. + /// </summary> + public ListViewRenderer() + { + _scrollToRequested = OnScrollToRequested; + _collectionChanged = OnCollectionChanged; + _groupedCollectionChanged = OnGroupedCollectionChanged; + + RegisterPropertyHandler(ListView.IsGroupingEnabledProperty, UpdateIsGroupingEnabled); + RegisterPropertyHandler(ListView.HasUnevenRowsProperty, UpdateHasUnevenRows); + RegisterPropertyHandler(ListView.RowHeightProperty, UpdateRowHeight); + RegisterPropertyHandler(ListView.HeaderProperty, UpdateHeader); + RegisterPropertyHandler(ListView.SelectedItemProperty, UpdateSelectedItem); + RegisterPropertyHandler(ListView.FooterProperty, UpdateFooter); + RegisterPropertyHandler(ListView.ItemsSourceProperty, UpdateSource); + RegisterPropertyHandler(ListView.FooterTemplateProperty, UpdateFooter); + RegisterPropertyHandler(ListView.HeaderTemplateProperty, UpdateHeader); + } + + /// <summary> + /// Invoked on creation of new ListView renderer. Handles the creation of a native + /// element and initialization of the renderer. + /// </summary> + /// <param name="e"><see cref="Xamarin.Forms.Platform.Tizen.ElementChangedEventArgs"/>.</param> + protected override void OnElementChanged(ElementChangedEventArgs<ListView> e) + { + if (Control == null) + { + SetNativeControl(new Native.ListView(Forms.Context.MainWindow)); + } + + if (e.OldElement != null) + { + e.OldElement.ScrollToRequested -= _scrollToRequested; + if (Element.IsGroupingEnabled) + { + e.OldElement.TemplatedItems.GroupedCollectionChanged -= _groupedCollectionChanged; + } + e.OldElement.TemplatedItems.CollectionChanged -= _collectionChanged; + Control.ItemSelected -= ListViewItemSelectedHandler; + Control.ItemUnselected -= ListViewItemUnselectedHandler; + } + + if (e.NewElement != null) + { + e.NewElement.ScrollToRequested += _scrollToRequested; + Element.TemplatedItems.CollectionChanged += _collectionChanged; + Control.ItemSelected += ListViewItemSelectedHandler; + Control.ItemUnselected += ListViewItemUnselectedHandler; + } + + base.OnElementChanged(e); + } + + /// <summary> + /// Handles the disposing of an existing renderer instance. Results in event handlers + /// being detached and a Dispose() method from base class (VisualElementRenderer) being invoked. + /// </summary> + /// <param name="disposing">A boolean flag passed to the invocation of base class' Dispose() method. + /// <c>True</c> if the memory release was requested on demand.</param> + protected override void Dispose(bool disposing) + { + Element.ScrollToRequested -= _scrollToRequested; + Element.TemplatedItems.CollectionChanged -= _collectionChanged; + Element.TemplatedItems.GroupedCollectionChanged -= _groupedCollectionChanged; + + base.Dispose(disposing); + } + + /// <summary> + /// Handles item selected event. Note that it has to handle selection also for grouping mode as well. + /// As a result of this method, ItemTapped event should be invoked in Xamarin. + /// </summary> + /// <param name="sender">A native list instance from which the event has originated.</param> + /// <param name="e">Argument associated with handler, it holds native item for which event was raised</param> + void ListViewItemSelectedHandler(object sender, GenListItemEventArgs e) + { + GenListItem item = e.Item; + + _lastSelectedItem = item; + + if (_selectedItemChanging == 0) + { + if (item != null) + { + int index = -1; + if (Element.IsGroupingEnabled) + { + Native.ListView.ItemContext itemContext = item.Data as Native.ListView.ItemContext; + if (itemContext.IsGroupItem) + { + return; + } + else + { + int groupIndex = (Element.TemplatedItems as System.Collections.IList).IndexOf(itemContext.ListOfSubItems); + int inGroupIndex = itemContext.ListOfSubItems.IndexOf(itemContext.Cell); + + ++_selectedItemChanging; + Element.NotifyRowTapped(groupIndex, inGroupIndex); + --_selectedItemChanging; + } + } + else + { + index = Element.TemplatedItems.IndexOf((item.Data as Native.ListView.ItemContext).Cell); + + ++_selectedItemChanging; + Element.NotifyRowTapped(index); + --_selectedItemChanging; + } + } + } + } + + /// <summary> + /// Handles item unselected event. + /// </summary> + /// <param name="sender">A native list instance from which the event has originated.</param> + /// <param name="e">Argument associated with handler, it holds native item for which event was raised</param> + void ListViewItemUnselectedHandler(object sender, GenListItemEventArgs e) + { + if (_selectedItemChanging == 0) + { + _lastSelectedItem = null; + } + } + + /// <summary> + /// This is method handles "scroll to" requests from xamarin events. + /// It allows for scrolling to specified item on list view. + /// </summary> + /// <param name="sender">A native list instance from which the event has originated.</param> + /// <param name="e">ScrollToRequestedEventArgs.</param> + void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e) + { + Cell cell; + int position; + var scrollArgs = (ITemplatedItemsListScrollToRequestedEventArgs)e; + + var templatedItems = Element.TemplatedItems; + if (Element.IsGroupingEnabled) + { + var results = templatedItems.GetGroupAndIndexOfItem(scrollArgs.Group, scrollArgs.Item); + if (results.Item1 == -1 || results.Item2 == -1) + return; + + var group = templatedItems.GetGroup(results.Item1); + cell = group[results.Item2]; + } + else + { + position = templatedItems.GetGlobalIndexOfItem(scrollArgs.Item); + cell = templatedItems[position]; + } + + Control.ApplyScrollTo(cell, e.Position, e.ShouldAnimate); + } + + /// <summary> + /// Helper class for managing proper postion of Header and Footer element. + /// Since both elements need to be implemented with ordinary list elements, + /// both header and footer are removed at first, then the list is being modified + /// and finally header and footer are prepended and appended to the list, respectively. + /// </summary> + class HeaderAndFooterHandler : IDisposable + { + VisualElement headerElement; + VisualElement footerElement; + + Native.ListView Control; + + public HeaderAndFooterHandler(Widget control) + { + Control = control as Native.ListView; + + if (Control.HasHeader()) + { + headerElement = Control.GetHeader(); + Control.RemoveHeader(); + } + if (Control.HasFooter()) + { + footerElement = Control.GetFooter(); + Control.RemoveFooter(); + } + } + + public void Dispose() + { + if (headerElement != null) + { + Control.SetHeader(headerElement); + } + if (footerElement != null) + { + Control.SetFooter(footerElement); + } + } + } + + /// <summary> + /// This method is called whenever something changes in list view data model. + /// Method will not be invoked for grouping mode, but for example event with + /// action reset will be handled here when switching between group and no-group mode. + /// </summary> + /// <param name="sender">TemplatedItemsList<ItemsView<Cell>, Cell>.</param> + /// <param name="e">NotifyCollectionChangedEventArgs.</param> + void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + using (new HeaderAndFooterHandler(Control)) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + Cell before = null; + if(e.NewStartingIndex + e.NewItems.Count < Element.TemplatedItems.Count) + { + before = Element.TemplatedItems[e.NewStartingIndex + e.NewItems.Count]; + } + Control.AddSource(e.NewItems, before); + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + Control.Remove(e.OldItems); + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + UpdateSource(); + } + } + } + + /// <summary> + /// This method is called whenever something changes in list view data model. + /// Method will be invoked for grouping mode, but some action can be also handled + /// by OnCollectionChanged handler. + /// </summary> + /// <param name="sender">TemplatedItemsList<ItemsView<Cell>, Cell>.</param> + /// <param name="e">NotifyCollectionChangedEventArgs.</param> + void OnGroupedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + using (new HeaderAndFooterHandler(Control)) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + TemplatedItemsList<ItemsView<Cell>,Cell> itemsGroup = sender as TemplatedItemsList<ItemsView<Cell>,Cell>; + Cell before = null; + if (e.NewStartingIndex + e.NewItems.Count < itemsGroup.Count) + { + before = itemsGroup[e.NewStartingIndex + e.NewItems.Count]; + } + Control.AddItemsToGroup(itemsGroup, e.NewItems, before); + } + else if (e.Action == NotifyCollectionChangedAction.Remove) + { + Control.Remove(e.OldItems); + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + Control.ResetGroup(sender as TemplatedItemsList<ItemsView<Cell>, Cell>); + } + } + } + + /// <summary> + /// Updates the source. + /// </summary> + void UpdateSource() + { + Control.Clear(); + Control.AddSource(Element.TemplatedItems); + } + + /// <summary> + /// Updates the header. + /// </summary> + void UpdateHeader() + { + if (Element.Header == null) + { + Control.SetHeader(null); + return; + } + + if (((IListViewController)Element).HeaderElement == null) + { + Device.StartTimer(new TimeSpan(0), () => + { + Control.SetHeader(((IListViewController)Element).HeaderElement as VisualElement); + return false; + }); + } + else + { + Control.SetHeader(((IListViewController)Element).HeaderElement as VisualElement); + } + } + + /// <summary> + /// Updates the footer. + /// </summary> + void UpdateFooter() + { + if (Element.Footer == null) + { + Control.SetFooter(null); + return; + } + + + if (((IListViewController)Element).FooterElement == null) + { + Device.StartTimer(new TimeSpan(0), () => + { + Control.SetFooter(((IListViewController)Element).FooterElement as VisualElement); + return false; + }); + } + else + { + Control.SetFooter(((IListViewController)Element).FooterElement as VisualElement); + } + } + + /// <summary> + /// Updates the has uneven rows. + /// </summary> + void UpdateHasUnevenRows() + { + Control.SetHasUnevenRows(Element.HasUnevenRows); + } + + /// <summary> + /// Updates the height of the row. + /// </summary> + void UpdateRowHeight() + { + Control.UpdateRealizedItems(); + } + + /// <summary> + /// Updates the is grouping enabled. + /// </summary> + /// <param name="initialize">If set to <c>true</c>, this method is invoked during initialization + /// (otherwise it will be invoked only after property changes).</param> + void UpdateIsGroupingEnabled(bool initialize) + { + Control.IsGroupingEnabled = Element.IsGroupingEnabled; + if (Element.IsGroupingEnabled) + { + Element.TemplatedItems.GroupedCollectionChanged += _groupedCollectionChanged; + } + else + { + Element.TemplatedItems.GroupedCollectionChanged -= _groupedCollectionChanged; + } + } + + /// <summary> + /// Method is used for programaticaly selecting choosen item. + /// </summary> + void UpdateSelectedItem() + { + if (_selectedItemChanging == 0) + { + if (Element.SelectedItem == null) + { + if (_lastSelectedItem != null) + { + _lastSelectedItem.IsSelected = false; + _lastSelectedItem = null; + } + } + else + { + var templatedItems = Element.TemplatedItems; + var results = templatedItems.GetGroupAndIndexOfItem(Element.SelectedItem); + if (results.Item1 != -1 && results.Item2 != -1) + { + var itemGroup = templatedItems.GetGroup(results.Item1); + var cell = itemGroup[results.Item2]; + + ++_selectedItemChanging; + Control.ApplySelectedItem(cell); + --_selectedItemChanging; + } + } + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs new file mode 100644 index 00000000..9da812e9 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/MasterDetailPageRenderer.cs @@ -0,0 +1,96 @@ +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class MasterDetailPageRenderer : VisualElementRenderer<MasterDetailPage> + { + Native.MasterDetailPage _mdpage; + + /// <summary> + /// Default constructor. + /// </summary> + public MasterDetailPageRenderer() + { + RegisterPropertyHandler("Master", UpdateMasterPage); + RegisterPropertyHandler("Detail", UpdateDetailPage); + RegisterPropertyHandler(MasterDetailPage.IsPresentedProperty, + UpdateIsPresented); + RegisterPropertyHandler(MasterDetailPage.MasterBehaviorProperty, + UpdateMasterBehavior); + RegisterPropertyHandler(MasterDetailPage.IsGestureEnabledProperty, + UpdateIsGestureEnabled); + } + + protected override void OnElementChanged(ElementChangedEventArgs<MasterDetailPage> e) + { + if (_mdpage == null) + { + _mdpage = new Native.MasterDetailPage(Forms.Context.MainWindow) + { + Master = GetNativePage(e.NewElement.Master), + Detail = GetNativePage(e.NewElement.Detail), + IsPresented = e.NewElement.IsPresented, + }; + + _mdpage.IsPresentedChanged += (sender, ev) => + { + Element.IsPresented = _mdpage.IsPresented; + }; + } + + if (e.OldElement != null) + { + (e.OldElement as IMasterDetailPageController).BackButtonPressed -= BackButtonPressedHandler; + } + + if (e.NewElement != null) + { + (e.NewElement as IMasterDetailPageController).BackButtonPressed += BackButtonPressedHandler; + } + + UpdateMasterBehavior(); + SetNativeControl(_mdpage); + + base.OnElementChanged(e); + } + + void BackButtonPressedHandler(object sender, BackButtonPressedEventArgs e) + { + if ((Element != null) && !Element.IsPresented) + { + Element.IsPresented = true; + e.Handled = true; + } + } + + EvasObject GetNativePage(Page page) + { + var pageRenderer = Platform.GetOrCreateRenderer(page); + return pageRenderer.NativeView; + } + + void UpdateMasterBehavior() { + _mdpage.MasterBehavior = Element.MasterBehavior; + } + + void UpdateMasterPage() + { + _mdpage.Master = GetNativePage(Element.Master); + } + + void UpdateDetailPage() + { + _mdpage.Detail = GetNativePage(Element.Detail); + } + + void UpdateIsPresented() + { + _mdpage.IsPresented = Element.IsPresented; + } + + void UpdateIsGestureEnabled() + { + _mdpage.IsGestureEnabled = Element.IsGestureEnabled; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs new file mode 100644 index 00000000..0d2ee2ed --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/NavigationPageRenderer.cs @@ -0,0 +1,390 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Xamarin.Forms.Internals; +using ElmSharp; +using EButton = ElmSharp.Button; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>, IDisposable, IVisualElementRenderer + { + Naviframe _naviFrame = null; + Page _previousPage = null; + TaskCompletionSource<bool> _currentTaskSource = null; + const string _partBackButton = "elm.swallow.prev_btn"; + const string _leftToolbar = "title_left_btn"; + const string _rightToolbar = "title_right_btn"; + const string _defaultToolbarIcon = "naviframe/drawers"; + const string _partTitle = "default"; + const string _styleBackButton = "naviframe/back_btn/default"; + readonly List<Widget> _naviItemContentPartList = new List<Widget>(); + ToolbarTracker _toolbarTracker = null; + + public NavigationPageRenderer() + { + } + + protected override void Dispose(bool disposing) + { + _naviFrame.AnimationFinished -= AnimationFinishedHandler; + base.Dispose(disposing); + } + + protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e) + { + if (_naviFrame == null) + { + _naviFrame = new Naviframe(Forms.Context.MainWindow); + _naviFrame.PreserveContentOnPop = true; + _naviFrame.DefaultBackButtonEnabled = true; + _naviFrame.AnimationFinished += AnimationFinishedHandler; + + SetNativeControl(_naviFrame); + } + + if (_toolbarTracker == null) + { + _toolbarTracker = new ToolbarTracker(); + _toolbarTracker.CollectionChanged += ToolbarTrackerOnCollectionChanged; + } + + if (e.OldElement != null) + { + var navigation = e.OldElement as INavigationPageController; + navigation.PopRequested -= PopRequestedHandler; + navigation.PopToRootRequested -= PopToRootRequestedHandler; + navigation.PushRequested -= PushRequestedHandler; + navigation.RemovePageRequested -= RemovePageRequestedHandler; + navigation.InsertPageBeforeRequested -= InsertPageBeforeRequestedHandler; + + var pageController = e.OldElement as IPageController; + pageController.InternalChildren.CollectionChanged -= PageCollectionChangedHandler; + } + + if (e.NewElement != null) + { + var navigation = e.NewElement as INavigationPageController; + navigation.PopRequested += PopRequestedHandler; + navigation.PopToRootRequested += PopToRootRequestedHandler; + navigation.PushRequested += PushRequestedHandler; + navigation.RemovePageRequested += RemovePageRequestedHandler; + navigation.InsertPageBeforeRequested += InsertPageBeforeRequestedHandler; + + var pageController = e.NewElement as IPageController; + pageController.InternalChildren.CollectionChanged += PageCollectionChangedHandler; + + foreach (Page page in pageController.InternalChildren) + { + _naviFrame.Push(Platform.GetOrCreateRenderer(page).NativeView, SpanTitle(page.Title)); + page.PropertyChanged += NavigationBarPropertyChangedHandler; + + UpdateHasNavigationBar(page); + } + + _toolbarTracker.Target = e.NewElement; + _previousPage = e.NewElement.CurrentPage; + } + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName) + { + (_previousPage as IPageController)?.SendDisappearing(); + _previousPage = Element.CurrentPage; + (_previousPage as IPageController)?.SendAppearing(); + } + } + + void PageCollectionChangedHandler(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + foreach (Page page in e.OldItems) + page.PropertyChanged -= NavigationBarPropertyChangedHandler; + if (e.NewItems != null) + foreach (Page page in e.NewItems) + page.PropertyChanged += NavigationBarPropertyChangedHandler; + } + + void ToolbarTrackerOnCollectionChanged(object sender, EventArgs eventArgs) + { + UpdateToolbarItem(Element.CurrentPage); + } + + void NavigationBarPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // this handler is invoked only for child pages (contained on a navigation stack) + if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName) + UpdateHasNavigationBar(sender as Page); + else if (e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName || + e.PropertyName == NavigationPage.BackButtonTitleProperty.PropertyName) + UpdateHasBackButton(sender as Page); + else if (e.PropertyName == Page.TitleProperty.PropertyName || + e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName || + e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName || + e.PropertyName == NavigationPage.TintProperty.PropertyName) + UpdateTitle(sender as Page); + } + + void UpdateHasNavigationBar(Page page) + { + NaviItem item = GetNaviItemForPage(page); + item.TitleBarVisible = (bool)page.GetValue(NavigationPage.HasNavigationBarProperty); + UpdateToolbarItem(page, item); + } + + void UpdateToolbarItem(Page page, NaviItem item = null) + { + if (item == null) + item = GetNaviItemForPage(page); + + if (_naviFrame.NavigationStack.Count == 0 || item == null || item != _naviFrame.NavigationStack.Last()) + return; + + item.SetPartContent(_leftToolbar, null, false); + item.SetPartContent(_rightToolbar, null, false); + + Native.Button rightButton = GetToolbarButtonIfExists(ToolbarItemOrder.Primary); + item.SetPartContent(_rightToolbar, rightButton); + + Native.Button leftButton = GetToolbarButtonIfExists(ToolbarItemOrder.Secondary); + if (leftButton == null) + UpdateHasBackButton(page, item); + else + item.SetPartContent(_leftToolbar, leftButton); + } + + void UpdateHasBackButton(Page page, NaviItem item = null) + { + if (item == null) + item = GetNaviItemForPage(page); + + EButton button = null; + + if ((bool)page.GetValue(NavigationPage.HasBackButtonProperty)) + button = CreateNavigationButton((string)page.GetValue(NavigationPage.BackButtonTitleProperty)); + item.SetPartContent(_partBackButton, button); + } + + void UpdateTitle(Page page) + { + NaviItem item = GetNaviItemForPage(page); + item.SetPartText(_partTitle, SpanTitle(page.Title)); + } + + string SpanTitle(string Title) + { + Native.Span span = new Native.Span { Text = Title }; + if (Element.BarTextColor != Color.Default) + { + span.ForegroundColor = Element.BarTextColor.ToNative(); + } + //TODO: changes only background of title not all bar + if (Element.BarBackgroundColor != Color.Default) + { + span.BackgroundColor = Element.BarBackgroundColor.ToNative(); + } + else if (Element.Tint != Color.Default) + { + //TODO: This is only for backward compatibility + //- remove when Tint is no longer in Xamarin API + span.BackgroundColor = Element.Tint.ToNative(); + } + return span.GetMarkupText(); + } + + EButton CreateNavigationButton(string text) + { + EButton button = new EButton(Forms.Context.MainWindow); + button.Clicked += (sender, e) => + { + if (!Element.SendBackButtonPressed()) + Forms.Context.Exit(); + }; + + button.Style = _styleBackButton; + button.Text = text; + + _naviItemContentPartList.Add(button); + button.Deleted += NaviItemPartContentDeletedHandler; + + return button; + } + + void NaviItemPartContentDeletedHandler(object sender, EventArgs e) + { + _naviItemContentPartList.Remove(sender as Widget); + } + + NaviItem GetNaviItemForPage(Page page) + { + NaviItem item = null; + + if (page != null) + { + IVisualElementRenderer renderer = Platform.GetRenderer(page); + if (renderer != null) + { + EvasObject content = renderer.NativeView; + + for (int i = _naviFrame.NavigationStack.Count - 1; i >= 0; --i) + if (_naviFrame.NavigationStack[i].Content == content) + { + item = _naviFrame.NavigationStack[i]; + break; + } + } + } + return item; + } + + Native.Button GetToolbarButtonIfExists(ToolbarItemOrder order) + { + ToolbarItem item = _toolbarTracker.ToolbarItems.Where( + (i => i.Order == order || + (order == ToolbarItemOrder.Primary && i.Order == ToolbarItemOrder.Default))) + .OrderBy(i => i.Priority) + .FirstOrDefault(); + + if (item != default(ToolbarItem)) + { + return GetToolbarButton(item); + } + return null; + } + + Native.Button GetToolbarButton(ToolbarItem item) + { + Native.Button button = new Native.Button(Forms.Context.MainWindow); + button.Clicked += (s, e) => + { + IMenuItemController control = item; + control.Activate(); + }; + button.Text = item.Text; + button.BackgroundColor = Xamarin.Forms.Color.Transparent.ToNative(); + + if (String.IsNullOrEmpty(item.Icon) && String.IsNullOrEmpty(item.Text)) + { + button.Style = _defaultToolbarIcon; + } + else + { + Native.Image iconImage = new Native.Image(Forms.Context.MainWindow); + iconImage.LoadFromImageSourceAsync(item.Icon); + button.Image = iconImage; + } + + return button; + } + + void PopRequestedHandler(object sender, NavigationRequestedEventArgs nre) + { + if ((Element as IPageController).InternalChildren.Count == _naviFrame.NavigationStack.Count) + { + if (nre.Animated) + { + _naviFrame.Pop(); + + _currentTaskSource = new TaskCompletionSource<bool>(); + nre.Task = _currentTaskSource.Task; + + // There is no TransitionFinished (AnimationFinished) event after Pop the last page + if (_naviFrame.NavigationStack.Count == 0) + CompleteCurrentNavigationTask(); + } + else + _naviFrame.NavigationStack.Last().Delete(); + } + } + + void PopToRootRequestedHandler(object sender, NavigationRequestedEventArgs nre) + { + List<NaviItem> copyOfStack = new List<NaviItem>(_naviFrame.NavigationStack); + copyOfStack.Reverse(); + NaviItem topItem = copyOfStack.FirstOrDefault(); + NaviItem rootItem = copyOfStack.LastOrDefault(); + + foreach (NaviItem naviItem in copyOfStack) + if (naviItem != rootItem && naviItem != topItem) + naviItem.Delete(); + + if (topItem != rootItem) + { + if (nre.Animated) + { + _naviFrame.Pop(); + + _currentTaskSource = new TaskCompletionSource<bool>(); + nre.Task = _currentTaskSource.Task; + } + else + topItem.Delete(); + } + } + + void PushRequestedHandler(object sender, NavigationRequestedEventArgs nre) + { + if (nre.Animated || _naviFrame.NavigationStack.Count == 0) + { + _naviFrame.Push(Platform.GetOrCreateRenderer(nre.Page).NativeView, SpanTitle(nre.Page.Title)); + _currentTaskSource = new TaskCompletionSource<bool>(); + nre.Task = _currentTaskSource.Task; + + // There is no TransitionFinished (AnimationFinished) event after the first Push + if (_naviFrame.NavigationStack.Count == 1) + CompleteCurrentNavigationTask(); + } + else + _naviFrame.InsertAfter(_naviFrame.NavigationStack.Last(), Platform.GetOrCreateRenderer(nre.Page).NativeView, SpanTitle(nre.Page.Title)); + + UpdateHasNavigationBar(nre.Page); + } + + void RemovePageRequestedHandler(object sender, NavigationRequestedEventArgs nre) + { + GetNaviItemForPage(nre.Page).Delete(); + } + + async void InsertPageBeforeRequestedHandler(object sender, NavigationRequestedEventArgs nre) + { + TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); + if (Element.CurrentNavigationTask != null && !Element.CurrentNavigationTask.IsCompleted) + { + await Element.CurrentNavigationTask; + } + Element.CurrentNavigationTask = tcs.Task; + + Device.StartTimer(TimeSpan.FromMilliseconds(0), () => + { + EvasObject page = Platform.GetOrCreateRenderer(nre.Page).NativeView; + _naviFrame.InsertBefore(GetNaviItemForPage(nre.BeforePage), page, SpanTitle(nre.Page.Title)); + tcs.SetResult(true); + + UpdateHasNavigationBar(nre.Page); + + return false; + }); + } + + void AnimationFinishedHandler(object sender, EventArgs e) + { + CompleteCurrentNavigationTask(); + } + + void CompleteCurrentNavigationTask() + { + if (_currentTaskSource != null) + { + var tmp = _currentTaskSource; + _currentTaskSource = null; + tmp.SetResult(true); + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs new file mode 100644 index 00000000..9abd5070 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/PickerRenderer.cs @@ -0,0 +1,119 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using ElmSharp; +using EButton = ElmSharp.Button; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class PickerRenderer : ViewRenderer<Picker, EButton> + { + internal List _list; + internal Native.Dialog _dialog; + Dictionary<ListItem, int> _itemToItemNumber = new Dictionary<ListItem, int>(); + + public PickerRenderer() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs<Picker> e) + { + if (Control == null) + { + var button = new EButton(Forms.Context.MainWindow); + SetNativeControl (button); + } + + if (e.OldElement != null) + { + Control.Clicked -= OnClick; + ((ObservableList<String>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged; + } + + if (e.NewElement != null) + { + UpdateSelectedIndex(); + + Control.Clicked += OnClick; + ((ObservableList<String>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged; + } + + base.OnElementChanged(e); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName) + { + UpdateSelectedIndex(); + } + } + + void UpdateSelectedIndex() + { + Control.Text = (Element.SelectedIndex == -1 || Element.Items == null ? + "" : Element.Items[Element.SelectedIndex]); + } + + void RowsCollectionChanged(object sender, EventArgs e) + { + UpdateSelectedIndex(); + } + + void OnClick(object sender, EventArgs e) + { + int i = 0; + _dialog = new Native.Dialog(Forms.Context.MainWindow); + _list = new List(_dialog); + _dialog.AlignmentX = -1; + _dialog.AlignmentY = -1; + + _dialog.Title = Element.Title; + _dialog.Dismissed += DialogDismissed; + _dialog.BackButtonPressed += (object senders, EventArgs es) => + { + _dialog.Dismiss(); + }; + + foreach (var s in Element.Items) + { + ListItem item = _list.Append(s); + _itemToItemNumber[item] = i; + i++; + } + _list.ItemSelected += ItemSelected; + _dialog.Content = _list; + + _dialog.Show(); + _list.Show(); + } + + void ItemSelected(object senderObject, EventArgs ev) + { + Element.SelectedIndex = _itemToItemNumber[(senderObject as List).SelectedItem]; + _dialog.Dismiss(); + } + + void DialogDismissed(object sender, EventArgs e) + { + CleanView(); + } + + void CleanView() + { + if (null != _list) + { + _list.Unrealize(); + _itemToItemNumber.Clear(); + _list = null; + } + if (null != _dialog) + { + _dialog.Unrealize(); + _dialog = null; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs new file mode 100644 index 00000000..3aacd3f4 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ProgressBarRenderer.cs @@ -0,0 +1,60 @@ +using System.ComponentModel; +using EProgressBar = ElmSharp.ProgressBar; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ProgressBarRenderer : ViewRenderer<ProgressBar, EProgressBar> + { + public ProgressBarRenderer() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e) + { + if (base.Control == null) + { + var progressBar = new EProgressBar(Forms.Context.MainWindow); + SetNativeControl(progressBar); + } + + if (e.OldElement != null) + { + } + + if (e.NewElement != null) + { + if (e.NewElement.MinimumWidthRequest == -1 && + e.NewElement.MinimumHeightRequest == -1 && + e.NewElement.WidthRequest == -1 && + e.NewElement.HeightRequest == -1) + { + Log.Warn("Need to size request"); + } + + UpdateAll(); + } + + base.OnElementChanged(e); + } + + void UpdateAll() + { + UpdateProgress(); + } + + void UpdateProgress() + { + Control.Value = Element.Progress; + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + if (e.PropertyName == ProgressBar.ProgressProperty.PropertyName) + { + UpdateProgress(); + } + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs new file mode 100755 index 00000000..29f15fbe --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ScrollViewRenderer.cs @@ -0,0 +1,130 @@ +using System; +using System.ComponentModel; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// This class provides a Renderer for a ScrollView widget. + /// </summary> + public class ScrollViewRenderer : ViewRenderer<ScrollView, Scroller> + { + EvasObject _content; + + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.ScrollViewRenderer"/> class. + /// </summary> + public ScrollViewRenderer() + { + RegisterPropertyHandler("Content", FillContent); + } + + /// <summary> + /// Handles the element change event. + /// </summary> + /// <param name="e">Event arguments.</param> + protected override void OnElementChanged(ElementChangedEventArgs<ScrollView> e) + { + if (Control == null) + { + var scrollView = new Scroller(Forms.Context.MainWindow); + SetNativeControl(scrollView); + } + + if (e.OldElement != null) + { + Control.Scrolled -= ScrollViewScrolledHandler; + (e.OldElement as IScrollViewController).ScrollToRequested -= ScrollRequestHandler; + } + + if (e.NewElement != null) + { + Control.Scrolled += ScrollViewScrolledHandler; + (e.NewElement as IScrollViewController).ScrollToRequested += ScrollRequestHandler; + } + + UpdateAll(); + + base.OnElementChanged(e); + } + + protected override void Dispose(bool disposing) + { + if (null != Control) + { + (Control as IScrollViewController).ScrollToRequested -= ScrollRequestHandler; + } + + base.Dispose(disposing); + } + + void FillContent() + { + if (_content != null) + { + Control.SetContent(null, true); + } + + _content = Platform.GetOrCreateRenderer(Element.Content).NativeView; + + if (_content != null) + { + Control.SetContent(_content, true); + } + } + + void UpdateAll() + { + UpdateOrientation(); + } + + void UpdateOrientation() + { + switch (Element.Orientation) + { + case ScrollOrientation.Horizontal: + Control.ScrollBlock = ScrollBlock.Vertical; + Control.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible; + Control.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible; + break; + case ScrollOrientation.Vertical: + Control.ScrollBlock = ScrollBlock.Horizontal; + Control.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Invisible; + Control.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible; + break; + default: + Control.ScrollBlock = ScrollBlock.None; + Control.HorizontalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible; + Control.VerticalScrollBarVisiblePolicy = ScrollBarVisiblePolicy.Visible; + break; + } + } + + /// <summary> + /// An event raised on element's property change. + /// </summary> + /// <param name="sender">Sender.</param> + /// <param name="e">Event arguments</param> + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (ScrollView.OrientationProperty.PropertyName == e.PropertyName) + { + UpdateOrientation(); + } + + base.OnElementPropertyChanged(sender, e); + } + + void ScrollViewScrolledHandler(object sender, EventArgs e) + { + var region = Control.CurrentRegion; + ((IScrollViewController)Element).SetScrolledPosition(region.X, region.Y); + } + + void ScrollRequestHandler(object sender, ScrollToRequestedEventArgs e) + { + Rect region = new Rect(ToNativeDimension(e.ScrollX), ToNativeDimension(e.ScrollY), ToNativeDimension(Element.Width), ToNativeDimension(Element.Height)); + Control.ScrollTo(region, e.ShouldAnimate); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs new file mode 100644 index 00000000..798a0497 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/SearchBarRenderer.cs @@ -0,0 +1,167 @@ +using System; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class SearchBarRenderer : ViewRenderer<SearchBar, Native.SearchBar> + { + //TODO need to add internationalization support + const string DefaultPlaceholderText = "Search"; + + static readonly EColor s_defaultCancelButtonColor = EColor.Aqua; + + //TODO: read default platform color + static readonly EColor s_defaultPlaceholderColor = EColor.Gray; + static readonly EColor s_defaultTextColor = EColor.Black; + /// <summary> + /// Creates a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.SearchBarRenderer"/> class. + /// Registers handlers for various properties of the SearchBar widget. + /// </summary> + public SearchBarRenderer() + { + RegisterPropertyHandler(SearchBar.CancelButtonColorProperty, CancelButtonColorPropertyHandler); + RegisterPropertyHandler(SearchBar.FontAttributesProperty, FontAttributesPropertyHandler); + RegisterPropertyHandler(SearchBar.FontFamilyProperty, FontFamilyPropertyHandler); + RegisterPropertyHandler(SearchBar.FontSizeProperty, FontSizePropertyHandler); + RegisterPropertyHandler(SearchBar.HorizontalTextAlignmentProperty, HorizontalTextAlignmentPropertyHandler); + RegisterPropertyHandler(SearchBar.PlaceholderProperty, PlaceholderPropertyHandler); + RegisterPropertyHandler(SearchBar.PlaceholderColorProperty, PlaceholderColorPropertyHandler); + RegisterPropertyHandler(SearchBar.TextProperty, TextPropertyHandler); + RegisterPropertyHandler(SearchBar.TextColorProperty, TextColorPropertyHandler); + } + + /// <summary> + /// A method called whenever the associated element has changed. + /// </summary> + protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e) + { + if (Control == null) + { + var searchBar = new Native.SearchBar(Forms.Context.MainWindow); + SetNativeControl(searchBar); + } + + if (e.OldElement != null) + { + Control.TextChanged -= SearchBarTextChangedHandler; + Control.SearchButtonPressed -= SearchButtonPressedHandler; + } + + if (e.NewElement != null) + { + Control.TextChanged += SearchBarTextChangedHandler; + Control.SearchButtonPressed += SearchButtonPressedHandler; + } + + base.OnElementChanged(e); + } + + protected override Size MinimumSize() + { + return new Size(250, 120); + } + + /// <summary> + /// Called upon changing of Xamarin widget's cancel button color property. + /// Converts current Color to ElmSharp.Color instance and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native widget. + /// </summary> + void CancelButtonColorPropertyHandler() + { + Control.CancelButtonColor = Element.CancelButtonColor.IsDefault ? s_defaultCancelButtonColor : Element.CancelButtonColor.ToNative(); + } + + /// <summary> + /// Called upon changing of Xamarin widget's font attributes property. + /// Converts current FontAttributes to ElmSharp.FontAttributes instance + /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget. + /// </summary> + void FontAttributesPropertyHandler() + { + Control.FontAttributes = Element.FontAttributes; + } + + /// <summary> + /// Called upon changing of Xamarin widget's font family property. + /// Sets current value of FontFamily property to the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget. + /// </summary> + void FontFamilyPropertyHandler() + { + Control.FontFamily = Element.FontFamily; + } + + /// <summary> + /// Called upon changing of Xamarin widget's font size property. + /// Sets current value of FontSize property to the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget. + /// </summary> + void FontSizePropertyHandler() + { + Control.FontSize = Element.FontSize; + } + + /// <summary> + /// Called upon changing of Xamarin widget's horizontal text alignment property. + /// Converts current HorizontalTextAlignment property's value to Xamarin.Forms.Platform.Tizen.Native.TextAlignment instance + /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget. + /// </summary> + void HorizontalTextAlignmentPropertyHandler() + { + Control.HorizontalTextAlignment = Element.HorizontalTextAlignment.ToNative(); + } + + /// <summary> + /// Called upon changing of Xamarin widget's placeholder color property. + /// Converts current PlaceholderColor property value to ElmSharp.Color instance + /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget. + /// </summary> + void PlaceholderColorPropertyHandler() + { + Control.PlaceholderColor = Element.PlaceholderColor.IsDefault ? s_defaultPlaceholderColor : Element.PlaceholderColor.ToNative(); + } + + /// <summary> + /// Called upon changing of Xamarin widget's placeholder text property. + /// </summary> + void PlaceholderPropertyHandler() + { + Control.Placeholder = Element.Placeholder == null ? DefaultPlaceholderText : Element.Placeholder; + } + + /// <summary> + /// Called on every change of underlying SearchBar's Text property. + /// Rewrites current underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar's Text contents to its Xamarin counterpart. + /// </summary> + /// <param name="sender">Sender.</param> + void SearchBarTextChangedHandler(object sender, EventArgs e) + { + Element.Text = Control.Text; + } + + /// <summary> + /// Called when the user clicks the Search button. + /// </summary> + /// <param name="sender">Sender.</param> + /// <param name="e">Event arguments.</param> + void SearchButtonPressedHandler(object sender, EventArgs e) + { + (Element as ISearchBarController).OnSearchButtonPressed(); + } + + /// <summary> + /// Called upon changing of Xamarin widget's text color property. + /// Converts current TextColor property value to ElmSharp.Color instance + /// and sets it in the underlying Xamarin.Forms.Platform.Tizen.Native.SearchBar widget. + /// </summary> + void TextColorPropertyHandler() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + /// <summary> + /// Called upon changing of Xamarin widget's text property. + /// </summary> + void TextPropertyHandler() + { + Control.Text = Element.Text; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs new file mode 100644 index 00000000..93cc6e84 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/SliderRenderer.cs @@ -0,0 +1,63 @@ +using System; +using ESlider = ElmSharp.Slider; +using ESize = ElmSharp.Size; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class SliderRenderer : ViewRenderer<Slider, ESlider> + { + + public SliderRenderer() + { + RegisterPropertyHandler(Slider.ValueProperty, UpdateValue); + RegisterPropertyHandler(Slider.MinimumProperty, UpdateMinMax); + RegisterPropertyHandler(Slider.MaximumProperty, UpdateMinMax); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Slider> e) + { + if (Control == null) + { + var slider = new ESlider(Forms.Context.MainWindow) + { + PropagateEvents = false, + }; + SetNativeControl(slider); + } + + if (e.OldElement != null) + { + Control.ValueChanged -= SliderValueChangedHandler; + } + + if (e.NewElement != null) + { + Control.ValueChanged += SliderValueChangedHandler; + } + + base.OnElementChanged(e); + } + + protected override ESize Measure(int availableWidth, int availableHeight) + { + return new ESize(Math.Min(200, availableWidth), 50); + } + + void SliderValueChangedHandler(object sender, EventArgs e) + { + Element.Value = Control.Value; + } + + protected void UpdateValue() + { + Control.Value = Element.Value; + } + + protected void UpdateMinMax() + { + Control.Minimum = Element.Minimum; + Control.Maximum = Element.Maximum; + UpdateValue(); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs new file mode 100644 index 00000000..09d1bf6b --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/StepperRenderer.cs @@ -0,0 +1,86 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class StepperRenderer : ViewRenderer<Stepper, Spinner> + { + + public StepperRenderer() + { + RegisterPropertyHandler(Stepper.ValueProperty, UpdateValue); + RegisterPropertyHandler(Stepper.MinimumProperty, UpdateMinMax); + RegisterPropertyHandler(Stepper.MaximumProperty, UpdateMinMax); + RegisterPropertyHandler(Stepper.IncrementProperty, UpdateStep); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e) + { + if (Control == null) + { + var stepper = new Spinner(Forms.Context.MainWindow) + { + IsEditable = false, + }; + + SetNativeControl(stepper); + } + + if (e.OldElement != null) + { + Control.ValueChanged -= StepperValueChangedHandler; + } + + if (e.NewElement != null) + { + Control.ValueChanged += StepperValueChangedHandler; + } + + base.OnElementChanged(e); + } + + void StepperValueChangedHandler(object sender, EventArgs e) + { + double newValue = Control.Value; + ((IElementController)Element).SetValueFromRenderer(Stepper.ValueProperty, newValue); + + // Determines how many decimal places are there in current Stepper's value. + // The 15 pound characters below correspond to the maximum precision of Double type. + var decimalValue = Decimal.Parse(newValue.ToString("0.###############")); + + // GetBits() method returns an array of four 32-bit integer values. + // The third (0-indexing) element of an array contains the following information: + // bits 00-15: unused, required to be 0 + // bits 16-23: an exponent between 0 and 28 indicating the power of 10 to divide the integer number passed as a parameter. + // Conversely this is the number of decimal digits in the number as well. + // bits 24-30: unused, required to be 0 + // bit 31: indicates the sign. 0 means positive number, 1 is for negative numbers. + // + // The precision information needs to be extracted from bits 16-23 of third element of an array + // returned by GetBits() call. Right-shifting by 16 bits followed by zeroing anything else results + // in a nice conversion of this data to integer variable. + var precision = (Decimal.GetBits(decimalValue)[3] >> 16) & 0x000000FF; + + // Sets Stepper's inner label decimal format to use exactly as many decimal places as needed: + Control.LabelFormat = string.Format("%.{0}f", precision); + } + + protected void UpdateValue() + { + Control.Value = Element.Value; + } + + protected void UpdateMinMax() + { + Control.Minimum = Element.Minimum; + Control.Maximum = Element.Maximum; + UpdateValue(); + } + + void UpdateStep() + { + Control.Step = Element.Increment; + } + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs new file mode 100644 index 00000000..b3539e6c --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/SwitchRenderer.cs @@ -0,0 +1,50 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class SwitchRenderer : ViewRenderer<Switch, Check> + { + public SwitchRenderer() + { + RegisterPropertyHandler(Switch.IsToggledProperty, HandleToggled); + } + + protected override void OnElementChanged(ElementChangedEventArgs<Switch> e) + { + if (Control == null) + { + var _switch = new Check(Forms.Context.MainWindow) + { + PropagateEvents = false, + }; + SetNativeControl(_switch); + } + + if (e.OldElement != null) + { + Control.StateChanged -= CheckChangedHandler; + } + + if (e.NewElement != null) + { + Control.Style = "toggle"; + + Control.StateChanged += CheckChangedHandler; + } + + base.OnElementChanged(e); + } + + void CheckChangedHandler(object sender, EventArgs e) + { + Element.SetValue(Switch.IsToggledProperty, Control.IsChecked); + } + + void HandleToggled() + { + Control.IsChecked = Element.IsToggled; + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs new file mode 100644 index 00000000..db97b339 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/TabbedPageRenderer.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using ElmSharp; +using EToolbarItem = ElmSharp.ToolbarItem; +using EToolbarItemEventArgs = ElmSharp.ToolbarItemEventArgs; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, IVisualElementRenderer + { + Box _box; + Toolbar _tpage; + EvasObject _tcontent; + Dictionary<EToolbarItem, Page> _itemToItemPage = new Dictionary<EToolbarItem, Page>(); + + public TabbedPageRenderer () + { + //Register for title change property + RegisterPropertyHandler(TabbedPage.TitleProperty, UpdateTitle); + //Register for current page change property + RegisterPropertyHandler("CurrentPage", CurrentPageChanged); + //TODO renderer should add item on EFL toolbar when new Page is added to TabbedPage + } + + protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e) + { + if (_tpage == null) + { + //Create box that holds toolbar and selected content + _box = new Box(Forms.Context.MainWindow) + { + AlignmentX = -1, + AlignmentY = -1, + WeightX = 1, + WeightY = 1, + IsHorizontal = false, + }; + _box.Show(); + + //Create toolbar that is placed inside the _box + _tpage = new Toolbar(Forms.Context.MainWindow) + { + AlignmentX = -1, + WeightX = 1, + ShrinkMode = ToolbarShrinkMode.Expand, + SelectionMode = ToolbarSelectionMode.Always, + }; + _tpage.Show(); + //Add callback for item selection + _tpage.Selected += OnCurrentPageChanged; + _box.PackEnd(_tpage); + + SetNativeControl(_box); + UpdateTitle(); + } + + base.OnElementChanged(e); + } + + protected override void Dispose(bool disposing) + { + if (_box != null) + { + _box.Unrealize(); + _box = null; + } + if (_tpage != null) + { + _tpage.Selected -= OnCurrentPageChanged; + + _tpage.Unrealize(); + _tpage = null; + } + base.Dispose(disposing); + } + + protected override void OnElementReady() + { + FillToolbar(); + base.OnElementReady(); + } + + void UpdateTitle() + { + _tpage.Text = Element.Title; + } + + void UpdateTitle(Page page) + { + if (_itemToItemPage.ContainsValue(page)) + { + var pair = _itemToItemPage.FirstOrDefault(x => x.Value == page); + pair.Key.SetPartText(null, pair.Value.Title); + } + } + + void OnPageTitleChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Page.TitleProperty.PropertyName) + { + UpdateTitle(sender as Page); + } + } + + void FillToolbar() + { + var logicalChildren = (Element as IElementController).LogicalChildren; + bool hasIcon = false; + + //add items to toolbar + foreach (Page child in logicalChildren) + { + var childRenderer = Platform.GetRenderer(child); + if (childRenderer != null) + { + childRenderer.NativeView.Hide(); + } + + EToolbarItem toolbarItem; + if (string.IsNullOrEmpty(child.Icon)) + { + toolbarItem = _tpage.Append(child.Title); + } + else + { + //elm_toobar style and size hint must be changed at least once before adding the toolbar item having icon. + if (!hasIcon) + { + int windowHeight = Forms.Context.MainWindow.Geometry.Height; + //This value is from efl-theme-tizen-mobile theme. (NAVIFRAME_TABBAR_HEIGHT_WITH_TITLE_INC 80) + double requiredToolbarHeight = 80.0; + double toolBarWeight = requiredToolbarHeight/windowHeight; + _tpage.Style="tabbar"; + _tpage.TransverseExpansion = true; + _tpage.SetAlignment(-1,-1); + _tpage.SetWeight(1, toolBarWeight); + _box.SetAlignment(-1,-1); + _box.SetWeight(1, 1- toolBarWeight); + hasIcon = true; + } + toolbarItem = _tpage.Append(child.Title, ResourcePath.GetPath(child.Icon)); + } + _itemToItemPage.Add(toolbarItem, child); + if (Element.CurrentPage == child) + { + //select item on the toolbar and fill content + toolbarItem.IsSelected = true; + OnCurrentPageChanged(null, null); + } + child.PropertyChanged += OnPageTitleChanged; + } + } + + void OnCurrentPageChanged(object sender, EToolbarItemEventArgs e) + { + if (_tpage.SelectedItem == null) + return; + Element.CurrentPage = _itemToItemPage[_tpage.SelectedItem]; + + //detach content from view without EvasObject changes + if (_tcontent != null) + { + //hide content that should not be visible + _tcontent.Hide(); + //unpack content that is hiden an prepare for new content + _box.UnPack(_tcontent); + } + //create EvasObject using renderer and remember to not destroy + //it for better performance (creat once) + _tcontent = Platform.GetOrCreateRenderer(Element.CurrentPage).NativeView; + _tcontent.SetAlignment(-1, -1); + _tcontent.SetWeight(1, 1); + _tcontent.Show(); + _box.PackEnd(_tcontent); + } + + void CurrentPageChanged() + { + foreach (KeyValuePair<EToolbarItem, Page> pair in _itemToItemPage) + { + if (pair.Value == Element.CurrentPage) + { + pair.Key.IsSelected = true; + return; + } + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs new file mode 100644 index 00000000..59123549 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/TableViewRenderer.cs @@ -0,0 +1,79 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class TableViewRenderer : ViewRenderer<TableView, Native.TableView>, IVisualElementRenderer + { + internal static BindableProperty PresentationProperty = BindableProperty.Create("Presentation", typeof(View), typeof(TableSectionBase), null, BindingMode.OneWay, null, null, null, null, null as BindableProperty.CreateDefaultValueDelegate); + + public TableViewRenderer() + { + RegisterPropertyHandler(TableView.HasUnevenRowsProperty, UpdateHasUnevenRows); + RegisterPropertyHandler(TableView.RowHeightProperty, UpdateRowHeight); + } + + protected override void OnElementChanged(ElementChangedEventArgs<TableView> e) + { + if (Control == null) + { + var _tableView = new Native.TableView(Forms.Context.MainWindow); + SetNativeControl(_tableView); + } + + if (e.OldElement != null) + { + Control.ItemSelected -= ListViewItemSelectedHandler; + e.OldElement.ModelChanged -= OnRootPropertyChanged; + } + + if (e.NewElement != null) + { + e.NewElement.ModelChanged += OnRootPropertyChanged; + Control.ItemSelected += ListViewItemSelectedHandler; + Control.ApplyTableRoot(e.NewElement.Root); + } + + base.OnElementChanged(e); + } + + protected override void Dispose(bool disposing) + { + Element.ModelChanged -= OnRootPropertyChanged; + base.Dispose(disposing); + } + + void ListViewItemSelectedHandler(object sender, GenListItemEventArgs e) + { + var item = e.Item as GenListItem; + + if (item != null) + { + var clickedCell = item.Data as Native.ListView.ItemContext; + if (null != clickedCell) + { + Element.Model.RowSelected(clickedCell.Cell); + } + } + } + + void OnRootPropertyChanged(object sender, EventArgs e) + { + if (Element != null) + { + Control.ApplyTableRoot(Element.Root); + } + } + + void UpdateHasUnevenRows() + { + //TODO Implement UnevenRows in ListView + } + + void UpdateRowHeight() + { + Control.UpdateRealizedItems(); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs new file mode 100644 index 00000000..9f964c18 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/TimePickerRenderer.cs @@ -0,0 +1,101 @@ +using System; +using System.Globalization; +using EColor = ElmSharp.Color; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class TimePickerRenderer : ViewRenderer<TimePicker, Native.Button> + { + //TODO need to add internationalization support + const string DialogTitle = "Choose Time"; + + static readonly string s_defaultFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; + + static readonly EColor s_defaultTextColor = EColor.White; + + string _format; + + TimeSpan _time; + + public TimePickerRenderer() + { + RegisterPropertyHandler(TimePicker.FormatProperty, UpdateFormat); + RegisterPropertyHandler(TimePicker.TimeProperty, UpdateTime); + RegisterPropertyHandler(TimePicker.TextColorProperty, UpdateTextColor); + } + + protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e) + { + if (Control == null) + { + var button = new Native.Button(Forms.Context.MainWindow); + SetNativeControl(button); + } + + if (e.OldElement != null) + { + Control.Clicked -= ButtonClickedHandler; + } + + if (e.NewElement != null) + { + _time = DateTime.Now.TimeOfDay; + _format = s_defaultFormat; + UpdateTimeAndFormat(); + + Control.Clicked += ButtonClickedHandler; + } + + base.OnElementChanged(e); + } + + void ButtonClickedHandler(object o, EventArgs e) + { + Native.DateTimePickerDialog dialog = new Native.DateTimePickerDialog(Forms.Context.MainWindow) + { + Title = DialogTitle + }; + + dialog.InitializeTimePicker(_time, _format); + dialog.DateTimeChanged += DialogDateTimeChangedHandler; + dialog.Dismissed += DialogDismissedHandler; + dialog.Show(); + } + + void DialogDateTimeChangedHandler(object sender, Native.DateChangedEventArgs dcea) + { + Element.Time = dcea.NewDate.TimeOfDay; + UpdateTime(); + } + + void DialogDismissedHandler(object sender, EventArgs e) + { + var dialog = sender as Native.DateTimePickerDialog; + dialog.DateTimeChanged -= DialogDateTimeChangedHandler; + dialog.Dismissed -= DialogDismissedHandler; + } + + void UpdateFormat() + { + _format = Element.Format ?? s_defaultFormat; + UpdateTimeAndFormat(); + } + + void UpdateTextColor() + { + Control.TextColor = Element.TextColor.IsDefault ? s_defaultTextColor : Element.TextColor.ToNative(); + } + + void UpdateTime() + { + _time = Element.Time; + UpdateTimeAndFormat(); + } + + void UpdateTimeAndFormat() + { + // Xamarin using DateTime formatting (https://developer.xamarin.com/api/property/Xamarin.Forms.TimePicker.Format/) + Control.Text = new DateTime(_time.Ticks).ToString(_format); + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs new file mode 100644 index 00000000..24debc8b --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/ViewRenderer.cs @@ -0,0 +1,46 @@ +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Base class for view renderers. + /// </summary> + public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView> + where TView : View + where TNativeView : Widget + { + /// <summary> + /// Default constructor. + /// </summary> + protected ViewRenderer() + { + } + + protected override void OnElementChanged(ElementChangedEventArgs<TView> e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + { + _gestureHandler.Clear(); + _gestureHandler = null; + } + + if (e.NewElement != null) + { + _gestureHandler = new GestureHandler(this); + } + } + + /// <summary> + /// Native control associated with this renderer. + /// </summary> + public TNativeView Control + { + get + { + return (TNativeView)NativeView; + } + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs new file mode 100644 index 00000000..8c5c0be0 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Renderers/VisualElementRenderer.cs @@ -0,0 +1,946 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.ComponentModel; +using ElmSharp; +using ELayout = ElmSharp.Layout; +using EColor = ElmSharp.Color; +using ESize = ElmSharp.Size; +using ERect = ElmSharp.Rect; +using ERectangle = ElmSharp.Rectangle; +using EBox = ElmSharp.Box; + +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Base class for rendering of a Xamarin element. + /// </summary> + public abstract class VisualElementRenderer<TElement> : IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement + { + /// <summary> + /// Holds registered element changed handlers. + /// </summary> + readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>(); + + /// <summary> + /// Handler for property changed events. + /// </summary> + PropertyChangedEventHandler _propertyChangedHandler; + + EventHandler<EventArg<VisualElement>> _batchCommittedHandler; + + /// <summary> + /// Flags which control status of renderer. + /// </summary> + VisualElementRendererFlags _flags = VisualElementRendererFlags.None; + + /// <summary> + /// Holds the native view. + /// </summary> + EvasObject _view; + + Dictionary<string, Action<bool>> _propertyHandlersWithInit = new Dictionary<string, Action<bool>>(); + + Dictionary<string, Action> _propertyHandlers = new Dictionary<string, Action>(); + + HashSet<string> _batchedProperties = new HashSet<string>(); + + ERectangle _opacityLayer; + + internal GestureHandler _gestureHandler; + + /// <summary> + /// Default constructor. + /// </summary> + protected VisualElementRenderer() + { + RegisterPropertyHandler(VisualElement.IsVisibleProperty, UpdateIsVisible); + RegisterPropertyHandler(VisualElement.OpacityProperty, UpdateOpacity); + RegisterPropertyHandler(VisualElement.IsEnabledProperty, UpdateIsEnabled); + RegisterPropertyHandler(VisualElement.InputTransparentProperty, UpdateInputTransparent); + RegisterPropertyHandler(VisualElement.BackgroundColorProperty, UpdateBackgroundColor); + + RegisterPropertyHandler(VisualElement.AnchorXProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.AnchorYProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.ScaleProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.RotationProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.RotationXProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.RotationYProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.TranslationXProperty, ApplyTransformation); + RegisterPropertyHandler(VisualElement.TranslationYProperty, ApplyTransformation); + } + + ~VisualElementRenderer() + { + Dispose(false); + } + + event EventHandler<VisualElementChangedEventArgs> ElementChanged + { + add + { + _elementChangedHandlers.Add(value); + } + remove + { + _elementChangedHandlers.Remove(value); + } + } + + /// <summary> + /// Gets the Xamarin element associated with this renderer. + /// </summary> + public TElement Element + { + get; + private set; + } + + VisualElement IVisualElementRenderer.Element + { + get + { + return this.Element; + } + } + + public EvasObject NativeView + { + get + { + return _view; + } + } + + protected bool IsDisposed => (_flags == VisualElementRendererFlags.Disposed); + + /// <summary> + /// Releases all resource used by the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> object. + /// </summary> + /// <remarks>Call <see cref="Dispose"/> when you are finished using the + /// <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/>. The <see cref="Dispose"/> method + /// leaves the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> in an unusable state. + /// After calling <see cref="Dispose"/>, you must release all references to the + /// <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> so the garbage collector can reclaim + /// the memory that the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementRenderer"/> was occupying.</remarks> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) + { + if (null == NativeView) + { + return new SizeRequest(new Size(0, 0)); + } + else + { + int availableWidth = ToNativeDimension(widthConstraint); + int availableHeight = ToNativeDimension(heightConstraint); + ESize measured; + + var nativeViewMeasurable = NativeView as Native.IMeasurable; + if (nativeViewMeasurable != null) + { + measured = nativeViewMeasurable.Measure(availableWidth, availableHeight); + } + else + { + measured = Measure(availableWidth, availableHeight); + } + + return new SizeRequest(new Size(measured.Width, measured.Height), MinimumSize()); + } + } + + /// <summary> + /// Sets the element associated with this renderer. + /// </summary> + public void SetElement(TElement newElement) + { + if (newElement == null) + { + throw new ArgumentNullException("newElement"); + } + + TElement oldElement = Element; + if (oldElement != null) + { + throw new InvalidOperationException("oldElement"); + } + + Element = newElement; + if (_propertyChangedHandler == null) + { + _propertyChangedHandler = new PropertyChangedEventHandler(OnElementPropertyChanged); + } + + if (_batchCommittedHandler == null) + { + _batchCommittedHandler = OnBatchCommitted; + } + + // send notification + OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, newElement)); + + // store renderer for the new element + Platform.SetRenderer(newElement, this); + + // add children + var logicalChildren = (newElement as IElementController).LogicalChildren; + foreach (Element child in logicalChildren) + { + AddChild(child); + } + + OnElementReady(); + } + + public void UpdateNativeGeometry() + { + var x = ComputeAbsoluteX(Element); + var y = ComputeAbsoluteY(Element); + NativeView.Geometry = new ERect(ToNativeDimension(x), ToNativeDimension(y), ToNativeDimension(Element.Width), ToNativeDimension(Element.Height)); + ApplyTransformation(); + UpdateOpacityLayer(); + } + + void IVisualElementRenderer.SetElement(VisualElement element) + { + TElement tElement = element as TElement; + if (tElement == null) + { + throw new ArgumentException("Element is not of type " + typeof(TElement), "Element"); + } + SetElement(tElement); + } + + /// <summary> + /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform. + /// </summary> + /// <param name="effect">The effect to register.</param> + void IEffectControlProvider.RegisterEffect(Effect effect) + { + RegisterEffect(effect); + } + + /// <summary> + /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform. + /// </summary> + /// <param name="effect">The effect to register.</param> + protected void RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect != null) + { + OnRegisterEffect(platformEffect); + } + } + + + protected virtual void UpdateLayout() + { + // we're updating the coordinates of native control only if they were modified + // via Xamarin (Settings.IgnoreBatchCommitted is set to false); + // otherwise native control is already in the right place + if (!Settings.IgnoreBatchCommitted && null != NativeView) + { + UpdateNativeGeometry(); + } + + // we're updating just immediate children + var logicalChildren = (Element as IElementController).LogicalChildren; + foreach (var child in logicalChildren) + { + Platform.GetRenderer(child)?.UpdateNativeGeometry(); + } + } + + /// <summary> + /// Disposes of underlying resources. + /// </summary> + /// <param name="disposing">True if the memory release was requested on demand.</param> + protected virtual void Dispose(bool disposing) + { + if ((_flags & VisualElementRendererFlags.Disposed) != 0) + { + return; + } + + _flags |= VisualElementRendererFlags.Disposed; + + if (disposing) + { + if (Element != null) + { + Element.PropertyChanged -= _propertyChangedHandler; + Element.BatchCommitted -= _batchCommittedHandler; + + Element.ChildAdded -= OnChildAdded; + Element.ChildRemoved -= OnChildRemoved; + Element.ChildrenReordered -= OnChildrenReordered; + + Element.FocusChangeRequested -= OnFocusChangeRequested; + + Settings.StartIgnoringBatchCommitted(); + Element.Layout(new Rectangle(0, 0, -1, -1)); + Settings.StopIgnoringBatchCommitted(); + + var logicalChildren = (Element as IElementController).LogicalChildren; + foreach (var child in logicalChildren) + { + Platform.GetRenderer(child)?.Dispose(); + } + + if (Platform.GetRenderer(Element) == this) + { + Platform.SetRenderer(Element, (IVisualElementRenderer)null); + } + Element = default(TElement); + } + + if (NativeView != null) + { + NativeView.Deleted -= NativeViewDeleted; + EnsureOpacityLayerIsDestroyed(); + NativeView.Unrealize(); + SetNativeView(null); + } + } + } + + /// <summary> + /// Notification that the associated element has changed. + /// </summary> + /// <param name="e">Event parameters.</param> + protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e) + { + if (null != e.OldElement) + { + e.OldElement.PropertyChanged -= _propertyChangedHandler; + e.OldElement.BatchCommitted -= _batchCommittedHandler; + + e.OldElement.ChildAdded -= OnChildAdded; + e.OldElement.ChildRemoved -= OnChildRemoved; + e.OldElement.ChildrenReordered -= OnChildrenReordered; + + e.OldElement.FocusChangeRequested -= OnFocusChangeRequested; + + Settings.StartIgnoringBatchCommitted(); + Element.Layout(new Rectangle(0, 0, -1, -1)); + Settings.StopIgnoringBatchCommitted(); + + var controller = e.OldElement as IElementController; + if (controller != null && controller.EffectControlProvider == this) + { + controller.EffectControlProvider = null; + } + } + + if (null != e.NewElement) + { + e.NewElement.PropertyChanged += _propertyChangedHandler; + e.NewElement.BatchCommitted += _batchCommittedHandler; + + e.NewElement.ChildAdded += OnChildAdded; + e.NewElement.ChildRemoved += OnChildRemoved; + e.NewElement.ChildrenReordered += OnChildrenReordered; + + e.NewElement.FocusChangeRequested += OnFocusChangeRequested; + + UpdateAllProperties(true); + + var controller = e.NewElement as IElementController; + if (controller != null) + { + controller.EffectControlProvider = this; + } + } + + // TODO: handle the event + } + + /// <summary> + /// Notification that the property of the associated element has changed. + /// </summary> + /// <param name="sender">Object which sent the notification.</param> + /// <param name="e">Event parameters.</param> + protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (Element.Batched) + { + if (e.PropertyName == VisualElement.XProperty.PropertyName || + e.PropertyName == VisualElement.YProperty.PropertyName || + e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + { + _flags |= VisualElementRendererFlags.NeedsLayout; + } + else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName || + e.PropertyName == VisualElement.TranslationYProperty.PropertyName || + e.PropertyName == VisualElement.RotationProperty.PropertyName || + e.PropertyName == VisualElement.RotationXProperty.PropertyName || + e.PropertyName == VisualElement.RotationYProperty.PropertyName || + e.PropertyName == VisualElement.ScaleProperty.PropertyName || + e.PropertyName == VisualElement.AnchorXProperty.PropertyName || + e.PropertyName == VisualElement.AnchorYProperty.PropertyName) + { + _flags |= VisualElementRendererFlags.NeedsTransformation; + } + else + { + _batchedProperties.Add(e.PropertyName); + } + return; + } + + Action<bool> init; + if (_propertyHandlersWithInit.TryGetValue(e.PropertyName, out init)) + { + init(false); + } + else + { + Action handler; + if (_propertyHandlers.TryGetValue(e.PropertyName, out handler)) + { + handler(); + } + } + } + + /// <summary> + /// Updates the attached event handlers, sets the native control. + /// </summary> + protected void SetNativeControl(EvasObject control) + { + if (NativeView != null) + { + NativeView.Moved -= OnMoved; + NativeView.Deleted -= NativeViewDeleted; + EnsureOpacityLayerIsDestroyed(); + } + + Widget widget = NativeView as Widget; + if (widget != null) + { + widget.Focused -= OnFocused; + widget.Unfocused -= OnUnfocused; + } + + SetNativeView(control); + + if (NativeView != null) + { + NativeView.Deleted += NativeViewDeleted; + NativeView.Moved += OnMoved; + } + + widget = NativeView as Widget; + if (widget != null) + { + widget.Focused += OnFocused; + widget.Unfocused += OnUnfocused; + } + } + + void NativeViewDeleted(object sender, EventArgs e) + { + Dispose(); + } + + void OnBatchCommitted(object sender, EventArg<VisualElement> e) + { + if (_flags.HasFlag(VisualElementRendererFlags.NeedsLayout)) + { + if (!Settings.IgnoreBatchCommitted) + { + UpdateLayout(); + // UpdateLayout already updates transformation, clear NeedsTranformation flag then + _flags &= ~VisualElementRendererFlags.NeedsTransformation; + } + _flags ^= VisualElementRendererFlags.NeedsLayout; + } + if (_flags.HasFlag(VisualElementRendererFlags.NeedsTransformation)) + { + ApplyTransformation(); + _flags ^= VisualElementRendererFlags.NeedsTransformation; + } + + foreach (string property in _batchedProperties) + { + OnElementPropertyChanged(this, new PropertyChangedEventArgs(property)); + } + _batchedProperties.Clear(); + } + + /// <summary> + /// Registers a handler which is executed when specified property changes. + /// </summary> + /// <param name="property">Handled property.</param> + /// <param name="handler">Action to be executed when property changes.</param> + protected void RegisterPropertyHandler(BindableProperty property, Action<bool> handler) + { + RegisterPropertyHandler(property.PropertyName, handler); + } + + /// <summary> + /// Registers a handler which is executed when specified property changes. + /// </summary> + /// <param name="name">Name of the handled property.</param> + /// <param name="handler">Action to be executed when property changes.</param> + protected void RegisterPropertyHandler(string name, Action<bool> handler) + { + _propertyHandlersWithInit.Add(name, handler); + } + + /// <summary> + /// Registers a handler which is executed when specified property changes. + /// </summary> + /// <param name="property">Handled property.</param> + /// <param name="handler">Action to be executed when property changes.</param> + protected void RegisterPropertyHandler(BindableProperty property, Action handler) + { + RegisterPropertyHandler(property.PropertyName, handler); + } + + /// <summary> + /// Registers a handler which is executed when specified property changes. + /// </summary> + /// <param name="name">Name of the handled property.</param> + /// <param name="handler">Action to be executed when property changes.</param> + protected void RegisterPropertyHandler(string name, Action handler) + { + _propertyHandlers.Add(name, handler); + } + + /// <summary> + /// Updates all registered properties. + /// </summary> + /// <param name="initialization">If set to <c>true</c> the method is called for an uninitialized object.</param> + protected void UpdateAllProperties(bool initialization) + { + foreach (KeyValuePair<string, Action<bool>> kvp in _propertyHandlersWithInit) + { + kvp.Value(initialization); + } + + foreach (KeyValuePair<string, Action> kvp in _propertyHandlers) + { + kvp.Value(); + } + } + + /// <summary> + /// Called when Element has been set and its native counterpart + /// is properly initialized. + /// </summary> + protected virtual void OnElementReady() + { + } + + protected void DoLayout(Native.LayoutEventArgs e) + { + Settings.StartIgnoringBatchCommitted(); + Element.Layout(new Rectangle(Element.X, Element.Y, e.Width, e.Height)); + if (e.HasChanged) + { + UpdateLayout(); + } + Settings.StopIgnoringBatchCommitted(); + } + + protected virtual Size MinimumSize() + { + return new Size(); + } + + /// <summary> + /// Calculates how much space this element should take, given how much room there is. + /// </summary> + /// <returns>a desired dimensions of the element</returns> + protected virtual ESize Measure(int availableWidth, int availableHeight) + { + return new ESize(NativeView.MinimumWidth, NativeView.MinimumHeight); + } + + protected virtual void UpdateBackgroundColor() + { + if (NativeView is Widget) + { + (NativeView as Widget).BackgroundColor = Element.BackgroundColor.ToNative(); + } + else + { + Log.Warn("{0} uses {1} which does not support background color", this, NativeView); + } + } + + /// <summary> + /// Converts provided value to native dimension. + /// </summary> + /// <param name="v">value to be converted.</param> + /// <returns>converted value</returns> + protected static int ToNativeDimension(double v) + { + return (int)Math.Round(v); + } + + static double ComputeAbsoluteX(VisualElement e) + { + return e.X + (e.RealParent is VisualElement ? Platform.GetRenderer(e.RealParent).NativeView.Geometry.X : 0.0); + } + + static double ComputeAbsoluteY(VisualElement e) + { + return e.Y + (e.RealParent is VisualElement ? Platform.GetRenderer(e.RealParent).NativeView.Geometry.Y : 0.0); + } + + /// <summary> + /// Handles focus events. + /// </summary> + void OnFocused(object sender, EventArgs e) + { + if (null != Element) + { + Element.SetValue(VisualElement.IsFocusedPropertyKey, true); + } + } + + /// <summary> + /// Handles unfocus events. + /// </summary> + void OnUnfocused(object sender, EventArgs e) + { + if (null != Element) + { + Element.SetValue(VisualElement.IsFocusedPropertyKey, false); + } + } + + /// <summary> + /// Sets the native control, updates the control's properties. + /// </summary> + void SetNativeView(EvasObject control) + { + _view = control; + } + + /// <summary> + /// Adds a new child if it's derived from the VisualElement class. Otherwise this method does nothing. + /// </summary> + /// <param name="child">Child to be added.</param> + void AddChild(Element child) + { + VisualElement vElement = child as VisualElement; + if (vElement != null) + { + var childRenderer = Platform.GetOrCreateRenderer(vElement); + + // if the native view can have children, attach the new child + if (NativeView is Native.IContainable<EvasObject>) + { + (NativeView as Native.IContainable<EvasObject>).Children.Add(childRenderer.NativeView); + } + } + } + + void RemoveChild(VisualElement view) + { + var renderer = Platform.GetRenderer(view); + var containerObject = NativeView as Native.IContainable<EvasObject>; + if (containerObject != null) + { + containerObject.Children.Remove(renderer.NativeView); + } + + renderer.Dispose(); + } + + void OnChildAdded(object sender, ElementEventArgs e) + { + var view = e.Element as VisualElement; + if (view != null) + { + AddChild(view); + } + + // changing the order makes sense only in case of Layouts + if (Element is Layout) + { + IElementController controller = Element as IElementController; + if (controller.LogicalChildren[controller.LogicalChildren.Count - 1] != view) + { + EnsureChildOrder(); + } + } + } + + void OnChildRemoved(object sender, ElementEventArgs e) + { + var view = e.Element as VisualElement; + if (view != null) + { + RemoveChild(view); + } + } + + void OnChildrenReordered(object sender, EventArgs e) + { + EnsureChildOrder(); + Layout layout = Element as Layout; + if (layout != null) + { + layout.InvalidateMeasureInternal(Internals.InvalidationTrigger.MeasureChanged); + layout.ForceLayout(); + } + } + + void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e) + { + Widget widget = NativeView as Widget; + if (widget == null) + { + Log.Warn("{0} is not a widget, it cannot receive focus", NativeView); + return; + } + + widget.SetFocus(e.Focus); + e.Result = true; + } + + /// <summary> + /// On register the effect + /// </summary> + /// <param name="effect">The effect to register.</param> + void OnRegisterEffect(PlatformEffect effect) + { + effect.Container = Element.Parent == null ? null : Platform.GetRenderer(Element.Parent).NativeView; + effect.Control = NativeView; + } + + void OnMoved(object sender, EventArgs e) + { + UpdateOpacityLayer(); + ApplyTransformation(); + _gestureHandler?.UpdateHitBox(); + } + + void EnsureChildOrder() + { + var logicalChildren = (Element as IElementController).LogicalChildren; + for (var i = logicalChildren.Count - 1; i >= 0; --i) + { + var element = logicalChildren[i] as VisualElement; + if (element != null) + { + Platform.GetRenderer(element).NativeView?.Lower(); + } + } + } + + void UpdateIsVisible() + { + if (null != NativeView) + { + if (Element.IsVisible) + { + NativeView.Show(); + } + else + { + NativeView.Hide(); + } + } + } + + void UpdateOpacity() + { + if (null != NativeView) + { + if (Element.Opacity < 1.0) + { + EnsureOpacityLayerExists(); + + var alpha = (int)(Element.Opacity * 255.0); + _opacityLayer.Color = new EColor(255, 255, 255, alpha); + } + else + { + EnsureOpacityLayerIsDestroyed(); + } + } + } + + /// <summary> + /// Updates the IsEnabled property. + /// </summary> + void UpdateIsEnabled() + { + Widget widget = NativeView as Widget; + if (widget != null) + { + widget.IsEnabled = Element.IsEnabled; + } + } + + /// <summary> + /// Updates the InputTransparent property. + /// </summary> + void UpdateInputTransparent() + { + NativeView.PassEvents = Element.InputTransparent; + } + + void ApplyRotation(EvasMap map, ERect geometry, ref bool changed) + { + var rotationX = Element.RotationX; + var rotationY = Element.RotationY; + var rotationZ = Element.Rotation; + var anchorX = Element.AnchorX; + var anchorY = Element.AnchorY; + + // apply rotations + if (rotationX != 0 || rotationY != 0 || rotationZ != 0) + { + map.Rotate3D(rotationX, rotationY, rotationZ, (int)(geometry.X + geometry.Width * anchorX), + (int)(geometry.Y + geometry.Height * anchorY), 0); + changed = true; + } + } + + void ApplyScale(EvasMap map, ERect geometry, ref bool changed) + { + var scale = Element.Scale; + + // apply scale factor + if (scale != 1.0) + { + map.Zoom(scale, scale, + geometry.X + (int)(geometry.Width * Element.AnchorX), + geometry.Y + (int)(geometry.Height * Element.AnchorY)); + changed = true; + } + } + + void ApplyTranslation(EvasMap map, ERect geometry, ref bool changed) + { + var shiftX = ToNativeDimension(Element.TranslationX); + var shiftY = ToNativeDimension(Element.TranslationY); + + // apply translation, i.e. move/shift the object a little + if (shiftX != 0 || shiftY != 0) + { + if (changed) + { + // special care is taken to apply the translation last + Point3D p; + for (int i = 0; i < 4; i++) + { + p = map.GetPointCoordinate(i); + p.X += shiftX; + p.Y += shiftY; + map.SetPointCoordinate(i, p); + } + } + else + { + // in case when we only need translation, then construct the map in a simpler way + geometry.X += shiftX; + geometry.Y += shiftY; + map.PopulatePoints(geometry, 0); + + changed = true; + } + } + } + + public void ApplyTransformation() + { + if (null == NativeView) + { + Log.Error("Trying to apply transformation to the non-existent native control"); + return; + } + + // prepare the EFL effect structure + ERect geometry = NativeView.Geometry; + EvasMap map = new EvasMap(4); + map.PopulatePoints(geometry, 0); + + bool changed = false; + ApplyRotation(map, geometry, ref changed); + ApplyScale(map, geometry, ref changed); + ApplyTranslation(map, geometry, ref changed); + + NativeView.IsMapEnabled = changed; + + if (changed) + { + NativeView.EvasMap = map; + + if (_opacityLayer != null) + { + _opacityLayer.IsMapEnabled = true; + _opacityLayer.EvasMap = map; + } + } + _gestureHandler?.UpdateHitBox(); + } + + protected virtual void UpdateOpacityLayer() + { + if (_opacityLayer != null) + { + _opacityLayer.Geometry = NativeView.Geometry; + NativeView.SetClip(_opacityLayer); + } + } + + void EnsureOpacityLayerExists() + { + if (_opacityLayer == null) + { + _opacityLayer = new ERectangle(NativeView); + UpdateOpacityLayer(); + _opacityLayer.Show(); + } + } + + void EnsureOpacityLayerIsDestroyed() + { + if (_opacityLayer != null) + { + NativeView.SetClip(null); + _opacityLayer.Unrealize(); + _opacityLayer = null; + } + } + } + + internal static class Settings + { + static int s_ignoreCount = 0; + + public static bool IgnoreBatchCommitted + { + get + { + return s_ignoreCount != 0; + } + } + + public static void StartIgnoringBatchCommitted() + { + ++s_ignoreCount; + } + + public static void StopIgnoringBatchCommitted() + { + Debug.Assert(s_ignoreCount > 0); + --s_ignoreCount; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ResourcePath.cs b/Xamarin.Forms.Platform.Tizen/ResourcePath.cs new file mode 100644 index 00000000..d2387793 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ResourcePath.cs @@ -0,0 +1,44 @@ +using System.IO; +#if NET45 +using System.Reflection; +#endif + +using AppFW = Tizen.Applications; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal static class ResourcePath + { + public static string GetPath(string res) + { + if (Path.IsPathRooted(res)) + { + return res; + } + + AppFW.Application app = AppFW.Application.Current; + if (app != null) + { + string resPath = app.DirectoryInfo.Resource + res; + if (File.Exists(resPath)) + { + return resPath; + } + } + +#if NET45 + string exedir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + // ind resource in "exepath/../res/" + { + string resPath = exedir + "/../res/" + res; + if (File.Exists(resPath)) + { + return resPath; + } + } +#endif + + return res; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ResourcesProvider.cs b/Xamarin.Forms.Platform.Tizen/ResourcesProvider.cs new file mode 100644 index 00000000..15e7485e --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ResourcesProvider.cs @@ -0,0 +1,72 @@ +namespace Xamarin.Forms.Platform.Tizen +{ + internal class ResourcesProvider : ISystemResourcesProvider + { + ResourceDictionary _dictionary; + + public IResourceDictionary GetSystemResources() + { + _dictionary = new ResourceDictionary(); + + UpdateStyles(); + + return _dictionary; + } + + void UpdateStyles() + { + _dictionary[Device.Styles.BodyStyleKey] = GetStyleByKey(Device.Styles.BodyStyleKey); + _dictionary[Device.Styles.TitleStyleKey] = GetStyleByKey(Device.Styles.TitleStyleKey); + _dictionary[Device.Styles.SubtitleStyleKey] = GetStyleByKey(Device.Styles.SubtitleStyleKey); + _dictionary[Device.Styles.CaptionStyleKey] = GetStyleByKey(Device.Styles.CaptionStyleKey); + _dictionary[Device.Styles.ListItemTextStyleKey] = GetStyleByKey(Device.Styles.ListItemTextStyleKey); + _dictionary[Device.Styles.ListItemDetailTextStyleKey] = GetStyleByKey(Device.Styles.ListItemDetailTextStyleKey); + } + + //TODO: need Tizen API to get system values + Style GetStyleByKey(string key) + { + Style style = null; + if (key == Device.Styles.TitleStyleKey) + { + style = GetStyle(50, Color.FromRgb(250, 250, 250)); + } + else if (key == Device.Styles.SubtitleStyleKey) + { + style = GetStyle(24, Color.FromRgb(250, 250, 250)); + } + else if (key == Device.Styles.CaptionStyleKey) + { + style = GetStyle(24, Color.FromRgb(115, 115, 115)); + } + else if (key == Device.Styles.ListItemTextStyleKey) + { + style = GetStyle(40, Color.FromRgb(0, 0, 0)); + } + else if (key == Device.Styles.ListItemDetailTextStyleKey) + { + style = GetStyle(24, Color.FromRgb(0, 0, 0)); + } + else + { + style = GetStyle(); + } + + return style; + } + + Style GetStyle(int? fontSize = null, Color? textColor = null) + { + Style style = new Style(typeof(Label)); + if (fontSize.HasValue) + { + style.Setters.Add(new Setter { Property = Label.FontSizeProperty, Value = fontSize }); + } + if (textColor.HasValue) + { + style.Setters.Add(new Setter { Property = Label.TextColorProperty, Value = textColor }); + } + return style; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/TizenIsolatedStorageFile.cs b/Xamarin.Forms.Platform.Tizen/TizenIsolatedStorageFile.cs new file mode 100644 index 00000000..0550d9df --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/TizenIsolatedStorageFile.cs @@ -0,0 +1,125 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using TApplication = Tizen.Applications.Application; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal class TizenIsolatedStorageFile : IIsolatedStorageFile + { + string _rootPath; + + internal TizenIsolatedStorageFile() + { + _rootPath = TApplication.Current.DirectoryInfo.Data; + } + + public void CreateDirectory(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + Directory.CreateDirectory(Path.Combine(_rootPath, path)); + } + + public Task CreateDirectoryAsync(string path) + { + CreateDirectory(path); + return Task.FromResult(true); + } + + public void MoveFile(string source, string dest) + { + if (source == null) + throw new ArgumentNullException("source"); + if (dest == null) + throw new ArgumentNullException("dest"); + + File.Move(Path.Combine(_rootPath, source), Path.Combine(_rootPath, dest)); + } + + public void DeleteFile(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Trim().Length == 0) + throw new ArgumentException("An empty path is not valid."); + + File.Delete(Path.Combine(_rootPath, path)); + } + + public bool DirectoryExists(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + return Directory.Exists(Path.Combine(_rootPath, path)); + } + + public Task<bool> GetDirectoryExistsAsync(string path) + { + return Task.FromResult(DirectoryExists(path)); + } + + public bool FileExists(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + return File.Exists(Path.Combine(_rootPath, path)); + } + + public Task<bool> GetFileExistsAsync(string path) + { + return Task.FromResult(FileExists(path)); + } + + public DateTimeOffset GetLastWriteTime(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Trim().Length == 0) + throw new ArgumentException("An empty path is not valid."); + + string fullPath = Path.Combine(_rootPath, path); + if (File.Exists(fullPath)) + return File.GetLastWriteTime(fullPath); + + return Directory.GetLastWriteTime(fullPath); + } + + public Task<DateTimeOffset> GetLastWriteTimeAsync(string path) + { + return Task.FromResult(GetLastWriteTime(path)); + } + + public Stream OpenFile(string path, System.IO.FileMode mode) + { + return OpenFile(path, mode, (mode == System.IO.FileMode.Append ? System.IO.FileAccess.Write : System.IO.FileAccess.ReadWrite)); + } + + public Stream OpenFile(string path, System.IO.FileMode mode, System.IO.FileAccess access) + { + return OpenFile(path, mode, access, System.IO.FileShare.Read); + } + + public Stream OpenFile(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Trim().Length == 0) + throw new ArgumentException("An empty path is not valid."); + + string fullPath = Path.Combine(_rootPath, path); + return new FileStream(fullPath, mode, access, share); + } + + public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access) + { + return Task.FromResult(OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access)); + } + + public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share) + { + return Task.FromResult(OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, (System.IO.FileShare)share)); + } + + } +} diff --git a/Xamarin.Forms.Platform.Tizen/TizenPlatformServices.cs b/Xamarin.Forms.Platform.Tizen/TizenPlatformServices.cs new file mode 100644 index 00000000..7d442318 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/TizenPlatformServices.cs @@ -0,0 +1,232 @@ +using System; +using System.IO; +using System.Reflection; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + internal class TizenPlatformServices : IPlatformServices + { + static MD5 checksum = MD5.Create(); + + static SynchronizationContext s_context; + + public TizenPlatformServices() + { + s_context = SynchronizationContext.Current; + } + + public class _Timer : ITimer + { + readonly Timer timer; + + public _Timer(Timer timer) + { + this.timer = timer; + } + + #region ITimer implementation + public void Change(int dueTime, int period) + { + timer.Change(dueTime, period); + } + public void Change(long dueTime, long period) + { + timer.Change((int)dueTime, (int)period); + } + public void Change(TimeSpan dueTime, TimeSpan period) + { + timer.Change(dueTime, period); + } + public void Change(uint dueTime, uint period) + { + timer.Change((int)dueTime, (int)period); + } + #endregion + } + + public class Ticker : Xamarin.Forms.Internals.Ticker + { + readonly ITimer _timer; + + public Ticker() + { + _timer = new _Timer(new Timer((object o) => HandleElapsed(o), this, Timeout.Infinite, Timeout.Infinite)); + } + + protected override void EnableTimer() + { + _timer.Change(16, 16); + } + + protected override void DisableTimer() + { + _timer.Change(-1, -1); + } + + void HandleElapsed(object state) + { + s_context.Post((o) => SendSignals(-1), null); + } + } + + #region IPlatformServices implementation + + public double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes) + { + switch (size) + { + case NamedSize.Micro: + return 16; + case NamedSize.Small: + return 20; + case NamedSize.Default: + case NamedSize.Medium: + return 24; + case NamedSize.Large: + return 30; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public void OpenUriAction(Uri uri) + { + throw new NotImplementedException(); + } + + public void BeginInvokeOnMainThread(Action action) + { + if (EcoreMainloop.IsMainThread) + { + action(); + } + else + { + s_context.Post((o) => action(), null); + } + } + + public Xamarin.Forms.Internals.Ticker CreateTicker() + { + return new Ticker(); + } + + public void StartTimer(TimeSpan interval, Func<bool> callback) + { + Timer timer = null; + bool invoking = false; + TimerCallback onTimeout = o => + { + if (!invoking) + { + invoking = true; + BeginInvokeOnMainThread(() => + { + if (!callback()) + { + timer.Dispose(); + } + invoking = false; + } + ); + } + }; + timer = new Timer(onTimeout, null, Timeout.Infinite, Timeout.Infinite); + // set interval separarately to prevent calling onTimeout before `timer' is assigned + timer.Change(interval, interval); + } + + public async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken) + { + using (var client = new HttpClient()) + using (HttpResponseMessage response = await client.GetAsync(uri, cancellationToken)) + return await response.Content.ReadAsStreamAsync(); + } + + public Assembly[] GetAssemblies() + { + return AppDomain.CurrentDomain.GetAssemblies(); + } + + public ITimer CreateTimer(Action<object> callback, object state, int dueTime, int period) + { + return new _Timer(new Timer((object o) => callback(o), state, dueTime, period)); + } + + public ITimer CreateTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period) + { + return new _Timer(new Timer((object o) => callback(o), state, dueTime, period)); + } + + public IIsolatedStorageFile GetUserStoreForApplication() + { + return new TizenIsolatedStorageFile(); + } + + static readonly char[] HexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + public string GetMD5Hash(string input) + { + byte[] bin = checksum.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input)); + char[] hex = new char[32]; + for (var i = 0; i < 16; ++i) + { + hex[2 * i] = HexDigits[bin[i] >> 4]; + hex[2 * i + 1] = HexDigits[bin[i] & 0xf]; + } + return new string(hex); + } + + public bool IsInvokeRequired + { + get + { + return !EcoreMainloop.IsMainThread; + } + } + + #endregion + +#if !NET45 + // In .NETCore, AppDomain is not supported. The list of the assemblies should be generated manually. + internal class AppDomain + { + public static AppDomain CurrentDomain { get; private set; } + + List<Assembly> _assemblies; + + static AppDomain() + { + CurrentDomain = new AppDomain(); + } + + AppDomain() + { + _assemblies = new List<Assembly>(); + + // Add this renderer assembly to the list + RegisterAssembly(GetType().GetTypeInfo().Assembly); + } + + internal void RegisterAssembly(Assembly asm) + { + if (!_assemblies.Contains(asm)) + { + _assemblies.Add(asm); + } + } + + public Assembly[] GetAssemblies() + { + return _assemblies.ToArray(); + } + } +#endif + } +} + diff --git a/Xamarin.Forms.Platform.Tizen/TizenTitleBarVisibility.cs b/Xamarin.Forms.Platform.Tizen/TizenTitleBarVisibility.cs new file mode 100644 index 00000000..4cd09d82 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/TizenTitleBarVisibility.cs @@ -0,0 +1,8 @@ +namespace Xamarin.Forms.Platform.Tizen +{ + public enum TizenTitleBarVisibility + { + Default, + Never + } +} diff --git a/Xamarin.Forms.Platform.Tizen/ViewInitializedEventArgs.cs b/Xamarin.Forms.Platform.Tizen/ViewInitializedEventArgs.cs new file mode 100644 index 00000000..fb05c1ad --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/ViewInitializedEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using ElmSharp; + +namespace Xamarin.Forms.Platform.Tizen +{ + public class ViewInitializedEventArgs : EventArgs + { + public EvasObject NativeView + { + get; + internal set; + } + + public VisualElement View + { + get; + internal set; + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/VisualElementChangedEventArgs.cs b/Xamarin.Forms.Platform.Tizen/VisualElementChangedEventArgs.cs new file mode 100644 index 00000000..35e2105a --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/VisualElementChangedEventArgs.cs @@ -0,0 +1,16 @@ +namespace Xamarin.Forms.Platform.Tizen +{ + /// <summary> + /// Represents the arguments passed to every VisualElement change event. + /// </summary> + public class VisualElementChangedEventArgs : ElementChangedEventArgs<VisualElement> + { + /// <summary> + /// Initializes a new instance of the <see cref="Xamarin.Forms.Platform.Tizen.VisualElementChangedEventArgs"/> class. + /// </summary> + /// <param name="oldElement">Old element.</param> + /// <param name="newElement">New element.</param> + public VisualElementChangedEventArgs(VisualElement oldElement, VisualElement newElement) : base(oldElement, newElement) { + } + } +} diff --git a/Xamarin.Forms.Platform.Tizen/VisualElementRendererFlags.cs b/Xamarin.Forms.Platform.Tizen/VisualElementRendererFlags.cs new file mode 100644 index 00000000..731dd6a1 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/VisualElementRendererFlags.cs @@ -0,0 +1,13 @@ +using System; + +namespace Xamarin.Forms.Platform.Tizen +{ + [Flags] + public enum VisualElementRendererFlags : byte + { + None = 0, + Disposed = 1, + NeedsLayout = 2, + NeedsTransformation = 4, + } +} diff --git a/Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.csproj b/Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.csproj new file mode 100644 index 00000000..818e4803 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.csproj @@ -0,0 +1,163 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Xamarin.Forms.Platform.Tizen</RootNamespace> + <AssemblyName>Xamarin.Forms.Platform.Tizen</AssemblyName> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup> + <TargetFrameworkIdentifier>.NETStandard</TargetFrameworkIdentifier> + <TargetFrameworkVersion>v1.6</TargetFrameworkVersion> + <NuGetTargetMoniker>.NETStandard,Version=v1.6</NuGetTargetMoniker> + <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences> + <NoStdLib>true</NoStdLib> + <NoWarn>$(NoWarn);1701;1702</NoWarn> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Compile Include="Cells\CellRenderer.cs" /> + <Compile Include="Cells\EntryCellRenderer.cs" /> + <Compile Include="Cells\ImageCellRenderer.cs" /> + <Compile Include="Cells\SwitchCellRenderer.cs" /> + <Compile Include="Cells\TextCellRenderer.cs" /> + <Compile Include="Cells\ViewCellRenderer.cs" /> + <Compile Include="Deserializer.cs" /> + <Compile Include="ElementChangedEventArgs.cs" /> + <Compile Include="EvasObjectWrapper.cs" /> + <Compile Include="ExportImageSourceHandlerAttribute.cs" /> + <Compile Include="ExportCellAttribute.cs" /> + <Compile Include="ExportRendererAttribute.cs" /> + <Compile Include="Extensions\ColorExtensions.cs" /> + <Compile Include="Extensions\KeyboardExtensions.cs" /> + <Compile Include="Extensions\LayoutExtensions.cs" /> + <Compile Include="Extensions\ScrollToPositionExtensions.cs" /> + <Compile Include="Extensions\TextAlignmentExtensions.cs" /> + <Compile Include="Forms.cs" /> + <Compile Include="GestureHandler.cs" /> + <Compile Include="FormsApplication.cs" /> + <Compile Include="Log\ConsoleLogger.cs" /> + <Compile Include="Log\DlogLogger.cs" /> + <Compile Include="Log\ILogger.cs" /> + <Compile Include="Log\Log.cs" /> + <Compile Include="Log\XamarinLogListener.cs" /> + <Compile Include="Native\Box.cs" /> + <Compile Include="Native\Button.cs" /> + <Compile Include="Native\Canvas.cs" /> + <Compile Include="Native\ContentPage.cs" /> + <Compile Include="Native\DateChangedEventArgs.cs" /> + <Compile Include="Native\DatePicker.cs" /> + <Compile Include="Native\DateTimePickerDialog.cs" /> + <Compile Include="Native\Dialog.cs" /> + <Compile Include="Native\DisplayOrientations.cs" /> + <Compile Include="Native\Entry.cs" /> + <Compile Include="Native\FormattedString.cs" /> + <Compile Include="Native\IContainable.cs" /> + <Compile Include="Native\Image.cs" /> + <Compile Include="Native\IMeasurable.cs" /> + <Compile Include="Native\ITextable.cs" /> + <Compile Include="Native\Keyboard.cs" /> + <Compile Include="Native\Label.cs" /> + <Compile Include="Native\LayoutEventArgs.cs" /> + <Compile Include="Native\LineBreakMode.cs" /> + <Compile Include="Native\ListView.cs" /> + <Compile Include="Native\MasterDetailPage.cs" /> + <Compile Include="Native\ObservableCollection.cs" /> + <Compile Include="Native\SearchBar.cs" /> + <Compile Include="Native\Span.cs" /> + <Compile Include="Native\TableView.cs" /> + <Compile Include="Native\TextAlignment.cs" /> + <Compile Include="Native\TextHelper.cs" /> + <Compile Include="Native\TimePicker.cs" /> + <Compile Include="Native\Window.cs" /> + <Compile Include="Platform.cs" /> + <Compile Include="PlatformEffect.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Renderers\ActivityIndicatorRenderer.cs" /> + <Compile Include="Renderers\BoxViewRenderer.cs" /> + <Compile Include="Renderers\ButtonRenderer.cs" /> + <Compile Include="Renderers\CarouselPageRenderer.cs" /> + <Compile Include="Renderers\ContentPageRenderer.cs" /> + <Compile Include="Renderers\DatePickerRenderer.cs" /> + <Compile Include="Renderers\EditorRenderer.cs" /> + <Compile Include="Renderers\EntryRenderer.cs" /> + <Compile Include="Renderers\EvasObjectWrapperRenderer.cs" /> + <Compile Include="Renderers\FrameRenderer.cs" /> + <Compile Include="Renderers\ImageRenderer.cs" /> + <Compile Include="Renderers\IVisualElementRenderer.cs" /> + <Compile Include="Renderers\LabelRenderer.cs" /> + <Compile Include="Renderers\LayoutRenderer.cs" /> + <Compile Include="Renderers\ListViewRenderer.cs" /> + <Compile Include="Renderers\MasterDetailPageRenderer.cs" /> + <Compile Include="Renderers\NavigationPageRenderer.cs" /> + <Compile Include="Renderers\PickerRenderer.cs" /> + <Compile Include="Renderers\ProgressBarRenderer.cs" /> + <Compile Include="Renderers\ScrollViewRenderer.cs" /> + <Compile Include="Renderers\SearchBarRenderer.cs" /> + <Compile Include="Renderers\SliderRenderer.cs" /> + <Compile Include="Renderers\StepperRenderer.cs" /> + <Compile Include="Renderers\SwitchRenderer.cs" /> + <Compile Include="Renderers\TabbedPageRenderer.cs" /> + <Compile Include="Renderers\TableViewRenderer.cs" /> + <Compile Include="Renderers\TimePickerRenderer.cs" /> + <Compile Include="Renderers\ViewRenderer.cs" /> + <Compile Include="Renderers\VisualElementRenderer.cs" /> + <Compile Include="ResourcePath.cs" /> + <Compile Include="ResourcesProvider.cs" /> + <Compile Include="TizenPlatformServices.cs" /> + <Compile Include="TizenTitleBarVisibility.cs" /> + <Compile Include="ViewInitializedEventArgs.cs" /> + <Compile Include="VisualElementChangedEventArgs.cs" /> + <Compile Include="VisualElementRendererFlags.cs" /> + <Compile Include="TizenIsolatedStorageFile.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Xamarin.Forms.Platform.Tizen.project.json" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> + <ItemGroup> + <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> + <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project> + <Name>Xamarin.Forms.Core</Name> + </ProjectReference> + </ItemGroup> + <PropertyGroup> + <!-- https://github.com/dotnet/corefxlab/tree/master/samples/NetCoreSample and + https://docs.microsoft.com/en-us/dotnet/articles/core/tutorials/target-dotnetcore-with-msbuild + --> + <!-- We don't use any of MSBuild's resolution logic for resolving the framework, so just set these two + properties to any folder that exists to skip the GetReferenceAssemblyPaths task (not target) and + to prevent it from outputting a warning (MSB3644). + --> + <_TargetFrameworkDirectories>$(MSBuildThisFileDirectory)</_TargetFrameworkDirectories> + <_FullFrameworkReferenceAssemblyPaths>$(MSBuildThisFileDirectory)</_FullFrameworkReferenceAssemblyPaths> + <AutoUnifyAssemblyReferences>true</AutoUnifyAssemblyReferences> + </PropertyGroup> +</Project> diff --git a/Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.project.json b/Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.project.json new file mode 100644 index 00000000..02b62b96 --- /dev/null +++ b/Xamarin.Forms.Platform.Tizen/Xamarin.Forms.Platform.Tizen.project.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "ElmSharp": "1.1.0-*", + "NETStandard.Library": "1.6.0", + "System.Runtime.Serialization.Xml": "4.1.1", + "Tizen.Applications": "1.0.2" + }, + "frameworks": { + "netstandard1.6": { + "imports": "portable-net45+win8+wpa81+wp8" + } + } +} diff --git a/Xamarin.Forms.Tizen.sln b/Xamarin.Forms.Tizen.sln new file mode 100644 index 00000000..ecc5d8f5 --- /dev/null +++ b/Xamarin.Forms.Tizen.sln @@ -0,0 +1,65 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Core", "Xamarin.Forms.Core\Xamarin.Forms.Core.csproj", "{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Platform", "Xamarin.Forms.Platform\Xamarin.Forms.Platform.csproj", "{67F9D3A8-F71E-4428-913F-C37AE82CDB24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Xaml", "Xamarin.Forms.Xaml\Xamarin.Forms.Xaml.csproj", "{9DB2F292-8034-4E06-89AD-98BBDA4306B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Platform.Tizen", "Xamarin.Forms.Platform.Tizen\Xamarin.Forms.Platform.Tizen.csproj", "{227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Maps", "Xamarin.Forms.Maps\Xamarin.Forms.Maps.csproj", "{7D13BAC2-C6A4-416A-B07E-C169B199E52B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Forms.Maps.Tizen", "Xamarin.Forms.Maps.Tizen\Xamarin.Forms.Maps.Tizen.csproj", "{D29D5750-9A39-4E92-A19E-62567D660B7D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Turkey|Any CPU = Turkey|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC}.Release|Any CPU.Build.0 = Release|Any CPU + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC}.Turkey|Any CPU.ActiveCfg = Turkey|Any CPU + {57B8B73D-C3B5-4C42-869E-7B2F17D354AC}.Turkey|Any CPU.Build.0 = Turkey|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|Any CPU.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Turkey|Any CPU.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Turkey|Any CPU.Build.0 = Release|Any CPU + {9DB2F292-8034-4E06-89AD-98BBDA4306B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DB2F292-8034-4E06-89AD-98BBDA4306B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DB2F292-8034-4E06-89AD-98BBDA4306B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DB2F292-8034-4E06-89AD-98BBDA4306B9}.Release|Any CPU.Build.0 = Release|Any CPU + {9DB2F292-8034-4E06-89AD-98BBDA4306B9}.Turkey|Any CPU.ActiveCfg = Turkey|Any CPU + {9DB2F292-8034-4E06-89AD-98BBDA4306B9}.Turkey|Any CPU.Build.0 = Turkey|Any CPU + {227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}.Release|Any CPU.Build.0 = Release|Any CPU + {227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}.Turkey|Any CPU.ActiveCfg = Release|Any CPU + {227D2CC5-26A1-4CE7-AE25-1B18AF625B9C}.Turkey|Any CPU.Build.0 = Release|Any CPU + {7D13BAC2-C6A4-416A-B07E-C169B199E52B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D13BAC2-C6A4-416A-B07E-C169B199E52B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D13BAC2-C6A4-416A-B07E-C169B199E52B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D13BAC2-C6A4-416A-B07E-C169B199E52B}.Release|Any CPU.Build.0 = Release|Any CPU + {7D13BAC2-C6A4-416A-B07E-C169B199E52B}.Turkey|Any CPU.ActiveCfg = Turkey|Any CPU + {7D13BAC2-C6A4-416A-B07E-C169B199E52B}.Turkey|Any CPU.Build.0 = Turkey|Any CPU + {D29D5750-9A39-4E92-A19E-62567D660B7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D29D5750-9A39-4E92-A19E-62567D660B7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D29D5750-9A39-4E92-A19E-62567D660B7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D29D5750-9A39-4E92-A19E-62567D660B7D}.Release|Any CPU.Build.0 = Release|Any CPU + {D29D5750-9A39-4E92-A19E-62567D660B7D}.Turkey|Any CPU.ActiveCfg = Release|Any CPU + {D29D5750-9A39-4E92-A19E-62567D660B7D}.Turkey|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/packaging/custom-find-requires b/packaging/custom-find-requires new file mode 100755 index 00000000..ebd22cbc --- /dev/null +++ b/packaging/custom-find-requires @@ -0,0 +1,12 @@ +#!/bin/bash + +if [ -z "$RPM_BUILD_ROOT" ] ; then + echo "The $0 script is not intended to be executed directly." > /dev/stderr + exit 1 +fi + +# this grep will consume whole list of files from stdin +if grep -qF /Xamarin.Forms.Platform.Tizen.dll ; then + # if it's a X.F.P.Tizen package, then find the version of elm-sharp (or elm-sharp-nuget) in use + rpm -qa --queryformat 'elm-sharp >= %{VERSION}\n' 'elm-sharp*' | head -n1 +fi diff --git a/packaging/xamarin-forms-tizen.manifest b/packaging/xamarin-forms-tizen.manifest new file mode 100755 index 00000000..75b0fa5e --- /dev/null +++ b/packaging/xamarin-forms-tizen.manifest @@ -0,0 +1,5 @@ +<manifest> + <request> + <domain name="_"/> + </request> +</manifest> diff --git a/packaging/xamarin-forms-tizen.spec b/packaging/xamarin-forms-tizen.spec new file mode 100644 index 00000000..074629b1 --- /dev/null +++ b/packaging/xamarin-forms-tizen.spec @@ -0,0 +1,94 @@ +%{!?dotnet_assembly_path: %define dotnet_assembly_path /opt/usr/share/dotnet.tizen/framework} + +%if 0%{?tizen_build_devel_mode} +%define BUILDCONF Debug +%else +%define BUILDCONF Release +%endif + +%define XF_VERSION 2.3.3.175 +#%define XF_PRETAG pre3 + +# Increase this XF_TIZEN_VERSION when any public APIs of Xamarin.Forms.Platform.Tizen are changed. +%define XF_TIZEN_VERSION b01 + +Name: xamarin-forms-tizen +Summary: Xamarin.Forms for Tizen platform +Version: %{XF_VERSION} +Release: %{XF_TIZEN_VERSION} +License: MIT +Group: Graphics & UI Framework/Libraries +Source0: %{name}-%{version}.tar.gz +Source1: %{name}.manifest + +%define NUPKG_VERSION %{XF_VERSION}%{?XF_PRETAG:-%{XF_PRETAG}}-%{XF_TIZEN_VERSION} + +# Instead of disabling Automatic Dependencies completely with `AutoReqProv: no' +# let's override Mono provided portion +%define __mono_requires %{_builddir}/%name-%version/packaging/custom-find-requires + +ExcludeArch: aarch64 %ix86 + +BuildRequires: mono-compiler +BuildRequires: mono-devel +BuildRequires: referenceassemblies-pcl + +BuildRequires: dotnet-build-tools + +# C# API Requires +BuildRequires: csapi-tizen-nuget +BuildRequires: csapi-application-nuget +BuildRequires: elm-sharp-nuget + +%description +Allows one to use portable controls subsets that are mapped to native +controls of Android, iOS, Windows Phone, and Tizen. + +%prep +%setup -q +cp %{SOURCE1} . + +%build +# Restore NuGet Dependencies +find . -name *.Tizen.project.json -print0 | xargs -n1 -0 nuget restore + +# Build +xbuild Xamarin.Forms.Tizen.sln \ + /p:Configuration=%{BUILDCONF} \ + /p:PackageSources=/nuget + +# Create NuGet Packages +nuget pack .nuspec/Xamarin.Forms.Platform.Tizen.nuspec \ + -BasePath ./.nuspec -Version %{NUPKG_VERSION} \ + -Properties "Configuration=%{BUILDCONF}" + +%install +function install_asm() +{ + mkdir -p %{buildroot}%{dotnet_assembly_path} + install -p -m 644 $1/bin/%{BUILDCONF}/$1.dll %{buildroot}%{dotnet_assembly_path} +} + +install_asm Xamarin.Forms.Core +install_asm Xamarin.Forms.Xaml +install_asm Xamarin.Forms.Platform +install_asm Xamarin.Forms.Platform.Tizen + +mkdir -p %{buildroot}/nuget +install -p -m 644 *.nupkg %{buildroot}/nuget + +%files +%manifest %{name}.manifest +%license LICENSE +%attr(644,root,root) %{dotnet_assembly_path}/*.dll + +%package nuget +Summary: NuGet package for %{name} +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description nuget +NuGet package for %{name} + +%files nuget +/nuget/*.nupkg |