Browse Source
* implement tizen platform, resolve #6110 * Fix enum types of touch event * Fix issue of touch tracking. * #6110 fix ui thread lock * #6110 send keypad from tizen window * #6110 fix touch update * #6110 add text editor for input fields * #6110 Hide text field * #6110 Fix issue of text selection and backspace * #6110 support multi and single line for input * #6110 temporary set multiline true by default. * #6110 fix issue of build with latest version and make it compatible * #6110 by pass double input when softkeyboard showing. * #6110 adding support for clipboard. * #6110 add permission helper to tizen platform * #6110 Implementation of storage provider for tizen * #6110 send text for key character from hard keyboard * #6110 Complete the input implementation. soft and hard keyboard * #6110 remove ElmSharp * Update Permissions.cs * #6110 remove open folder for tizen * #6110 fix the issue of keyboard frezee for tizen * #6110 add workload to build tizen * #6110 try to setup tizen workloads for windows * #6110 change tizen workload install to powershell * #6110 skip api diff check when package not exists * #6110 try to fix linux pipeline * #6110 implement tizen native view * #6110 add dev analyzer and trimming * #6110 Migrate to GLES and remove framebuffer * #6110 Temporary solution to make GL work * #6110 WIP and add comments * #6110 optimise gles rendering and timing. * #6110 fix dependency issue of tizen HarfBuzzSharp * #6110 patch a fix for clipboard issue * #6110 Support tizen sdk for old proj type, WIP * #6110 wip setup app * #6110 support only net6 for tizen * #6110 resolve comments --------- Co-authored-by: Kangho Hur <rookiejava+github@gmail.com> Co-authored-by: Max Katz <maxkatz6@outlook.com>pull/12987/head
committed by
GitHub
33 changed files with 2112 additions and 2 deletions
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0-tizen</TargetFramework> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<TizenSharedResource Remove="shared\res\Avalonia.png" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj" /> |
||||
|
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Folder Include="lib\" /> |
||||
|
<Folder Include="res\" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Tizen; |
||||
|
using ControlCatalog.Pages; |
||||
|
using Tizen.NUI.BaseComponents; |
||||
|
using Tizen.NUI.Components; |
||||
|
using Tizen.Pims.Contacts.ContactsViews; |
||||
|
|
||||
|
namespace ControlCatalog.Tizen; |
||||
|
public class EmbedSampleNuiTizen : INativeDemoControl |
||||
|
{ |
||||
|
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault) |
||||
|
{ |
||||
|
if (isSecond) |
||||
|
{ |
||||
|
var webView = new WebView(); |
||||
|
webView.LoadUrl("https://avaloniaui.net/"); |
||||
|
return new NuiViewControlHandle(webView); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var clickCount = 0; |
||||
|
var button = new Button |
||||
|
{ |
||||
|
Text = "Hello world" |
||||
|
}; |
||||
|
|
||||
|
button.Clicked += (sender, e) => |
||||
|
{ |
||||
|
clickCount++; |
||||
|
button.Text = $"Click count {clickCount}"; |
||||
|
}; |
||||
|
|
||||
|
return new NuiViewControlHandle(button); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Tizen; |
||||
|
using ElmSharp; |
||||
|
using SkiaSharp; |
||||
|
using Tizen.Applications; |
||||
|
|
||||
|
namespace ControlCatalog.Tizen; |
||||
|
|
||||
|
class Program : NuiTizenApplication<App> |
||||
|
{ |
||||
|
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) => |
||||
|
base.CustomizeAppBuilder(builder).AfterSetup(_ => |
||||
|
{ |
||||
|
Pages.EmbedSample.Implementation = new EmbedSampleNuiTizen(); |
||||
|
}); |
||||
|
|
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
var app = new Program(); |
||||
|
app.Run(args); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,23 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<manifest package="com.avalonia.control-catalog" version="1.0.0" api-version="7.0" xmlns="http://tizen.org/ns/packages"> |
||||
|
<profile name="common" /> |
||||
|
<ui-application appid="com.avalonia.control-catalog" exec="ControlCatalog.Tizen.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" hw-acceleration="on" launch_mode="single"> |
||||
|
<label>Avalonia</label> |
||||
|
<icon>Avalonia.png</icon> |
||||
|
<metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" /> |
||||
|
<splash-screens /> |
||||
|
</ui-application> |
||||
|
<shortcut-list /> |
||||
|
<privileges> |
||||
|
<privilege>http://tizen.org/privilege/appdir.shareddata</privilege> |
||||
|
<privilege>http://tizen.org/privilege/appmanager.launch</privilege> |
||||
|
<privilege>http://tizen.org/privilege/externalstorage</privilege> |
||||
|
<privilege>http://tizen.org/privilege/externalstorage.appdata</privilege> |
||||
|
<privilege>http://tizen.org/privilege/internet</privilege> |
||||
|
<privilege>http://tizen.org/privilege/network.get</privilege> |
||||
|
</privileges> |
||||
|
<dependencies /> |
||||
|
<provides-appdefined-privileges /> |
||||
|
<feature>http://tizen.org/feature/opengles.surfaceless_context</feature> |
||||
|
<feature>http://tizen.org/feature/opengles.version.2_0</feature> |
||||
|
</manifest> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>net6.0-tizen</TargetFrameworks> |
||||
|
<ImplicitUsings>enable</ImplicitUsings> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" /> |
||||
|
<PackageReference Include="HarfBuzzSharp.NativeAssets.Tizen" Version="2.8.2.3" /> |
||||
|
|
||||
|
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> |
||||
|
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\..\build\DevAnalyzers.props" /> |
||||
|
<Import Project="..\..\..\build\TrimmingEnable.props" /> |
||||
|
</Project> |
||||
@ -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; } |
||||
|
} |
||||
@ -0,0 +1,179 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Embedding; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.TextInput; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Rendering.Composition; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Threading; |
||||
|
using Tizen.NUI; |
||||
|
using Tizen.NUI.BaseComponents; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Avalonia View for Tizen NUI controls
|
||||
|
/// </summary>
|
||||
|
public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl |
||||
|
{ |
||||
|
private readonly NuiKeyboardHandler _keyboardHandler; |
||||
|
private readonly NuiTouchHandler _touchHandler; |
||||
|
private readonly NuiAvaloniaViewTextEditable _textEditor; |
||||
|
private TizenRenderTimer? _renderTimer; |
||||
|
private TopLevelImpl _topLevelImpl; |
||||
|
private EmbeddableControlRoot _topLevel; |
||||
|
private TouchDevice _device = new(); |
||||
|
private ServerCompositionTarget _compositionTargetServer; |
||||
|
|
||||
|
public IInputRoot InputRoot { get; set; } |
||||
|
public INativeControlHostImpl NativeControlHost { get; } |
||||
|
internal TopLevelImpl TopLevelImpl => _topLevelImpl; |
||||
|
public double Scaling => 1; |
||||
|
public Size ClientSize => new(Size.Width, Size.Height); |
||||
|
|
||||
|
public Control? Content |
||||
|
{ |
||||
|
get => _topLevel.Content as Control; |
||||
|
set => _topLevel.Content = value; |
||||
|
} |
||||
|
|
||||
|
internal NuiAvaloniaViewTextEditable TextEditor => _textEditor; |
||||
|
internal NuiKeyboardHandler KeyboardHandler => _keyboardHandler; |
||||
|
|
||||
|
#region Setup
|
||||
|
|
||||
|
public event Action OnSurfaceInit; |
||||
|
|
||||
|
public NuiAvaloniaView() : base(ColorFormat.RGBA8888) |
||||
|
{ |
||||
|
RenderingMode = GLRenderingMode.OnDemand; |
||||
|
SetGraphicsConfig(true, true, 0, GLESVersion.Version30); |
||||
|
RegisterGLCallbacks(GlInit, GlRenderFrame, GlTerminate); |
||||
|
|
||||
|
_textEditor = new NuiAvaloniaViewTextEditable(this); |
||||
|
_keyboardHandler = new NuiKeyboardHandler(this); |
||||
|
_touchHandler = new NuiTouchHandler(this); |
||||
|
NativeControlHost = new NuiNativeControlHostImpl(this); |
||||
|
|
||||
|
Layout = new CustomLayout |
||||
|
{ |
||||
|
SizeUpdated = OnResized |
||||
|
}; |
||||
|
|
||||
|
TouchEvent += OnTouchEvent; |
||||
|
WheelEvent += OnWheelEvent; |
||||
|
} |
||||
|
|
||||
|
private void GlInit() |
||||
|
{ |
||||
|
OnSurfaceInit?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
private int GlRenderFrame() |
||||
|
{ |
||||
|
if (_renderTimer == null || _topLevel == null) |
||||
|
return 0; |
||||
|
|
||||
|
var rev = _compositionTargetServer.Revision; |
||||
|
_renderTimer.ManualTick(); |
||||
|
return rev == _compositionTargetServer.Revision ? 0 : 1; |
||||
|
} |
||||
|
|
||||
|
private void GlTerminate() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
internal void Initialise() |
||||
|
{ |
||||
|
_topLevelImpl = new TopLevelImpl(this, new[] { new NuiGlLayerSurface(this) }); |
||||
|
_topLevelImpl.Compositor.AfterCommit += RenderOnce; |
||||
|
TizenPlatform.ThreadingInterface.TickExecuted += RenderOnce; |
||||
|
|
||||
|
_topLevel = new(_topLevelImpl); |
||||
|
_topLevel.Prepare(); |
||||
|
_topLevel.StartRendering(); |
||||
|
|
||||
|
_compositionTargetServer = ((CompositingRenderer)((IRenderRoot)_topLevel).Renderer).CompositionTarget.Server; |
||||
|
|
||||
|
_renderTimer = (TizenRenderTimer)AvaloniaLocator.Current.GetRequiredService<IRenderTimer>(); |
||||
|
_renderTimer.RenderTick += RenderOnce; |
||||
|
|
||||
|
OnResized(); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region Resize and layout
|
||||
|
|
||||
|
private class CustomLayout : AbsoluteLayout |
||||
|
{ |
||||
|
float _width; |
||||
|
float _height; |
||||
|
|
||||
|
public Action? SizeUpdated { get; set; } |
||||
|
|
||||
|
protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) |
||||
|
{ |
||||
|
var sizeChanged = _width != Owner.SizeWidth || _height != Owner.SizeHeight; |
||||
|
_width = Owner.SizeWidth; |
||||
|
_height = Owner.SizeHeight; |
||||
|
if (sizeChanged) |
||||
|
{ |
||||
|
SizeUpdated?.Invoke(); |
||||
|
} |
||||
|
base.OnLayout(changed, left, top, right, bottom); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void OnResized() |
||||
|
{ |
||||
|
if (Size.Width == 0 || Size.Height == 0) |
||||
|
return; |
||||
|
|
||||
|
_topLevelImpl?.Resized?.Invoke(_topLevelImpl.ClientSize, WindowResizeReason.Layout); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
#region Event handlers
|
||||
|
|
||||
|
private bool OnTouchEvent(object source, TouchEventArgs e) |
||||
|
{ |
||||
|
_touchHandler?.Handle(e); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private bool OnWheelEvent(object source, WheelEventArgs e) |
||||
|
{ |
||||
|
_touchHandler?.Handle(e); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void SetClient(TextInputMethodClient? client) |
||||
|
{ |
||||
|
_textEditor.SetClient(client); |
||||
|
} |
||||
|
|
||||
|
public void SetCursorRect(Rect rect) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void SetOptions(TextInputOptions options) => |
||||
|
_textEditor.SetOptions(options); |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (disposing) |
||||
|
{ |
||||
|
_topLevel.StopRendering(); |
||||
|
_topLevel.Dispose(); |
||||
|
_topLevelImpl.Dispose(); |
||||
|
_device.Dispose(); |
||||
|
} |
||||
|
base.Dispose(disposing); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,205 @@ |
|||||
|
using Avalonia.Input.TextInput; |
||||
|
using Tizen.NUI; |
||||
|
using Tizen.NUI.BaseComponents; |
||||
|
using Window = Tizen.NUI.Window; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
internal class NuiAvaloniaViewTextEditable |
||||
|
{ |
||||
|
private readonly NuiAvaloniaView _avaloniaView; |
||||
|
|
||||
|
private INuiTextInput TextInput => _multiline ? _multiLineTextInput : _singleLineTextInput; |
||||
|
private readonly NuiSingleLineTextInput _singleLineTextInput; |
||||
|
private readonly NuiMultiLineTextInput _multiLineTextInput; |
||||
|
private bool _updating; |
||||
|
private bool _keyboardPresented; |
||||
|
private bool _multiline; |
||||
|
|
||||
|
private TextInputMethodClient? _client; |
||||
|
|
||||
|
public bool IsActive => _client != null && _keyboardPresented; |
||||
|
|
||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
public NuiAvaloniaViewTextEditable(NuiAvaloniaView avaloniaView) |
||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
{ |
||||
|
_avaloniaView = avaloniaView; |
||||
|
_singleLineTextInput = new NuiSingleLineTextInput |
||||
|
{ |
||||
|
HeightResizePolicy = ResizePolicyType.Fixed, |
||||
|
WidthResizePolicy = ResizePolicyType.Fixed, |
||||
|
Size = new(1, 1), |
||||
|
Position = new Position(-1000, -1000), |
||||
|
FontSizeScale = 0.1f, |
||||
|
}; |
||||
|
|
||||
|
_multiLineTextInput = new NuiMultiLineTextInput |
||||
|
{ |
||||
|
HeightResizePolicy = ResizePolicyType.Fixed, |
||||
|
WidthResizePolicy = ResizePolicyType.Fixed, |
||||
|
Size = new(1, 1), |
||||
|
Position = new Position(-1000, -1000), |
||||
|
FontSizeScale = 0.1f, |
||||
|
}; |
||||
|
|
||||
|
SetupTextInput(_singleLineTextInput); |
||||
|
SetupTextInput(_multiLineTextInput); |
||||
|
} |
||||
|
|
||||
|
private void SetupTextInput(INuiTextInput input) |
||||
|
{ |
||||
|
input.Hide(); |
||||
|
|
||||
|
input.GetInputMethodContext().StatusChanged += OnStatusChanged; |
||||
|
input.GetInputMethodContext().EventReceived += OnEventReceived; |
||||
|
} |
||||
|
|
||||
|
private InputMethodContext.CallbackData OnEventReceived(object source, InputMethodContext.EventReceivedEventArgs e) |
||||
|
{ |
||||
|
switch (e.EventData.EventName) |
||||
|
{ |
||||
|
case InputMethodContext.EventType.Preedit: |
||||
|
_client?.SetPreeditText(e.EventData.PredictiveString); |
||||
|
break; |
||||
|
case InputMethodContext.EventType.Commit: |
||||
|
_client?.SetPreeditText(null); |
||||
|
_avaloniaView.TopLevelImpl.TextInput(e.EventData.PredictiveString); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return new InputMethodContext.CallbackData(); |
||||
|
} |
||||
|
|
||||
|
private void OnStatusChanged(object? sender, InputMethodContext.StatusChangedEventArgs e) |
||||
|
{ |
||||
|
_keyboardPresented = e.StatusChanged; |
||||
|
if (!_keyboardPresented) |
||||
|
DettachAndHide(); |
||||
|
} |
||||
|
|
||||
|
internal void SetClient(TextInputMethodClient? client) |
||||
|
{ |
||||
|
if (client == null || !_keyboardPresented) |
||||
|
DettachAndHide(); |
||||
|
|
||||
|
if (client != null) |
||||
|
AttachAndShow(client); |
||||
|
} |
||||
|
|
||||
|
internal void SetOptions(TextInputOptions options) |
||||
|
{ |
||||
|
//TODO: This should be revert when Avalonia used Multiline property
|
||||
|
_multiline = true; |
||||
|
//if (_multiline != options.Multiline)
|
||||
|
//{
|
||||
|
// DettachAndHide();
|
||||
|
// _multiline = options.Multiline;
|
||||
|
//}
|
||||
|
|
||||
|
TextInput.Sensitive = options.IsSensitive; |
||||
|
} |
||||
|
|
||||
|
private void AttachAndShow(TextInputMethodClient client) |
||||
|
{ |
||||
|
_updating = true; |
||||
|
try |
||||
|
{ |
||||
|
TextInput.Text = client.SurroundingText; |
||||
|
TextInput.PrimaryCursorPosition = client.Selection.Start; |
||||
|
Window.Instance.GetDefaultLayer().Add((View)TextInput); |
||||
|
TextInput.Show(); |
||||
|
TextInput.EnableSelection = true; |
||||
|
|
||||
|
var inputContext = TextInput.GetInputMethodContext(); |
||||
|
inputContext.Activate(); |
||||
|
inputContext.ShowInputPanel(); |
||||
|
inputContext.RestoreAfterFocusLost(); |
||||
|
|
||||
|
_client = client; |
||||
|
client.TextViewVisualChanged += OnTextViewVisualChanged; |
||||
|
client.SurroundingTextChanged += OnSurroundingTextChanged; |
||||
|
client.SelectionChanged += OnClientSelectionChanged; |
||||
|
|
||||
|
TextInput.SelectWholeText(); |
||||
|
OnClientSelectionChanged(this, EventArgs.Empty); |
||||
|
} |
||||
|
finally { _updating = false; } |
||||
|
} |
||||
|
|
||||
|
private void OnClientSelectionChanged(object? sender, EventArgs e) => InvokeUpdate(client => |
||||
|
{ |
||||
|
if (client.Selection.End == 0 || client.Selection.Start == client.Selection.End) |
||||
|
TextInput.PrimaryCursorPosition = client.Selection.Start; |
||||
|
else |
||||
|
TextInput.SelectText(client.Selection.Start, client.Selection.End); |
||||
|
}); |
||||
|
|
||||
|
private void OnSurroundingTextChanged(object? sender, EventArgs e) => InvokeUpdate(client => |
||||
|
{ |
||||
|
TextInput.Text = client.SurroundingText; |
||||
|
TextInput.GetInputMethodContext().SetSurroundingText(client.SurroundingText); |
||||
|
OnClientSelectionChanged(sender, e); |
||||
|
}); |
||||
|
|
||||
|
private void OnTextViewVisualChanged(object? sender, EventArgs e) => InvokeUpdate(client => |
||||
|
{ |
||||
|
TextInput.Text = client.SurroundingText; |
||||
|
}); |
||||
|
|
||||
|
private void DettachAndHide() |
||||
|
{ |
||||
|
if (IsActive) |
||||
|
{ |
||||
|
_client!.TextViewVisualChanged -= OnTextViewVisualChanged; |
||||
|
_client!.SurroundingTextChanged -= OnSurroundingTextChanged; |
||||
|
_client!.SelectionChanged -= OnClientSelectionChanged; |
||||
|
} |
||||
|
|
||||
|
if (Window.Instance.GetDefaultLayer().Children.Contains((View)TextInput)) |
||||
|
Window.Instance.GetDefaultLayer().Remove((View)TextInput); |
||||
|
|
||||
|
TextInput.Hide(); |
||||
|
|
||||
|
var inputContext = TextInput.GetInputMethodContext(); |
||||
|
inputContext.Deactivate(); |
||||
|
inputContext.HideInputPanel(); |
||||
|
} |
||||
|
|
||||
|
private void InvokeUpdate(Action<TextInputMethodClient> action) |
||||
|
{ |
||||
|
if (_updating || !IsActive) |
||||
|
return; |
||||
|
|
||||
|
_updating = true; |
||||
|
try |
||||
|
{ |
||||
|
action(_client!); |
||||
|
} |
||||
|
finally { _updating = false; } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal interface INuiTextInput |
||||
|
{ |
||||
|
string Text { get; set; } |
||||
|
int PrimaryCursorPosition { get; set; } |
||||
|
bool EnableSelection { get; set; } |
||||
|
bool Sensitive { get; set; } |
||||
|
int SelectedTextStart { get; } |
||||
|
int SelectedTextEnd { get; } |
||||
|
|
||||
|
void Show(); |
||||
|
InputMethodContext GetInputMethodContext(); |
||||
|
void Hide(); |
||||
|
void SelectText(int selectedTextStart, int value); |
||||
|
void SelectWholeText(); |
||||
|
} |
||||
|
|
||||
|
public class NuiMultiLineTextInput : TextEditor, INuiTextInput |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public class NuiSingleLineTextInput : TextField, INuiTextInput |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Platform; |
||||
|
using Avalonia.Threading; |
||||
|
using Tizen.NUI; |
||||
|
using Tizen.NUI.BaseComponents; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
internal class NuiClipboardImpl : IClipboard |
||||
|
{ |
||||
|
private TextEditor _textEditor; |
||||
|
public NuiClipboardImpl() |
||||
|
{ |
||||
|
_textEditor = new TextEditor() |
||||
|
{ |
||||
|
HeightResizePolicy = ResizePolicyType.Fixed, |
||||
|
WidthResizePolicy = ResizePolicyType.Fixed, |
||||
|
Position = new Position(-1000, -1000), |
||||
|
Size = new(1, 1) |
||||
|
}; |
||||
|
|
||||
|
Window.Instance.GetDefaultLayer().Add(_textEditor); |
||||
|
_textEditor.LowerToBottom(); |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() => |
||||
|
SetTextAsync(""); |
||||
|
|
||||
|
public Task<string?> GetTextAsync() |
||||
|
{ |
||||
|
_textEditor.Show(); |
||||
|
_textEditor.Text = ""; |
||||
|
|
||||
|
//The solution suggested by Samsung, The method PasteTo will execute async and need delay
|
||||
|
TextUtils.PasteTo(_textEditor); |
||||
|
|
||||
|
return Task.Run<string?>(async () => |
||||
|
{ |
||||
|
await Task.Delay(10); |
||||
|
|
||||
|
return await Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
_textEditor.Hide(); |
||||
|
return _textEditor.Text; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public Task SetTextAsync(string? text) |
||||
|
{ |
||||
|
_textEditor.Show(); |
||||
|
_textEditor.Text = text; |
||||
|
|
||||
|
//The solution suggested by Samsung, The method SelectWholeText will execute async and need delay
|
||||
|
_textEditor.SelectWholeText(); |
||||
|
|
||||
|
return Task.Run(async () => |
||||
|
{ |
||||
|
await Task.Delay(10); |
||||
|
await Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
TextUtils.CopyToClipboard(_textEditor); |
||||
|
_textEditor.Hide(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public Task<object?> GetDataAsync(string format) => |
||||
|
throw new PlatformNotSupportedException(); |
||||
|
|
||||
|
public Task SetDataObjectAsync(IDataObject data) => |
||||
|
throw new PlatformNotSupportedException(); |
||||
|
|
||||
|
public Task<string[]> GetFormatsAsync() => |
||||
|
throw new PlatformNotSupportedException(); |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.OpenGL.Surfaces; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class NuiGlLayerSurface : IGlPlatformSurface |
||||
|
{ |
||||
|
private readonly NuiAvaloniaView _nuiAvaloniaView; |
||||
|
|
||||
|
public NuiGlLayerSurface(NuiAvaloniaView nuiAvaloniaView) |
||||
|
{ |
||||
|
_nuiAvaloniaView = nuiAvaloniaView; |
||||
|
} |
||||
|
|
||||
|
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) |
||||
|
{ |
||||
|
var ctx = TizenPlatform.GlPlatform.Context; |
||||
|
if (ctx != context) |
||||
|
throw new InvalidOperationException("Platform surface is only usable with tha main context"); |
||||
|
using (ctx.MakeCurrent()) |
||||
|
{ |
||||
|
return new RenderTarget(ctx, _nuiAvaloniaView); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class RenderTarget : IGlPlatformSurfaceRenderTarget |
||||
|
{ |
||||
|
private readonly GlContext _ctx; |
||||
|
private readonly NuiAvaloniaView _nuiAvaloniaView; |
||||
|
|
||||
|
public RenderTarget(GlContext ctx, NuiAvaloniaView nuiAvaloniaView) |
||||
|
{ |
||||
|
_ctx = ctx; |
||||
|
_nuiAvaloniaView = nuiAvaloniaView; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public IGlPlatformSurfaceRenderingSession BeginDraw() |
||||
|
{ |
||||
|
var restoreContext = _ctx.MakeCurrent(); |
||||
|
return new RenderSession(_ctx, restoreContext, _nuiAvaloniaView); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class RenderSession : IGlPlatformSurfaceRenderingSession |
||||
|
{ |
||||
|
private readonly GlContext _ctx; |
||||
|
private readonly IDisposable _restoreContext; |
||||
|
|
||||
|
public RenderSession(GlContext ctx, IDisposable restoreContext, NuiAvaloniaView nuiAvaloniaView) |
||||
|
{ |
||||
|
_ctx = ctx; |
||||
|
_restoreContext = restoreContext; |
||||
|
Size = new PixelSize((int)nuiAvaloniaView.Size.Width, (int)nuiAvaloniaView.Size.Height); |
||||
|
Scaling = nuiAvaloniaView.Scaling; |
||||
|
Context = ctx; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_ctx.GlInterface.Finish(); |
||||
|
_restoreContext.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public IGlContext Context { get; } |
||||
|
public PixelSize Size { get; } |
||||
|
public double Scaling { get; } |
||||
|
public bool IsYFlipped { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
using Avalonia.Compatibility; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class NuiGlPlatform : IPlatformGraphics |
||||
|
{ |
||||
|
|
||||
|
public IPlatformGraphicsContext GetSharedContext() => Context; |
||||
|
|
||||
|
public bool UsesSharedContext => true; |
||||
|
public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException(); |
||||
|
public GlContext Context { get; } |
||||
|
public static GlVersion GlVersion { get; } = new(GlProfileType.OpenGLES, 3, 0); |
||||
|
|
||||
|
public NuiGlPlatform() |
||||
|
{ |
||||
|
const string library = "/usr/lib/driver/libGLESv2.so"; |
||||
|
var libGl = NativeLibraryEx.Load(library); |
||||
|
if (libGl == IntPtr.Zero) |
||||
|
throw new OpenGlException("Unable to load " + library); |
||||
|
var iface = new GlInterface(GlVersion, proc => |
||||
|
{ |
||||
|
if (NativeLibraryEx.TryGetExport(libGl, proc, out var address)) |
||||
|
return address; |
||||
|
return default; |
||||
|
}); |
||||
|
Context = new(iface); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class GlContext : IGlContext |
||||
|
{ |
||||
|
public GlContext(GlInterface glInterface) |
||||
|
{ |
||||
|
GlInterface = glInterface; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public IDisposable MakeCurrent() |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public bool IsLost => false; |
||||
|
|
||||
|
public IDisposable EnsureCurrent() |
||||
|
{ |
||||
|
return MakeCurrent(); |
||||
|
} |
||||
|
|
||||
|
public bool IsSharedWith(IGlContext context) => true; |
||||
|
public bool CanCreateSharedContext => true; |
||||
|
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public GlVersion Version => new GlVersion(GlProfileType.OpenGLES, 3, 0); |
||||
|
public GlInterface GlInterface { get; } |
||||
|
public int SampleCount |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
GlInterface.GetIntegerv(GlConsts.GL_SAMPLES, out var samples); |
||||
|
return samples; |
||||
|
} |
||||
|
} |
||||
|
public int StencilSize |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
GlInterface.GetIntegerv(GlConsts.GL_STENCIL_BITS, out var stencil); |
||||
|
return stencil; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public object TryGetFeature(Type featureType) => null; |
||||
|
|
||||
|
public IntPtr GetProcAddress(string procName) |
||||
|
{ |
||||
|
return GlInterface.GetProcAddress(procName); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Logging; |
||||
|
using Avalonia.Tizen.Platform.Input; |
||||
|
using Tizen.NUI; |
||||
|
using Key = Tizen.NUI.Key; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class NuiKeyboardHandler |
||||
|
{ |
||||
|
private const string LogKey = "TIZENHKEY"; |
||||
|
|
||||
|
private readonly NuiAvaloniaView _view; |
||||
|
|
||||
|
public NuiKeyboardHandler(NuiAvaloniaView view) |
||||
|
{ |
||||
|
_view = view; |
||||
|
} |
||||
|
|
||||
|
public void Handle(Window.KeyEventArgs e) |
||||
|
{ |
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Key fired {text}", e.Key.KeyPressedName); |
||||
|
|
||||
|
if (_view.TextEditor.IsActive) |
||||
|
return; |
||||
|
|
||||
|
if (ShouldSendKeyEvent(e, out var keyCode)) |
||||
|
{ |
||||
|
var mapped = TizenKeyboardDevice.ConvertKey(keyCode); |
||||
|
if (mapped == Input.Key.None) |
||||
|
return; |
||||
|
|
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Triggering key event {text}", e.Key.KeyString); |
||||
|
SendKeyEvent(e, mapped); |
||||
|
} |
||||
|
else if (e.Key.State == Key.StateType.Up) |
||||
|
{ |
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Triggering text input {text}", e.Key.KeyString); |
||||
|
_view.TopLevelImpl.TextInput(e.Key.KeyString); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void SendKeyEvent(Window.KeyEventArgs e, Input.Key mapped) |
||||
|
{ |
||||
|
var type = GetKeyEventType(e); |
||||
|
var modifiers = GetModifierKey(e); |
||||
|
|
||||
|
_view.TopLevelImpl.Input?.Invoke( |
||||
|
new RawKeyEventArgs( |
||||
|
KeyboardDevice.Instance!, |
||||
|
e.Key.Time, |
||||
|
_view.InputRoot, |
||||
|
type, |
||||
|
mapped, |
||||
|
modifiers)); |
||||
|
} |
||||
|
|
||||
|
private bool ShouldSendKeyEvent(Window.KeyEventArgs e, out global::Tizen.Uix.InputMethod.KeyCode keyCode) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(e.Key.KeyString) || |
||||
|
e.Key.KeyPressedName is "Delete" or "BackSpace" || |
||||
|
e.Key.IsCtrlModifier() || |
||||
|
e.Key.IsAltModifier()) |
||||
|
{ |
||||
|
if (Enum.TryParse(e.Key.KeyPressedName, true, out keyCode) || |
||||
|
Enum.TryParse($"Keypad{e.Key.KeyPressedName}", true, out keyCode)) |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
keyCode = 0; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private RawKeyEventType GetKeyEventType(Window.KeyEventArgs ev) |
||||
|
{ |
||||
|
switch (ev.Key.State) |
||||
|
{ |
||||
|
case Key.StateType.Down: |
||||
|
return RawKeyEventType.KeyDown; |
||||
|
case Key.StateType.Up: |
||||
|
return RawKeyEventType.KeyUp; |
||||
|
default: |
||||
|
throw new ArgumentOutOfRangeException(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private RawInputModifiers GetModifierKey(Window.KeyEventArgs ev) |
||||
|
{ |
||||
|
var modifiers = RawInputModifiers.None; |
||||
|
|
||||
|
if (ev.Key.IsShiftModifier()) |
||||
|
modifiers |= RawInputModifiers.Shift; |
||||
|
|
||||
|
if (ev.Key.IsAltModifier()) |
||||
|
modifiers |= RawInputModifiers.Alt; |
||||
|
|
||||
|
if (ev.Key.IsCtrlModifier()) |
||||
|
modifiers |= RawInputModifiers.Control; |
||||
|
|
||||
|
return modifiers; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Platform; |
||||
|
using Tizen.NUI.BaseComponents; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
internal class NuiNativeControlHostImpl : INativeControlHostImpl |
||||
|
{ |
||||
|
private readonly NuiAvaloniaView _avaloniaView; |
||||
|
|
||||
|
public NuiNativeControlHostImpl(NuiAvaloniaView avaloniaView) |
||||
|
{ |
||||
|
_avaloniaView = avaloniaView; |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) => |
||||
|
new NuiViewControlHandle(new View()); |
||||
|
|
||||
|
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create) |
||||
|
{ |
||||
|
var parent = new NuiViewControlHandle(_avaloniaView); |
||||
|
NativeControlAttachment? attachment = null; |
||||
|
try |
||||
|
{ |
||||
|
var child = create(parent); |
||||
|
// It has to be assigned to the variable before property setter is called so we dispose it on exception
|
||||
|
#pragma warning disable IDE0017 // Simplify object initialization
|
||||
|
attachment = new NativeControlAttachment(child); |
||||
|
#pragma warning restore IDE0017 // Simplify object initialization
|
||||
|
attachment.AttachedTo = this; |
||||
|
return attachment; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
attachment?.Dispose(); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) => |
||||
|
new NativeControlAttachment(handle); |
||||
|
|
||||
|
public bool IsCompatibleWith(IPlatformHandle handle) => |
||||
|
handle.HandleDescriptor == NuiViewControlHandle.ViewDescriptor; |
||||
|
|
||||
|
private class NativeControlAttachment : INativeControlHostControlTopLevelAttachment |
||||
|
{ |
||||
|
private IPlatformHandle? _child; |
||||
|
private View? _view; |
||||
|
private NuiNativeControlHostImpl? _attachedTo; |
||||
|
|
||||
|
public NativeControlAttachment(IPlatformHandle child) |
||||
|
{ |
||||
|
_child = child; |
||||
|
_view = (child as NuiViewControlHandle)?.View; |
||||
|
} |
||||
|
|
||||
|
[MemberNotNull(nameof(_view))] |
||||
|
private void CheckDisposed() |
||||
|
{ |
||||
|
if (_view == null) |
||||
|
throw new ObjectDisposedException(nameof(NativeControlAttachment)); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_view?.Unparent(); |
||||
|
_child = null; |
||||
|
_attachedTo = null; |
||||
|
_view?.Dispose(); |
||||
|
_view = null; |
||||
|
} |
||||
|
|
||||
|
public INativeControlHostImpl? AttachedTo |
||||
|
{ |
||||
|
get => _attachedTo; |
||||
|
set |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
|
||||
|
_attachedTo = (NuiNativeControlHostImpl?)value; |
||||
|
if (_attachedTo == null) |
||||
|
{ |
||||
|
_view.Unparent(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_attachedTo._avaloniaView.Add(_view); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool IsCompatibleWith(INativeControlHostImpl host) => host is NuiNativeControlHostImpl; |
||||
|
|
||||
|
public void HideWithSize(Size size) |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
if (_attachedTo == null) |
||||
|
return; |
||||
|
|
||||
|
_view.Hide(); |
||||
|
_view.Size = new global::Tizen.NUI.Size(MathF.Max(1f, (float)size.Width), Math.Max(1f, (float)size.Height)); |
||||
|
} |
||||
|
|
||||
|
public void ShowInBounds(Rect bounds) |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
if (_attachedTo == null) |
||||
|
throw new InvalidOperationException("The control isn't currently attached to a toplevel"); |
||||
|
|
||||
|
_view.Size = new global::Tizen.NUI.Size(MathF.Max(1f, (float)bounds.Width), Math.Max(1f, (float)bounds.Height)); |
||||
|
_view.Position = new global::Tizen.NUI.Position((float)bounds.X, (float)bounds.Y); |
||||
|
_view.Show(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
using Tizen.NUI; |
||||
|
using Window = Tizen.NUI.Window; |
||||
|
using Avalonia.Logging; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
public class NuiTizenApplication<TApp> : NUIApplication |
||||
|
where TApp : Application, new() |
||||
|
{ |
||||
|
private const string LogKey = "TIZENAPP"; |
||||
|
|
||||
|
private SingleViewLifetime? _lifetime; |
||||
|
|
||||
|
private class SingleViewLifetime : ISingleViewApplicationLifetime |
||||
|
{ |
||||
|
public NuiAvaloniaView View { get; } |
||||
|
|
||||
|
public SingleViewLifetime(NuiAvaloniaView view) |
||||
|
{ |
||||
|
View = view; |
||||
|
} |
||||
|
|
||||
|
public Control MainView |
||||
|
{ |
||||
|
get => View.Content; |
||||
|
set => View.Content = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder; |
||||
|
|
||||
|
protected override void OnCreate() |
||||
|
{ |
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Creating application"); |
||||
|
|
||||
|
base.OnCreate(); |
||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
|
TizenThreadingInterface.MainloopContext = SynchronizationContext.Current; |
||||
|
#pragma warning restore CS8601 // Possible null reference assignment.
|
||||
|
|
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup view"); |
||||
|
_lifetime = new SingleViewLifetime(new NuiAvaloniaView()); |
||||
|
|
||||
|
_lifetime.View.HeightResizePolicy = ResizePolicyType.FillToParent; |
||||
|
_lifetime.View.WidthResizePolicy = ResizePolicyType.FillToParent; |
||||
|
_lifetime.View.OnSurfaceInit += ContinueSetupApplication; |
||||
|
|
||||
|
Window.Instance.RenderingBehavior = RenderingBehaviorType.Continuously; |
||||
|
Window.Instance.GetDefaultLayer().Add(_lifetime.View); |
||||
|
Window.Instance.KeyEvent += (s, e) => _lifetime?.View?.KeyboardHandler.Handle(e); |
||||
|
} |
||||
|
|
||||
|
private void ContinueSetupApplication() |
||||
|
{ |
||||
|
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); |
||||
|
|
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "App builder"); |
||||
|
var builder = AppBuilder.Configure<TApp>().UseTizen(); |
||||
|
|
||||
|
TizenThreadingInterface.MainloopContext.Post(_ => |
||||
|
{ |
||||
|
CustomizeAppBuilder(builder); |
||||
|
|
||||
|
builder.AfterSetup(_ => _lifetime.View.Initialise()); |
||||
|
|
||||
|
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup lifetime"); |
||||
|
builder.SetupWithLifetime(_lifetime); |
||||
|
}, null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Tizen.NUI; |
||||
|
using static Tizen.NUI.BaseComponents.View; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class NuiTouchHandler |
||||
|
{ |
||||
|
private readonly NuiAvaloniaView _view; |
||||
|
public TouchDevice _device = new TouchDevice(); |
||||
|
|
||||
|
public NuiTouchHandler(NuiAvaloniaView view) |
||||
|
{ |
||||
|
_view = view; |
||||
|
} |
||||
|
|
||||
|
private IInputRoot InputRoot => _view.InputRoot; |
||||
|
private static uint _nextTouchPointId = 1; |
||||
|
private List<uint> _knownTouches = new(); |
||||
|
|
||||
|
public void Handle(TouchEventArgs e) |
||||
|
{ |
||||
|
var count = e.Touch.GetPointCount(); |
||||
|
for (var i = 0u; i < count; i++) |
||||
|
{ |
||||
|
uint id; |
||||
|
if (_knownTouches.Count > i) |
||||
|
{ |
||||
|
id = _knownTouches[(int)i]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
unchecked |
||||
|
{ |
||||
|
id = _nextTouchPointId++; |
||||
|
} |
||||
|
_knownTouches.Add(id); |
||||
|
} |
||||
|
|
||||
|
var point = e.Touch.GetLocalPosition(i); |
||||
|
var state = e.Touch.GetState(i); |
||||
|
var timestamp = e.Touch.GetTime(); |
||||
|
var avaloniaState = state switch |
||||
|
{ |
||||
|
PointStateType.Down => RawPointerEventType.TouchBegin, |
||||
|
PointStateType.Up => RawPointerEventType.TouchEnd, |
||||
|
PointStateType.Motion => RawPointerEventType.TouchUpdate, |
||||
|
PointStateType.Interrupted => RawPointerEventType.TouchCancel, |
||||
|
_ => RawPointerEventType.TouchUpdate |
||||
|
}; |
||||
|
|
||||
|
var touchEvent = new RawTouchEventArgs( |
||||
|
_device, |
||||
|
timestamp, |
||||
|
InputRoot, |
||||
|
avaloniaState, |
||||
|
new Point(point.X, point.Y), |
||||
|
RawInputModifiers.None, |
||||
|
id); |
||||
|
_view.TopLevelImpl.Input?.Invoke(touchEvent); |
||||
|
|
||||
|
if (state is PointStateType.Up or PointStateType.Interrupted) |
||||
|
{ |
||||
|
_knownTouches.Remove(id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Handle(WheelEventArgs e) |
||||
|
{ |
||||
|
var mouseWheelEvent = new RawMouseWheelEventArgs( |
||||
|
_device, |
||||
|
e.Wheel.TimeStamp, |
||||
|
InputRoot, |
||||
|
new Point(e.Wheel.Point.X, e.Wheel.Point.Y), |
||||
|
new Vector( |
||||
|
e.Wheel.Direction == 1 ? e.Wheel.Z : 0, |
||||
|
e.Wheel.Direction == 0 ? e.Wheel.Z : 0), |
||||
|
GetModifierKey(e)); |
||||
|
|
||||
|
_view.TopLevelImpl.Input?.Invoke(mouseWheelEvent); |
||||
|
} |
||||
|
|
||||
|
private RawInputModifiers GetModifierKey(WheelEventArgs ev) |
||||
|
{ |
||||
|
var modifiers = RawInputModifiers.None; |
||||
|
|
||||
|
if (ev.Wheel.IsShiftModifier()) |
||||
|
modifiers |= RawInputModifiers.Shift; |
||||
|
|
||||
|
if (ev.Wheel.IsAltModifier()) |
||||
|
modifiers |= RawInputModifiers.Alt; |
||||
|
|
||||
|
if (ev.Wheel.IsCtrlModifier()) |
||||
|
modifiers |= RawInputModifiers.Control; |
||||
|
|
||||
|
return modifiers; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Avalonia.Controls.Platform; |
||||
|
using Tizen.NUI.BaseComponents; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tizen Nui native view handle for native view attachment
|
||||
|
/// </summary>
|
||||
|
public class NuiViewControlHandle : INativeControlHostDestroyableControlHandle |
||||
|
{ |
||||
|
internal const string ViewDescriptor = "NuiView"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create handle with native view
|
||||
|
/// </summary>
|
||||
|
/// <param name="view">NUI Tizen native view to attach</param>
|
||||
|
public NuiViewControlHandle(View view) |
||||
|
{ |
||||
|
View = view; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// NUI Tizen View
|
||||
|
/// </summary>
|
||||
|
public View View { get; set; } |
||||
|
/// <summary>
|
||||
|
/// NUI Tizen not supporting handle
|
||||
|
/// </summary>
|
||||
|
/// <exception cref="NotSupportedException"></exception>
|
||||
|
public IntPtr Handle => throw new NotSupportedException(); |
||||
|
/// <summary>
|
||||
|
/// Return `ViewDescriptor` all the time
|
||||
|
/// </summary>
|
||||
|
public string? HandleDescriptor => ViewDescriptor; |
||||
|
/// <summary>
|
||||
|
/// Dispose Tizen View when it call
|
||||
|
/// </summary>
|
||||
|
public void Destroy() => View.Dispose(); |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
namespace Avalonia.Tizen.Platform; |
||||
|
internal static class Consts |
||||
|
{ |
||||
|
public const int DpiX = 96; |
||||
|
public const int DpiY = 96; |
||||
|
public static readonly Vector Dpi = new Vector(DpiX, DpiY); |
||||
|
|
||||
|
public const string VertexShader = |
||||
|
"attribute mediump vec2 aPosition;\n" + |
||||
|
"varying mediump vec2 vTexCoord;\n" + |
||||
|
"uniform highp mat4 uMvpMatrix;\n" + |
||||
|
"uniform mediump vec3 uSize;\n" + |
||||
|
"varying mediump vec2 sTexCoordRect;\n" + |
||||
|
"void main()\n" + |
||||
|
"{\n" + |
||||
|
" gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);\n" + |
||||
|
" vTexCoord = aPosition + vec2(0.5);\n" + |
||||
|
"}\n"; |
||||
|
|
||||
|
public const string FragmentShader = |
||||
|
"#extension GL_OES_EGL_image_external:require\n" + |
||||
|
"uniform lowp vec4 uColor;\n" + |
||||
|
"varying mediump vec2 vTexCoord;\n" + |
||||
|
"uniform samplerExternalOES sTexture;\n" + |
||||
|
"void main()\n" + |
||||
|
"{\n" + |
||||
|
" gl_FragColor = texture2D(sTexture, vTexCoord) * uColor;\n" + |
||||
|
"}\n"; |
||||
|
} |
||||
@ -0,0 +1,221 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Input; |
||||
|
using Tizen.Uix.InputMethod; |
||||
|
|
||||
|
namespace Avalonia.Tizen.Platform.Input; |
||||
|
internal class TizenKeyboardDevice: KeyboardDevice, IKeyboardDevice |
||||
|
{ |
||||
|
private static readonly Dictionary<KeyCode, Key> KeyDic = new Dictionary<KeyCode, Key> |
||||
|
{ |
||||
|
// { KeyCode.Cancel?, Key.Cancel },
|
||||
|
{ KeyCode.BackSpace, Key.Back }, |
||||
|
{ KeyCode.Tab, Key.Tab }, |
||||
|
// { KeyCode.Linefeed?, Key.LineFeed },
|
||||
|
{ KeyCode.Clear, Key.Clear }, |
||||
|
{ KeyCode.Return, Key.Return }, |
||||
|
{ KeyCode.Pause, Key.Pause }, |
||||
|
{ KeyCode.CapsLock, Key.CapsLock }, |
||||
|
//{ KeyCode.?, Key.HangulMode }
|
||||
|
//{ KeyCode.?, Key.JunjaMode }
|
||||
|
//{ KeyCode.?, Key.FinalMode }
|
||||
|
//{ KeyCode.?, Key.KanjiMode }
|
||||
|
{ KeyCode.Escape, Key.Escape }, |
||||
|
//{ KeyCode.?, Key.ImeConvert }
|
||||
|
//{ KeyCode.?, Key.ImeNonConvert }
|
||||
|
//{ KeyCode.?, Key.ImeAccept }
|
||||
|
//{ KeyCode.?, Key.ImeModeChange }
|
||||
|
{ KeyCode.Space, Key.Space }, |
||||
|
{ KeyCode.Page_Up, Key.Prior }, |
||||
|
{ KeyCode.Page_Down, Key.PageDown }, |
||||
|
{ KeyCode.End, Key.End }, |
||||
|
{ KeyCode.Home, Key.Home }, |
||||
|
{ KeyCode.Left, Key.Left }, |
||||
|
{ KeyCode.Up, Key.Up }, |
||||
|
{ KeyCode.Right, Key.Right }, |
||||
|
{ KeyCode.Down, Key.Down }, |
||||
|
// { KeyCode.ButtonSelect?, Key.Select },
|
||||
|
// { KeyCode.print?, Key.Print },
|
||||
|
//{ KeyCode.execute?, Key.Execute },
|
||||
|
//{ KeyCode.snap?, Key.Snapshot }
|
||||
|
{ KeyCode.Insert, Key.Insert }, |
||||
|
{ KeyCode.Delete, Key.Delete }, |
||||
|
{ KeyCode.Help, Key.Help }, |
||||
|
{ KeyCode.Keypad0, Key.D0 }, |
||||
|
{ KeyCode.Keypad1, Key.D1 }, |
||||
|
{ KeyCode.Keypad2, Key.D2 }, |
||||
|
{ KeyCode.Keypad3, Key.D3 }, |
||||
|
{ KeyCode.Keypad4, Key.D4 }, |
||||
|
{ KeyCode.Keypad5, Key.D5 }, |
||||
|
{ KeyCode.Keypad6, Key.D6 }, |
||||
|
{ KeyCode.Keypad7, Key.D7 }, |
||||
|
{ KeyCode.Keypad8, Key.D8 }, |
||||
|
{ KeyCode.Keypad9, Key.D9 }, |
||||
|
{ KeyCode.KeypadA, Key.A }, |
||||
|
{ KeyCode.KeypadB, Key.B }, |
||||
|
{ KeyCode.KeypadC, Key.C }, |
||||
|
{ KeyCode.KeypadD, Key.D }, |
||||
|
{ KeyCode.KeypadE, Key.E }, |
||||
|
{ KeyCode.KeypadF, Key.F }, |
||||
|
{ KeyCode.KeypadG, Key.G }, |
||||
|
{ KeyCode.KeypadH, Key.H }, |
||||
|
{ KeyCode.KeypadI, Key.I }, |
||||
|
{ KeyCode.KeypadJ, Key.J }, |
||||
|
{ KeyCode.KeypadK, Key.K }, |
||||
|
{ KeyCode.KeypadL, Key.L }, |
||||
|
{ KeyCode.KeypadM, Key.M }, |
||||
|
{ KeyCode.KeypadN, Key.N }, |
||||
|
{ KeyCode.KeypadO, Key.O }, |
||||
|
{ KeyCode.KeypadP, Key.P }, |
||||
|
{ KeyCode.KeypadQ, Key.Q }, |
||||
|
{ KeyCode.KeypadR, Key.R }, |
||||
|
{ KeyCode.KeypadS, Key.S }, |
||||
|
{ KeyCode.KeypadT, Key.T }, |
||||
|
{ KeyCode.KeypadU, Key.U }, |
||||
|
{ KeyCode.KeypadV, Key.V }, |
||||
|
{ KeyCode.KeypadW, Key.W }, |
||||
|
{ KeyCode.KeypadX, Key.X }, |
||||
|
{ KeyCode.KeypadY, Key.Y }, |
||||
|
{ KeyCode.KeypadZ, Key.Z }, |
||||
|
{ KeyCode.Keypada, Key.A }, |
||||
|
{ KeyCode.Keypadb, Key.B }, |
||||
|
{ KeyCode.Keypadc, Key.C }, |
||||
|
{ KeyCode.Keypadd, Key.D }, |
||||
|
{ KeyCode.Keypade, Key.E }, |
||||
|
{ KeyCode.Keypadf, Key.F }, |
||||
|
{ KeyCode.Keypadg, Key.G }, |
||||
|
{ KeyCode.Keypadh, Key.H }, |
||||
|
{ KeyCode.Keypadi, Key.I }, |
||||
|
{ KeyCode.Keypadj, Key.J }, |
||||
|
{ KeyCode.Keypadk, Key.K }, |
||||
|
{ KeyCode.Keypadl, Key.L }, |
||||
|
{ KeyCode.Keypadm, Key.M }, |
||||
|
{ KeyCode.Keypadn, Key.N }, |
||||
|
{ KeyCode.Keypado, Key.O }, |
||||
|
{ KeyCode.Keypadp, Key.P }, |
||||
|
{ KeyCode.Keypadq, Key.Q }, |
||||
|
{ KeyCode.Keypadr, Key.R }, |
||||
|
{ KeyCode.Keypads, Key.S }, |
||||
|
{ KeyCode.Keypadt, Key.T }, |
||||
|
{ KeyCode.Keypadu, Key.U }, |
||||
|
{ KeyCode.Keypadv, Key.V }, |
||||
|
{ KeyCode.Keypadw, Key.W }, |
||||
|
{ KeyCode.Keypadx, Key.X }, |
||||
|
{ KeyCode.Keypady, Key.Y }, |
||||
|
{ KeyCode.Keypadz, Key.Z }, |
||||
|
//{ KeyCode.?, Key.LWin }
|
||||
|
//{ KeyCode.?, Key.RWin }
|
||||
|
//{ KeyCode.?, Key.Apps }
|
||||
|
//{ KeyCode.Sle, Key.Sleep },
|
||||
|
{ KeyCode.KP0, Key.NumPad0 }, |
||||
|
{ KeyCode.KP1, Key.NumPad1 }, |
||||
|
{ KeyCode.KP2, Key.NumPad2 }, |
||||
|
{ KeyCode.KP3, Key.NumPad3 }, |
||||
|
{ KeyCode.KP4, Key.NumPad4 }, |
||||
|
{ KeyCode.KP5, Key.NumPad5 }, |
||||
|
{ KeyCode.KP6, Key.NumPad6 }, |
||||
|
{ KeyCode.KP7, Key.NumPad7 }, |
||||
|
{ KeyCode.KP8, Key.NumPad8 }, |
||||
|
{ KeyCode.KP9, Key.NumPad9 }, |
||||
|
{ KeyCode.KPMultiply, Key.Multiply }, |
||||
|
{ KeyCode.KPAdd, Key.Add }, |
||||
|
{ KeyCode.KPSeparator, Key.Separator }, |
||||
|
{ KeyCode.KPSubtract, Key.Subtract }, |
||||
|
{ KeyCode.KPDecimal, Key.Decimal }, |
||||
|
{ KeyCode.KPDivide, Key.Divide }, |
||||
|
{ KeyCode.F1, Key.F1 }, |
||||
|
{ KeyCode.F2, Key.F2 }, |
||||
|
{ KeyCode.F3, Key.F3 }, |
||||
|
{ KeyCode.F4, Key.F4 }, |
||||
|
{ KeyCode.F5, Key.F5 }, |
||||
|
{ KeyCode.F6, Key.F6 }, |
||||
|
{ KeyCode.F7, Key.F7 }, |
||||
|
{ KeyCode.F8, Key.F8 }, |
||||
|
{ KeyCode.F9, Key.F9 }, |
||||
|
{ KeyCode.F10, Key.F10 }, |
||||
|
{ KeyCode.F11, Key.F11 }, |
||||
|
{ KeyCode.F12, Key.F12 }, |
||||
|
//{ KeyCode.f13, Key.F13 },
|
||||
|
//{ KeyCode.F14, Key.F14 },
|
||||
|
//{ KeyCode.L5, Key.F15 },
|
||||
|
//{ KeyCode.F16, Key.F16 },
|
||||
|
//{ KeyCode.F17, Key.F17 },
|
||||
|
//{ KeyCode.L8, Key.F18 },
|
||||
|
//{ KeyCode.L9, Key.F19 },
|
||||
|
//{ KeyCode.L10, Key.F20 },
|
||||
|
//{ KeyCode.R1, Key.F21 },
|
||||
|
//{ KeyCode.R2, Key.F22 },
|
||||
|
//{ KeyCode.F23, Key.F23 },
|
||||
|
//{ KeyCode.R4, Key.F24 },
|
||||
|
{ KeyCode.Num_Lock, Key.NumLock }, |
||||
|
{ KeyCode.ScrollLock, Key.Scroll }, |
||||
|
{ KeyCode.ShiftL, Key.LeftShift }, |
||||
|
{ KeyCode.ShiftR, Key.RightShift }, |
||||
|
{ KeyCode.ControlL, Key.LeftCtrl }, |
||||
|
{ KeyCode.ControlR, Key.RightCtrl }, |
||||
|
{ KeyCode.AltL, Key.LeftAlt }, |
||||
|
{ KeyCode.AltR, Key.RightAlt }, |
||||
|
//{ KeyCode.?, Key.BrowserBack }
|
||||
|
//{ KeyCode.?, Key.BrowserForward }
|
||||
|
//{ KeyCode.?, Key.BrowserRefresh }
|
||||
|
//{ KeyCode.?, Key.BrowserStop }
|
||||
|
//{ KeyCode.?, Key.BrowserSearch }
|
||||
|
//{ KeyCode.?, Key.BrowserFavorites }
|
||||
|
//{ KeyCode.?, Key.BrowserHome }
|
||||
|
//{ KeyCode.?, Key.VolumeMute }
|
||||
|
//{ KeyCode.VolumeDown, Key.VolumeDown },
|
||||
|
//{ KeyCode.VolumeUp, Key.VolumeUp },
|
||||
|
//{ KeyCode.MediaNext, Key.MediaNextTrack },
|
||||
|
//{ KeyCode.MediaPrevious, Key.MediaPreviousTrack },
|
||||
|
//{ KeyCode.MediaStop, Key.MediaStop },
|
||||
|
//{ KeyCode.MediaPlayPause, Key.MediaPlayPause },
|
||||
|
//{ KeyCode.?, Key.LaunchMail }
|
||||
|
//{ KeyCode.?, Key.SelectMedia }
|
||||
|
//{ KeyCode.?, Key.LaunchApplication1 }
|
||||
|
//{ KeyCode.?, Key.LaunchApplication2 }
|
||||
|
{ KeyCode.Semicolon, Key.OemSemicolon }, |
||||
|
{ KeyCode.Plus, Key.OemPlus }, |
||||
|
{ KeyCode.Comma, Key.OemComma }, |
||||
|
{ KeyCode.Minus, Key.OemMinus }, |
||||
|
{ KeyCode.Period, Key.OemPeriod }, |
||||
|
//{ KeyCode.?, Key.Oem2 }
|
||||
|
{ KeyCode.Grave, Key.OemTilde }, |
||||
|
//{ KeyCode.?, Key.AbntC1 }
|
||||
|
//{ KeyCode.?, Key.AbntC2 }
|
||||
|
//{ KeyCode.?, Key.OemPipe }
|
||||
|
{ KeyCode.Apostrophe, Key.OemQuotes }, |
||||
|
{ KeyCode.Slash, Key.OemQuestion }, |
||||
|
{ KeyCode.BraceLeft, Key.OemOpenBrackets }, |
||||
|
{ KeyCode.BracketRight, Key.OemCloseBrackets }, |
||||
|
//{ KeyCode.?, Key.Oem7 }
|
||||
|
//{ KeyCode.?, Key.Oem8 }
|
||||
|
//{ KeyCode.?, Key.Oem102 }
|
||||
|
//{ KeyCode.?, Key.ImeProcessed }
|
||||
|
//{ KeyCode.?, Key.System }
|
||||
|
//{ KeyCode.?, Key.OemAttn }
|
||||
|
//{ KeyCode.?, Key.OemFinish }
|
||||
|
//{ KeyCode.?, Key.DbeHiragana }
|
||||
|
//{ KeyCode.?, Key.OemAuto }
|
||||
|
//{ KeyCode.?, Key.DbeDbcsChar }
|
||||
|
//{ KeyCode.?, Key.OemBackTab }
|
||||
|
//{ KeyCode.?, Key.Attn }
|
||||
|
//{ KeyCode.?, Key.DbeEnterWordRegisterMode }
|
||||
|
//{ KeyCode.?, Key.DbeEnterImeConfigureMode }
|
||||
|
//{ KeyCode.?, Key.EraseEof }
|
||||
|
//{ KeyCode.MediaPlay, Key.Play },
|
||||
|
//{ KeyCode.?, Key.Zoom }
|
||||
|
//{ KeyCode.?, Key.NoName }
|
||||
|
//{ KeyCode.?, Key.DbeEnterDialogConversionMode }
|
||||
|
//{ KeyCode.?, Key.OemClear }
|
||||
|
//{ KeyCode.?, Key.DeadCharProcessed }
|
||||
|
{ KeyCode.Backslash, Key.OemBackslash } |
||||
|
}; |
||||
|
|
||||
|
internal static Key ConvertKey(KeyCode key) |
||||
|
{ |
||||
|
return KeyDic.TryGetValue(key, out var result) ? result : Key.None; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace Avalonia.Tizen.Platform.Interop; |
||||
|
|
||||
|
struct TexturedQuadVertex |
||||
|
{ |
||||
|
public Vec2 position; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct Vec2 |
||||
|
{ |
||||
|
float x; |
||||
|
float y; |
||||
|
public Vec2(float xIn, float yIn) |
||||
|
{ |
||||
|
x = xIn; |
||||
|
y = yIn; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
using System.Security; |
||||
|
using System.Security.Permissions; |
||||
|
using Tizen.Applications; |
||||
|
using Tizen.Security; |
||||
|
|
||||
|
namespace Avalonia.Tizen.Platform; |
||||
|
internal class Permissions |
||||
|
{ |
||||
|
public record Privilege (string Path, bool IsRuntime); |
||||
|
|
||||
|
public static readonly Privilege InternetPrivilege = new("http://tizen.org/privilege/internet", false); |
||||
|
public static readonly Privilege NetworkPrivilege = new("http://tizen.org/privilege/network.get", false); |
||||
|
public static readonly Privilege CameraPrivilege = new("http://tizen.org/privilege/camera", false); |
||||
|
public static readonly Privilege ContactReadPrivilege = new("http://tizen.org/privilege/contact.read", true); |
||||
|
public static readonly Privilege ContactWritePrivilege = new("http://tizen.org/privilege/contact.write", true); |
||||
|
public static readonly Privilege LedPrivilege = new("http://tizen.org/privilege/led", false); |
||||
|
public static readonly Privilege AppManagerLaunchPrivilege = new("http://tizen.org/privilege/appmanager.launch", false); |
||||
|
public static readonly Privilege LocationPrivilege = new("http://tizen.org/privilege/location", true); |
||||
|
public static readonly Privilege MapServicePrivilege = new("http://tizen.org/privilege/mapservice", false); |
||||
|
public static readonly Privilege MediaStoragePrivilege = new("http://tizen.org/privilege/mediastorage", true); |
||||
|
public static readonly Privilege RecorderPrivilege = new("http://tizen.org/privilege/recorder", false); |
||||
|
public static readonly Privilege HapticPrivilege = new("http://tizen.org/privilege/haptic", false); |
||||
|
|
||||
|
public static readonly Privilege[] NetworkPrivileges = { InternetPrivilege, NetworkPrivilege }; |
||||
|
public static readonly Privilege[] MapsPrivileges = { InternetPrivilege, MapServicePrivilege, NetworkPrivilege }; |
||||
|
|
||||
|
public static Package CurrentPackage |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var packageId = global::Tizen.Applications.Application.Current.ApplicationInfo.PackageId; |
||||
|
return PackageManager.GetPackage(packageId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static bool IsPrivilegeDeclared(string tizenPrivilege) |
||||
|
{ |
||||
|
var tizenPrivileges = tizenPrivilege; |
||||
|
|
||||
|
if (tizenPrivileges == null || !tizenPrivileges.Any()) |
||||
|
return false; |
||||
|
|
||||
|
var package = CurrentPackage; |
||||
|
|
||||
|
if (!package.Privileges.Contains(tizenPrivilege)) |
||||
|
return false; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public static void EnsureDeclared(params Privilege[] requiredPrivileges) |
||||
|
{ |
||||
|
if (requiredPrivileges?.Any() != true) |
||||
|
return; |
||||
|
|
||||
|
foreach (var (tizenPrivilege, isRuntime) in requiredPrivileges) |
||||
|
{ |
||||
|
if (!IsPrivilegeDeclared(tizenPrivilege)) |
||||
|
throw new SecurityException($"You need to declare the privilege: `{tizenPrivilege}` in your tizen-manifest.xml"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static Task<bool> CheckPrivilegeAsync(params Privilege[] requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, false); |
||||
|
public static Task<bool> RequestPrivilegeAsync(params Privilege[] requiredPrivileges) => CheckPrivilegeAsync(requiredPrivileges, true); |
||||
|
private static async Task<bool> CheckPrivilegeAsync(Privilege[] requiredPrivileges, bool ask) |
||||
|
{ |
||||
|
var ret = global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile); |
||||
|
if (!ret || (ret && (!profile.Equals("mobile") || !profile.Equals("wearable")))) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (requiredPrivileges == null || !requiredPrivileges.Any()) |
||||
|
return true; |
||||
|
|
||||
|
EnsureDeclared(); |
||||
|
|
||||
|
var tizenPrivileges = requiredPrivileges.Where(p => p.IsRuntime); |
||||
|
|
||||
|
foreach (var (tizenPrivilege, isRuntime) in tizenPrivileges) |
||||
|
{ |
||||
|
var checkResult = PrivacyPrivilegeManager.CheckPermission(tizenPrivilege); |
||||
|
if (checkResult == CheckResult.Ask) |
||||
|
{ |
||||
|
if (ask) |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<bool>(); |
||||
|
PrivacyPrivilegeManager.GetResponseContext(tizenPrivilege) |
||||
|
.TryGetTarget(out var context); |
||||
|
void OnResponseFetched(object sender, RequestResponseEventArgs e) |
||||
|
{ |
||||
|
tcs.TrySetResult(e.result == RequestResult.AllowForever); |
||||
|
} |
||||
|
context.ResponseFetched += OnResponseFetched; |
||||
|
PrivacyPrivilegeManager.RequestPermission(tizenPrivilege); |
||||
|
var result = await tcs.Task; |
||||
|
context.ResponseFetched -= OnResponseFetched; |
||||
|
if (result) |
||||
|
continue; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
else if (checkResult == CheckResult.Deny) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Platform; |
||||
|
using Tizen.NUI; |
||||
|
|
||||
|
namespace Avalonia.Tizen.Platform; |
||||
|
|
||||
|
internal class TizenPlatformSettings : DefaultPlatformSettings |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
internal class WindowingPlatformStub : IWindowingPlatform |
||||
|
{ |
||||
|
public IWindowImpl CreateWindow() => throw new NotSupportedException(); |
||||
|
|
||||
|
public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException(); |
||||
|
|
||||
|
public ITrayIconImpl CreateTrayIcon() => null; |
||||
|
} |
||||
|
|
||||
|
internal class PlatformIconLoaderStub : IPlatformIconLoader |
||||
|
{ |
||||
|
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) |
||||
|
{ |
||||
|
using (var stream = new MemoryStream()) |
||||
|
{ |
||||
|
bitmap.Save(stream); |
||||
|
return LoadIcon(stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IWindowIconImpl LoadIcon(Stream stream) |
||||
|
{ |
||||
|
var ms = new MemoryStream(); |
||||
|
stream.CopyTo(ms); |
||||
|
return new IconStub(ms); |
||||
|
} |
||||
|
|
||||
|
public IWindowIconImpl LoadIcon(string fileName) |
||||
|
{ |
||||
|
using (var file = File.Open(fileName, FileMode.Open)) |
||||
|
return LoadIcon(file); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class IconStub : IWindowIconImpl |
||||
|
{ |
||||
|
private readonly MemoryStream _ms; |
||||
|
|
||||
|
public IconStub(MemoryStream stream) |
||||
|
{ |
||||
|
_ms = stream; |
||||
|
} |
||||
|
|
||||
|
public void Save(Stream outputStream) |
||||
|
{ |
||||
|
_ms.Position = 0; |
||||
|
_ms.CopyTo(outputStream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class CursorFactoryStub : ICursorFactory |
||||
|
{ |
||||
|
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorImplStub(); |
||||
|
ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new CursorImplStub(); |
||||
|
|
||||
|
private class CursorImplStub : ICursorImpl |
||||
|
{ |
||||
|
public void Dispose() { } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Extension to setup app builder with tizen backend
|
||||
|
/// </summary>
|
||||
|
public static class TizenApplicationExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Use tizen builder to setup tizen sub system
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder">Avalonia App Builder</param>
|
||||
|
/// <returns>Return same builder</returns>
|
||||
|
public static AppBuilder UseTizen(this AppBuilder builder) |
||||
|
{ |
||||
|
return builder |
||||
|
.UseWindowingSubsystem(TizenPlatform.Initialize, "Tizen") |
||||
|
.UseStandardRuntimePlatformSubsystem() |
||||
|
.UseSkia(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Platform; |
||||
|
using Avalonia.OpenGL.Egl; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Rendering.Composition; |
||||
|
using Avalonia.Tizen.Platform.Input; |
||||
|
using Avalonia.Tizen.Platform; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
internal class TizenPlatform |
||||
|
{ |
||||
|
public static readonly TizenPlatform Instance = new(); |
||||
|
internal static NuiGlPlatform GlPlatform { get; set; } |
||||
|
internal static Compositor Compositor { get; private set; } |
||||
|
internal static TizenThreadingInterface ThreadingInterface { get; private set; } = new(); |
||||
|
|
||||
|
public static void Initialize() |
||||
|
{ |
||||
|
AvaloniaLocator.CurrentMutable |
||||
|
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>() |
||||
|
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub()) |
||||
|
.Bind<IKeyboardDevice>().ToSingleton<TizenKeyboardDevice>() |
||||
|
.Bind<IPlatformSettings>().ToSingleton<TizenPlatformSettings>() |
||||
|
.Bind<IPlatformThreadingInterface>().ToConstant(ThreadingInterface) |
||||
|
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>() |
||||
|
.Bind<IRenderTimer>().ToConstant(new TizenRenderTimer()) |
||||
|
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>() |
||||
|
.Bind<IPlatformGraphics>().ToConstant(GlPlatform = new NuiGlPlatform()); |
||||
|
|
||||
|
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Rendering; |
||||
|
using Tizen.Account.AccountManager; |
||||
|
using Tizen.System; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class TizenRenderTimer : IRenderTimer |
||||
|
{ |
||||
|
private readonly Stopwatch _st = Stopwatch.StartNew(); |
||||
|
private Timer _timer; |
||||
|
|
||||
|
public bool RunsInBackground => true; |
||||
|
|
||||
|
public event Action<TimeSpan>? Tick; |
||||
|
public event Action? RenderTick; |
||||
|
|
||||
|
public TizenRenderTimer() |
||||
|
{ |
||||
|
_timer = new Timer(TimerTick, null, 16, 16); |
||||
|
Display.StateChanged += Display_StateChanged; |
||||
|
} |
||||
|
|
||||
|
private void TimerTick(object? state) |
||||
|
{ |
||||
|
RenderTick?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
private void Display_StateChanged(object? sender, DisplayStateChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.State == DisplayState.Off) |
||||
|
{ |
||||
|
_timer.Change(Timeout.Infinite, Timeout.Infinite); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_timer.Change(16, 16); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal void ManualTick() |
||||
|
{ |
||||
|
Tick?.Invoke(_st.Elapsed); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,152 @@ |
|||||
|
using System.Security; |
||||
|
using Avalonia.Platform.Storage; |
||||
|
using Avalonia.Platform.Storage.FileIO; |
||||
|
using Avalonia.Tizen.Platform; |
||||
|
using Tizen.Applications; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class TizenStorageProvider : IStorageProvider |
||||
|
{ |
||||
|
public bool CanOpen => true; |
||||
|
|
||||
|
public bool CanSave => false; |
||||
|
|
||||
|
public bool CanPickFolder => false; |
||||
|
|
||||
|
public Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark) |
||||
|
{ |
||||
|
return Task.FromException<IStorageBookmarkFile?>( |
||||
|
new PlatformNotSupportedException("Bookmark is not supported by Tizen")); |
||||
|
} |
||||
|
|
||||
|
private static async Task CheckPermission() |
||||
|
{ |
||||
|
Permissions.EnsureDeclared(Permissions.AppManagerLaunchPrivilege); |
||||
|
if (await Permissions.RequestPrivilegeAsync(Permissions.MediaStoragePrivilege) == false) |
||||
|
{ |
||||
|
throw new SecurityException("Application doesn't have storage permission."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options) |
||||
|
{ |
||||
|
await CheckPermission(); |
||||
|
|
||||
|
var tcs = new TaskCompletionSource<IReadOnlyList<IStorageFile>>(); |
||||
|
|
||||
|
#pragma warning disable CS8603 // Possible null reference return.
|
||||
|
var fileType = options.FileTypeFilter? |
||||
|
.Where(w => w.MimeTypes != null) |
||||
|
.SelectMany(s => s.MimeTypes); |
||||
|
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
|
||||
|
var appControl = new AppControl |
||||
|
{ |
||||
|
Operation = AppControlOperations.Pick, |
||||
|
Mime = fileType?.Any() == true |
||||
|
? fileType.Aggregate((o, n) => o + ";" + n) |
||||
|
: "*/*" |
||||
|
}; |
||||
|
appControl.ExtraData.Add(AppControlData.SectionMode, options.AllowMultiple ? "multiple" : "single"); |
||||
|
if (options.SuggestedStartLocation?.Path is { } startupPath) |
||||
|
appControl.ExtraData.Add(AppControlData.Path, startupPath.ToString()); |
||||
|
appControl.LaunchMode = AppControlLaunchMode.Single; |
||||
|
|
||||
|
var fileResults = new List<IStorageFile>(); |
||||
|
|
||||
|
AppControl.SendLaunchRequest(appControl, (_, reply, result) => |
||||
|
{ |
||||
|
if (result == AppControlReplyResult.Succeeded) |
||||
|
{ |
||||
|
if (reply.ExtraData.Count() > 0) |
||||
|
{ |
||||
|
var selectedFiles = reply.ExtraData.Get<IEnumerable<string>>(AppControlData.Selected).ToList(); |
||||
|
fileResults.AddRange(selectedFiles.Select(f => new BclStorageFile(new(f)))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
tcs.TrySetResult(fileResults); |
||||
|
}); |
||||
|
|
||||
|
return await tcs.Task; |
||||
|
} |
||||
|
|
||||
|
public Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options) |
||||
|
{ |
||||
|
return Task.FromException<IReadOnlyList<IStorageFolder>>( |
||||
|
new PlatformNotSupportedException("Open folder is not supported by Tizen")); |
||||
|
} |
||||
|
|
||||
|
public Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark) |
||||
|
{ |
||||
|
return Task.FromException<IStorageBookmarkFolder?>( |
||||
|
new PlatformNotSupportedException("Open folder is not supported by Tize")); |
||||
|
} |
||||
|
|
||||
|
public Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options) |
||||
|
{ |
||||
|
return Task.FromException<IStorageFile?>( |
||||
|
new PlatformNotSupportedException("Save file picker is not supported by Tizen")); |
||||
|
} |
||||
|
|
||||
|
public async Task<IStorageFile?> TryGetFileFromPathAsync(Uri filePath) |
||||
|
{ |
||||
|
await CheckPermission(); |
||||
|
|
||||
|
if (filePath is not { IsAbsoluteUri: true, Scheme: "file" }) |
||||
|
{ |
||||
|
throw new ArgumentException("File path is expected to be an absolute link with \"file\" scheme."); |
||||
|
} |
||||
|
|
||||
|
var path = Path.Combine(global::Tizen.Applications.Application.Current.DirectoryInfo.Resource, filePath.AbsolutePath); |
||||
|
var file = new FileInfo(path); |
||||
|
if (!file.Exists) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return new BclStorageFile(file); |
||||
|
} |
||||
|
|
||||
|
public async Task<IStorageFolder?> TryGetFolderFromPathAsync(Uri folderPath) |
||||
|
{ |
||||
|
if (folderPath is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(folderPath)); |
||||
|
} |
||||
|
|
||||
|
await CheckPermission(); |
||||
|
|
||||
|
if (folderPath is not { IsAbsoluteUri: true, Scheme: "file" }) |
||||
|
{ |
||||
|
throw new ArgumentException("File path is expected to be an absolute link with \"file\" scheme."); |
||||
|
} |
||||
|
|
||||
|
var path = Path.Combine(global::Tizen.Applications.Application.Current.DirectoryInfo.Resource, folderPath.AbsolutePath); |
||||
|
var directory = new System.IO.DirectoryInfo(path); |
||||
|
if (!directory.Exists) |
||||
|
return null; |
||||
|
|
||||
|
return new BclStorageFolder(directory); |
||||
|
} |
||||
|
|
||||
|
public Task<IStorageFolder?> TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder) |
||||
|
{ |
||||
|
var folder = wellKnownFolder switch |
||||
|
{ |
||||
|
WellKnownFolder.Desktop => null, |
||||
|
WellKnownFolder.Documents => global::Tizen.Applications.Application.Current.DirectoryInfo.Data, |
||||
|
WellKnownFolder.Downloads => global::Tizen.Applications.Application.Current.DirectoryInfo.SharedData, |
||||
|
WellKnownFolder.Music => null, |
||||
|
WellKnownFolder.Pictures => null, |
||||
|
WellKnownFolder.Videos => null, |
||||
|
_ => throw new ArgumentOutOfRangeException(nameof(wellKnownFolder), wellKnownFolder, null), |
||||
|
}; |
||||
|
|
||||
|
if (folder == null) |
||||
|
return Task.FromResult<IStorageFolder?>(null); |
||||
|
|
||||
|
var storageFolder = new BclStorageFolder(new System.IO.DirectoryInfo(folder)); |
||||
|
return Task.FromResult<IStorageFolder?>(storageFolder); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
internal class TizenThreadingInterface : IPlatformThreadingInterface |
||||
|
{ |
||||
|
internal event Action? TickExecuted; |
||||
|
private bool _signaled; |
||||
|
|
||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
internal static SynchronizationContext MainloopContext { get; set; } |
||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
|
||||
|
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) |
||||
|
{ |
||||
|
return new Timer(_ => |
||||
|
{ |
||||
|
EnsureInvokeOnMainThread(tick); |
||||
|
}, null, interval, interval); |
||||
|
} |
||||
|
|
||||
|
private void EnsureInvokeOnMainThread(Action action) |
||||
|
{ |
||||
|
if (SynchronizationContext.Current != null) |
||||
|
action(); |
||||
|
else |
||||
|
MainloopContext.Post(static arg => ((Action)arg!).Invoke(), action); |
||||
|
} |
||||
|
|
||||
|
public void Signal(DispatcherPriority prio) |
||||
|
{ |
||||
|
if (_signaled) |
||||
|
return; |
||||
|
|
||||
|
_signaled = true; |
||||
|
var interval = TimeSpan.FromMilliseconds(1); |
||||
|
|
||||
|
IDisposable? disp = null; |
||||
|
disp = new Timer(_ => |
||||
|
{ |
||||
|
_signaled = false; |
||||
|
disp?.Dispose(); |
||||
|
|
||||
|
EnsureInvokeOnMainThread(() => Signaled?.Invoke(prio)); |
||||
|
}, null, interval, interval); |
||||
|
} |
||||
|
|
||||
|
public bool CurrentThreadIsLoopThread => SynchronizationContext.Current != null; |
||||
|
public event Action<DispatcherPriority?>? Signaled; |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Platform; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Input.TextInput; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Platform.Storage; |
||||
|
using Avalonia.Rendering.Composition; |
||||
|
using Avalonia.Tizen.Platform.Input; |
||||
|
|
||||
|
namespace Avalonia.Tizen; |
||||
|
|
||||
|
internal class TopLevelImpl : ITopLevelImpl |
||||
|
{ |
||||
|
private readonly ITizenView _view; |
||||
|
private readonly NuiClipboardImpl _clipboard; |
||||
|
private readonly IStorageProvider _storageProvider; |
||||
|
|
||||
|
public TopLevelImpl(ITizenView view, IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
_view = view; |
||||
|
Surfaces = surfaces; |
||||
|
|
||||
|
_storageProvider = new TizenStorageProvider(); |
||||
|
_clipboard = new NuiClipboardImpl(); |
||||
|
} |
||||
|
|
||||
|
public Size ClientSize => _view.ClientSize; |
||||
|
|
||||
|
public Size? FrameSize => null; |
||||
|
|
||||
|
public double RenderScaling => 1; |
||||
|
|
||||
|
public IEnumerable<object> Surfaces { get; set; } |
||||
|
|
||||
|
public Action<RawInputEventArgs>? Input { get; set; } |
||||
|
public Action<Rect>? Paint { get; set; } |
||||
|
public Action<Size, WindowResizeReason>? Resized { get; set; } |
||||
|
public Action<double>? ScalingChanged { get; set; } |
||||
|
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; } |
||||
|
|
||||
|
public Compositor Compositor => TizenPlatform.Compositor; |
||||
|
|
||||
|
public Action? Closed { get; set; } |
||||
|
public Action? LostFocus { get; set; } |
||||
|
|
||||
|
public WindowTransparencyLevel TransparencyLevel { get; set; } |
||||
|
|
||||
|
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(); |
||||
|
public IPopupImpl? CreatePopup() |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
//
|
||||
|
} |
||||
|
|
||||
|
public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y); |
||||
|
|
||||
|
public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y); |
||||
|
|
||||
|
public void SetCursor(ICursorImpl? cursor) |
||||
|
{ |
||||
|
//
|
||||
|
} |
||||
|
|
||||
|
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) |
||||
|
{ |
||||
|
//
|
||||
|
} |
||||
|
|
||||
|
public void SetInputRoot(IInputRoot inputRoot) |
||||
|
{ |
||||
|
_view.InputRoot = inputRoot; |
||||
|
} |
||||
|
|
||||
|
public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) |
||||
|
{ |
||||
|
//
|
||||
|
} |
||||
|
|
||||
|
public object? TryGetFeature(Type featureType) |
||||
|
{ |
||||
|
if (featureType == typeof(IStorageProvider)) |
||||
|
{ |
||||
|
return _storageProvider; |
||||
|
} |
||||
|
|
||||
|
if (featureType == typeof(ITextInputMethodImpl)) |
||||
|
{ |
||||
|
return _view; |
||||
|
} |
||||
|
|
||||
|
if (featureType == typeof(INativeControlHostImpl)) |
||||
|
{ |
||||
|
return _view.NativeControlHost; |
||||
|
} |
||||
|
|
||||
|
if (featureType == typeof(IClipboard)) |
||||
|
{ |
||||
|
return _clipboard; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
internal void TextInput(string text) |
||||
|
{ |
||||
|
if (Input == null) return; |
||||
|
var args = new RawTextInputEventArgs(TizenKeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, _view.InputRoot, text); |
||||
|
|
||||
|
Input(args); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue