diff --git a/Avalonia.sln b/Avalonia.sln
index 5ffa3dca50..2777f1b923 100644
--- a/Avalonia.sln
+++ b/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}
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 8bab6e68e2..12cfdd015f 100644
--- a/azure-pipelines.yml
+++ b/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:
diff --git a/nukebuild/ApiDiffValidation.cs b/nukebuild/ApiDiffValidation.cs
index 483bfd336a..ecffce47cb 100644
--- a/nukebuild/ApiDiffValidation.cs
+++ b/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);
diff --git a/samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj b/samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj
new file mode 100644
index 0000000000..20a93029ad
--- /dev/null
+++ b/samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj
@@ -0,0 +1,20 @@
+
+
+ net6.0-tizen
+ Exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog.Tizen/EmbedSampleNuiTizen.cs b/samples/ControlCatalog.Tizen/EmbedSampleNuiTizen.cs
new file mode 100644
index 0000000000..728ea58e3b
--- /dev/null
+++ b/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 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);
+ }
+ }
+}
diff --git a/samples/ControlCatalog.Tizen/Main.cs b/samples/ControlCatalog.Tizen/Main.cs
new file mode 100644
index 0000000000..2d611ef5a3
--- /dev/null
+++ b/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
+{
+ 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);
+ }
+}
diff --git a/samples/ControlCatalog.Tizen/shared/res/Avalonia.png b/samples/ControlCatalog.Tizen/shared/res/Avalonia.png
new file mode 100644
index 0000000000..ea0bb4986f
Binary files /dev/null and b/samples/ControlCatalog.Tizen/shared/res/Avalonia.png differ
diff --git a/samples/ControlCatalog.Tizen/tizen-manifest.xml b/samples/ControlCatalog.Tizen/tizen-manifest.xml
new file mode 100644
index 0000000000..5924d18bd6
--- /dev/null
+++ b/samples/ControlCatalog.Tizen/tizen-manifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Avalonia.png
+
+
+
+
+
+ http://tizen.org/privilege/appdir.shareddata
+ http://tizen.org/privilege/appmanager.launch
+ http://tizen.org/privilege/externalstorage
+ http://tizen.org/privilege/externalstorage.appdata
+ http://tizen.org/privilege/internet
+ http://tizen.org/privilege/network.get
+
+
+
+ http://tizen.org/feature/opengles.surfaceless_context
+ http://tizen.org/feature/opengles.version.2_0
+
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 9aeb71f649..d7f740d1e4 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -54,6 +54,7 @@
+
diff --git a/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj b/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
new file mode 100644
index 0000000000..7fd1d96097
--- /dev/null
+++ b/src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net6.0-tizen
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tizen/Avalonia.Tizen/ITizenView.cs b/src/Tizen/Avalonia.Tizen/ITizenView.cs
new file mode 100644
index 0000000000..87ddba0b6b
--- /dev/null
+++ b/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; }
+}
diff --git a/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs b/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
new file mode 100644
index 0000000000..485d9bee79
--- /dev/null
+++ b/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;
+
+///
+/// Avalonia View for Tizen NUI controls
+///
+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();
+ _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);
+ }
+}
diff --git a/src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs b/src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs
new file mode 100644
index 0000000000..da7b57e378
--- /dev/null
+++ b/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 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
+{
+}
diff --git a/src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs b/src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs
new file mode 100644
index 0000000000..549582b034
--- /dev/null
+++ b/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 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(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