committed by
GitHub
41 changed files with 1 additions and 2333 deletions
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 16 KiB |
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 16 KiB |
@ -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,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> |
|||
@ -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; } |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
@ -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() |
|||
{ |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -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"; |
|||
} |
|||
@ -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 |
|||
}; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Tizen.NUI; |
|||
|
|||
namespace Avalonia.Tizen.Platform; |
|||
|
|||
internal class TizenPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
|
|||
} |
|||
@ -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() { } |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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>()); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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…
Reference in new issue