Browse Source

Remove Tizen (#19722)

* Remove Tizen

* Address review
pull/19753/head
Julien Lebosquain 4 months ago
committed by GitHub
parent
commit
467063b54b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      Avalonia.sln
  2. 2
      build/DevSingleProject.targets
  3. 3
      build/TargetFrameworks.props
  4. 6
      dirs.proj
  5. 2
      packages/Avalonia/AvaloniaSingleProject.targets
  6. 25
      samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj
  7. 41
      samples/ControlCatalog.Tizen/EmbedSampleNuiTizen.cs
  8. 23
      samples/ControlCatalog.Tizen/Main.cs
  9. BIN
      samples/ControlCatalog.Tizen/shared/res/Avalonia.png
  10. 23
      samples/ControlCatalog.Tizen/tizen-manifest.xml
  11. 16
      samples/SingleProjectSandbox/Platforms/Tizen/Main.cs
  12. BIN
      samples/SingleProjectSandbox/Platforms/Tizen/shared/res/Avalonia.png
  13. 23
      samples/SingleProjectSandbox/Platforms/Tizen/tizen-manifest.xml
  14. 1
      samples/SingleProjectSandbox/SingleProjectSandbox.csproj
  15. 26
      src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj
  16. 12
      src/Tizen/Avalonia.Tizen/ITizenView.cs
  17. 188
      src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
  18. 211
      src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs
  19. 98
      src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs
  20. 73
      src/Tizen/Avalonia.Tizen/NuiGlLayerSurface.cs
  21. 87
      src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs
  22. 114
      src/Tizen/Avalonia.Tizen/NuiKeyboardHandler.cs
  23. 117
      src/Tizen/Avalonia.Tizen/NuiNativeControlHostImpl.cs
  24. 73
      src/Tizen/Avalonia.Tizen/NuiScreens.cs
  25. 72
      src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
  26. 99
      src/Tizen/Avalonia.Tizen/NuiTouchHandler.cs
  27. 39
      src/Tizen/Avalonia.Tizen/NuiViewControlHandle.cs
  28. 29
      src/Tizen/Avalonia.Tizen/Platform/Consts.cs
  29. 139
      src/Tizen/Avalonia.Tizen/Platform/Input/TizenKeyboardDevice.cs
  30. 20
      src/Tizen/Avalonia.Tizen/Platform/Interop/Strucs.cs
  31. 115
      src/Tizen/Avalonia.Tizen/Platform/Permissions.cs
  32. 71
      src/Tizen/Avalonia.Tizen/Platform/TizenLauncher.cs
  33. 10
      src/Tizen/Avalonia.Tizen/Platform/TizenPlatformSettings.cs
  34. 67
      src/Tizen/Avalonia.Tizen/Stubs.cs
  35. 23
      src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs
  36. 48
      src/Tizen/Avalonia.Tizen/TizenPlatform.cs
  37. 44
      src/Tizen/Avalonia.Tizen/TizenRenderTimer.cs
  38. 38
      src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs
  39. 152
      src/Tizen/Avalonia.Tizen/TizenStorageProvider.cs
  40. 54
      src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs
  41. 132
      src/Tizen/Avalonia.Tizen/TopLevelImpl.cs

18
Avalonia.sln

@ -274,12 +274,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.NUnit.Uni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}"
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Build.Tasks.UnitTest", "tests\Avalonia.Build.Tasks.UnitTest\Avalonia.Build.Tasks.UnitTest.csproj", "{B0FD6A48-FBAB-4676-B36A-DE76B0922B12}"
@ -666,16 +660,6 @@ Global
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.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
@ -797,8 +781,6 @@ Global
{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{2999D79E-3C20-4A90-B651-CA7E0AC92D35} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{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}

2
build/DevSingleProject.targets

@ -9,8 +9,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" Condition="'$(_AvaloniaAndroidTarget)' == 'true'" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\iOS\Avalonia.iOS\Avalonia.iOS.csproj" Condition="'$(_AvaloniaIOSTarget)' == 'true'" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\Browser\Avalonia.Browser\Avalonia.Browser.csproj" Condition="'$(_AvaloniaBrowserTarget)' == 'true'" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj" Condition="'$(_AvaloniaTizenTarget)' == 'true'" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj" Condition="'$(_AvaloniaTizenTarget)' == 'true'" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" Condition="'$(_AvaloniaDesktopTarget)' == 'true'" />
</ItemGroup>

3
build/TargetFrameworks.props

@ -8,8 +8,6 @@
<AvsCurrentIOSTargetFramework>$(AvsCurrentTargetFramework)-ios17.0</AvsCurrentIOSTargetFramework>
<AvsCurrentTvOSTargetFramework>$(AvsCurrentTargetFramework)-tvos17.0</AvsCurrentTvOSTargetFramework>
<AvsCurrentBrowserTargetFramework>$(AvsCurrentTargetFramework)-browser</AvsCurrentBrowserTargetFramework>
<AvsCurrentTizenTargetFramework>$(AvsCurrentTargetFramework)-tizen</AvsCurrentTizenTargetFramework>
<AvsCurrentTizenTargetSdk>8.0.155</AvsCurrentTizenTargetSdk>
</PropertyGroup>
<PropertyGroup Condition="'$(AvsSkipBuildingLegacyTargetFrameworks)' != 'True'">
<AvsLegacyTargetFrameworks>net6.0</AvsLegacyTargetFrameworks>
@ -22,7 +20,6 @@
<AvsMinSupportedIOSVersion>13.0</AvsMinSupportedIOSVersion>
<AvsMinSupportedTvOSVersion>13.0</AvsMinSupportedTvOSVersion>
<AvsMinSupportedMacCatalystVersion>13.1</AvsMinSupportedMacCatalystVersion>
<AvsMinSupportedTizenVersion>6.5</AvsMinSupportedTizenVersion>
<AvsMinSupportedAndroidVersion>21.0</AvsMinSupportedAndroidVersion>
<!-- Desktop OS min version is not set in any of ours backends, but only used in some samples. -->
<!-- Avalonia technically supports 10.12. -->

6
dirs.proj

@ -14,7 +14,6 @@
<ProjectReference Remove="samples/*.Android/*.csproj" />
<ProjectReference Remove="samples/*.Browser/*.csproj" />
<ProjectReference Remove="samples/*.Blazor/*.csproj" />
<ProjectReference Remove="samples/*.Tizen/*.csproj" />
<ProjectReference Remove="samples/SingleProjectSandbox/*.csproj" />
<ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" />
</ItemGroup>
@ -23,11 +22,6 @@
<!-- Build mobile backends only on Windows, where we have installed android workload -->
<ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="src/iOS/**/*.*proj" />
<ProjectReference Remove="src/Tizen/**/*.*proj" />
</ItemGroup>
<ItemGroup Condition="'$(SkipObscurePlatforms)' == 'True'">
<ProjectReference Remove="**/*Tizen/**/*.*proj" />
</ItemGroup>
<ItemGroup>

2
packages/Avalonia/AvaloniaSingleProject.targets

@ -278,7 +278,7 @@
Include="$(TizenSharedPrefix)\**\*"
Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);@(TizenTpkUserExcludeFiles)" />
</ItemGroup>
<!-- Android workaround to allow projects without AndroidManifest.xml, as it used to be supported -->
<Target Name="BeforeGetAndroidPackageName"
BeforeTargets="_GetAndroidPackageName">

25
samples/ControlCatalog.Tizen/ControlCatalog.Tizen.csproj

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(AvsCurrentTizenTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<Import Project="AutoImport.props" Sdk="Samsung.Tizen.Sdk" Version="$(AvsCurrentTizenTargetSdk)" />
<ItemGroup>
<TizenSharedResource Remove="shared\res\Avalonia.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Samsung.Tizen.Sdk" Version="$(AvsCurrentTizenTargetSdk)" />
<ItemGroup>
<KnownFrameworkReference Update="Samsung.Tizen" TargetingPackVersion="$(AvsCurrentTizenTargetSdk)" />
</ItemGroup>
</Project>

41
samples/ControlCatalog.Tizen/EmbedSampleNuiTizen.cs

@ -1,41 +0,0 @@
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
samples/ControlCatalog.Tizen/Main.cs

@ -1,23 +0,0 @@
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

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

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.avalonia.control-catalog" version="1.0.0" api-version="8.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>

16
samples/SingleProjectSandbox/Platforms/Tizen/Main.cs

@ -1,16 +0,0 @@
using Avalonia;
using Avalonia.Tizen;
namespace SingleProjectSandbox;
internal class Program : NuiTizenApplication<App>
{
protected override AppBuilder CreateAppBuilder() =>
App.BuildAvaloniaApp().UseTizen();
internal static void Main(string[] args)
{
var app = new Program();
app.Run(args);
}
}

BIN
samples/SingleProjectSandbox/Platforms/Tizen/shared/res/Avalonia.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

23
samples/SingleProjectSandbox/Platforms/Tizen/tizen-manifest.xml

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.avalonia.control-catalog" version="1.0.0" api-version="8.0" xmlns="http://tizen.org/ns/packages">
<profile name="common" />
<ui-application appid="com.avalonia.SingleProjectSandbox" exec="SingleProjectSandbox.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
samples/SingleProjectSandbox/SingleProjectSandbox.csproj

@ -3,7 +3,6 @@
<TargetFrameworks>$(AvsCurrentTargetFramework);$(AvsCurrentAndroidTargetFramework);$(AvsCurrentBrowserTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(IncludeIOSTargetSamples)' == 'true'">$(TargetFrameworks);$(AvsCurrentIOSTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(IncludeMacTargetSamples)' == 'true'">$(TargetFrameworks);$(AvsCurrentMacOSTargetFramework)</TargetFrameworks>
<TargetFrameworks Condition="'$(IncludeTizenTargetSamples)' == 'true'">$(TargetFrameworks);$(AvsCurrentTizenTargetFramework)</TargetFrameworks>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>

26
src/Tizen/Avalonia.Tizen/Avalonia.Tizen.csproj

@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(AvsCurrentTizenTargetFramework)</TargetFramework>
<SupportedOSPlatformVersion>$(AvsMinSupportedTizenVersion)</SupportedOSPlatformVersion>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<Import Project="AutoImport.props" Sdk="Samsung.Tizen.Sdk" Version="$(AvsCurrentTizenTargetSdk)" />
<ItemGroup>
<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" />
<Import Project="..\..\..\build\NullableEnable.props" />
<Import Project="Sdk.targets" Sdk="Samsung.Tizen.Sdk" Version="$(AvsCurrentTizenTargetSdk)" />
<ItemGroup>
<KnownFrameworkReference Update="Samsung.Tizen" TargetingPackVersion="$(AvsCurrentTizenTargetSdk)" />
</ItemGroup>
</Project>

12
src/Tizen/Avalonia.Tizen/ITizenView.cs

@ -1,12 +0,0 @@
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; }
}

188
src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs

@ -1,188 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server;
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 readonly TouchDevice _device = new();
private ServerCompositionTarget? _compositionTargetServer;
private IInputRoot? _inputRoot;
public INativeControlHostImpl NativeControlHost { get; }
public double Scaling => 1;
public Size ClientSize => new(Size.Width, Size.Height);
public IInputRoot InputRoot
{
get => _inputRoot ?? throw new InvalidOperationException($"{nameof(InputRoot)} hasn't been set");
set => _inputRoot = value;
}
internal TopLevel TopLevel
=> _topLevel ?? throw new InvalidOperationException($"{nameof(NuiAvaloniaView)} hasn't been initialized");
internal TopLevelImpl TopLevelImpl
=> _topLevelImpl ?? throw new InvalidOperationException($"{nameof(NuiAvaloniaView)} hasn't been initialized");
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 || _compositionTargetServer == 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);
}
}

211
src/Tizen/Avalonia.Tizen/NuiAvaloniaViewTextEditable.cs

@ -1,211 +0,0 @@
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;
public NuiAvaloniaViewTextEditable(NuiAvaloniaView avaloniaView)
{
_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;
client.InputPaneActivationRequested += OnInputPaneActivationRequested;
TextInput.SelectWholeText();
OnClientSelectionChanged(this, EventArgs.Empty);
}
finally { _updating = false; }
}
private void OnInputPaneActivationRequested(object? sender, EventArgs e)
{
var inputContext = TextInput.GetInputMethodContext();
inputContext.ShowInputPanel();
}
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;
_client!.InputPaneActivationRequested -= OnInputPaneActivationRequested;
}
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
{
}

98
src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs

@ -1,98 +0,0 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Threading;
using Tizen.NUI;
using Tizen.NUI.BaseComponents;
namespace Avalonia.Tizen;
internal class NuiClipboardImpl : IClipboardImpl, IAsyncDataTransfer, IAsyncDataTransferItem
{
private readonly DataFormat[] _formats;
private readonly IAsyncDataTransferItem[] _items;
private readonly TextEditor _textEditor;
public NuiClipboardImpl()
{
_formats = [DataFormat.Text];
_items = [this];
_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();
}
IReadOnlyList<DataFormat> IAsyncDataTransfer.Formats
=> _formats;
IReadOnlyList<DataFormat> IAsyncDataTransferItem.Formats
=> _formats;
IReadOnlyList<IAsyncDataTransferItem> IAsyncDataTransfer.Items
=> _items;
public Task ClearAsync()
=> SetTextAsync(string.Empty);
public Task<IAsyncDataTransfer?> TryGetDataAsync()
=> Task.FromResult<IAsyncDataTransfer?>(this);
public async Task SetDataAsync(IAsyncDataTransfer dataTransfer)
{
var text = await dataTransfer.TryGetTextAsync();
await SetTextAsync(text ?? string.Empty);
}
public Task<object?> TryGetRawAsync(DataFormat format)
=> DataFormat.Text.Equals(format) ? GetTextAsync() : Task.FromResult<object?>(null);
private Task<object?> 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<object?>(async () =>
{
await Task.Delay(10);
return await Dispatcher.UIThread.InvokeAsync(() =>
{
_textEditor.Hide();
return _textEditor.Text;
});
});
}
private 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();
});
});
}
void IDisposable.Dispose()
{
}
}

73
src/Tizen/Avalonia.Tizen/NuiGlLayerSurface.cs

@ -1,73 +0,0 @@
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
src/Tizen/Avalonia.Tizen/NuiGlPlatform.cs

@ -1,87 +0,0 @@
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);
}
}

114
src/Tizen/Avalonia.Tizen/NuiKeyboardHandler.cs

@ -1,114 +0,0 @@
using System.Diagnostics;
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))
{
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Triggering key event {text}", e.Key.KeyString);
SendKeyEvent(e, keyCode);
}
else if (e.Key.State == Key.StateType.Up && !string.IsNullOrEmpty(e.Key.KeyString))
{
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);
var deviceType = GetDeviceType(e);
_view.TopLevelImpl.Input?.Invoke(
new RawKeyEventArgs(
KeyboardDevice.Instance!,
e.Key.Time,
_view.InputRoot,
type,
mapped,
modifiers,
PhysicalKey.None,
deviceType,
e.Key.KeyString
));
}
private bool ShouldSendKeyEvent(Window.KeyEventArgs e, out Input.Key keyCode)
{
keyCode = TizenKeyboardDevice.GetSpecialKey(e.Key.KeyPressedName);
if (keyCode != Input.Key.None)
return true;
if ((e.Key.IsCtrlModifier() || e.Key.IsAltModifier()) && !string.IsNullOrEmpty(e.Key.KeyString))
{
var c = e.Key.KeyPressedName.Length == 1 ? e.Key.KeyPressedName[0] : (char)e.Key.KeyCode;
return (keyCode = TizenKeyboardDevice.GetAsciiKey(c)) != Input.Key.None;
}
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;
}
private KeyDeviceType GetDeviceType(Window.KeyEventArgs ev)
{
if (ev.Key.DeviceClass == DeviceClassType.Gamepad)
return KeyDeviceType.Gamepad;
if (ev.Key.DeviceSubClass == DeviceSubClassType.Remocon)
return KeyDeviceType.Remote;
return KeyDeviceType.Keyboard;
}
}

117
src/Tizen/Avalonia.Tizen/NuiNativeControlHostImpl.cs

@ -1,117 +0,0 @@
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
src/Tizen/Avalonia.Tizen/NuiScreens.cs

@ -1,73 +0,0 @@
using Avalonia.Platform;
using Tizen.Applications;
using Tizen.Multimedia;
using Tizen.NUI;
using Tizen.System;
namespace Avalonia.Tizen;
internal class NuiScreens : ScreensBase<int, SingleTizenScreen>
{
// See https://github.com/dotnet/maui/blob/8.0.70/src/Essentials/src/DeviceDisplay/DeviceDisplay.tizen.cs
internal const float BaseLogicalDpi = 160.0f;
internal static DeviceOrientation LastDeviceOrientation { get; private set; }
internal static int DisplayWidth =>
Information.TryGetValue<int>("http://tizen.org/feature/screen.width", out var value) ? value : 0;
internal static int DisplayHeight =>
Information.TryGetValue<int>("http://tizen.org/feature/screen.height", out var value) ? value : 0;
internal static int DisplayDpi => TizenRuntimePlatform.Info.Value.IsTV ? 72 :
Information.TryGetValue<int>("http://tizen.org/feature/screen.dpi", out var value) ? value : 72;
public NuiScreens()
{
((CoreApplication)global::Tizen.Applications.Application.Current).DeviceOrientationChanged += (sender, args) =>
{
LastDeviceOrientation = args.DeviceOrientation;
OnChanged();
};
}
protected override int GetScreenCount() => 1;
protected override IReadOnlyList<int> GetAllScreenKeys() => [1];
protected override SingleTizenScreen CreateScreenFromKey(int key)
{
var screen = new SingleTizenScreen(key);
screen.Refresh();
return screen;
}
protected override void ScreenChanged(SingleTizenScreen screen) => screen.Refresh();
}
internal class SingleTizenScreen(int index) : PlatformScreen(new PlatformHandle(new IntPtr(index), nameof(SingleTizenScreen)))
{
public void Refresh()
{
IsPrimary = index == 1;
if (IsPrimary)
{
Bounds = WorkingArea = new PixelRect(0, 0, NuiScreens.DisplayWidth, NuiScreens.DisplayHeight);
Scaling = NuiScreens.DisplayDpi / NuiScreens.BaseLogicalDpi;
var isNaturalLandscape = Bounds.Width > Bounds.Height;
CurrentOrientation = (isNaturalLandscape, NuiScreens.LastDeviceOrientation) switch
{
(true, DeviceOrientation.Orientation_0) => ScreenOrientation.Landscape,
(true, DeviceOrientation.Orientation_90) => ScreenOrientation.Portrait,
(true, DeviceOrientation.Orientation_180) => ScreenOrientation.LandscapeFlipped,
(true, DeviceOrientation.Orientation_270) => ScreenOrientation.PortraitFlipped,
(false, DeviceOrientation.Orientation_0) => ScreenOrientation.Portrait,
(false, DeviceOrientation.Orientation_90) => ScreenOrientation.Landscape,
(false, DeviceOrientation.Orientation_180) => ScreenOrientation.PortraitFlipped,
(false, DeviceOrientation.Orientation_270) => ScreenOrientation.LandscapeFlipped,
_ => ScreenOrientation.None
};
}
}
}

72
src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs

@ -1,72 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Tizen.NUI;
using Window = Tizen.NUI.Window;
using Avalonia.Logging;
namespace Avalonia.Tizen;
public class NuiTizenApplication<TApp> : NUIApplication
where TApp : Application, new()
{
private const string LogKey = "TIZENAPP";
private SingleViewLifetime? _lifetime;
private class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
public NuiAvaloniaView View { get; }
public SingleViewLifetime(NuiAvaloniaView view)
{
View = view;
}
public Control? MainView
{
get => View.Content;
set => View.Content = value;
}
public TopLevel? TopLevel => View.TopLevel;
}
protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure<TApp>().UseTizen();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
protected override void OnCreate()
{
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Creating application");
base.OnCreate();
TizenThreadingInterface.MainloopContext = SynchronizationContext.Current!;
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 += (_, e) => _lifetime?.View.KeyboardHandler.Handle(e);
}
private void ContinueSetupApplication()
{
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "App builder");
var builder = CreateAppBuilder();
TizenThreadingInterface.MainloopContext.Post(_ =>
{
builder = CustomizeAppBuilder(builder);
builder.AfterApplicationSetup(_ => _lifetime!.View.Initialise());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup lifetime");
builder.SetupWithLifetime(_lifetime!);
}, null);
}
}

99
src/Tizen/Avalonia.Tizen/NuiTouchHandler.cs

@ -1,99 +0,0 @@
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
src/Tizen/Avalonia.Tizen/NuiViewControlHandle.cs

