committed by
GitHub
159 changed files with 1016 additions and 4142 deletions
@ -0,0 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup> |
|||
<EnableNETAnalyzers>true</EnableNETAnalyzers> |
|||
</PropertyGroup> |
|||
</Project> |
|||
@ -0,0 +1,17 @@ |
|||
using Avalonia; |
|||
using Avalonia.Web.Blazor; |
|||
|
|||
namespace ControlCatalog.Blazor.Web; |
|||
|
|||
public partial class App |
|||
{ |
|||
protected override void OnParametersSet() |
|||
{ |
|||
AppBuilder.Configure<ControlCatalog.App>() |
|||
.UseBlazor() |
|||
// .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
|
|||
.SetupWithSingleViewLifetime(); |
|||
|
|||
base.OnParametersSet(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> |
|||
<PropertyGroup> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier> |
|||
<Nullable>enable</Nullable> |
|||
<EmccTotalMemory>16777216</EmccTotalMemory> |
|||
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> |
|||
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0-rc.1.22427.2" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0-rc.1.22427.2" PrivateAssets="all" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
|||
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" /> |
|||
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> |
|||
<Import Project="..\..\build\BuildTargets.targets" /> |
|||
|
|||
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" /> |
|||
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" /> |
|||
|
|||
</Project> |
|||
|
|||
@ -0,0 +1,29 @@ |
|||
using System; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using ControlCatalog.Blazor.Web; |
|||
|
|||
public class Program |
|||
{ |
|||
public static async Task Main(string[] args) |
|||
{ |
|||
await CreateHostBuilder(args).Build().RunAsync(); |
|||
} |
|||
|
|||
public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) |
|||
{ |
|||
var builder = WebAssemblyHostBuilder.CreateDefault(args); |
|||
|
|||
builder.RootComponents.Add<App>("#app"); |
|||
|
|||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
@ -1,20 +0,0 @@ |
|||
using Avalonia; |
|||
using Avalonia.Web.Blazor; |
|||
|
|||
namespace ControlCatalog.Web; |
|||
|
|||
public partial class App |
|||
{ |
|||
protected override void OnParametersSet() |
|||
{ |
|||
WebAppBuilder.Configure<ControlCatalog.App>() |
|||
.AfterSetup(_ => |
|||
{ |
|||
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb(); |
|||
}) |
|||
.With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
|
|||
.SetupWithSingleViewLifetime(); |
|||
|
|||
base.OnParametersSet(); |
|||
} |
|||
} |
|||
@ -1,34 +1,42 @@ |
|||
using System; |
|||
|
|||
using Avalonia; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Web.Blazor; |
|||
using Avalonia.Web; |
|||
|
|||
using ControlCatalog.Pages; |
|||
|
|||
using Microsoft.JSInterop; |
|||
|
|||
namespace ControlCatalog.Web; |
|||
|
|||
public class EmbedSampleWeb : INativeDemoControl |
|||
{ |
|||
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault) |
|||
{ |
|||
var runtime = AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>(); |
|||
|
|||
if (isSecond) |
|||
{ |
|||
var iframe = runtime.Invoke<IJSInProcessObjectReference>("document.createElement", "iframe"); |
|||
iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70"); |
|||
var iframe = EmbedInterop.CreateElement("iframe"); |
|||
iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70"); |
|||
|
|||
return new JSObjectControlHandle(iframe); |
|||
} |
|||
else |
|||
{ |
|||
// window.createAppButton source is defined in "app.js" file.
|
|||
var button = runtime.Invoke<IJSInProcessObjectReference>("window.createAppButton"); |
|||
var defaultHandle = (JSObjectControlHandle)createDefault(); |
|||
|
|||
_ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ => |
|||
{ |
|||
EmbedInterop.AddAppButton(defaultHandle.Object); |
|||
}); |
|||
|
|||
return new JSObjectControlHandle(button); |
|||
return defaultHandle; |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal static partial class EmbedInterop |
|||
{ |
|||
[JSImport("globalThis.document.createElement")] |
|||
public static partial JSObject CreateElement(string tagName); |
|||
|
|||
[JSImport("addAppButton", "embed.js")] |
|||
public static partial void AddAppButton(JSObject parentObject); |
|||
} |
|||
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,6 @@ |
|||
<linker> |
|||
<assembly fullname="ControlCatalog" preserve="All" /> |
|||
<assembly fullname="ControlCatalog.Web" preserve="All" /> |
|||
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" /> |
|||
<assembly fullname="Avalonia.Themes.Simple" preserve="All" /> |
|||
</linker> |
|||
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
@ -1,105 +0,0 @@ |
|||
using Android.OS; |
|||
using AndroidX.AppCompat.App; |
|||
using Android.Content.Res; |
|||
using AndroidX.Lifecycle; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Controls; |
|||
using Android.Runtime; |
|||
using Android.App; |
|||
using Android.Content; |
|||
using System; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
public abstract class AvaloniaActivity : AppCompatActivity |
|||
{ |
|||
internal class SingleViewLifetime : ISingleViewApplicationLifetime |
|||
{ |
|||
public AvaloniaView View { get; internal set; } |
|||
|
|||
public Control MainView |
|||
{ |
|||
get => (Control)View.Content; |
|||
set => View.Content = value; |
|||
} |
|||
} |
|||
|
|||
internal Action<int, Result, Intent> ActivityResult; |
|||
internal AvaloniaView View; |
|||
internal AvaloniaViewModel _viewModel; |
|||
|
|||
protected abstract AppBuilder CreateAppBuilder(); |
|||
|
|||
protected override void OnCreate(Bundle savedInstanceState) |
|||
{ |
|||
var builder = CreateAppBuilder(); |
|||
|
|||
|
|||
var lifetime = new SingleViewLifetime(); |
|||
|
|||
builder.AfterSetup(x => |
|||
{ |
|||
_viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; |
|||
|
|||
View = new AvaloniaView(this); |
|||
if (_viewModel.Content != null) |
|||
{ |
|||
View.Content = _viewModel.Content; |
|||
} |
|||
|
|||
SetContentView(View); |
|||
lifetime.View = View; |
|||
|
|||
View.Prepare(); |
|||
}); |
|||
|
|||
builder.SetupWithLifetime(lifetime); |
|||
|
|||
base.OnCreate(savedInstanceState); |
|||
} |
|||
public object Content |
|||
{ |
|||
get |
|||
{ |
|||
return _viewModel.Content; |
|||
} |
|||
set |
|||
{ |
|||
_viewModel.Content = value; |
|||
if (View != null) |
|||
View.Content = value; |
|||
} |
|||
} |
|||
|
|||
public override void OnConfigurationChanged(Configuration newConfig) |
|||
{ |
|||
base.OnConfigurationChanged(newConfig); |
|||
} |
|||
|
|||
protected override void OnDestroy() |
|||
{ |
|||
View.Content = null; |
|||
|
|||
base.OnDestroy(); |
|||
} |
|||
|
|||
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data) |
|||
{ |
|||
base.OnActivityResult(requestCode, resultCode, data); |
|||
|
|||
ActivityResult?.Invoke(requestCode, resultCode, data); |
|||
} |
|||
} |
|||
|
|||
public abstract class AvaloniaActivity<TApp> : AvaloniaActivity where TApp : Application, new() |
|||
{ |
|||
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); |
|||
|
|||
protected override AppBuilder CreateAppBuilder() |
|||
{ |
|||
var builder = AppBuilder.Configure<TApp>(); |
|||
|
|||
return CustomizeAppBuilder(builder); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
using System; |
|||
using Android.App; |
|||
using Android.Content; |
|||
using Android.Content.Res; |
|||
using Android.OS; |
|||
using Android.Runtime; |
|||
using AndroidX.AppCompat.App; |
|||
using AndroidX.Lifecycle; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
public abstract class AvaloniaMainActivity : AppCompatActivity |
|||
{ |
|||
internal static object ViewContent; |
|||
|
|||
internal Action<int, Result, Intent> ActivityResult; |
|||
internal AvaloniaView View; |
|||
|
|||
protected override void OnCreate(Bundle savedInstanceState) |
|||
{ |
|||
View = new AvaloniaView(this); |
|||
if (ViewContent != null) |
|||
{ |
|||
View.Content = ViewContent; |
|||
} |
|||
|
|||
View.Prepare(); |
|||
|
|||
if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime) |
|||
{ |
|||
lifetime.View = View; |
|||
} |
|||
|
|||
base.OnCreate(savedInstanceState); |
|||
|
|||
SetContentView(View); |
|||
} |
|||
|
|||
public object Content |
|||
{ |
|||
get |
|||
{ |
|||
return ViewContent; |
|||
} |
|||
set |
|||
{ |
|||
ViewContent = value; |
|||
if (View != null) |
|||
View.Content = value; |
|||
} |
|||
} |
|||
|
|||
public override void OnConfigurationChanged(Configuration newConfig) |
|||
{ |
|||
base.OnConfigurationChanged(newConfig); |
|||
} |
|||
|
|||
protected override void OnDestroy() |
|||
{ |
|||
View.Content = null; |
|||
|
|||
base.OnDestroy(); |
|||
} |
|||
|
|||
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data) |
|||
{ |
|||
base.OnActivityResult(requestCode, resultCode, data); |
|||
|
|||
ActivityResult?.Invoke(requestCode, resultCode, data); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using Android.OS; |
|||
using AndroidX.AppCompat.App; |
|||
using AndroidX.Lifecycle; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
public abstract class AvaloniaSplashActivity : AppCompatActivity |
|||
{ |
|||
protected abstract AppBuilder CreateAppBuilder(); |
|||
|
|||
protected override void OnCreate(Bundle? savedInstanceState) |
|||
{ |
|||
base.OnCreate(savedInstanceState); |
|||
|
|||
var builder = CreateAppBuilder(); |
|||
|
|||
var lifetime = new SingleViewLifetime(); |
|||
|
|||
builder.SetupWithLifetime(lifetime); |
|||
} |
|||
} |
|||
|
|||
public abstract class AvaloniaSplashActivity<TApp> : AvaloniaSplashActivity where TApp : Application, new() |
|||
{ |
|||
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); |
|||
|
|||
protected override AppBuilder CreateAppBuilder() |
|||
{ |
|||
var builder = AppBuilder.Configure<TApp>(); |
|||
|
|||
return CustomizeAppBuilder(builder); |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel |
|||
{ |
|||
public object Content { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
internal class SingleViewLifetime : ISingleViewApplicationLifetime |
|||
{ |
|||
private AvaloniaView _view; |
|||
|
|||
public AvaloniaView View |
|||
{ |
|||
get => _view; internal set |
|||
{ |
|||
if (_view != null) |
|||
{ |
|||
_view.Content = null; |
|||
_view.Dispose(); |
|||
} |
|||
_view = value; |
|||
_view.Content = MainView; |
|||
} |
|||
} |
|||
|
|||
public Control MainView { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,359 @@ |
|||
using Avalonia.Media; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Implements a reduced version of the 2014 Material Design color palette.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This palette is based on the one outlined here:
|
|||
///
|
|||
/// https://material.io/design/color/the-color-system.html#tools-for-picking-colors
|
|||
///
|
|||
/// In order to make the palette uniform and rectangular the following
|
|||
/// alterations were made:
|
|||
///
|
|||
/// 1. The A100-A700 shades of each color are excluded.
|
|||
/// These shades do not exist for all colors (brown/gray).
|
|||
/// 2. Black/White are stand-alone and are also excluded.
|
|||
///
|
|||
/// </remarks>
|
|||
public class MaterialColorPalette : IColorPalette |
|||
{ |
|||
// See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors
|
|||
// This is a reduced palette for uniformity
|
|||
private static Color[,]? _colorChart = null; |
|||
private static int _colorChartColorCount = 0; |
|||
private static int _colorChartShadeCount = 0; |
|||
private static object _colorChartMutex = new object(); |
|||
|
|||
/// <summary>
|
|||
/// Initializes all color chart colors.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is pulled out separately to lazy load for performance.
|
|||
/// If no material color palette is ever used, no colors will be created.
|
|||
/// </remarks>
|
|||
private void InitColorChart() |
|||
{ |
|||
lock (_colorChartMutex) |
|||
{ |
|||
_colorChart = new Color[,] |
|||
{ |
|||
// Red
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFF, 0xEB, 0xEE), |
|||
Color.FromArgb(0xFF, 0xFF, 0xCD, 0xD2), |
|||
Color.FromArgb(0xFF, 0xEF, 0x9A, 0x9A), |
|||
Color.FromArgb(0xFF, 0xE5, 0x73, 0x73), |
|||
Color.FromArgb(0xFF, 0xEF, 0x53, 0x50), |
|||
Color.FromArgb(0xFF, 0xF4, 0x43, 0x36), |
|||
Color.FromArgb(0xFF, 0xE5, 0x39, 0x35), |
|||
Color.FromArgb(0xFF, 0xD3, 0x2F, 0x2F), |
|||
Color.FromArgb(0xFF, 0xC6, 0x28, 0x28), |
|||
Color.FromArgb(0xFF, 0xB7, 0x1C, 0x1C), |
|||
}, |
|||
|
|||
// Pink
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFC, 0xE4, 0xEC), |
|||
Color.FromArgb(0xFF, 0xF8, 0xBB, 0xD0), |
|||
Color.FromArgb(0xFF, 0xF4, 0x8F, 0xB1), |
|||
Color.FromArgb(0xFF, 0xF0, 0x62, 0x92), |
|||
Color.FromArgb(0xFF, 0xEC, 0x40, 0x7A), |
|||
Color.FromArgb(0xFF, 0xE9, 0x1E, 0x63), |
|||
Color.FromArgb(0xFF, 0xD8, 0x1B, 0x60), |
|||
Color.FromArgb(0xFF, 0xC2, 0x18, 0x5B), |
|||
Color.FromArgb(0xFF, 0xAD, 0x14, 0x57), |
|||
Color.FromArgb(0xFF, 0x88, 0x0E, 0x4F), |
|||
}, |
|||
|
|||
// Purple
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xF3, 0xE5, 0xF5), |
|||
Color.FromArgb(0xFF, 0xE1, 0xBE, 0xE7), |
|||
Color.FromArgb(0xFF, 0xCE, 0x93, 0xD8), |
|||
Color.FromArgb(0xFF, 0xBA, 0x68, 0xC8), |
|||
Color.FromArgb(0xFF, 0xAB, 0x47, 0xBC), |
|||
Color.FromArgb(0xFF, 0x9C, 0x27, 0xB0), |
|||
Color.FromArgb(0xFF, 0x8E, 0x24, 0xAA), |
|||
Color.FromArgb(0xFF, 0x7B, 0x1F, 0xA2), |
|||
Color.FromArgb(0xFF, 0x6A, 0x1B, 0x9A), |
|||
Color.FromArgb(0xFF, 0x4A, 0x14, 0x8C), |
|||
}, |
|||
|
|||
// Deep Purple
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xED, 0xE7, 0xF6), |
|||
Color.FromArgb(0xFF, 0xD1, 0xC4, 0xE9), |
|||
Color.FromArgb(0xFF, 0xB3, 0x9D, 0xDB), |
|||
Color.FromArgb(0xFF, 0x95, 0x75, 0xCD), |
|||
Color.FromArgb(0xFF, 0x7E, 0x57, 0xC2), |
|||
Color.FromArgb(0xFF, 0x67, 0x3A, 0xB7), |
|||
Color.FromArgb(0xFF, 0x5E, 0x35, 0xB1), |
|||
Color.FromArgb(0xFF, 0x51, 0x2D, 0xA8), |
|||
Color.FromArgb(0xFF, 0x45, 0x27, 0xA0), |
|||
Color.FromArgb(0xFF, 0x31, 0x1B, 0x92), |
|||
}, |
|||
|
|||
// Indigo
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xE8, 0xEA, 0xF6), |
|||
Color.FromArgb(0xFF, 0xC5, 0xCA, 0xE9), |
|||
Color.FromArgb(0xFF, 0x9F, 0xA8, 0xDA), |
|||
Color.FromArgb(0xFF, 0x79, 0x86, 0xCB), |
|||
Color.FromArgb(0xFF, 0x5C, 0x6B, 0xC0), |
|||
Color.FromArgb(0xFF, 0x3F, 0x51, 0xB5), |
|||
Color.FromArgb(0xFF, 0x39, 0x49, 0xAB), |
|||
Color.FromArgb(0xFF, 0x30, 0x3F, 0x9F), |
|||
Color.FromArgb(0xFF, 0x28, 0x35, 0x93), |
|||
Color.FromArgb(0xFF, 0x1A, 0x23, 0x7E), |
|||
}, |
|||
|
|||
// Blue
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xE3, 0xF2, 0xFD), |
|||
Color.FromArgb(0xFF, 0xBB, 0xDE, 0xFB), |
|||
Color.FromArgb(0xFF, 0x90, 0xCA, 0xF9), |
|||
Color.FromArgb(0xFF, 0x64, 0xB5, 0xF6), |
|||
Color.FromArgb(0xFF, 0x42, 0xA5, 0xF5), |
|||
Color.FromArgb(0xFF, 0x21, 0x96, 0xF3), |
|||
Color.FromArgb(0xFF, 0x1E, 0x88, 0xE5), |
|||
Color.FromArgb(0xFF, 0x19, 0x76, 0xD2), |
|||
Color.FromArgb(0xFF, 0x15, 0x65, 0xC0), |
|||
Color.FromArgb(0xFF, 0x0D, 0x47, 0xA1), |
|||
}, |
|||
|
|||
// Light Blue
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xE1, 0xF5, 0xFE), |
|||
Color.FromArgb(0xFF, 0xB3, 0xE5, 0xFC), |
|||
Color.FromArgb(0xFF, 0x81, 0xD4, 0xFA), |
|||
Color.FromArgb(0xFF, 0x4F, 0xC3, 0xF7), |
|||
Color.FromArgb(0xFF, 0x29, 0xB6, 0xF6), |
|||
Color.FromArgb(0xFF, 0x03, 0xA9, 0xF4), |
|||
Color.FromArgb(0xFF, 0x03, 0x9B, 0xE5), |
|||
Color.FromArgb(0xFF, 0x02, 0x88, 0xD1), |
|||
Color.FromArgb(0xFF, 0x02, 0x77, 0xBD), |
|||
Color.FromArgb(0xFF, 0x01, 0x57, 0x9B), |
|||
}, |
|||
|
|||
// Cyan
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xE0, 0xF7, 0xFA), |
|||
Color.FromArgb(0xFF, 0xB2, 0xEB, 0xF2), |
|||
Color.FromArgb(0xFF, 0x80, 0xDE, 0xEA), |
|||
Color.FromArgb(0xFF, 0x4D, 0xD0, 0xE1), |
|||
Color.FromArgb(0xFF, 0x26, 0xC6, 0xDA), |
|||
Color.FromArgb(0xFF, 0x00, 0xBC, 0xD4), |
|||
Color.FromArgb(0xFF, 0x00, 0xAC, 0xC1), |
|||
Color.FromArgb(0xFF, 0x00, 0x97, 0xA7), |
|||
Color.FromArgb(0xFF, 0x00, 0x83, 0x8F), |
|||
Color.FromArgb(0xFF, 0x00, 0x60, 0x64), |
|||
}, |
|||
|
|||
// Teal
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xE0, 0xF2, 0xF1), |
|||
Color.FromArgb(0xFF, 0xB2, 0xDF, 0xDB), |
|||
Color.FromArgb(0xFF, 0x80, 0xCB, 0xC4), |
|||
Color.FromArgb(0xFF, 0x4D, 0xB6, 0xAC), |
|||
Color.FromArgb(0xFF, 0x26, 0xA6, 0x9A), |
|||
Color.FromArgb(0xFF, 0x00, 0x96, 0x88), |
|||
Color.FromArgb(0xFF, 0x00, 0x89, 0x7B), |
|||
Color.FromArgb(0xFF, 0x00, 0x79, 0x6B), |
|||
Color.FromArgb(0xFF, 0x00, 0x69, 0x5C), |
|||
Color.FromArgb(0xFF, 0x00, 0x4D, 0x40), |
|||
}, |
|||
|
|||
// Green
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xE8, 0xF5, 0xE9), |
|||
Color.FromArgb(0xFF, 0xC8, 0xE6, 0xC9), |
|||
Color.FromArgb(0xFF, 0xA5, 0xD6, 0xA7), |
|||
Color.FromArgb(0xFF, 0x81, 0xC7, 0x84), |
|||
Color.FromArgb(0xFF, 0x66, 0xBB, 0x6A), |
|||
Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50), |
|||
Color.FromArgb(0xFF, 0x43, 0xA0, 0x47), |
|||
Color.FromArgb(0xFF, 0x38, 0x8E, 0x3C), |
|||
Color.FromArgb(0xFF, 0x2E, 0x7D, 0x32), |
|||
Color.FromArgb(0xFF, 0x1B, 0x5E, 0x20), |
|||
}, |
|||
|
|||
// Light Green
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xF1, 0xF8, 0xE9), |
|||
Color.FromArgb(0xFF, 0xDC, 0xED, 0xC8), |
|||
Color.FromArgb(0xFF, 0xC5, 0xE1, 0xA5), |
|||
Color.FromArgb(0xFF, 0xAE, 0xD5, 0x81), |
|||
Color.FromArgb(0xFF, 0x9C, 0xCC, 0x65), |
|||
Color.FromArgb(0xFF, 0x8B, 0xC3, 0x4A), |
|||
Color.FromArgb(0xFF, 0x7C, 0xB3, 0x42), |
|||
Color.FromArgb(0xFF, 0x68, 0x9F, 0x38), |
|||
Color.FromArgb(0xFF, 0x55, 0x8B, 0x2F), |
|||
Color.FromArgb(0xFF, 0x33, 0x69, 0x1E), |
|||
}, |
|||
|
|||
// Lime
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xF9, 0xFB, 0xE7), |
|||
Color.FromArgb(0xFF, 0xF0, 0xF4, 0xC3), |
|||
Color.FromArgb(0xFF, 0xE6, 0xEE, 0x9C), |
|||
Color.FromArgb(0xFF, 0xDC, 0xE7, 0x75), |
|||
Color.FromArgb(0xFF, 0xD4, 0xE1, 0x57), |
|||
Color.FromArgb(0xFF, 0xCD, 0xDC, 0x39), |
|||
Color.FromArgb(0xFF, 0xC0, 0xCA, 0x33), |
|||
Color.FromArgb(0xFF, 0xAF, 0xB4, 0x2B), |
|||
Color.FromArgb(0xFF, 0x9E, 0x9D, 0x24), |
|||
Color.FromArgb(0xFF, 0x82, 0x77, 0x17), |
|||
}, |
|||
|
|||
// Yellow
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFF, 0xFD, 0xE7), |
|||
Color.FromArgb(0xFF, 0xFF, 0xF9, 0xC4), |
|||
Color.FromArgb(0xFF, 0xFF, 0xF5, 0x9D), |
|||
Color.FromArgb(0xFF, 0xFF, 0xF1, 0x76), |
|||
Color.FromArgb(0xFF, 0xFF, 0xEE, 0x58), |
|||
Color.FromArgb(0xFF, 0xFF, 0xEB, 0x3B), |
|||
Color.FromArgb(0xFF, 0xFD, 0xD8, 0x35), |
|||
Color.FromArgb(0xFF, 0xFB, 0xC0, 0x2D), |
|||
Color.FromArgb(0xFF, 0xF9, 0xA8, 0x25), |
|||
Color.FromArgb(0xFF, 0xF5, 0x7F, 0x17), |
|||
}, |
|||
|
|||
// Amber
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFF, 0xF8, 0xE1), |
|||
Color.FromArgb(0xFF, 0xFF, 0xEC, 0xB3), |
|||
Color.FromArgb(0xFF, 0xFF, 0xE0, 0x82), |
|||
Color.FromArgb(0xFF, 0xFF, 0xD5, 0x4F), |
|||
Color.FromArgb(0xFF, 0xFF, 0xCA, 0x28), |
|||
Color.FromArgb(0xFF, 0xFF, 0xC1, 0x07), |
|||
Color.FromArgb(0xFF, 0xFF, 0xB3, 0x00), |
|||
Color.FromArgb(0xFF, 0xFF, 0xA0, 0x00), |
|||
Color.FromArgb(0xFF, 0xFF, 0x8F, 0x00), |
|||
Color.FromArgb(0xFF, 0xFF, 0x6F, 0x00), |
|||
}, |
|||
|
|||
// Orange
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFF, 0xF3, 0xE0), |
|||
Color.FromArgb(0xFF, 0xFF, 0xE0, 0xB2), |
|||
Color.FromArgb(0xFF, 0xFF, 0xCC, 0x80), |
|||
Color.FromArgb(0xFF, 0xFF, 0xB7, 0x4D), |
|||
Color.FromArgb(0xFF, 0xFF, 0xA7, 0x26), |
|||
Color.FromArgb(0xFF, 0xFF, 0x98, 0x00), |
|||
Color.FromArgb(0xFF, 0xFB, 0x8C, 0x00), |
|||
Color.FromArgb(0xFF, 0xF5, 0x7C, 0x00), |
|||
Color.FromArgb(0xFF, 0xEF, 0x6C, 0x00), |
|||
Color.FromArgb(0xFF, 0xE6, 0x51, 0x00), |
|||
}, |
|||
|
|||
// Deep Orange
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFB, 0xE9, 0xE7), |
|||
Color.FromArgb(0xFF, 0xFF, 0xCC, 0xBC), |
|||
Color.FromArgb(0xFF, 0xFF, 0xAB, 0x91), |
|||
Color.FromArgb(0xFF, 0xFF, 0x8A, 0x65), |
|||
Color.FromArgb(0xFF, 0xFF, 0x70, 0x43), |
|||
Color.FromArgb(0xFF, 0xFF, 0x57, 0x22), |
|||
Color.FromArgb(0xFF, 0xF4, 0x51, 0x1E), |
|||
Color.FromArgb(0xFF, 0xE6, 0x4A, 0x19), |
|||
Color.FromArgb(0xFF, 0xD8, 0x43, 0x15), |
|||
Color.FromArgb(0xFF, 0xBF, 0x36, 0x0C), |
|||
}, |
|||
|
|||
// Brown
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xEF, 0xEB, 0xE9), |
|||
Color.FromArgb(0xFF, 0xD7, 0xCC, 0xC8), |
|||
Color.FromArgb(0xFF, 0xBC, 0xAA, 0xA4), |
|||
Color.FromArgb(0xFF, 0xA1, 0x88, 0x7F), |
|||
Color.FromArgb(0xFF, 0x8D, 0x6E, 0x63), |
|||
Color.FromArgb(0xFF, 0x79, 0x55, 0x48), |
|||
Color.FromArgb(0xFF, 0x6D, 0x4C, 0x41), |
|||
Color.FromArgb(0xFF, 0x5D, 0x40, 0x37), |
|||
Color.FromArgb(0xFF, 0x4E, 0x34, 0x2E), |
|||
Color.FromArgb(0xFF, 0x3E, 0x27, 0x23), |
|||
}, |
|||
|
|||
// Gray
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xFA, 0xFA, 0xFA), |
|||
Color.FromArgb(0xFF, 0xF5, 0xF5, 0xF5), |
|||
Color.FromArgb(0xFF, 0xEE, 0xEE, 0xEE), |
|||
Color.FromArgb(0xFF, 0xE0, 0xE0, 0xE0), |
|||
Color.FromArgb(0xFF, 0xBD, 0xBD, 0xBD), |
|||
Color.FromArgb(0xFF, 0x9E, 0x9E, 0x9E), |
|||
Color.FromArgb(0xFF, 0x75, 0x75, 0x75), |
|||
Color.FromArgb(0xFF, 0x61, 0x61, 0x61), |
|||
Color.FromArgb(0xFF, 0x42, 0x42, 0x42), |
|||
Color.FromArgb(0xFF, 0x21, 0x21, 0x21), |
|||
}, |
|||
|
|||
// Blue Gray
|
|||
{ |
|||
Color.FromArgb(0xFF, 0xEC, 0xEF, 0xF1), |
|||
Color.FromArgb(0xFF, 0xCF, 0xD8, 0xDC), |
|||
Color.FromArgb(0xFF, 0xB0, 0xBE, 0xC5), |
|||
Color.FromArgb(0xFF, 0x90, 0xA4, 0xAE), |
|||
Color.FromArgb(0xFF, 0x78, 0x90, 0x9C), |
|||
Color.FromArgb(0xFF, 0x60, 0x7D, 0x8B), |
|||
Color.FromArgb(0xFF, 0x54, 0x6E, 0x7A), |
|||
Color.FromArgb(0xFF, 0x45, 0x5A, 0x64), |
|||
Color.FromArgb(0xFF, 0x37, 0x47, 0x4F), |
|||
Color.FromArgb(0xFF, 0x26, 0x32, 0x38), |
|||
}, |
|||
}; |
|||
|
|||
_colorChartColorCount = _colorChart.GetLength(0); |
|||
_colorChartShadeCount = _colorChart.GetLength(1); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ColorCount |
|||
{ |
|||
get |
|||
{ |
|||
if (_colorChart == null) |
|||
{ |
|||
InitColorChart(); |
|||
} |
|||
|
|||
return _colorChartColorCount; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ShadeCount |
|||
{ |
|||
get |
|||
{ |
|||
if (_colorChart == null) |
|||
{ |
|||
InitColorChart(); |
|||
} |
|||
|
|||
return _colorChartShadeCount; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Color GetColor(int colorIndex, int shadeIndex) |
|||
{ |
|||
if (_colorChart == null) |
|||
{ |
|||
InitColorChart(); |
|||
} |
|||
|
|||
return _colorChart![ |
|||
MathUtilities.Clamp(colorIndex, 0, _colorChartColorCount - 1), |
|||
MathUtilities.Clamp(shadeIndex, 0, _colorChartShadeCount - 1)]; |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +0,0 @@ |
|||
<Project> |
|||
<PropertyGroup> |
|||
<EmccTotalMemory>16777216</EmccTotalMemory> |
|||
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> |
|||
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> |
|||
</PropertyGroup> |
|||
</Project> |
|||
@ -1,53 +1,40 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<TargetFramework>net7.0</TargetFramework> |
|||
<PackageId>Avalonia.Web.Blazor</PackageId> |
|||
<LangVersion>preview</LangVersion> |
|||
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver> |
|||
<StaticWebAssetsDisableProjectBuildPropsFileGeneration>true</StaticWebAssetsDisableProjectBuildPropsFileGeneration> |
|||
<ResolveStaticWebAssetsInputsDependsOn>_IncludeGeneratedAvaloniaStaticFiles;$(ResolveStaticWebAssetsInputsDependsOn)</ResolveStaticWebAssetsInputsDependsOn> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<SupportedPlatform Include="browser" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\..\build\BuildTargets.targets" /> |
|||
<Import Project="..\..\..\build\SkiaSharp.props" /> |
|||
<Import Project="..\..\..\build\HarfBuzzSharp.props" /> |
|||
<Import Project="..\..\..\build\NullableEnable.props" /> |
|||
|
|||
<ItemGroup> |
|||
<Content Include="*.props"> |
|||
<Pack>true</Pack> |
|||
<PackagePath>build\</PackagePath> |
|||
</Content> |
|||
<Content Include="*.targets"> |
|||
<Pack>true</Pack> |
|||
<PackagePath>build\;buildTransitive\</PackagePath> |
|||
</Content> |
|||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0-*" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.8" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<Folder Include="wwwroot\" /> |
|||
<ProjectReference Include="..\Avalonia.Web\Avalonia.Web.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Target Name="NpmInstall" Inputs="webapp/package.json" Outputs="webapp/node_modules/.install-stamp"> |
|||
<Exec Command="npm install" WorkingDirectory="webapp" /> |
|||
<!-- Write the stamp file, so incremental builds work --> |
|||
<Touch Files="webapp/node_modules/.install-stamp" AlwaysCreate="true" /> |
|||
</Target> |
|||
<Target Name="NpmRunBuild" DependsOnTargets="NpmInstall" BeforeTargets="BeforeBuild"> |
|||
<Exec Command="npm run build" WorkingDirectory="webapp" /> |
|||
<Target Name="_IncludeGeneratedAvaloniaStaticFiles"> |
|||
<ItemGroup> |
|||
<_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/**/*.*" /> |
|||
</ItemGroup> |
|||
<DefineStaticWebAssets SourceId="$(PackageId)" |
|||
SourceType="Computed" |
|||
AssetKind="All" |
|||
AssetRole="Primary" |
|||
CopyToOutputDirectory="PreserveNewest" |
|||
CopyToPublishDirectory="PreserveNewest" |
|||
ContentRoot="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/" |
|||
BasePath="_content\$(PackageId)" |
|||
CandidateAssets="@(_AvaloniaWebAssets)" |
|||
RelativePathFilter="**.js"> |
|||
<Output TaskParameter="Assets" ItemName="StaticWebAsset" /> |
|||
</DefineStaticWebAssets> |
|||
</Target> |
|||
</Project> |
|||
|
|||
@ -1,4 +0,0 @@ |
|||
<Project> |
|||
<Import Project="Microsoft.AspNetCore.StaticWebAssets.props" /> |
|||
<Import Project="Avalonia.Web.Blazor.CompilationTuning.props" /> |
|||
</Project> |
|||
@ -1,6 +0,0 @@ |
|||
<Project> |
|||
<ItemGroup> |
|||
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\2.0.23\libHarfBuzzSharp.a" /> |
|||
<NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\2.0.23\libSkiaSharp.a" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,20 +0,0 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
public class AvaloniaBlazorAppBuilder : AppBuilderBase<AvaloniaBlazorAppBuilder> |
|||
{ |
|||
public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action<AvaloniaBlazorAppBuilder> platformServices) |
|||
: base(platform, platformServices) |
|||
{ |
|||
} |
|||
|
|||
public AvaloniaBlazorAppBuilder() |
|||
: base(new StandardRuntimePlatform(), |
|||
builder => StandardRuntimePlatformServices.Register(builder.ApplicationType!.Assembly)) |
|||
{ |
|||
UseWindowingSubsystem(BlazorWindowingPlatform.Register); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
using System.Runtime.Versioning; |
|||
using System.Threading.Tasks; |
|||
using System; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Microsoft.AspNetCore.Components; |
|||
using Microsoft.AspNetCore.Components.Rendering; |
|||
using BrowserView = Avalonia.Web.AvaloniaView; |
|||
|
|||
namespace Avalonia.Web.Blazor; |
|||
|
|||
[SupportedOSPlatform("browser")] |
|||
public class AvaloniaView : ComponentBase |
|||
{ |
|||
private BrowserView? _browserView; |
|||
private readonly string _containerId; |
|||
|
|||
public AvaloniaView() |
|||
{ |
|||
_containerId = "av_container_" + Guid.NewGuid(); |
|||
} |
|||
|
|||
protected override void BuildRenderTree(RenderTreeBuilder builder) |
|||
{ |
|||
builder.OpenElement(0, "div"); |
|||
builder.AddAttribute(1, "id", _containerId); |
|||
builder.AddAttribute(2, "style", "width:100vw;height:100vh"); |
|||
builder.CloseElement(); |
|||
} |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
if (OperatingSystem.IsBrowser()) |
|||
{ |
|||
await Avalonia.Web.Interop.AvaloniaModule.ImportMain(); |
|||
|
|||
_browserView = new BrowserView(_containerId); |
|||
if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) |
|||
{ |
|||
_browserView.Content = lifetime.MainView; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
<div id="container" @ref="_containerElement" |
|||
class="avalonia-container" |
|||
tabindex="0" oncontextmenu="return false;" |
|||
@onwheel="OnWheel" |
|||
@onkeydown="OnKeyDown" |
|||
@onkeyup="OnKeyUp" |
|||
@onkeyup:preventDefault="@KeyPreventDefault" |
|||
@onkeydown:preventDefault="@KeyPreventDefault" |
|||
@onpointerdown="OnPointerDown" |
|||
@onpointerup="OnPointerUp" |
|||
@onpointermove="OnPointerMove" |
|||
@onpointercancel="OnPointerCancel" |
|||
@onfocus="OnFocus"> |
|||
|
|||
<canvas id="htmlCanvas" @ref="_htmlCanvas" @attributes="AdditionalAttributes"/> |
|||
|
|||
<div id="nativeControlsContainer" @ref="_nativeControlsContainer" /> |
|||
|
|||
<input id="inputElement" @ref="_inputElement" type="text" |
|||
spellcheck="false" onpaste="return false;" |
|||
oncopy="return false;" |
|||
oncut="return false;" |
|||
autocapitalize="none"/> |
|||
</div> |
|||
|
|||
<style> |
|||
#container{ |
|||
touch-action: none; |
|||
} |
|||
#htmlCanvas { |
|||
opacity: 1; |
|||
background-color: #ccc; |
|||
position: fixed; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
top: 0px; |
|||
left: 0px; |
|||
z-index: 500; |
|||
} |
|||
|
|||
#nativeControlsContainer { |
|||
position: fixed; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
top: 0px; |
|||
left: 0px; |
|||
z-index: 700; |
|||
} |
|||
|
|||
#inputElement { |
|||
padding: 0; |
|||
margin: 0; |
|||
position: absolute; |
|||
height: 20px; |
|||
z-index: 1000; |
|||
overflow: hidden; |
|||
caret-color: transparent; |
|||
border-top-style: hidden; |
|||
border-bottom-style: hidden; |
|||
border-right-style: hidden; |
|||
border-left-style: hidden; |
|||
outline: none; |
|||
background: transparent; |
|||
color: transparent; |
|||
} |
|||
|
|||
</style> |
|||
@ -1,500 +0,0 @@ |
|||
using System.Diagnostics; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Controls.Embedding; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Input.TextInput; |
|||
using Avalonia.Platform.Storage; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Web.Blazor.Interop; |
|||
using Avalonia.Web.Blazor.Interop.Storage; |
|||
|
|||
using Microsoft.AspNetCore.Components; |
|||
using Microsoft.AspNetCore.Components.Forms; |
|||
using Microsoft.AspNetCore.Components.Web; |
|||
using Microsoft.JSInterop; |
|||
|
|||
using SkiaSharp; |
|||
using HTMLPointerEventArgs = Microsoft.AspNetCore.Components.Web.PointerEventArgs; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
public partial class AvaloniaView : ITextInputMethodImpl |
|||
{ |
|||
private readonly RazorViewTopLevelImpl _topLevelImpl; |
|||
private EmbeddableControlRoot _topLevel; |
|||
|
|||
// Interop
|
|||
private SKHtmlCanvasInterop? _interop = null; |
|||
private SizeWatcherInterop? _sizeWatcher = null; |
|||
private DpiWatcherInterop? _dpiWatcher = null; |
|||
private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null; |
|||
private AvaloniaModule? _avaloniaModule = null; |
|||
private InputHelperInterop? _inputHelper = null; |
|||
private FocusHelperInterop? _canvasHelper = null; |
|||
private FocusHelperInterop? _containerHelper = null; |
|||
private NativeControlHostInterop? _nativeControlHost = null; |
|||
private StorageProviderInterop? _storageProvider = null; |
|||
private ElementReference _htmlCanvas; |
|||
private ElementReference _inputElement; |
|||
private ElementReference _containerElement; |
|||
private ElementReference _nativeControlsContainer; |
|||
private double _dpi = 1; |
|||
private SKSize _canvasSize = new (100, 100); |
|||
|
|||
private GRContext? _context; |
|||
private GRGlInterface? _glInterface; |
|||
private const SKColorType ColorType = SKColorType.Rgba8888; |
|||
|
|||
private bool _useGL; |
|||
private bool _inputElementFocused; |
|||
|
|||
private ITextInputMethodClient? _client; |
|||
|
|||
|
|||
[Inject] private IJSRuntime Js { get; set; } = null!; |
|||
|
|||
public AvaloniaView() |
|||
{ |
|||
_topLevelImpl = new RazorViewTopLevelImpl(this); |
|||
|
|||
_topLevel = new EmbeddableControlRoot(_topLevelImpl); |
|||
|
|||
if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime) |
|||
{ |
|||
_topLevel.Content = lifetime.MainView; |
|||
} |
|||
} |
|||
|
|||
public bool KeyPreventDefault { get; set; } |
|||
|
|||
public ITextInputMethodClient? Client => _client; |
|||
|
|||
public bool IsActive => _client != null; |
|||
|
|||
public bool IsComposing { get; private set; } |
|||
|
|||
internal INativeControlHostImpl GetNativeControlHostImpl() |
|||
{ |
|||
return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); |
|||
} |
|||
|
|||
internal IStorageProvider GetStorageProvider() |
|||
{ |
|||
return _storageProvider ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); |
|||
} |
|||
|
|||
private void OnPointerCancel(HTMLPointerEventArgs e) |
|||
{ |
|||
if (e.PointerType == "touch") |
|||
{ |
|||
_topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, e.PointerType, GetPointFromEventArgs(e), |
|||
GetModifiers(e), e.PointerId); |
|||
} |
|||
} |
|||
|
|||
private void OnPointerMove(HTMLPointerEventArgs e) |
|||
{ |
|||
var type = e.PointerType switch |
|||
{ |
|||
"touch" => RawPointerEventType.TouchUpdate, |
|||
_ => RawPointerEventType.Move |
|||
}; |
|||
|
|||
_topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); |
|||
} |
|||
|
|||
private void OnPointerUp(HTMLPointerEventArgs e) |
|||
{ |
|||
var type = e.PointerType switch |
|||
{ |
|||
"touch" => RawPointerEventType.TouchEnd, |
|||
_ => e.Button switch |
|||
{ |
|||
0 => RawPointerEventType.LeftButtonUp, |
|||
1 => RawPointerEventType.MiddleButtonUp, |
|||
2 => RawPointerEventType.RightButtonUp, |
|||
3 => RawPointerEventType.XButton1Up, |
|||
4 => RawPointerEventType.XButton2Up, |
|||
// 5 => Pen eraser button,
|
|||
_ => RawPointerEventType.Move |
|||
} |
|||
}; |
|||
|
|||
_topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); |
|||
} |
|||
|
|||
private void OnPointerDown(HTMLPointerEventArgs e) |
|||
{ |
|||
var type = e.PointerType switch |
|||
{ |
|||
"touch" => RawPointerEventType.TouchBegin, |
|||
_ => e.Button switch |
|||
{ |
|||
0 => RawPointerEventType.LeftButtonDown, |
|||
1 => RawPointerEventType.MiddleButtonDown, |
|||
2 => RawPointerEventType.RightButtonDown, |
|||
3 => RawPointerEventType.XButton1Down, |
|||
4 => RawPointerEventType.XButton2Down, |
|||
// 5 => Pen eraser button,
|
|||
_ => RawPointerEventType.Move |
|||
} |
|||
}; |
|||
|
|||
_topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId); |
|||
} |
|||
|
|||
private static RawPointerPoint GetPointFromEventArgs(HTMLPointerEventArgs args) |
|||
{ |
|||
return new RawPointerPoint |
|||
{ |
|||
Position = new Point(args.ClientX, args.ClientY), |
|||
Pressure = args.Pressure, |
|||
XTilt = args.TiltX, |
|||
YTilt = args.TiltY |
|||
// Twist = args.Twist - read from JS code directly when
|
|||
}; |
|||
} |
|||
|
|||
private void OnWheel(WheelEventArgs e) |
|||
{ |
|||
_topLevelImpl.RawMouseWheelEvent( new Point(e.ClientX, e.ClientY), |
|||
new Vector(-(e.DeltaX / 50), -(e.DeltaY / 50)), GetModifiers(e)); |
|||
} |
|||
|
|||
private static RawInputModifiers GetModifiers(MouseEventArgs e) |
|||
{ |
|||
var modifiers = RawInputModifiers.None; |
|||
|
|||
if (e.CtrlKey) |
|||
modifiers |= RawInputModifiers.Control; |
|||
if (e.AltKey) |
|||
modifiers |= RawInputModifiers.Alt; |
|||
if (e.ShiftKey) |
|||
modifiers |= RawInputModifiers.Shift; |
|||
if (e.MetaKey) |
|||
modifiers |= RawInputModifiers.Meta; |
|||
|
|||
if ((e.Buttons & 1L) == 1) |
|||
modifiers |= RawInputModifiers.LeftMouseButton; |
|||
|
|||
if ((e.Buttons & 2L) == 2) |
|||
modifiers |= e.Type == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton; |
|||
|
|||
if ((e.Buttons & 4L) == 4) |
|||
modifiers |= RawInputModifiers.MiddleMouseButton; |
|||
|
|||
if ((e.Buttons & 8L) == 8) |
|||
modifiers |= RawInputModifiers.XButton1MouseButton; |
|||
|
|||
if ((e.Buttons & 16L) == 16) |
|||
modifiers |= RawInputModifiers.XButton2MouseButton; |
|||
|
|||
if ((e.Buttons & 32L) == 32) |
|||
modifiers |= RawInputModifiers.PenEraser; |
|||
|
|||
return modifiers; |
|||
} |
|||
|
|||
private static RawInputModifiers GetModifiers(KeyboardEventArgs e) |
|||
{ |
|||
var modifiers = RawInputModifiers.None; |
|||
|
|||
if (e.CtrlKey) |
|||
modifiers |= RawInputModifiers.Control; |
|||
if (e.AltKey) |
|||
modifiers |= RawInputModifiers.Alt; |
|||
if (e.ShiftKey) |
|||
modifiers |= RawInputModifiers.Shift; |
|||
if (e.MetaKey) |
|||
modifiers |= RawInputModifiers.Meta; |
|||
|
|||
return modifiers; |
|||
} |
|||
|
|||
private void OnKeyDown(KeyboardEventArgs e) |
|||
{ |
|||
KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, e.Key, GetModifiers(e)); |
|||
} |
|||
|
|||
private void OnKeyUp(KeyboardEventArgs e) |
|||
{ |
|||
KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, e.Key, GetModifiers(e)); |
|||
} |
|||
|
|||
private void OnFocus(FocusEventArgs e) |
|||
{ |
|||
// if focus has unexpectedly moved from the input element to the container element,
|
|||
// shift it back to the input element
|
|||
if (_inputElementFocused && _inputHelper is not null) |
|||
{ |
|||
_inputHelper.Focus(); |
|||
} |
|||
} |
|||
|
|||
[Parameter(CaptureUnmatchedValues = true)] |
|||
public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; } |
|||
|
|||
protected override async Task OnAfterRenderAsync(bool firstRender) |
|||
{ |
|||
if (firstRender) |
|||
{ |
|||
AvaloniaLocator.CurrentMutable.Bind<IJSInProcessRuntime>().ToConstant((IJSInProcessRuntime)Js); |
|||
|
|||
_avaloniaModule = await AvaloniaModule.ImportAsync(Js); |
|||
|
|||
_canvasHelper = new FocusHelperInterop(_avaloniaModule, _htmlCanvas); |
|||
_containerHelper = new FocusHelperInterop(_avaloniaModule, _containerElement); |
|||
_inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement); |
|||
|
|||
_inputHelper.CompositionEvent += InputHelperOnCompositionEvent; |
|||
_inputHelper.InputEvent += InputHelperOnInputEvent; |
|||
|
|||
HideIme(); |
|||
_canvasHelper.SetCursor("default"); |
|||
_topLevelImpl.SetCssCursor = x => |
|||
{ |
|||
_inputHelper.SetCursor(x); //macOS
|
|||
_canvasHelper.SetCursor(x); //windows
|
|||
}; |
|||
|
|||
_nativeControlHost = new NativeControlHostInterop(_avaloniaModule, _nativeControlsContainer); |
|||
_storageProvider = await StorageProviderInterop.ImportAsync(Js); |
|||
|
|||
Console.WriteLine("starting html canvas setup"); |
|||
_interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame); |
|||
|
|||
Console.WriteLine("Interop created"); |
|||
|
|||
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>(); |
|||
_useGL = skiaOptions?.CustomGpuFactory != null; |
|||
|
|||
if (_useGL) |
|||
{ |
|||
_jsGlInfo = _interop.InitGL(); |
|||
Console.WriteLine("jsglinfo created - init gl"); |
|||
} |
|||
else |
|||
{ |
|||
var rasterInitialized = _interop.InitRaster(); |
|||
Console.WriteLine("raster initialized: {0}", rasterInitialized); |
|||
} |
|||
|
|||
if (_useGL) |
|||
{ |
|||
// create the SkiaSharp context
|
|||
if (_context == null) |
|||
{ |
|||
_glInterface = GRGlInterface.Create(); |
|||
_context = GRContext.CreateGl(_glInterface); |
|||
|
|||
|
|||
// bump the default resource cache limit
|
|||
_context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); |
|||
Console.WriteLine("glcontext created and resource limit set"); |
|||
} |
|||
|
|||
_topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType, |
|||
new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi); |
|||
} |
|||
else |
|||
{ |
|||
_topLevelImpl.SetSurface(ColorType, |
|||
new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData); |
|||
} |
|||
|
|||
_interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); |
|||
|
|||
Threading.Dispatcher.UIThread.Post(async () => |
|||
{ |
|||
_interop.RequestAnimationFrame(true); |
|||
|
|||
_sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged); |
|||
_dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged); |
|||
|
|||
_sizeWatcher.Start(); |
|||
_topLevel.Prepare(); |
|||
|
|||
_topLevel.Renderer.Start(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private void InputHelperOnInputEvent(object? sender, WebInputEventArgs e) |
|||
{ |
|||
if (IsComposing) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_topLevelImpl.RawTextEvent(e.Data); |
|||
|
|||
e.Handled = true; |
|||
} |
|||
|
|||
private void InputHelperOnCompositionEvent(object? sender, WebCompositionEventArgs e) |
|||
{ |
|||
if(_client == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
switch (e.Type) |
|||
{ |
|||
case WebCompositionEventArgs.WebCompositionEventType.Start: |
|||
_client.SetPreeditText(null); |
|||
IsComposing = true; |
|||
break; |
|||
case WebCompositionEventArgs.WebCompositionEventType.Update: |
|||
_client.SetPreeditText(e.Data); |
|||
break; |
|||
case WebCompositionEventArgs.WebCompositionEventType.End: |
|||
IsComposing = false; |
|||
_client.SetPreeditText(null); |
|||
_topLevelImpl.RawTextEvent(e.Data); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private void OnRenderFrame() |
|||
{ |
|||
if (_useGL && (_jsGlInfo == null)) |
|||
{ |
|||
Console.WriteLine("nothing to render"); |
|||
return; |
|||
} |
|||
if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0) |
|||
{ |
|||
Console.WriteLine("nothing to render"); |
|||
return; |
|||
} |
|||
|
|||
ManualTriggerRenderTimer.Instance.RaiseTick(); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_dpiWatcher?.Unsubscribe(OnDpiChanged); |
|||
_sizeWatcher?.Dispose(); |
|||
_interop?.Dispose(); |
|||
} |
|||
|
|||
private void ForceBlit() |
|||
{ |
|||
// Note: this is technically a hack, but it's a kinda unique use case when
|
|||
// we want to blit the previous frame
|
|||
// renderer doesn't have much control over the render target
|
|||
// we render on the UI thread
|
|||
// We also don't want to have it as a meaningful public API.
|
|||
// Therefore we have InternalsVisibleTo hack here.
|
|||
|
|||
if (_topLevel.Renderer is CompositingRenderer dr) |
|||
{ |
|||
dr.CompositionTarget.ImmediateUIThreadRender(); |
|||
} |
|||
} |
|||
|
|||
private void OnDpiChanged(double newDpi) |
|||
{ |
|||
if (Math.Abs(_dpi - newDpi) > 0.0001) |
|||
{ |
|||
_dpi = newDpi; |
|||
|
|||
_interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); |
|||
|
|||
_topLevelImpl.SetClientSize(_canvasSize, _dpi); |
|||
|
|||
ForceBlit(); |
|||
} |
|||
} |
|||
|
|||
private void OnSizeChanged(SKSize newSize) |
|||
{ |
|||
if (_canvasSize != newSize) |
|||
{ |
|||
_canvasSize = newSize; |
|||
|
|||
_interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); |
|||
|
|||
_topLevelImpl.SetClientSize(_canvasSize, _dpi); |
|||
|
|||
ForceBlit(); |
|||
} |
|||
} |
|||
|
|||
private void HideIme() |
|||
{ |
|||
_inputHelper?.Hide(); |
|||
_containerHelper?.Focus(); |
|||
} |
|||
|
|||
public void SetClient(ITextInputMethodClient? client) |
|||
{ |
|||
if (_inputHelper is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if(_client != null) |
|||
{ |
|||
_client.SurroundingTextChanged -= SurroundingTextChanged; |
|||
} |
|||
|
|||
if(client != null) |
|||
{ |
|||
client.SurroundingTextChanged += SurroundingTextChanged; |
|||
} |
|||
|
|||
_inputHelper.Clear(); |
|||
|
|||
_client = client; |
|||
|
|||
if (IsActive && _client != null) |
|||
{ |
|||
_inputHelper.Show(); |
|||
_inputElementFocused = true; |
|||
_inputHelper.Focus(); |
|||
|
|||
var surroundingText = _client.SurroundingText; |
|||
|
|||
_inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); |
|||
} |
|||
else |
|||
{ |
|||
_inputElementFocused = false; |
|||
HideIme(); |
|||
} |
|||
} |
|||
|
|||
private void SurroundingTextChanged(object? sender, EventArgs e) |
|||
{ |
|||
if(_client != null) |
|||
{ |
|||
var surroundingText = _client.SurroundingText; |
|||
|
|||
_inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset); |
|||
} |
|||
} |
|||
|
|||
public void SetCursorRect(Rect rect) |
|||
{ |
|||
_inputHelper?.Focus(); |
|||
var bounds = new PixelRect((int)rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height); |
|||
|
|||
_inputHelper?.SetBounds(bounds, _client?.SurroundingText.CursorOffset ?? 0); |
|||
_inputHelper?.Focus(); |
|||
} |
|||
|
|||
public void SetOptions(TextInputOptions options) |
|||
{ |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
_inputHelper?.Clear(); |
|||
_inputHelper?.SetSurroundingText("", 0, 0); |
|||
} |
|||
} |
|||
} |
|||
@ -1,31 +1,39 @@ |
|||
using Avalonia.Controls; |
|||
using System.Runtime.Versioning; |
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
namespace Avalonia.Web.Blazor; |
|||
|
|||
[SupportedOSPlatform("browser")] |
|||
public static class WebAppBuilder |
|||
{ |
|||
public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime |
|||
public static T SetupWithSingleViewLifetime<T>( |
|||
this T builder) |
|||
where T : AppBuilderBase<T>, new() |
|||
{ |
|||
public Control? MainView { get; set; } |
|||
return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); |
|||
} |
|||
|
|||
public static class WebAppBuilder |
|||
public static T UseBlazor<T>(this T builder) where T : AppBuilderBase<T>, new() |
|||
{ |
|||
public static T SetupWithSingleViewLifetime<T>( |
|||
this T builder) |
|||
where T : AppBuilderBase<T>, new() |
|||
{ |
|||
return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); |
|||
} |
|||
return builder |
|||
.UseBrowser() |
|||
.With(new BrowserPlatformOptions |
|||
{ |
|||
FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Web.Blazor/{filePath}") |
|||
}); |
|||
} |
|||
|
|||
public static AvaloniaBlazorAppBuilder Configure<TApp>() |
|||
where TApp : Application, new() |
|||
{ |
|||
var builder = AvaloniaBlazorAppBuilder.Configure<TApp>() |
|||
.UseSkia() |
|||
.With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() }); |
|||
public static AppBuilder Configure<TApp>() |
|||
where TApp : Application, new() |
|||
{ |
|||
return AppBuilder.Configure<TApp>() |
|||
.UseBlazor(); |
|||
} |
|||
|
|||
return builder; |
|||
} |
|||
internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime |
|||
{ |
|||
public Control? MainView { get; set; } |
|||
} |
|||
} |
|||
|
|||
@ -1,25 +0,0 @@ |
|||
using Avalonia.Skia; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
public class BlazorSkiaGpu : ISkiaGpu |
|||
{ |
|||
public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces) |
|||
{ |
|||
foreach (var surface in surfaces) |
|||
{ |
|||
if (surface is BlazorSkiaSurface blazorSkiaSurface) |
|||
{ |
|||
return new BlazorSkiaGpuRenderTarget(blazorSkiaSurface); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
using Avalonia.Skia; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
internal class BlazorSkiaGpuRenderSession : ISkiaGpuRenderSession |
|||
{ |
|||
private readonly SKSurface _surface; |
|||
|
|||
|
|||
public BlazorSkiaGpuRenderSession(BlazorSkiaSurface blazorSkiaSurface, GRBackendRenderTarget renderTarget) |
|||
{ |
|||
_surface = SKSurface.Create(blazorSkiaSurface.Context, renderTarget, blazorSkiaSurface.Origin, blazorSkiaSurface.ColorType); |
|||
|
|||
GrContext = blazorSkiaSurface.Context; |
|||
|
|||
ScaleFactor = blazorSkiaSurface.Scaling; |
|||
|
|||
SurfaceOrigin = blazorSkiaSurface.Origin; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_surface.Flush(); |
|||
|
|||
_surface.Dispose(); |
|||
} |
|||
|
|||
public GRContext GrContext { get; } |
|||
|
|||
public SKSurface SkSurface => _surface; |
|||
|
|||
public double ScaleFactor { get; } |
|||
|
|||
public GRSurfaceOrigin SurfaceOrigin { get; } |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
using Avalonia.Skia; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
internal class BlazorSkiaGpuRenderTarget : ISkiaGpuRenderTarget |
|||
{ |
|||
private readonly GRBackendRenderTarget _renderTarget; |
|||
private readonly BlazorSkiaSurface _blazorSkiaSurface; |
|||
private readonly PixelSize _size; |
|||
|
|||
public BlazorSkiaGpuRenderTarget(BlazorSkiaSurface blazorSkiaSurface) |
|||
{ |
|||
_size = blazorSkiaSurface.Size; |
|||
|
|||
var glFbInfo = new GRGlFramebufferInfo(blazorSkiaSurface.GlInfo.FboId, blazorSkiaSurface.ColorType.ToGlSizedFormat()); |
|||
{ |
|||
_blazorSkiaSurface = blazorSkiaSurface; |
|||
_renderTarget = new GRBackendRenderTarget( |
|||
(int)(blazorSkiaSurface.Size.Width * blazorSkiaSurface.Scaling), |
|||
(int)(blazorSkiaSurface.Size.Height * blazorSkiaSurface.Scaling), |
|||
blazorSkiaSurface.GlInfo.Samples, |
|||
blazorSkiaSurface.GlInfo.Stencils, glFbInfo); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_renderTarget.Dispose(); |
|||
} |
|||
|
|||
public ISkiaGpuRenderSession BeginRenderingSession() |
|||
{ |
|||
return new BlazorSkiaGpuRenderSession(_blazorSkiaSurface, _renderTarget); |
|||
} |
|||
|
|||
public bool IsCorrupted => _blazorSkiaSurface.Size != _size; |
|||
} |
|||
} |
|||
@ -1,87 +0,0 @@ |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Controls.Platform.Surfaces; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Skia; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable |
|||
{ |
|||
public SKColorType ColorType { get; set; } |
|||
|
|||
public PixelSize Size { get; set; } |
|||
|
|||
public double Scaling { get; set; } |
|||
|
|||
private FramebufferData? _fbData; |
|||
private readonly Action<IntPtr, SKSizeI> _blitCallback; |
|||
private readonly Action _onDisposeAction; |
|||
|
|||
public BlazorSkiaRasterSurface( |
|||
SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> blitCallback) |
|||
{ |
|||
ColorType = colorType; |
|||
Size = size; |
|||
Scaling = scaling; |
|||
_blitCallback = blitCallback; |
|||
_onDisposeAction = Blit; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_fbData?.Dispose(); |
|||
_fbData = null; |
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() |
|||
{ |
|||
var bytesPerPixel = 4; // TODO: derive from ColorType
|
|||
var dpi = Scaling * 96.0; |
|||
var width = (int)(Size.Width * Scaling); |
|||
var height = (int)(Size.Height * Scaling); |
|||
|
|||
if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height) |
|||
{ |
|||
_fbData?.Dispose(); |
|||
_fbData = new FramebufferData(width, height, bytesPerPixel); |
|||
} |
|||
|
|||
var pixelFormat = ColorType.ToPixelFormat(); |
|||
var data = _fbData.Value; |
|||
return new LockedFramebuffer( |
|||
data.Address, data.Size, data.RowBytes, |
|||
new Vector(dpi, dpi), pixelFormat, _onDisposeAction); |
|||
} |
|||
|
|||
private void Blit() |
|||
{ |
|||
if (_fbData != null) |
|||
{ |
|||
var data = _fbData.Value; |
|||
_blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height)); |
|||
} |
|||
} |
|||
|
|||
private readonly struct FramebufferData |
|||
{ |
|||
public PixelSize Size { get; } |
|||
|
|||
public int RowBytes { get; } |
|||
|
|||
public IntPtr Address { get; } |
|||
|
|||
public FramebufferData(int width, int height, int bytesPerPixel) |
|||
{ |
|||
Size = new PixelSize(width, height); |
|||
RowBytes = width * bytesPerPixel; |
|||
Address = Marshal.AllocHGlobal(width * height * bytesPerPixel); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Marshal.FreeHGlobal(Address); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue