Browse Source

Samsung Tizen (#12151)

* implement tizen platform, resolve #6110

* Fix enum types of touch event

* Fix issue of touch tracking.

* #6110 fix ui thread lock

* #6110 send keypad from tizen window

* #6110 fix touch update

* #6110 add text editor for input fields

* #6110 Hide text field

* #6110 Fix issue of text selection and backspace

* #6110 support multi and single line for input

* #6110 temporary set multiline true by default.

* #6110 fix issue of build with latest version and make it compatible

* #6110 by pass double input when softkeyboard showing.

* #6110 adding support for clipboard.

* #6110 add permission helper to tizen platform

* #6110 Implementation of storage provider for tizen

* #6110 send text for key character from hard keyboard

* #6110 Complete the input implementation. soft and hard keyboard

* #6110 remove ElmSharp

* Update Permissions.cs

* #6110 remove open folder for tizen

* #6110 fix the issue of keyboard frezee for tizen

* #6110 add workload to build tizen

* #6110 try to setup tizen workloads for windows

* #6110 change tizen workload install to powershell

* #6110 skip api diff check when package not exists

* #6110 try to fix linux pipeline

* #6110 implement tizen native view

* #6110 add dev analyzer and trimming

* #6110 Migrate to GLES and remove framebuffer

* #6110 Temporary solution to make GL work

* #6110 WIP and add comments

* #6110 optimise gles rendering and timing.

* #6110 fix dependency issue of tizen HarfBuzzSharp

* #6110 patch a fix for clipboard issue

* #6110 Support tizen sdk for old proj type, WIP

* #6110 wip setup app

* #6110 support only net6 for tizen

* #6110 resolve comments

---------

Co-authored-by: Kangho Hur <[email protected]>
Co-authored-by: Max Katz <[email protected]>
Omid Mafakher 2 years ago
parent
commit
3687cb9ecc
33 changed files with 2112 additions and 2 deletions
  1. 18 0
      Avalonia.sln
  2. 22 1
      azure-pipelines.yml
  3. 9 1
      nukebuild/ApiDiffValidation.cs
  4. 20 0
      samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj
  5. 41 0
      samples/ControlCatalog.Tizen/EmbedSampleNuiTizen.cs
  6. 23 0
      samples/ControlCatalog.Tizen/Main.cs
  7. BIN
      samples/ControlCatalog.Tizen/shared/res/Avalonia.png
  8. 23 0
      samples/ControlCatalog.Tizen/tizen-manifest.xml
  9. 1 0
      src/Avalonia.Base/Avalonia.Base.csproj
  10. 20 0
      src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
  11. 12 0
      src/Tizen/Avalonia.Tizen/ITizenView.cs
  12. 179 0
      src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
  13. 205 0
      src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs
  14. 76 0
      src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs
  15. 73 0
      src/Tizen/Avalonia.Tizen/NuiGlLayerSurface.cs
  16. 87 0
      src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs
  17. 102 0
      src/Tizen/Avalonia.Tizen/NuiKeyboardHandler.cs
  18. 117 0
      src/Tizen/Avalonia.Tizen/NuiNativeControlHostImpl.cs
  19. 73 0
      src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
  20. 99 0
      src/Tizen/Avalonia.Tizen/NuiTouchHandler.cs
  21. 39 0
      src/Tizen/Avalonia.Tizen/NuiViewControlHandle.cs
  22. 29 0
      src/Tizen/Avalonia.Tizen/Platform/Consts.cs
  23. 221 0
      src/Tizen/Avalonia.Tizen/Platform/Input/TizenKeyboardDevice.cs
  24. 20 0
      src/Tizen/Avalonia.Tizen/Platform/Interop/Strucs.cs
  25. 110 0
      src/Tizen/Avalonia.Tizen/Platform/Permissions.cs
  26. 10 0
      src/Tizen/Avalonia.Tizen/Platform/TizenPlatformSettings.cs
  27. 65 0
      src/Tizen/Avalonia.Tizen/Stubs.cs
  28. 20 0
      src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs
  29. 35 0
      src/Tizen/Avalonia.Tizen/TizenPlatform.cs
  30. 44 0
      src/Tizen/Avalonia.Tizen/TizenRenderTimer.cs
  31. 152 0
      src/Tizen/Avalonia.Tizen/TizenStorageProvider.cs
  32. 50 0
      src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs
  33. 117 0
      src/Tizen/Avalonia.Tizen/TopLevelImpl.cs

+ 18 - 0
Avalonia.sln

@@ -273,6 +273,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit.Uni
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Browser", "samples\MobileSandbox.Browser\MobileSandbox.Browser.csproj", "{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tizen", "Tizen", "{D1300000-7217-4693-8B0F-57CBD5814302}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Tizen", "src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj", "{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Tizen", "samples\ControlCatalog.Tizen\ControlCatalog.Tizen.csproj", "{A0B29221-2B6F-4B29-A4D5-2227811B5915}"
+EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Metal", "src\Avalonia.Metal\Avalonia.Metal.csproj", "{60B4ED1F-ECFA-453B-8A70-1788261C8355}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks.UnitTest", "tests\Avalonia.Build.Tasks.UnitTest\Avalonia.Build.Tasks.UnitTest.csproj", "{B0FD6A48-FBAB-4676-B36A-DE76B0922B12}"
@@ -670,6 +676,16 @@ Global
 		{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915}.Release|Any CPU.Deploy.0 = Release|Any CPU
 		{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -764,6 +780,8 @@ Global
 		{2999D79E-3C20-4A90-B651-CA7E0AC92D35} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+		{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8} = {D1300000-7217-4693-8B0F-57CBD5814302}
+		{A0B29221-2B6F-4B29-A4D5-2227811B5915} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{B0FD6A48-FBAB-4676-B36A-DE76B0922B12} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{9D6AEF22-221F-4F4B-B335-A4BA510F002C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{5BF0C3B8-E595-4940-AB30-2DA206C2F085} = {9D6AEF22-221F-4F4B-B335-A4BA510F002C}

+ 22 - 1
azure-pipelines.yml

@@ -45,6 +45,13 @@ jobs:
       script: |
        dotnet workload install wasm-tools wasm-experimental
 
+  - task: CmdLine@2
+    displayName: 'Install Tizen Workload'
+    inputs:
+      targetType: 'inline'
+      script: |
+       curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | sudo bash -s -- -d "/opt/hostedtoolcache/dotnet"
+
   - task: CmdLine@2
     displayName: 'Run Build'
     inputs:
@@ -81,7 +88,14 @@ jobs:
     inputs:
       script: |
        dotnet workload install wasm-tools wasm-experimental
-      
+
+  - task: CmdLine@2
+    displayName: 'Install Tizen Workload'
+    inputs:
+      targetType: 'inline'
+      script: |
+       curl -sSL https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.sh | sudo bash
+  
   - task: CmdLine@2
     displayName: 'Generate avalonia-native'
     inputs:
@@ -153,6 +167,13 @@ jobs:
       script: |
        dotnet workload install android ios wasm-tools wasm-experimental
 
+  - task: PowerShell@2
+    displayName: 'Install Tizen Workload'
+    inputs:
+      targetType: 'inline'
+      script: |
+       (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Samsung/Tizen.NET/main/workload/scripts/workload-install.ps1') | Invoke-Expression
+
   - task: CmdLine@2
     displayName: 'Install Nuke'
     inputs:

+ 9 - 1
nukebuild/ApiDiffValidation.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO.Compression;
 using System.Linq;
+using System.Net;
 using System.Net.Http;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
@@ -27,7 +28,10 @@ public static class ApiDiffValidation
             Directory.CreateDirectory(suppressionFilesFolder!);
         }
 
-        await using (var baselineStream = await DownloadBaselinePackage(packagePath, baselineVersion))
+        await using var baselineStream = await DownloadBaselinePackage(packagePath, baselineVersion);
+        if (baselineStream == null) 
+            return;
+
         using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read))
         using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read))
         using (Helpers.UseTempDir(out var tempFolder))
@@ -139,6 +143,10 @@ public static class ApiDiffValidation
             memoryStream.Seek(0, SeekOrigin.Begin);
             return memoryStream;
         }
+        catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.NotFound)
+        {
+            return null;
+        }
         catch (Exception ex)
         {
             throw new InvalidOperationException($"Downloading baseline package for {packageId} {baselineVersion} failed.\r" + ex.Message, ex);

+ 20 - 0
samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net6.0-tizen</TargetFramework>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  
+  <ItemGroup>
+    <TizenSharedResource Remove="shared\res\Avalonia.png" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj" />
+    <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
+  </ItemGroup>
+
+
+  <ItemGroup>
+    <Folder Include="lib\" />
+    <Folder Include="res\" />
+  </ItemGroup>
+</Project>

+ 41 - 0
samples/ControlCatalog.Tizen/EmbedSampleNuiTizen.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using Avalonia.Tizen;
+using ControlCatalog.Pages;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+using Tizen.Pims.Contacts.ContactsViews;
+
+namespace ControlCatalog.Tizen;
+public class EmbedSampleNuiTizen : INativeDemoControl
+{
+    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
+    {
+        if (isSecond)
+        {
+            var webView = new WebView();
+            webView.LoadUrl("https://avaloniaui.net/");
+            return new NuiViewControlHandle(webView);
+        }
+        else
+        {
+            var clickCount = 0;
+            var button = new Button
+            {
+                Text = "Hello world"
+            };
+
+            button.Clicked += (sender, e) =>
+            {
+                clickCount++;
+                button.Text = $"Click count {clickCount}";
+            };
+
+            return new NuiViewControlHandle(button);
+        }
+    }
+}

+ 23 - 0
samples/ControlCatalog.Tizen/Main.cs

@@ -0,0 +1,23 @@
+using System;
+using Avalonia;
+using Avalonia.Tizen;
+using ElmSharp;
+using SkiaSharp;
+using Tizen.Applications;
+
+namespace ControlCatalog.Tizen;
+
+class Program : NuiTizenApplication<App>
+{
+    protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) => 
+        base.CustomizeAppBuilder(builder).AfterSetup(_ =>
+        {
+            Pages.EmbedSample.Implementation = new EmbedSampleNuiTizen();
+        });
+
+    static void Main(string[] args)
+    {
+        var app = new Program();
+        app.Run(args);
+    }
+}

BIN
samples/ControlCatalog.Tizen/shared/res/Avalonia.png


+ 23 - 0
samples/ControlCatalog.Tizen/tizen-manifest.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="com.avalonia.control-catalog" version="1.0.0" api-version="7.0" xmlns="http://tizen.org/ns/packages">
+    <profile name="common" />
+    <ui-application appid="com.avalonia.control-catalog" exec="ControlCatalog.Tizen.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" hw-acceleration="on" launch_mode="single">
+        <label>Avalonia</label>
+        <icon>Avalonia.png</icon>
+        <metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
+        <splash-screens />
+    </ui-application>
+    <shortcut-list />
+    <privileges>
+        <privilege>http://tizen.org/privilege/appdir.shareddata</privilege>
+        <privilege>http://tizen.org/privilege/appmanager.launch</privilege>
+        <privilege>http://tizen.org/privilege/externalstorage</privilege>
+        <privilege>http://tizen.org/privilege/externalstorage.appdata</privilege>
+        <privilege>http://tizen.org/privilege/internet</privilege>
+        <privilege>http://tizen.org/privilege/network.get</privilege>
+    </privileges>
+    <dependencies />
+    <provides-appdefined-privileges />
+    <feature>http://tizen.org/feature/opengles.surfaceless_context</feature>
+    <feature>http://tizen.org/feature/opengles.version.2_0</feature>
+</manifest>

+ 1 - 0
src/Avalonia.Base/Avalonia.Base.csproj

@@ -54,6 +54,7 @@
     <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Android, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.iOS, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Tizen, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.LinuxFramebuffer, PublicKey=$(AvaloniaPublicKey)" />

+ 20 - 0
src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>net6.0-tizen</TargetFrameworks>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
+    <PackageReference Include="HarfBuzzSharp.NativeAssets.Tizen" Version="2.8.2.3" />
+    
+    <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
+  </ItemGroup>
+
+  <Import Project="..\..\..\build\DevAnalyzers.props" />
+  <Import Project="..\..\..\build\TrimmingEnable.props" />
+</Project>

+ 12 - 0
src/Tizen/Avalonia.Tizen/ITizenView.cs

@@ -0,0 +1,12 @@
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+
+namespace Avalonia.Tizen;
+
+internal interface ITizenView
+{
+    Size ClientSize { get; }
+    double Scaling { get; }
+    IInputRoot InputRoot { get; set; }
+    INativeControlHostImpl NativeControlHost { get; }
+}

+ 179 - 0
src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs

@@ -0,0 +1,179 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Embedding;
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Avalonia.Input.TextInput;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Server;
+using Avalonia.Threading;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+
+namespace Avalonia.Tizen;
+
+/// <summary>
+/// Avalonia View for Tizen NUI controls
+/// </summary>
+public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
+{
+    private readonly NuiKeyboardHandler _keyboardHandler;
+    private readonly NuiTouchHandler _touchHandler;
+    private readonly NuiAvaloniaViewTextEditable _textEditor;
+    private TizenRenderTimer? _renderTimer;
+    private TopLevelImpl _topLevelImpl;
+    private EmbeddableControlRoot _topLevel;
+    private TouchDevice _device = new();
+    private ServerCompositionTarget _compositionTargetServer;
+
+    public IInputRoot InputRoot { get; set; }
+    public INativeControlHostImpl NativeControlHost { get; }
+    internal TopLevelImpl TopLevelImpl => _topLevelImpl;
+    public double Scaling => 1;
+    public Size ClientSize => new(Size.Width, Size.Height);
+
+    public Control? Content
+    {
+        get => _topLevel.Content as Control;
+        set => _topLevel.Content = value;
+    }
+
+    internal NuiAvaloniaViewTextEditable TextEditor => _textEditor;
+    internal NuiKeyboardHandler KeyboardHandler => _keyboardHandler;
+
+    #region Setup
+
+    public event Action OnSurfaceInit;
+
+    public NuiAvaloniaView() : base(ColorFormat.RGBA8888)
+    {
+        RenderingMode = GLRenderingMode.OnDemand;
+        SetGraphicsConfig(true, true, 0, GLESVersion.Version30);
+        RegisterGLCallbacks(GlInit, GlRenderFrame, GlTerminate);
+
+        _textEditor = new NuiAvaloniaViewTextEditable(this);
+        _keyboardHandler = new NuiKeyboardHandler(this);
+        _touchHandler = new NuiTouchHandler(this);
+        NativeControlHost = new NuiNativeControlHostImpl(this);
+
+        Layout = new CustomLayout
+        {
+            SizeUpdated = OnResized
+        };
+
+        TouchEvent += OnTouchEvent;
+        WheelEvent += OnWheelEvent;
+    }
+
+    private void GlInit()
+    {
+        OnSurfaceInit?.Invoke();
+    }
+
+    private int GlRenderFrame()
+    {
+        if (_renderTimer == null || _topLevel == null)
+            return 0;
+
+        var rev = _compositionTargetServer.Revision;
+        _renderTimer.ManualTick();
+        return rev == _compositionTargetServer.Revision ? 0 : 1;
+    }
+
+    private void GlTerminate()
+    {
+    }
+
+    internal void Initialise()
+    {
+        _topLevelImpl = new TopLevelImpl(this, new[] { new NuiGlLayerSurface(this) });
+        _topLevelImpl.Compositor.AfterCommit += RenderOnce;
+        TizenPlatform.ThreadingInterface.TickExecuted += RenderOnce;
+
+        _topLevel = new(_topLevelImpl);
+        _topLevel.Prepare();
+        _topLevel.StartRendering();
+
+        _compositionTargetServer = ((CompositingRenderer)((IRenderRoot)_topLevel).Renderer).CompositionTarget.Server;
+
+        _renderTimer = (TizenRenderTimer)AvaloniaLocator.Current.GetRequiredService<IRenderTimer>();
+        _renderTimer.RenderTick += RenderOnce;
+
+        OnResized();
+    }
+
+    #endregion
+
+    #region Resize and layout
+
+    private class CustomLayout : AbsoluteLayout
+    {
+        float _width;
+        float _height;
+
+        public Action? SizeUpdated { get; set; }
+
+        protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
+        {
+            var sizeChanged = _width != Owner.SizeWidth || _height != Owner.SizeHeight;
+            _width = Owner.SizeWidth;
+            _height = Owner.SizeHeight;
+            if (sizeChanged)
+            {
+                SizeUpdated?.Invoke();
+            }
+            base.OnLayout(changed, left, top, right, bottom);
+        }
+    }
+
+    protected void OnResized()
+    {
+        if (Size.Width == 0 || Size.Height == 0)
+            return;
+
+        _topLevelImpl?.Resized?.Invoke(_topLevelImpl.ClientSize, WindowResizeReason.Layout);
+    }
+
+    #endregion
+
+    #region Event handlers
+
+    private bool OnTouchEvent(object source, TouchEventArgs e)
+    {
+        _touchHandler?.Handle(e);
+        return true;
+    }
+
+    private bool OnWheelEvent(object source, WheelEventArgs e)
+    {
+        _touchHandler?.Handle(e);
+        return true;
+    }
+
+    public void SetClient(TextInputMethodClient? client)
+    {
+        _textEditor.SetClient(client);
+    }
+
+    public void SetCursorRect(Rect rect)
+    {
+    }
+
+    public void SetOptions(TextInputOptions options) =>
+        _textEditor.SetOptions(options);
+
+    #endregion
+
+    protected override void Dispose(bool disposing)
+    {
+        if (disposing)
+        {
+            _topLevel.StopRendering();
+            _topLevel.Dispose();
+            _topLevelImpl.Dispose();
+            _device.Dispose();
+        }
+        base.Dispose(disposing);
+    }
+}

+ 205 - 0
src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs

@@ -0,0 +1,205 @@
+using Avalonia.Input.TextInput;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Window = Tizen.NUI.Window;
+
+namespace Avalonia.Tizen;
+
+internal class NuiAvaloniaViewTextEditable
+{
+    private readonly NuiAvaloniaView _avaloniaView;
+
+    private INuiTextInput TextInput => _multiline ? _multiLineTextInput : _singleLineTextInput;
+    private readonly NuiSingleLineTextInput _singleLineTextInput;
+    private readonly NuiMultiLineTextInput _multiLineTextInput;
+    private bool _updating;
+    private bool _keyboardPresented;
+    private bool _multiline;
+
+    private TextInputMethodClient? _client;
+
+    public bool IsActive => _client != null && _keyboardPresented;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+    public NuiAvaloniaViewTextEditable(NuiAvaloniaView avaloniaView)
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+    {
+        _avaloniaView = avaloniaView;
+        _singleLineTextInput = new NuiSingleLineTextInput
+        {
+            HeightResizePolicy = ResizePolicyType.Fixed,
+            WidthResizePolicy = ResizePolicyType.Fixed,
+            Size = new(1, 1),
+            Position = new Position(-1000, -1000),
+            FontSizeScale = 0.1f,
+        };
+
+        _multiLineTextInput = new NuiMultiLineTextInput
+        {
+            HeightResizePolicy = ResizePolicyType.Fixed,
+            WidthResizePolicy = ResizePolicyType.Fixed,
+            Size = new(1, 1),
+            Position = new Position(-1000, -1000),
+            FontSizeScale = 0.1f,
+        };
+
+        SetupTextInput(_singleLineTextInput);
+        SetupTextInput(_multiLineTextInput);
+    }
+
+    private void SetupTextInput(INuiTextInput input)
+    {
+        input.Hide();
+
+        input.GetInputMethodContext().StatusChanged += OnStatusChanged;
+        input.GetInputMethodContext().EventReceived += OnEventReceived;
+    }
+
+    private InputMethodContext.CallbackData OnEventReceived(object source, InputMethodContext.EventReceivedEventArgs e)
+    {
+        switch (e.EventData.EventName)
+        {
+            case InputMethodContext.EventType.Preedit:
+                _client?.SetPreeditText(e.EventData.PredictiveString);
+                break;
+            case InputMethodContext.EventType.Commit:
+                _client?.SetPreeditText(null);
+                _avaloniaView.TopLevelImpl.TextInput(e.EventData.PredictiveString);
+                break;
+        }
+
+        return new InputMethodContext.CallbackData();
+    }
+
+    private void OnStatusChanged(object? sender, InputMethodContext.StatusChangedEventArgs e)
+    {
+        _keyboardPresented = e.StatusChanged;
+        if (!_keyboardPresented)
+            DettachAndHide();
+    }
+
+    internal void SetClient(TextInputMethodClient? client)
+    {
+        if (client == null || !_keyboardPresented)
+            DettachAndHide();
+
+        if (client != null)
+            AttachAndShow(client);
+    }
+
+    internal void SetOptions(TextInputOptions options)
+    {
+        //TODO: This should be revert when Avalonia used Multiline property
+        _multiline = true;
+        //if (_multiline != options.Multiline)
+        //{
+        //    DettachAndHide();
+        //    _multiline = options.Multiline;
+        //}
+
+        TextInput.Sensitive = options.IsSensitive;
+    }
+
+    private void AttachAndShow(TextInputMethodClient client)
+    {
+        _updating = true;
+        try
+        {
+            TextInput.Text = client.SurroundingText;
+            TextInput.PrimaryCursorPosition = client.Selection.Start;
+            Window.Instance.GetDefaultLayer().Add((View)TextInput);
+            TextInput.Show();
+            TextInput.EnableSelection = true;
+
+            var inputContext = TextInput.GetInputMethodContext();
+            inputContext.Activate();
+            inputContext.ShowInputPanel();
+            inputContext.RestoreAfterFocusLost();
+
+            _client = client;
+            client.TextViewVisualChanged += OnTextViewVisualChanged;
+            client.SurroundingTextChanged += OnSurroundingTextChanged;
+            client.SelectionChanged += OnClientSelectionChanged;
+
+            TextInput.SelectWholeText();
+            OnClientSelectionChanged(this, EventArgs.Empty);
+        }
+        finally { _updating = false; }
+    }
+
+    private void OnClientSelectionChanged(object? sender, EventArgs e) => InvokeUpdate(client =>
+    {
+        if (client.Selection.End == 0 || client.Selection.Start == client.Selection.End)
+            TextInput.PrimaryCursorPosition = client.Selection.Start;
+        else
+            TextInput.SelectText(client.Selection.Start, client.Selection.End);
+    });
+
+    private void OnSurroundingTextChanged(object? sender, EventArgs e) => InvokeUpdate(client =>
+    {
+        TextInput.Text = client.SurroundingText;
+        TextInput.GetInputMethodContext().SetSurroundingText(client.SurroundingText);
+        OnClientSelectionChanged(sender, e);
+    });
+
+    private void OnTextViewVisualChanged(object? sender, EventArgs e) => InvokeUpdate(client =>
+    {
+        TextInput.Text = client.SurroundingText;
+    });
+
+    private void DettachAndHide()
+    {
+        if (IsActive)
+        {
+            _client!.TextViewVisualChanged -= OnTextViewVisualChanged;
+            _client!.SurroundingTextChanged -= OnSurroundingTextChanged;
+            _client!.SelectionChanged -= OnClientSelectionChanged;
+        }
+
+        if (Window.Instance.GetDefaultLayer().Children.Contains((View)TextInput))
+            Window.Instance.GetDefaultLayer().Remove((View)TextInput);
+
+        TextInput.Hide();
+
+        var inputContext = TextInput.GetInputMethodContext();
+        inputContext.Deactivate();
+        inputContext.HideInputPanel();
+    }
+
+    private void InvokeUpdate(Action<TextInputMethodClient> action)
+    {
+        if (_updating || !IsActive)
+            return;
+
+        _updating = true;
+        try
+        {
+            action(_client!);
+        }
+        finally { _updating = false; }
+    }
+}
+
+internal interface INuiTextInput
+{
+    string Text { get; set; }
+    int PrimaryCursorPosition { get; set; }
+    bool EnableSelection { get; set; }
+    bool Sensitive { get; set; }
+    int SelectedTextStart { get; }
+    int SelectedTextEnd { get; }
+
+    void Show();
+    InputMethodContext GetInputMethodContext();
+    void Hide();
+    void SelectText(int selectedTextStart, int value);
+    void SelectWholeText();
+}
+
+public class NuiMultiLineTextInput : TextEditor, INuiTextInput
+{
+}
+
+public class NuiSingleLineTextInput : TextField, INuiTextInput
+{
+}

+ 76 - 0
src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs

@@ -0,0 +1,76 @@
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Threading;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+
+namespace Avalonia.Tizen;
+
+internal class NuiClipboardImpl : IClipboard
+{
+    private TextEditor _textEditor;
+    public NuiClipboardImpl()
+    {
+        _textEditor = new TextEditor()
+        {
+            HeightResizePolicy = ResizePolicyType.Fixed,
+            WidthResizePolicy = ResizePolicyType.Fixed,
+            Position = new Position(-1000, -1000),
+            Size = new(1, 1)
+        };
+
+        Window.Instance.GetDefaultLayer().Add(_textEditor);
+        _textEditor.LowerToBottom();
+    }
+
+    public Task ClearAsync() =>
+        SetTextAsync("");
+
+    public Task<string?> GetTextAsync()
+    {
+        _textEditor.Show();
+        _textEditor.Text = "";
+
+        //The solution suggested by Samsung, The method PasteTo will execute async and need delay
+        TextUtils.PasteTo(_textEditor);
+
+        return Task.Run<string?>(async () =>
+        {
+            await Task.Delay(10);
+
+            return await Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                _textEditor.Hide();
+                return _textEditor.Text;
+            });
+        });
+    }
+
+    public Task SetTextAsync(string? text)
+    {
+        _textEditor.Show();
+        _textEditor.Text = text;
+
+        //The solution suggested by Samsung, The method SelectWholeText will execute async and need delay
+        _textEditor.SelectWholeText();
+
+        return Task.Run(async () =>
+        {
+            await Task.Delay(10);
+            await Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                TextUtils.CopyToClipboard(_textEditor);
+                _textEditor.Hide();
+            });
+        });
+    }
+
+    public Task<object?> GetDataAsync(string format) =>
+        throw new PlatformNotSupportedException();
+
+    public Task SetDataObjectAsync(IDataObject data) =>
+        throw new PlatformNotSupportedException();
+
+    public Task<string[]> GetFormatsAsync() =>
+        throw new PlatformNotSupportedException();
+}

+ 73 - 0
src/Tizen/Avalonia.Tizen/NuiGlLayerSurface.cs

@@ -0,0 +1,73 @@
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Surfaces;
+
+namespace Avalonia.Tizen;
+internal class NuiGlLayerSurface : IGlPlatformSurface
+{
+    private readonly NuiAvaloniaView _nuiAvaloniaView;
+
+    public NuiGlLayerSurface(NuiAvaloniaView nuiAvaloniaView)
+    {
+        _nuiAvaloniaView = nuiAvaloniaView;
+    }
+
+    public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
+    {
+        var ctx = TizenPlatform.GlPlatform.Context;
+        if (ctx != context)
+            throw new InvalidOperationException("Platform surface is only usable with tha main context");
+        using (ctx.MakeCurrent())
+        {
+            return new RenderTarget(ctx, _nuiAvaloniaView);
+        }
+    }
+
+    class RenderTarget : IGlPlatformSurfaceRenderTarget
+    {
+        private readonly GlContext _ctx;
+        private readonly NuiAvaloniaView _nuiAvaloniaView;
+
+        public RenderTarget(GlContext ctx, NuiAvaloniaView nuiAvaloniaView)
+        {
+            _ctx = ctx;
+            _nuiAvaloniaView = nuiAvaloniaView;
+        }
+
+        public void Dispose()
+        {
+            
+        }
+
+        public IGlPlatformSurfaceRenderingSession BeginDraw()
+        {
+            var restoreContext = _ctx.MakeCurrent();
+            return new RenderSession(_ctx, restoreContext, _nuiAvaloniaView);
+        }
+    }
+
+    class RenderSession : IGlPlatformSurfaceRenderingSession
+    {
+        private readonly GlContext _ctx;
+        private readonly IDisposable _restoreContext;
+
+        public RenderSession(GlContext ctx, IDisposable restoreContext, NuiAvaloniaView nuiAvaloniaView)
+        {
+            _ctx = ctx;
+            _restoreContext = restoreContext;
+            Size = new PixelSize((int)nuiAvaloniaView.Size.Width, (int)nuiAvaloniaView.Size.Height);
+            Scaling = nuiAvaloniaView.Scaling;
+            Context = ctx;
+        }
+
+        public void Dispose()
+        {
+            _ctx.GlInterface.Finish();
+            _restoreContext.Dispose();
+        }
+
+        public IGlContext Context { get; }
+        public PixelSize Size { get; }
+        public double Scaling { get; }
+        public bool IsYFlipped { get; }
+    }
+}

+ 87 - 0
src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs

@@ -0,0 +1,87 @@
+using Avalonia.Compatibility;
+using Avalonia.OpenGL;
+using Avalonia.Platform;
+
+namespace Avalonia.Tizen;
+internal class NuiGlPlatform : IPlatformGraphics
+{
+
+    public IPlatformGraphicsContext GetSharedContext() => Context;
+
+    public bool UsesSharedContext => true;
+    public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException();
+    public GlContext Context { get; }
+    public static GlVersion GlVersion { get; } = new(GlProfileType.OpenGLES, 3, 0);
+
+    public NuiGlPlatform()
+    {
+        const string library = "/usr/lib/driver/libGLESv2.so";
+        var libGl = NativeLibraryEx.Load(library);
+        if (libGl == IntPtr.Zero)
+            throw new OpenGlException("Unable to load " + library);
+        var iface = new GlInterface(GlVersion, proc =>
+        {
+            if (NativeLibraryEx.TryGetExport(libGl, proc, out var address))
+                return address;
+            return default;
+        });
+        Context = new(iface);
+    }
+}
+
+class GlContext : IGlContext
+{
+    public GlContext(GlInterface glInterface)
+    {
+        GlInterface = glInterface;
+    }
+
+    public void Dispose()
+    {
+    }
+
+    public IDisposable MakeCurrent()
+    {
+        return this;
+    }
+
+    public bool IsLost => false;
+
+    public IDisposable EnsureCurrent()
+    {
+        return MakeCurrent();
+    }
+
+    public bool IsSharedWith(IGlContext context) => true;
+    public bool CanCreateSharedContext => true;
+    public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null)
+    {
+        return this;
+    }
+
+    public GlVersion Version => new GlVersion(GlProfileType.OpenGLES, 3, 0);
+    public GlInterface GlInterface { get; }
+    public int SampleCount
+    {
+        get
+        {
+            GlInterface.GetIntegerv(GlConsts.GL_SAMPLES, out var samples);
+            return samples;
+        }
+    }
+    public int StencilSize
+    {
+        get
+        {
+            GlInterface.GetIntegerv(GlConsts.GL_STENCIL_BITS, out var stencil);
+            return stencil;
+        }
+    }
+
+    public object TryGetFeature(Type featureType) => null;
+
+    public IntPtr GetProcAddress(string procName)
+    {
+        return GlInterface.GetProcAddress(procName);
+    }
+}

+ 102 - 0
src/Tizen/Avalonia.Tizen/NuiKeyboardHandler.cs

@@ -0,0 +1,102 @@
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Logging;
+using Avalonia.Tizen.Platform.Input;
+using Tizen.NUI;
+using Key = Tizen.NUI.Key;
+
+namespace Avalonia.Tizen;
+internal class NuiKeyboardHandler
+{
+    private const string LogKey = "TIZENHKEY";
+
+    private readonly NuiAvaloniaView _view;
+
+    public NuiKeyboardHandler(NuiAvaloniaView view)
+    {
+        _view = view;
+    }
+
+    public void Handle(Window.KeyEventArgs e)
+    {
+        Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Key fired {text}", e.Key.KeyPressedName);
+
+        if (_view.TextEditor.IsActive)
+            return;
+
+        if (ShouldSendKeyEvent(e, out var keyCode))
+        {
+            var mapped = TizenKeyboardDevice.ConvertKey(keyCode);
+            if (mapped == Input.Key.None)
+                return;
+
+            Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Triggering key event {text}", e.Key.KeyString);
+            SendKeyEvent(e, mapped);
+        }
+        else if (e.Key.State == Key.StateType.Up)
+        {
+            Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Triggering text input {text}", e.Key.KeyString);
+            _view.TopLevelImpl.TextInput(e.Key.KeyString);
+        }
+    }
+
+    private void SendKeyEvent(Window.KeyEventArgs e, Input.Key mapped)
+    {
+        var type = GetKeyEventType(e);
+        var modifiers = GetModifierKey(e);
+
+        _view.TopLevelImpl.Input?.Invoke(
+            new RawKeyEventArgs(
+                KeyboardDevice.Instance!,
+                e.Key.Time,
+                _view.InputRoot,
+                type,
+                mapped,
+                modifiers));
+    }
+
+    private bool ShouldSendKeyEvent(Window.KeyEventArgs e, out global::Tizen.Uix.InputMethod.KeyCode keyCode)
+    {
+        if (string.IsNullOrEmpty(e.Key.KeyString) || 
+            e.Key.KeyPressedName is "Delete" or "BackSpace" ||
+            e.Key.IsCtrlModifier() || 
+            e.Key.IsAltModifier())
+        {
+            if (Enum.TryParse(e.Key.KeyPressedName, true, out keyCode) ||
+                Enum.TryParse($"Keypad{e.Key.KeyPressedName}", true, out keyCode))
+                return true;
+        }
+
+        keyCode = 0;
+        return false;
+    }
+
+    private RawKeyEventType GetKeyEventType(Window.KeyEventArgs ev)
+    {
+        switch (ev.Key.State)
+        {
+            case Key.StateType.Down:
+                return RawKeyEventType.KeyDown;
+            case Key.StateType.Up:
+                return RawKeyEventType.KeyUp;
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
+    }
+
+    private RawInputModifiers GetModifierKey(Window.KeyEventArgs ev)
+    {
+        var modifiers = RawInputModifiers.None;
+
+        if (ev.Key.IsShiftModifier())
+            modifiers |= RawInputModifiers.Shift;
+
+        if (ev.Key.IsAltModifier())
+            modifiers |= RawInputModifiers.Alt;
+
+        if (ev.Key.IsCtrlModifier())
+            modifiers |= RawInputModifiers.Control;
+
+        return modifiers;
+    }
+}

+ 117 - 0
src/Tizen/Avalonia.Tizen/NuiNativeControlHostImpl.cs

@@ -0,0 +1,117 @@
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Tizen.NUI.BaseComponents;
+
+namespace Avalonia.Tizen;
+
+internal class NuiNativeControlHostImpl : INativeControlHostImpl
+{
+    private readonly NuiAvaloniaView _avaloniaView;
+
+    public NuiNativeControlHostImpl(NuiAvaloniaView avaloniaView)
+    {
+        _avaloniaView = avaloniaView;
+    }
+
+    public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) =>
+        new NuiViewControlHandle(new View());
+
+    public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
+    {
+        var parent = new NuiViewControlHandle(_avaloniaView);
+        NativeControlAttachment? attachment = null;
+        try
+        {
+            var child = create(parent);
+            // It has to be assigned to the variable before property setter is called so we dispose it on exception
+#pragma warning disable IDE0017 // Simplify object initialization
+            attachment = new NativeControlAttachment(child);
+#pragma warning restore IDE0017 // Simplify object initialization
+            attachment.AttachedTo = this;
+            return attachment;
+        }
+        catch
+        {
+            attachment?.Dispose();
+            throw;
+        }
+    }
+
+    public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) =>
+        new NativeControlAttachment(handle);
+
+    public bool IsCompatibleWith(IPlatformHandle handle) =>
+        handle.HandleDescriptor == NuiViewControlHandle.ViewDescriptor;
+
+    private class NativeControlAttachment : INativeControlHostControlTopLevelAttachment
+    {
+        private IPlatformHandle? _child;
+        private View? _view;
+        private NuiNativeControlHostImpl? _attachedTo;
+
+        public NativeControlAttachment(IPlatformHandle child)
+        {
+            _child = child;
+            _view = (child as NuiViewControlHandle)?.View;
+        }
+
+        [MemberNotNull(nameof(_view))]
+        private void CheckDisposed()
+        {
+            if (_view == null)
+                throw new ObjectDisposedException(nameof(NativeControlAttachment));
+        }
+
+        public void Dispose()
+        {
+            _view?.Unparent();
+            _child = null;
+            _attachedTo = null;
+            _view?.Dispose();
+            _view = null;
+        }
+
+        public INativeControlHostImpl? AttachedTo
+        {
+            get => _attachedTo;
+            set
+            {
+                CheckDisposed();
+
+                _attachedTo = (NuiNativeControlHostImpl?)value;
+                if (_attachedTo == null)
+                {
+                    _view.Unparent();
+                }
+                else
+                {
+                    _attachedTo._avaloniaView.Add(_view);
+                }
+            }
+        }
+
+        public bool IsCompatibleWith(INativeControlHostImpl host) => host is NuiNativeControlHostImpl;
+
+        public void HideWithSize(Size size)
+        {
+            CheckDisposed();
+            if (_attachedTo == null)
+                return;
+
+            _view.Hide();
+            _view.Size = new global::Tizen.NUI.Size(MathF.Max(1f, (float)size.Width), Math.Max(1f, (float)size.Height));
+        }
+
+        public void ShowInBounds(Rect bounds)
+        {
+            CheckDisposed();
+            if (_attachedTo == null)
+                throw new InvalidOperationException("The control isn't currently attached to a toplevel");
+
+            _view.Size = new global::Tizen.NUI.Size(MathF.Max(1f, (float)bounds.Width), Math.Max(1f, (float)bounds.Height));
+            _view.Position = new global::Tizen.NUI.Position((float)bounds.X, (float)bounds.Y);
+            _view.Show();
+        }
+    }
+}

+ 73 - 0
src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs

@@ -0,0 +1,73 @@
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Tizen.NUI;
+using Window = Tizen.NUI.Window;
+using Avalonia.Logging;
+using Avalonia.Threading;
+
+namespace Avalonia.Tizen;
+
+public class NuiTizenApplication<TApp> : NUIApplication
+    where TApp : Application, new()
+{
+    private const string LogKey = "TIZENAPP";
+
+    private SingleViewLifetime? _lifetime;
+
+    private class SingleViewLifetime : ISingleViewApplicationLifetime
+    {
+        public NuiAvaloniaView View { get; }
+
+        public SingleViewLifetime(NuiAvaloniaView view)
+        {
+            View = view;
+        }
+
+        public Control MainView
+        {
+            get => View.Content;
+            set => View.Content = value;
+        }
+    }
+
+    protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
+
+    protected override void OnCreate()
+    {
+        Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Creating application");
+
+        base.OnCreate();
+#pragma warning disable CS8601 // Possible null reference assignment.
+        TizenThreadingInterface.MainloopContext = SynchronizationContext.Current;
+#pragma warning restore CS8601 // Possible null reference assignment.
+
+        Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup view");
+        _lifetime = new SingleViewLifetime(new NuiAvaloniaView());
+
+        _lifetime.View.HeightResizePolicy = ResizePolicyType.FillToParent;
+        _lifetime.View.WidthResizePolicy = ResizePolicyType.FillToParent;
+        _lifetime.View.OnSurfaceInit += ContinueSetupApplication;
+
+        Window.Instance.RenderingBehavior = RenderingBehaviorType.Continuously;
+        Window.Instance.GetDefaultLayer().Add(_lifetime.View);
+        Window.Instance.KeyEvent += (s, e) => _lifetime?.View?.KeyboardHandler.Handle(e);
+    }
+
+    private void ContinueSetupApplication()
+    {
+        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+
+        Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "App builder");
+        var builder = AppBuilder.Configure<TApp>().UseTizen();
+
+        TizenThreadingInterface.MainloopContext.Post(_ =>
+        {
+            CustomizeAppBuilder(builder);
+
+            builder.AfterSetup(_ => _lifetime.View.Initialise());
+
+            Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup lifetime");
+            builder.SetupWithLifetime(_lifetime);
+        }, null);
+    }
+}

+ 99 - 0
src/Tizen/Avalonia.Tizen/NuiTouchHandler.cs

@@ -0,0 +1,99 @@
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Tizen.NUI;
+using static Tizen.NUI.BaseComponents.View;
+
+namespace Avalonia.Tizen;
+internal class NuiTouchHandler
+{
+    private readonly NuiAvaloniaView _view;
+    public TouchDevice _device = new TouchDevice();
+
+    public NuiTouchHandler(NuiAvaloniaView view)
+    {
+        _view = view;
+    }
+
+    private IInputRoot InputRoot => _view.InputRoot;
+    private static uint _nextTouchPointId = 1;
+    private List<uint> _knownTouches = new();
+
+    public void Handle(TouchEventArgs e)
+    {
+        var count = e.Touch.GetPointCount();
+        for (var i = 0u; i < count; i++)
+        {
+            uint id;
+            if (_knownTouches.Count > i)
+            {
+                id = _knownTouches[(int)i];
+            }
+            else
+            {
+                unchecked
+                {
+                    id = _nextTouchPointId++;
+                }
+                _knownTouches.Add(id);
+            }
+
+            var point = e.Touch.GetLocalPosition(i);
+            var state = e.Touch.GetState(i);
+            var timestamp = e.Touch.GetTime();
+            var avaloniaState = state switch
+            {
+                PointStateType.Down => RawPointerEventType.TouchBegin,
+                PointStateType.Up => RawPointerEventType.TouchEnd,
+                PointStateType.Motion => RawPointerEventType.TouchUpdate,
+                PointStateType.Interrupted => RawPointerEventType.TouchCancel,
+                _ => RawPointerEventType.TouchUpdate
+            };
+
+            var touchEvent = new RawTouchEventArgs(
+                _device,
+                timestamp,
+                InputRoot,
+                avaloniaState,
+                new Point(point.X, point.Y),
+                RawInputModifiers.None,
+                id);
+            _view.TopLevelImpl.Input?.Invoke(touchEvent);
+
+            if (state is PointStateType.Up or PointStateType.Interrupted)
+            {
+                _knownTouches.Remove(id);
+            }
+        }
+    }
+
+    public void Handle(WheelEventArgs e)
+    {
+        var mouseWheelEvent = new RawMouseWheelEventArgs(
+            _device,
+            e.Wheel.TimeStamp,
+            InputRoot,
+            new Point(e.Wheel.Point.X, e.Wheel.Point.Y),
+            new Vector(
+                e.Wheel.Direction == 1 ? e.Wheel.Z : 0, 
+                e.Wheel.Direction == 0 ? e.Wheel.Z : 0),
+            GetModifierKey(e));
+
+        _view.TopLevelImpl.Input?.Invoke(mouseWheelEvent);
+    }
+
+    private RawInputModifiers GetModifierKey(WheelEventArgs ev)
+    {
+        var modifiers = RawInputModifiers.None;
+
+        if (ev.Wheel.IsShiftModifier())
+            modifiers |= RawInputModifiers.Shift;
+
+        if (ev.Wheel.IsAltModifier())
+            modifiers |= RawInputModifiers.Alt;
+
+        if (ev.Wheel.IsCtrlModifier())
+            modifiers |= RawInputModifiers.Control;
+
+        return modifiers;
+    }
+}

+ 39 - 0
src/Tizen/Avalonia.Tizen/NuiViewControlHandle.cs

@@ -0,0 +1,39 @@
+using Avalonia.Controls.Platform;
+using Tizen.NUI.BaseComponents;
+
+namespace Avalonia.Tizen;
+
+/// <summary>
+/// Tizen Nui native view handle for native view attachment
+/// </summary>
+public class NuiViewControlHandle : INativeControlHostDestroyableControlHandle
+{
+    internal const string ViewDescriptor = "NuiView";
+
+    /// <summary>
+    /// Create handle with native view
+    /// </summary>
+    /// <param name="view">NUI Tizen native view to attach</param>
+    public NuiViewControlHandle(View view)
+    {
+        View = view;
+    }
+
+    /// <summary>
+    /// NUI Tizen View
+    /// </summary>
+    public View View { get; set; }
+    /// <summary>
+    /// NUI Tizen not supporting handle
+    /// </summary>
+    /// <exception cref="NotSupportedException"></exception>
+    public IntPtr Handle => throw new NotSupportedException();
+    /// <summary>
+    /// Return `ViewDescriptor` all the time
+    /// </summary>
+    public string? HandleDescriptor => ViewDescriptor;
+    /// <summary>
+    /// Dispose Tizen View when it call
+    /// </summary>
+    public void Destroy() => View.Dispose();
+}

+ 29 - 0
src/Tizen/Avalonia.Tizen/Platform/Consts.cs

@@ -0,0 +1,29 @@
+namespace Avalonia.Tizen.Platform;
+internal static class Consts
+{
+    public const int DpiX = 96;
+    public const int DpiY = 96;
+    public static readonly Vector Dpi = new Vector(DpiX, DpiY);
+
+    public const string VertexShader =
+           "attribute mediump vec2 aPosition;\n" +
+           "varying mediump vec2 vTexCoord;\n" +
+           "uniform highp mat4 uMvpMatrix;\n" +
+           "uniform mediump vec3 uSize;\n" +
+           "varying mediump vec2 sTexCoordRect;\n" +
+           "void main()\n" +
+           "{\n" +
+           "   gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);\n" +
+           "   vTexCoord = aPosition + vec2(0.5);\n" +
+           "}\n";
+
+    public const string FragmentShader =
+        "#extension GL_OES_EGL_image_external:require\n" +
+        "uniform lowp vec4 uColor;\n" +
+        "varying mediump vec2 vTexCoord;\n" +
+        "uniform samplerExternalOES sTexture;\n" +
+        "void main()\n" +
+        "{\n" +
+        "   gl_FragColor = texture2D(sTexture, vTexCoord) * uColor;\n" +
+        "}\n";
+}

+ 221 - 0
src/Tizen/Avalonia.Tizen/Platform/Input/TizenKeyboardDevice.cs

@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Input;
+using Tizen.Uix.InputMethod;
+
+namespace Avalonia.Tizen.Platform.Input;
+internal class TizenKeyboardDevice: KeyboardDevice, IKeyboardDevice
+{
+    private static readonly Dictionary<KeyCode, Key> KeyDic = new Dictionary<KeyCode, Key>
+     {
+         //   { KeyCode.Cancel?, Key.Cancel },
+            { KeyCode.BackSpace, Key.Back },
+            { KeyCode.Tab, Key.Tab },
+          //  { KeyCode.Linefeed?, Key.LineFeed },
+            { KeyCode.Clear, Key.Clear },
+            { KeyCode.Return, Key.Return },
+            { KeyCode.Pause, Key.Pause },
+            { KeyCode.CapsLock, Key.CapsLock },
+            //{ KeyCode.?, Key.HangulMode }
+            //{ KeyCode.?, Key.JunjaMode }
+            //{ KeyCode.?, Key.FinalMode }
+            //{ KeyCode.?, Key.KanjiMode }
+            { KeyCode.Escape, Key.Escape },
+            //{ KeyCode.?, Key.ImeConvert }
+            //{ KeyCode.?, Key.ImeNonConvert }
+            //{ KeyCode.?, Key.ImeAccept }
+            //{ KeyCode.?, Key.ImeModeChange }
+            { KeyCode.Space, Key.Space },
+            { KeyCode.Page_Up, Key.Prior },
+            { KeyCode.Page_Down, Key.PageDown },
+            { KeyCode.End, Key.End },
+            { KeyCode.Home, Key.Home },
+            { KeyCode.Left, Key.Left },
+            { KeyCode.Up, Key.Up },
+            { KeyCode.Right, Key.Right },
+            { KeyCode.Down, Key.Down },
+           // { KeyCode.ButtonSelect?, Key.Select },
+           // { KeyCode.print?, Key.Print },
+            //{ KeyCode.execute?, Key.Execute },
+            //{ KeyCode.snap?, Key.Snapshot }
+            { KeyCode.Insert, Key.Insert },
+            { KeyCode.Delete, Key.Delete },
+            { KeyCode.Help, Key.Help },
+            { KeyCode.Keypad0, Key.D0 },
+            { KeyCode.Keypad1, Key.D1 },
+            { KeyCode.Keypad2, Key.D2 },
+            { KeyCode.Keypad3, Key.D3 },
+            { KeyCode.Keypad4, Key.D4 },
+            { KeyCode.Keypad5, Key.D5 },
+            { KeyCode.Keypad6, Key.D6 },
+            { KeyCode.Keypad7, Key.D7 },
+            { KeyCode.Keypad8, Key.D8 },
+            { KeyCode.Keypad9, Key.D9 },
+            { KeyCode.KeypadA, Key.A },
+            { KeyCode.KeypadB, Key.B },
+            { KeyCode.KeypadC, Key.C },
+            { KeyCode.KeypadD, Key.D },
+            { KeyCode.KeypadE, Key.E },
+            { KeyCode.KeypadF, Key.F },
+            { KeyCode.KeypadG, Key.G },
+            { KeyCode.KeypadH, Key.H },
+            { KeyCode.KeypadI, Key.I },
+            { KeyCode.KeypadJ, Key.J },
+            { KeyCode.KeypadK, Key.K },
+            { KeyCode.KeypadL, Key.L },
+            { KeyCode.KeypadM, Key.M },
+            { KeyCode.KeypadN, Key.N },
+            { KeyCode.KeypadO, Key.O },
+            { KeyCode.KeypadP, Key.P },
+            { KeyCode.KeypadQ, Key.Q },
+            { KeyCode.KeypadR, Key.R },
+            { KeyCode.KeypadS, Key.S },
+            { KeyCode.KeypadT, Key.T },
+            { KeyCode.KeypadU, Key.U },
+            { KeyCode.KeypadV, Key.V },
+            { KeyCode.KeypadW, Key.W },
+            { KeyCode.KeypadX, Key.X },
+            { KeyCode.KeypadY, Key.Y },
+            { KeyCode.KeypadZ, Key.Z },
+            { KeyCode.Keypada, Key.A },
+            { KeyCode.Keypadb, Key.B },
+            { KeyCode.Keypadc, Key.C },
+            { KeyCode.Keypadd, Key.D },
+            { KeyCode.Keypade, Key.E },
+            { KeyCode.Keypadf, Key.F },
+            { KeyCode.Keypadg, Key.G },
+            { KeyCode.Keypadh, Key.H },
+            { KeyCode.Keypadi, Key.I },
+            { KeyCode.Keypadj, Key.J },
+            { KeyCode.Keypadk, Key.K },
+            { KeyCode.Keypadl, Key.L },
+            { KeyCode.Keypadm, Key.M },
+            { KeyCode.Keypadn, Key.N },
+            { KeyCode.Keypado, Key.O },
+            { KeyCode.Keypadp, Key.P },
+            { KeyCode.Keypadq, Key.Q },
+            { KeyCode.Keypadr, Key.R },
+            { KeyCode.Keypads, Key.S },
+            { KeyCode.Keypadt, Key.T },
+            { KeyCode.Keypadu, Key.U },
+            { KeyCode.Keypadv, Key.V },
+            { KeyCode.Keypadw, Key.W },
+            { KeyCode.Keypadx, Key.X },
+            { KeyCode.Keypady, Key.Y },
+            { KeyCode.Keypadz, Key.Z },
+            //{ KeyCode.?, Key.LWin }
+            //{ KeyCode.?, Key.RWin }
+            //{ KeyCode.?, Key.Apps }
+            //{ KeyCode.Sle, Key.Sleep },
+            { KeyCode.KP0, Key.NumPad0 },
+            { KeyCode.KP1, Key.NumPad1 },
+            { KeyCode.KP2, Key.NumPad2 },
+            { KeyCode.KP3, Key.NumPad3 },
+            { KeyCode.KP4, Key.NumPad4 },
+            { KeyCode.KP5, Key.NumPad5 },
+            { KeyCode.KP6, Key.NumPad6 },
+            { KeyCode.KP7, Key.NumPad7 },
+            { KeyCode.KP8, Key.NumPad8 },
+            { KeyCode.KP9, Key.NumPad9 },
+            { KeyCode.KPMultiply, Key.Multiply },
+            { KeyCode.KPAdd, Key.Add },
+            { KeyCode.KPSeparator, Key.Separator },
+            { KeyCode.KPSubtract, Key.Subtract },
+            { KeyCode.KPDecimal, Key.Decimal },
+            { KeyCode.KPDivide, Key.Divide },
+            { KeyCode.F1, Key.F1 },
+            { KeyCode.F2, Key.F2 },
+            { KeyCode.F3, Key.F3 },
+            { KeyCode.F4, Key.F4 },
+            { KeyCode.F5, Key.F5 },
+            { KeyCode.F6, Key.F6 },
+            { KeyCode.F7, Key.F7 },
+            { KeyCode.F8, Key.F8 },
+            { KeyCode.F9, Key.F9 },
+            { KeyCode.F10, Key.F10 },
+            { KeyCode.F11, Key.F11 },
+            { KeyCode.F12, Key.F12 },
+            //{ KeyCode.f13, Key.F13 },
+            //{ KeyCode.F14, Key.F14 },
+            //{ KeyCode.L5, Key.F15 },
+            //{ KeyCode.F16, Key.F16 },
+            //{ KeyCode.F17, Key.F17 },
+            //{ KeyCode.L8, Key.F18 },
+            //{ KeyCode.L9, Key.F19 },
+            //{ KeyCode.L10, Key.F20 },
+            //{ KeyCode.R1, Key.F21 },
+            //{ KeyCode.R2, Key.F22 },
+            //{ KeyCode.F23, Key.F23 },
+            //{ KeyCode.R4, Key.F24 },
+            { KeyCode.Num_Lock, Key.NumLock },
+            { KeyCode.ScrollLock, Key.Scroll },
+            { KeyCode.ShiftL, Key.LeftShift },
+            { KeyCode.ShiftR, Key.RightShift },
+            { KeyCode.ControlL, Key.LeftCtrl },
+            { KeyCode.ControlR, Key.RightCtrl },
+            { KeyCode.AltL, Key.LeftAlt },
+            { KeyCode.AltR, Key.RightAlt },
+            //{ KeyCode.?, Key.BrowserBack }
+            //{ KeyCode.?, Key.BrowserForward }
+            //{ KeyCode.?, Key.BrowserRefresh }
+            //{ KeyCode.?, Key.BrowserStop }
+            //{ KeyCode.?, Key.BrowserSearch }
+            //{ KeyCode.?, Key.BrowserFavorites }
+            //{ KeyCode.?, Key.BrowserHome }
+            //{ KeyCode.?, Key.VolumeMute }
+            //{ KeyCode.VolumeDown, Key.VolumeDown },
+            //{ KeyCode.VolumeUp, Key.VolumeUp },
+            //{ KeyCode.MediaNext, Key.MediaNextTrack },
+            //{ KeyCode.MediaPrevious, Key.MediaPreviousTrack },
+            //{ KeyCode.MediaStop, Key.MediaStop },
+            //{ KeyCode.MediaPlayPause, Key.MediaPlayPause },
+            //{ KeyCode.?, Key.LaunchMail }
+            //{ KeyCode.?, Key.SelectMedia }
+            //{ KeyCode.?, Key.LaunchApplication1 }
+            //{ KeyCode.?, Key.LaunchApplication2 }
+            { KeyCode.Semicolon, Key.OemSemicolon },
+            { KeyCode.Plus, Key.OemPlus },
+            { KeyCode.Comma, Key.OemComma },
+            { KeyCode.Minus, Key.OemMinus },
+            { KeyCode.Period, Key.OemPeriod },
+            //{ KeyCode.?, Key.Oem2 }
+            { KeyCode.Grave, Key.OemTilde },
+            //{ KeyCode.?, Key.AbntC1 }
+            //{ KeyCode.?, Key.AbntC2 }
+            //{ KeyCode.?, Key.OemPipe }
+            { KeyCode.Apostrophe, Key.OemQuotes },
+            { KeyCode.Slash, Key.OemQuestion },
+            { KeyCode.BraceLeft, Key.OemOpenBrackets },
+            { KeyCode.BracketRight, Key.OemCloseBrackets },
+            //{ KeyCode.?, Key.Oem7 }
+            //{ KeyCode.?, Key.Oem8 }
+            //{ KeyCode.?, Key.Oem102 }
+            //{ KeyCode.?, Key.ImeProcessed }
+            //{ KeyCode.?, Key.System }
+            //{ KeyCode.?, Key.OemAttn }
+            //{ KeyCode.?, Key.OemFinish }
+            //{ KeyCode.?, Key.DbeHiragana }
+            //{ KeyCode.?, Key.OemAuto }
+            //{ KeyCode.?, Key.DbeDbcsChar }
+            //{ KeyCode.?, Key.OemBackTab }
+            //{ KeyCode.?, Key.Attn }
+            //{ KeyCode.?, Key.DbeEnterWordRegisterMode }
+            //{ KeyCode.?, Key.DbeEnterImeConfigureMode }
+            //{ KeyCode.?, Key.EraseEof }
+            //{ KeyCode.MediaPlay, Key.Play },
+            //{ KeyCode.?, Key.Zoom }
+            //{ KeyCode.?, Key.NoName }
+            //{ KeyCode.?, Key.DbeEnterDialogConversionMode }
+            //{ KeyCode.?, Key.OemClear }
+            //{ KeyCode.?, Key.DeadCharProcessed }
+            { KeyCode.Backslash, Key.OemBackslash }
+        };
+
+    internal static Key ConvertKey(KeyCode key)
+    {
+        return KeyDic.TryGetValue(key, out var result) ? result : Key.None;
+    }
+}

+ 20 - 0
src/Tizen/Avalonia.Tizen/Platform/Interop/Strucs.cs

@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+
+namespace Avalonia.Tizen.Platform.Interop;
+
+struct TexturedQuadVertex
+{
+    public Vec2 position;
+};
+
+[StructLayout(LayoutKind.Sequential)]
+struct Vec2
+{
+    float x;
+    float y;
+    public Vec2(float xIn, float yIn)
+    {
+        x = xIn;
+        y = yIn;
+    }
+}

+ 110 - 0
src/Tizen/Avalonia.Tizen/Platform/Permissions.cs

@@ -0,0 +1,110 @@
+using System.Security;
+using System.Security.Permissions;
+using Tizen.Applications;
+using Tizen.Security;
+
+namespace Avalonia.Tizen.Platform;
+internal class Permissions
+{
+    public record Privilege (string Path, bool IsRuntime);
+
+    public static readonly Privilege InternetPrivilege = new("http://tizen.org/privilege/internet", false);
+    public static readonly Privilege NetworkPrivilege = new("http://tizen.org/privilege/network.get", false);
+    public static readonly Privilege CameraPrivilege = new("http://tizen.org/privilege/camera", false);
+    public static readonly Privilege ContactReadPrivilege = new("http://tizen.org/privilege/contact.read", true);
+    public static readonly Privilege ContactWritePrivilege = new("http://tizen.org/privilege/contact.write", true);
+    public static readonly Privilege LedPrivilege = new("http://tizen.org/privilege/led", false);
+    public static readonly Privilege AppManagerLaunchPrivilege = new("http://tizen.org/privilege/appmanager.launch", false);
+    public static readonly Privilege LocationPrivilege = new("http://tizen.org/privilege/location", true);
+    public static readonly Privilege MapServicePrivilege = new("http://tizen.org/privilege/mapservice", false);
+    public static readonly Privilege MediaStoragePrivilege = new("http://tizen.org/privilege/mediastorage", true);
+    public static readonly Privilege RecorderPrivilege = new("http://tizen.org/privilege/recorder", false);
+    public static readonly Privilege HapticPrivilege = new("http://tizen.org/privilege/haptic", false);
+
+    public static readonly Privilege[] NetworkPrivileges = { InternetPrivilege, NetworkPrivilege };
+    public static readonly Privilege[] MapsPrivileges = { InternetPrivilege, MapServicePrivilege, NetworkPrivilege };
+
+    public static Package CurrentPackage
+    {
+        get
+        {
+            var packageId = global::Tizen.Applications.Application.Current.ApplicationInfo.PackageId;
+            return PackageManager.GetPackage(packageId);
+        }
+    }
+
+    public static bool IsPrivilegeDeclared(string tizenPrivilege)
+    {
+        var tizenPrivileges = tizenPrivilege;
+
+        if (tizenPrivileges == null || !tizenPrivileges.Any())
+            return false;
+
+        var package = CurrentPackage;
+
+        if (!package.Privileges.Contains(tizenPrivilege))
+            return false;
+
+        return true;
+    }
+
+    public static void EnsureDeclared(params Privilege[] requiredPrivileges)
+    {
+        if (requiredPrivileges?.Any() != true)
+            return;
+
+        foreach (var (tizenPrivilege, isRuntime) in requiredPrivileges)
+        {
+            if (!IsPrivilegeDeclared(tizenPrivilege))
+                throw new SecurityException($"You need to declare the privilege: `{tizenPrivilege}` in your tizen-manifest.xml");
+        }
+    }
+
+    public static Task<bool> CheckPrivilegeAsync(params Privilege[] requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, false);
+    public static Task<bool> RequestPrivilegeAsync(params Privilege[] requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, true);
+    private static async Task<bool> CheckPrivilegeAsync(Privilege[] requiredPrivileges, bool ask)
+    {
+        var ret = global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile);
+        if (!ret || (ret && (!profile.Equals("mobile") || !profile.Equals("wearable"))))
+        {
+            return true;
+        }
+
+        if (requiredPrivileges == null || !requiredPrivileges.Any())
+            return true;
+
+        EnsureDeclared();
+
+        var tizenPrivileges = requiredPrivileges.Where(p => p.IsRuntime);
+
+        foreach (var (tizenPrivilege, isRuntime) in tizenPrivileges)
+        {
+            var checkResult = PrivacyPrivilegeManager.CheckPermission(tizenPrivilege);
+            if (checkResult == CheckResult.Ask)
+            {
+                if (ask)
+                {
+                    var tcs = new TaskCompletionSource<bool>();
+                    PrivacyPrivilegeManager.GetResponseContext(tizenPrivilege)
+                        .TryGetTarget(out var context);
+                    void OnResponseFetched(object sender, RequestResponseEventArgs e)
+                    {
+                        tcs.TrySetResult(e.result == RequestResult.AllowForever);
+                    }
+                    context.ResponseFetched += OnResponseFetched;
+                    PrivacyPrivilegeManager.RequestPermission(tizenPrivilege);
+                    var result = await tcs.Task;
+                    context.ResponseFetched -= OnResponseFetched;
+                    if (result)
+                        continue;
+                }
+                return false;
+            }
+            else if (checkResult == CheckResult.Deny)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 10 - 0
src/Tizen/Avalonia.Tizen/Platform/TizenPlatformSettings.cs

@@ -0,0 +1,10 @@
+using System;
+using Avalonia.Platform;
+using Tizen.NUI;
+
+namespace Avalonia.Tizen.Platform;
+
+internal class TizenPlatformSettings : DefaultPlatformSettings
+{
+    
+}

+ 65 - 0
src/Tizen/Avalonia.Tizen/Stubs.cs

@@ -0,0 +1,65 @@
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.Tizen;
+
+internal class WindowingPlatformStub : IWindowingPlatform
+{
+    public IWindowImpl CreateWindow() => throw new NotSupportedException();
+
+    public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
+
+    public ITrayIconImpl CreateTrayIcon() => null;
+}
+
+internal class PlatformIconLoaderStub : IPlatformIconLoader
+{
+    public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
+    {
+        using (var stream = new MemoryStream())
+        {
+            bitmap.Save(stream);
+            return LoadIcon(stream);
+        }
+    }
+
+    public IWindowIconImpl LoadIcon(Stream stream)
+    {
+        var ms = new MemoryStream();
+        stream.CopyTo(ms);
+        return new IconStub(ms);
+    }
+
+    public IWindowIconImpl LoadIcon(string fileName)
+    {
+        using (var file = File.Open(fileName, FileMode.Open))
+            return LoadIcon(file);
+    }
+}
+
+internal class IconStub : IWindowIconImpl
+{
+    private readonly MemoryStream _ms;
+
+    public IconStub(MemoryStream stream)
+    {
+        _ms = stream;
+    }
+
+    public void Save(Stream outputStream)
+    {
+        _ms.Position = 0;
+        _ms.CopyTo(outputStream);
+    }
+}
+
+internal class CursorFactoryStub : ICursorFactory
+{
+    public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorImplStub();
+    ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new CursorImplStub();
+
+    private class CursorImplStub : ICursorImpl
+    {
+        public void Dispose() { }
+    }
+}

+ 20 - 0
src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs

@@ -0,0 +1,20 @@
+namespace Avalonia.Tizen;
+
+/// <summary>
+/// Extension to setup app builder with tizen backend 
+/// </summary>
+public static class TizenApplicationExtensions
+{
+    /// <summary>
+    /// Use tizen builder to setup tizen sub system
+    /// </summary>
+    /// <param name="builder">Avalonia App Builder</param>
+    /// <returns>Return same builder</returns>
+    public static AppBuilder UseTizen(this AppBuilder builder)
+    {
+        return builder
+            .UseWindowingSubsystem(TizenPlatform.Initialize, "Tizen")
+            .UseStandardRuntimePlatformSubsystem()
+            .UseSkia();
+    }
+}

+ 35 - 0
src/Tizen/Avalonia.Tizen/TizenPlatform.cs

@@ -0,0 +1,35 @@
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Tizen.Platform.Input;
+using Avalonia.Tizen.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Tizen;
+
+internal class TizenPlatform
+{
+    public static readonly TizenPlatform Instance = new();
+    internal static NuiGlPlatform GlPlatform { get; set; }
+    internal static Compositor Compositor { get; private set; }
+    internal static TizenThreadingInterface ThreadingInterface { get; private set; } = new();
+
+    public static void Initialize()
+    {   
+        AvaloniaLocator.CurrentMutable
+            .Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
+            .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
+            .Bind<IKeyboardDevice>().ToSingleton<TizenKeyboardDevice>()
+            .Bind<IPlatformSettings>().ToSingleton<TizenPlatformSettings>()
+            .Bind<IPlatformThreadingInterface>().ToConstant(ThreadingInterface)
+            .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
+            .Bind<IRenderTimer>().ToConstant(new TizenRenderTimer())
+            .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
+            .Bind<IPlatformGraphics>().ToConstant(GlPlatform = new NuiGlPlatform());
+
+        Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
+    }
+}

+ 44 - 0
src/Tizen/Avalonia.Tizen/TizenRenderTimer.cs

@@ -0,0 +1,44 @@
+using System.Diagnostics;
+using Avalonia.Rendering;
+using Tizen.Account.AccountManager;
+using Tizen.System;
+
+namespace Avalonia.Tizen;
+internal class TizenRenderTimer : IRenderTimer
+{
+    private readonly Stopwatch _st = Stopwatch.StartNew();
+    private Timer _timer;
+
+    public bool RunsInBackground => true;
+
+    public event Action<TimeSpan>? Tick;
+    public event Action? RenderTick;
+
+    public TizenRenderTimer()
+    {
+        _timer = new Timer(TimerTick, null, 16, 16);
+        Display.StateChanged += Display_StateChanged;
+    }
+
+    private void TimerTick(object? state)
+    {
+        RenderTick?.Invoke();
+    }
+
+    private void Display_StateChanged(object? sender, DisplayStateChangedEventArgs e)
+    {
+        if (e.State == DisplayState.Off)
+        {
+            _timer.Change(Timeout.Infinite, Timeout.Infinite);
+        }
+        else
+        {
+            _timer.Change(16, 16);
+        }
+    }
+
+    internal void ManualTick()
+    {
+        Tick?.Invoke(_st.Elapsed);
+    }
+}

+ 152 - 0
src/Tizen/Avalonia.Tizen/TizenStorageProvider.cs

@@ -0,0 +1,152 @@
+using System.Security;
+using Avalonia.Platform.Storage;
+using Avalonia.Platform.Storage.FileIO;
+using Avalonia.Tizen.Platform;
+using Tizen.Applications;
+
+namespace Avalonia.Tizen;
+internal class TizenStorageProvider : IStorageProvider
+{
+    public bool CanOpen => true;
+
+    public bool CanSave => false;
+
+    public bool CanPickFolder => false;
+
+    public Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
+    {
+        return Task.FromException<IStorageBookmarkFile?>(
+           new PlatformNotSupportedException("Bookmark is not supported by Tizen"));
+    }
+
+    private static async Task CheckPermission()
+    {
+        Permissions.EnsureDeclared(Permissions.AppManagerLaunchPrivilege);
+        if (await Permissions.RequestPrivilegeAsync(Permissions.MediaStoragePrivilege) == false)
+        {
+            throw new SecurityException("Application doesn't have storage permission.");
+        }
+    }
+
+    public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
+    {
+        await CheckPermission();
+
+        var tcs = new TaskCompletionSource<IReadOnlyList<IStorageFile>>();
+
+#pragma warning disable CS8603 // Possible null reference return.
+        var fileType = options.FileTypeFilter?
+            .Where(w => w.MimeTypes != null)
+            .SelectMany(s => s.MimeTypes);
+#pragma warning restore CS8603 // Possible null reference return.
+
+        var appControl = new AppControl
+        {
+            Operation = AppControlOperations.Pick,
+            Mime = fileType?.Any() == true
+                ? fileType.Aggregate((o, n) => o + ";" + n)
+                : "*/*"
+        };
+        appControl.ExtraData.Add(AppControlData.SectionMode, options.AllowMultiple ? "multiple" : "single");
+        if (options.SuggestedStartLocation?.Path is { } startupPath)
+            appControl.ExtraData.Add(AppControlData.Path, startupPath.ToString());
+        appControl.LaunchMode = AppControlLaunchMode.Single;
+
+        var fileResults = new List<IStorageFile>();
+
+        AppControl.SendLaunchRequest(appControl, (_, reply, result) =>
+        {
+            if (result == AppControlReplyResult.Succeeded)
+            {
+                if (reply.ExtraData.Count() > 0)
+                {
+                    var selectedFiles = reply.ExtraData.Get<IEnumerable<string>>(AppControlData.Selected).ToList();
+                    fileResults.AddRange(selectedFiles.Select(f => new BclStorageFile(new(f))));
+                }
+            }
+
+            tcs.TrySetResult(fileResults);
+        });
+
+        return await tcs.Task;
+    }
+
+    public Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
+    {
+        return Task.FromException<IReadOnlyList<IStorageFolder>>(
+            new PlatformNotSupportedException("Open folder is not supported by Tizen"));
+    }
+
+    public Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
+    {
+        return Task.FromException<IStorageBookmarkFolder?>(
+           new PlatformNotSupportedException("Open folder is not supported by Tize"));
+    }
+
+    public Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
+    {
+        return Task.FromException<IStorageFile?>(
+            new PlatformNotSupportedException("Save file picker is not supported by Tizen"));
+    }
+
+    public async Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath)
+    {
+        await CheckPermission();
+
+        if (filePath is not { IsAbsoluteUri: true, Scheme: "file" })
+        {
+            throw new ArgumentException("File path is expected to be an absolute link with \"file\" scheme.");
+        }
+
+        var path = Path.Combine(global::Tizen.Applications.Application.Current.DirectoryInfo.Resource, filePath.AbsolutePath);
+        var file = new FileInfo(path);
+        if (!file.Exists)
+        {
+            return null;
+        }
+
+        return new BclStorageFile(file);
+    }
+
+    public async Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath)
+    {
+        if (folderPath is null)
+        {
+            throw new ArgumentNullException(nameof(folderPath));
+        }
+
+        await CheckPermission();
+
+        if (folderPath is not { IsAbsoluteUri: true, Scheme: "file" })
+        {
+            throw new ArgumentException("File path is expected to be an absolute link with \"file\" scheme.");
+        }
+
+        var path = Path.Combine(global::Tizen.Applications.Application.Current.DirectoryInfo.Resource, folderPath.AbsolutePath);
+        var directory = new System.IO.DirectoryInfo(path);
+        if (!directory.Exists)
+            return null;
+
+        return new BclStorageFolder(directory);
+    }
+
+    public Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
+    {
+        var folder = wellKnownFolder switch
+        {
+            WellKnownFolder.Desktop => null,
+            WellKnownFolder.Documents => global::Tizen.Applications.Application.Current.DirectoryInfo.Data,
+            WellKnownFolder.Downloads => global::Tizen.Applications.Application.Current.DirectoryInfo.SharedData,
+            WellKnownFolder.Music => null,
+            WellKnownFolder.Pictures => null,
+            WellKnownFolder.Videos => null,
+            _ => throw new ArgumentOutOfRangeException(nameof(wellKnownFolder), wellKnownFolder, null),
+        };
+
+        if (folder == null)
+            return Task.FromResult<IStorageFolder?>(null);
+
+        var storageFolder = new BclStorageFolder(new System.IO.DirectoryInfo(folder));
+        return Task.FromResult<IStorageFolder?>(storageFolder);
+    }
+}