@ -1,39 +0,0 @@
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
src/Tizen/Avalonia.Tizen/Platform/Consts.cs

@ -1,29 +0,0 @@
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";
}

139
src/Tizen/Avalonia.Tizen/Platform/Input/TizenKeyboardDevice.cs

@ -1,139 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Tizen.NUI.Components;
using Tizen.Uix.InputMethod;
using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Tizen.Platform.Input;
internal class TizenKeyboardDevice : KeyboardDevice, IKeyboardDevice
{
private static readonly Dictionary<string, Key> SpecialKeys = new Dictionary<string, Key>
{
// Media keys
{ "XF86Red", Key.MediaRed },
{ "XF86Green", Key.MediaGreen },
{ "XF86Yellow", Key.MediaYellow },
{ "XF86Blue", Key.MediaBlue },
{ "XF86Info", Key.MediaInfo },
{ "XF86SimpleMenu", Key.MediaMenu },
{ "XF86Caption", Key.MediaSubtitle },
{ "XF86MTS", Key.None },
{ "XF86PictureSize", Key.None },
{ "XF86More", Key.MediaMore },
{ "XF86Search", Key.MediaSearch },
{ "XF863D", Key.None },
{ "XF86AudioRewind", Key.MediaPreviousTrack },
{ "XF86AudioPause", Key.MediaPlayPause },
{ "XF86AudioNext", Key.MediaNextTrack },
{ "XF86AudioRecord", Key.MediaRecord },
{ "XF86AudioPlay", Key.MediaPlayPause },
{ "XF86AudioStop", Key.MediaStop },
{ "XF86ChannelGuide", Key.MediaTvGuide },
{ "XF86SysMenu", Key.Apps },
{ "minus", Key.OemMinus },
{ "XF86PreviousChannel", Key.MediaPreviousChannel },
{ "XF86AudioMute", Key.VolumeMute },
{ "XF86ChannelList", Key.MediaChannelList },
{ "XF86RaiseChannel", Key.MediaChannelRaise },
{ "XF86LowerChannel", Key.MediaChannelLower },
{ "XF86AudioRaiseVolume", Key.VolumeUp },
{ "XF86AudioLowerVolume", Key.VolumeDown },
{ "XF86Display", Key.None },
{ "XF86PowerOff", Key.Sleep },
{ "XF86PlayBack", Key.MediaPlayPause },
{ "XF86Home", Key.MediaHome },
{ "XF86Back", Key.Escape }, // Back button should be mapped as Esc
{ "XF86Exit", Key.Cancel },
{ "Shift_L", Key.LeftShift },
{ "Control_L", Key.LeftCtrl },
{ "Alt_L", Key.LeftAlt },
{ "Super_L", Key.LWin },
{ "Alt_R", Key.RightAlt },
{ "Control_R", Key.RightCtrl },
{ "Shift_R", Key.RightShift },
{ "Super_R", Key.RWin },
{ "Menu", Key.Apps },
{ "Tab", Key.Tab },
{ "BackSpace", Key.Back },
{ "Return", Key.Return },
{ "Delete", Key.Delete },
{ "End", Key.End },
{ "Next", Key.Next },
{ "Prior", Key.Prior },
{ "Home", Key.Home },
{ "Insert", Key.Insert },
{ "Num_Lock", Key.NumLock },
{ "Left", Key.Left },
{ "Up", Key.Up },
{ "Right", Key.Right },
{ "Down", Key.Down },
{ "Escape", Key.Escape },
{ "Caps_Lock", Key.CapsLock },
{ "Pause", Key.Pause },
{ "Scroll_Lock", Key.Scroll },
{ "Scroll", Key.Scroll },
};
internal static Key GetSpecialKey(string key)
{
return SpecialKeys.TryGetValue(key, out var result) ? result : Key.None;
}
internal static Key GetAsciiKey(char keyCode) => keyCode switch
{
'`' or '~' => Key.Oem7,
'0' or ')' => Key.D0,
'1' or '!' => Key.D1,
'2' or '@' => Key.D2,
'3' or '#' => Key.D3,
'4' or '$' => Key.D4,
'5' or '%' => Key.D5,
'6' or '^' => Key.D6,
'7' or '&' => Key.D7,
'8' or '*' => Key.D8,
'9' or '(' => Key.D9,
'\'' or '"' => Key.OemQuotes,
'-' or '_' => Key.OemMinus,
'=' or '+' => Key.OemPlus,
'<' or ',' => Key.OemComma,
'>' or '.' => Key.OemPeriod,
';' or ':' => Key.OemSemicolon,
'/' or '?' => Key.OemQuestion,
'[' or '{' => Key.OemOpenBrackets,
']' or '}' => Key.OemCloseBrackets,
'\\' or '|' => Key.OemPipe,
'a' or 'A' => Key.A,
'b' or 'B' => Key.B,
'c' or 'C' => Key.C,
'd' or 'D' => Key.D,
'e' or 'E' => Key.E,
'f' or 'F' => Key.F,
'g' or 'G' => Key.G,
'h' or 'H' => Key.H,
'i' or 'I' => Key.I,
'j' or 'J' => Key.J,
'k' or 'K' => Key.K,
'l' or 'L' => Key.L,
'm' or 'M' => Key.M,
'n' or 'N' => Key.N,
'o' or 'O' => Key.O,
'p' or 'P' => Key.P,
'q' or 'Q' => Key.Q,
'r' or 'R' => Key.R,
's' or 'S' => Key.S,
't' or 'T' => Key.T,
'u' or 'U' => Key.U,
'v' or 'V' => Key.V,
'w' or 'W' => Key.W,
'x' or 'X' => Key.X,
'y' or 'Y' => Key.Y,
'z' or 'Z' => Key.Z,
_ => Key.None
};
}

20
src/Tizen/Avalonia.Tizen/Platform/Interop/Strucs.cs

@ -1,20 +0,0 @@
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;
}
}

115
src/Tizen/Avalonia.Tizen/Platform/Permissions.cs

@ -1,115 +0,0 @@
using System.Security;
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 LaunchPrivilege = new("http://tizen.org/privilege/appmanager.launch", 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, _) 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, _) 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);
}
if (context != null)
{
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;
}
}

71
src/Tizen/Avalonia.Tizen/Platform/TizenLauncher.cs

@ -1,71 +0,0 @@
using Avalonia.Platform.Storage;
using Avalonia.Tizen.Platform;
using Tizen.Applications;
namespace Avalonia.Tizen;
internal class TizenLauncher : ILauncher
{
public async Task<bool> LaunchUriAsync(Uri uri)
{
if (uri is null)
{
throw new ArgumentNullException(nameof(uri));
}
if (!uri.IsAbsoluteUri)
{
return false;
}
if (!await Permissions.RequestPrivilegeAsync(Permissions.LaunchPrivilege))
{
return false;
}
var appControl = new AppControl
{
Operation = AppControlOperations.ShareText,
Uri = uri.AbsoluteUri
};
if (uri.AbsoluteUri.StartsWith("geo:"))
appControl.Operation = AppControlOperations.Pick;
else if (uri.AbsoluteUri.StartsWith("http"))
appControl.Operation = AppControlOperations.View;
else if (uri.AbsoluteUri.StartsWith("mailto:"))
appControl.Operation = AppControlOperations.Compose;
else if (uri.AbsoluteUri.StartsWith("sms:"))
appControl.Operation = AppControlOperations.Compose;
else if (uri.AbsoluteUri.StartsWith("tel:"))
appControl.Operation = AppControlOperations.Dial;
AppControl.SendLaunchRequest(appControl);
return true;
}
public async Task<bool> LaunchFileAsync(IStorageItem storageItem)
{
if (storageItem is null)
{
throw new ArgumentNullException(nameof(storageItem));
}
if (!await Permissions.RequestPrivilegeAsync(Permissions.LaunchPrivilege))
{
return false;
}
var appControl = new AppControl
{
Operation = AppControlOperations.View,
Mime = "*/*",
Uri = "file://" + storageItem.Path,
};
AppControl.SendLaunchRequest(appControl);
return true;
}
}

10
src/Tizen/Avalonia.Tizen/Platform/TizenPlatformSettings.cs

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

67
src/Tizen/Avalonia.Tizen/Stubs.cs

@ -1,67 +0,0 @@
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 ITopLevelImpl CreateEmbeddableTopLevel() => CreateEmbeddableWindow();
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() { }
}
}

23
src/Tizen/Avalonia.Tizen/TizenApplicationExtensions.cs