+ 50 - 0
src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs

@@ -0,0 +1,50 @@
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Tizen;
+internal class TizenThreadingInterface : IPlatformThreadingInterface
+{
+    internal event Action? TickExecuted;
+    private bool _signaled;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+    internal static SynchronizationContext MainloopContext { get; set; }
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+    public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
+    {
+        return new Timer(_ =>
+        {
+            EnsureInvokeOnMainThread(tick);
+        }, null, interval, interval);
+    }
+
+    private void EnsureInvokeOnMainThread(Action action)
+    {
+        if (SynchronizationContext.Current != null)
+            action();
+        else
+            MainloopContext.Post(static arg => ((Action)arg!).Invoke(), action);
+    }
+
+    public void Signal(DispatcherPriority prio)
+    {
+        if (_signaled)
+            return;
+
+        _signaled = true;
+        var interval = TimeSpan.FromMilliseconds(1);
+
+        IDisposable? disp = null;
+        disp = new Timer(_ =>
+        {
+            _signaled = false;
+            disp?.Dispose();
+            
+            EnsureInvokeOnMainThread(() => Signaled?.Invoke(prio));
+        }, null, interval, interval);
+    }
+
+    public bool CurrentThreadIsLoopThread => SynchronizationContext.Current != null;
+    public event Action<DispatcherPriority?>? Signaled;
+}

+ 117 - 0
src/Tizen/Avalonia.Tizen/TopLevelImpl.cs

@@ -0,0 +1,117 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
+using Avalonia.Platform;
+using Avalonia.Platform.Storage;
+using Avalonia.Rendering.Composition;
+using Avalonia.Tizen.Platform.Input;
+
+namespace Avalonia.Tizen;
+
+internal class TopLevelImpl : ITopLevelImpl
+{
+    private readonly ITizenView _view;
+    private readonly NuiClipboardImpl _clipboard;
+    private readonly IStorageProvider _storageProvider;
+
+    public TopLevelImpl(ITizenView view, IEnumerable<object> surfaces)
+    {
+        _view = view;
+        Surfaces = surfaces;
+
+        _storageProvider = new TizenStorageProvider();
+        _clipboard = new NuiClipboardImpl();
+    }
+
+    public Size ClientSize => _view.ClientSize;
+
+    public Size? FrameSize => null;
+
+    public double RenderScaling => 1;
+
+    public IEnumerable<object> Surfaces { get; set; }
+
+    public Action<RawInputEventArgs>? Input { get; set; }
+    public Action<Rect>? Paint { get; set; }
+    public Action<Size, WindowResizeReason>? Resized { get; set; }
+    public Action<double>? ScalingChanged { get; set; }
+    public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
+
+    public Compositor Compositor => TizenPlatform.Compositor;
+
+    public Action? Closed { get; set; }
+    public Action? LostFocus { get; set; }
+
+    public WindowTransparencyLevel TransparencyLevel { get; set; }
+
+    public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels();
+    public IPopupImpl? CreatePopup()
+    {
+        return null;
+    }
+
+    public void Dispose()
+    {
+        //
+    }
+
+    public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
+
+    public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y);
+
+    public void SetCursor(ICursorImpl? cursor)
+    {
+        //
+    }
+
+    public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
+    {
+        //
+    }
+
+    public void SetInputRoot(IInputRoot inputRoot)
+    {
+        _view.InputRoot = inputRoot;
+    }
+
+    public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
+    {
+        //
+    }
+
+    public object? TryGetFeature(Type featureType)
+    {
+        if (featureType == typeof(IStorageProvider))
+        {
+            return _storageProvider;
+        }
+
+        if (featureType == typeof(ITextInputMethodImpl))
+        {
+            return _view;
+        }
+
+        if (featureType == typeof(INativeControlHostImpl))
+        {
+            return _view.NativeControlHost;
+        }
+
+        if (featureType == typeof(IClipboard))
+        {
+            return _clipboard;
+        }
+
+        return null;
+    }
+
+    internal void TextInput(string text)
+    {
+        if (Input == null) return;
+        var args = new RawTextInputEventArgs(TizenKeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, _view.InputRoot, text);
+
+        Input(args);
+    }
+}