@ -1,23 +0,0 @@
using System.Reflection;
using Avalonia.Platform;
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
.UseTizenRuntimePlatformSubsystem()
.UseWindowingSubsystem(TizenPlatform.Initialize, "Tizen")
.UseSkia();
}
}

48
src/Tizen/Avalonia.Tizen/TizenPlatform.cs

@ -1,48 +0,0 @@
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Tizen.Platform.Input;
using Avalonia.Tizen.Platform;
namespace Avalonia.Tizen;
internal class TizenPlatform
{
public static readonly TizenPlatform Instance = new();
private static NuiGlPlatform? s_glPlatform;
private static Compositor? s_compositor;
internal static NuiGlPlatform GlPlatform
{
get => s_glPlatform ?? throw new InvalidOperationException($"{nameof(TizenPlatform)} hasn't been initialized");
private set => s_glPlatform = value;
}
internal static Compositor Compositor
{
get => s_compositor ?? throw new InvalidOperationException($"{nameof(TizenPlatform)} hasn't been initialized");
private set => s_compositor = value;
}
internal static TizenThreadingInterface ThreadingInterface { get; } = 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<KeyGestureFormatInfo>().ToConstant(new KeyGestureFormatInfo(new Dictionary<Key, string>() { }))
.Bind<IPlatformGraphics>().ToConstant(GlPlatform = new NuiGlPlatform());
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
}
}

44
src/Tizen/Avalonia.Tizen/TizenRenderTimer.cs

@ -1,44 +0,0 @@
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);
}
}

38
src/Tizen/Avalonia.Tizen/TizenRuntimePlatform.cs

@ -1,38 +0,0 @@
using System.Reflection;
using Avalonia.Platform;
namespace Avalonia.Tizen;
internal static class TizenRuntimePlatformServices
{
public static AppBuilder UseTizenRuntimePlatformSubsystem(this AppBuilder builder)
{
builder.UseRuntimePlatformSubsystem(() => Register(builder.ApplicationType?.Assembly), nameof(TizenRuntimePlatform));
return builder;
}
public static void Register(Assembly? assembly = null)
{
AssetLoader.RegisterResUriParsers();
AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToSingleton<TizenRuntimePlatform>()
.Bind<IAssetLoader>().ToConstant(new StandardAssetLoader(assembly));
}
}
internal class TizenRuntimePlatform : StandardRuntimePlatform
{
public static readonly Lazy<RuntimePlatformInfo> Info = new(() =>
{
global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile);
return new RuntimePlatformInfo
{
IsMobile = profile.Equals("mobile", StringComparison.OrdinalIgnoreCase),
IsTV = profile.Equals("tv", StringComparison.OrdinalIgnoreCase),
IsDesktop = false
};
});
public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value;
}

152
src/Tizen/Avalonia.Tizen/TizenStorageProvider.cs

@ -1,152 +0,0 @@
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);
}
}

54
src/Tizen/Avalonia.Tizen/TizenThreadingInterface.cs

@ -1,54 +0,0 @@
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Tizen;
internal class TizenThreadingInterface : IPlatformThreadingInterface
{
internal event Action? TickExecuted;
private bool _signaled;
private static SynchronizationContext? s_mainloopContext;
internal static SynchronizationContext MainloopContext
{
get => s_mainloopContext ?? throw new InvalidOperationException($"{nameof(MainloopContext)} hasn't been set");
set => s_mainloopContext = value;
}
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;
}

132
src/Tizen/Avalonia.Tizen/TopLevelImpl.cs

@ -1,132 +0,0 @@
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 IClipboard _clipboard;
private readonly IStorageProvider _storageProvider;
private readonly NuiScreens _screen;
public TopLevelImpl(ITizenView view, IEnumerable<object> surfaces)
{
_view = view;
Surfaces = surfaces;
_storageProvider = new TizenStorageProvider();
_clipboard = new Clipboard(new NuiClipboardImpl());
_screen = new NuiScreens();
}
public double DesktopScaling => RenderScaling;
public IPlatformHandle? Handle { get; }
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;
}
if (featureType == typeof(ILauncher))
{
return new TizenLauncher();
}
if (featureType == typeof(IScreenImpl))
{
return _screen;
}
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);
}
}
Loading…
Cancel
Save