1.0.4-alpha
This commit is contained in:
Родитель
a896c01bcf
Коммит
f527b2ad1e
|
@ -1,4 +1,6 @@
|
|||
using FluidSharp.Engine;
|
||||
#if !__IOS__
|
||||
|
||||
using FluidSharp.Engine;
|
||||
using FluidSharp.Interop;
|
||||
using FluidSharp.State;
|
||||
using FluidSharp.Widgets;
|
||||
|
@ -231,3 +233,5 @@ namespace FluidSharp.Views.UWP
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -3,6 +3,13 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<Version>1.0.3-alpha</Version>
|
||||
<Authors>Wouter Steenbergen</Authors>
|
||||
<Company>My Daily Bits LLC</Company>
|
||||
<Product>FluidSharp</Product>
|
||||
<Description>FluidSharp is a high performance mobile first multi-platform UI layout framework based on Skia.</Description>
|
||||
<Copyright>2020 Wouter Steenbergen</Copyright>
|
||||
<PackageLicenseFile>License.txt</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
@ -15,6 +22,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<None Remove="License.txt" />
|
||||
<None Include="License.txt">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -28,11 +39,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp.Views.WindowsForms" Version="2.80.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FluidSharp\FluidSharp.csproj" />
|
||||
<PackageReference Include="SkiaSharp.Views.WindowsForms" Version="2.80.3" />
|
||||
<PackageReference Include="FluidSharp" Version="1.0.3-alpha" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>FluidSharp.Views.WindowsForms.Core</id>
|
||||
<version>1.0.1-alpha</version>
|
||||
<title>$title$</title>
|
||||
<authors>Wouter Steenbergen</authors>
|
||||
<owners>My Daily Bits LLC</owners>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<license type="file">License.txt</license>
|
||||
<description>FluidSharp is a high performance mobile first multi-platform UI layout framework based on Skia.</description>
|
||||
<releaseNotes>Initial release</releaseNotes>
|
||||
<copyright>Copyright 2021 Wouter Steenbergen</copyright>
|
||||
<dependencies>
|
||||
<group targetFramework="netcoreapp3.1">
|
||||
<dependency id="FluidSharp" version="1.0.1-alpha" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<!-- Cross-platform reference assemblies -->
|
||||
<file src="License.txt"/>
|
||||
<file src="bin\Release\netcoreapp3.1\FluidSharp.Views.WindowsForms.Core.dll" target="lib\netcoreapp3.1\FluidSharp.Views.WindowsForms.Core.dll" />
|
||||
</files>
|
||||
|
||||
</package>
|
|
@ -39,6 +39,9 @@
|
|||
<Compile Include="..\FluidSharp.Views.Shared\FluidWidgetView.cs">
|
||||
<Link>FluidWidgetView.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="FluidUICanvasViewController.cs" />
|
||||
<Compile Include="FluidWidgetGLView.cs" />
|
||||
<Compile Include="FluidWidgetCanvasView.cs" />
|
||||
<Compile Include="NativeViewManager.cs" />
|
||||
<Compile Include="NativeViews\INativeTextboxImpl.cs" />
|
||||
<Compile Include="NativeViews\NativeMultiLineTextboxImpl.cs">
|
||||
|
@ -51,18 +54,22 @@
|
|||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\KeyboardTracker.cs" />
|
||||
<Compile Include="SkiaView.cs" />
|
||||
<Compile Include="SkiaViews.cs" />
|
||||
<Compile Include="TouchRecognizer.cs" />
|
||||
<Compile Include="FluidUIViewController.cs" />
|
||||
<Compile Include="FluidUIGLViewController.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp">
|
||||
<Version>2.80.2</Version>
|
||||
<Version>2.80.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz">
|
||||
<Version>2.80.2</Version>
|
||||
<Version>2.80.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SkiaSharp.Views">
|
||||
<Version>2.80.2</Version>
|
||||
<Version>2.80.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Drawing.Common">
|
||||
<Version>5.0.2</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package >
|
||||
<metadata>
|
||||
<id>FluidSharp.Views.iOS</id>
|
||||
<version>1.0.3-alpha</version>
|
||||
<version>1.0.4-alpha</version>
|
||||
<title>$title$</title>
|
||||
<authors>Wouter Steenbergen</authors>
|
||||
<owners>My Daily Bits LLC</owners>
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
using CoreGraphics;
|
||||
using FluidSharp.Layouts;
|
||||
using FluidSharp.Views.iOS;
|
||||
using FluidSharp.Views.iOS.Services;
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
|
||||
namespace FluidSharp.Views.iOS
|
||||
{
|
||||
public class FluidUICanvasViewController : UIViewController
|
||||
{
|
||||
|
||||
public readonly IWidgetSource WidgetSource;
|
||||
private FluidWidgetCanvasView FluidWidgetView;
|
||||
|
||||
private KeyboardTracker KeyboardTracker;
|
||||
private nfloat OriginalInsetBottom;
|
||||
|
||||
private Action<Margins> OnDeviceMarginsChanged;
|
||||
|
||||
public FluidUICanvasViewController(IWidgetSource widgetSource, Action<Margins> onDeviceMarginsChanged)
|
||||
{
|
||||
WidgetSource = widgetSource;
|
||||
OnDeviceMarginsChanged = onDeviceMarginsChanged;
|
||||
KeyboardTracker = new KeyboardTracker(h =>
|
||||
{
|
||||
if (h == 0)
|
||||
{
|
||||
AdditionalSafeAreaInsets = new UIEdgeInsets(0, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AdditionalSafeAreaInsets.Bottom == 0)
|
||||
OriginalInsetBottom = View.SafeAreaInsets.Bottom;
|
||||
AdditionalSafeAreaInsets = new UIEdgeInsets(0, 0, h - OriginalInsetBottom, 0);
|
||||
}
|
||||
View.LayoutIfNeeded();
|
||||
});
|
||||
}
|
||||
|
||||
public override void ViewDidLayoutSubviews() { if (FluidWidgetView != null) Task.Run(RequestRedraw); }
|
||||
|
||||
public Task RequestRedraw() => FluidWidgetView?.VisualState.RequestRedraw() ?? Task.CompletedTask;
|
||||
|
||||
public override void LoadView()
|
||||
{
|
||||
#if USEGL
|
||||
FluidWidgetView = new FluidWidgetGLView();
|
||||
#else
|
||||
FluidWidgetView = new FluidWidgetCanvasView();
|
||||
#endif
|
||||
FluidWidgetView.WidgetSource = WidgetSource;
|
||||
View = FluidWidgetView;
|
||||
}
|
||||
|
||||
public override void ViewWillAppear(bool animated) => KeyboardTracker.RegisterForKeyboardNotifications();
|
||||
public override void ViewWillDisappear(bool animated) => KeyboardTracker.UnregisterForKeyboardNotifications();
|
||||
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
base.ViewDidLoad();
|
||||
// Perform any additional setup after loading the view, typically from a nib.
|
||||
}
|
||||
|
||||
public override void DidReceiveMemoryWarning()
|
||||
{
|
||||
base.DidReceiveMemoryWarning();
|
||||
// Release any cached data, images, etc that aren't in use.
|
||||
}
|
||||
|
||||
public override void ViewSafeAreaInsetsDidChange()
|
||||
{
|
||||
var sai = View.SafeAreaInsets;
|
||||
OnDeviceMarginsChanged(new Margins((float)sai.Left, (float)sai.Top, (float)sai.Right, (float)sai.Bottom));
|
||||
base.ViewSafeAreaInsetsDidChange();
|
||||
}
|
||||
|
||||
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
|
||||
{
|
||||
base.TraitCollectionDidChange(previousTraitCollection);
|
||||
// View.TraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,18 +9,18 @@ using UIKit;
|
|||
|
||||
namespace FluidSharp.Views.iOS
|
||||
{
|
||||
public class FluidUIViewController : UIViewController
|
||||
public class FluidUIGLViewController : UIViewController
|
||||
{
|
||||
|
||||
public readonly IWidgetSource WidgetSource;
|
||||
private FluidWidgetView FluidWidgetView;
|
||||
private FluidWidgetGLView FluidWidgetView;
|
||||
|
||||
private KeyboardTracker KeyboardTracker;
|
||||
private nfloat OriginalInsetBottom;
|
||||
|
||||
private Action<Margins> OnDeviceMarginsChanged;
|
||||
|
||||
public FluidUIViewController(IWidgetSource widgetSource, Action<Margins> onDeviceMarginsChanged)
|
||||
public FluidUIGLViewController(IWidgetSource widgetSource, Action<Margins> onDeviceMarginsChanged)
|
||||
{
|
||||
WidgetSource = widgetSource;
|
||||
OnDeviceMarginsChanged = onDeviceMarginsChanged;
|
||||
|
@ -46,7 +46,7 @@ namespace FluidSharp.Views.iOS
|
|||
|
||||
public override void LoadView()
|
||||
{
|
||||
FluidWidgetView = new FluidWidgetView();
|
||||
FluidWidgetView = new FluidWidgetGLView();
|
||||
FluidWidgetView.WidgetSource = WidgetSource;
|
||||
View = FluidWidgetView;
|
||||
}
|
||||
|
@ -79,4 +79,4 @@ namespace FluidSharp.Views.iOS
|
|||
// View.TraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
using FluidSharp.Engine;
|
||||
using FluidSharp.Interop;
|
||||
using FluidSharp.State;
|
||||
using FluidSharp.Widgets;
|
||||
using FluidSharp.Widgets.Native;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
|
||||
#if __FORMS__
|
||||
using FluidSharp.Views.Forms.NativeViews;
|
||||
namespace FluidSharp.Views.Forms
|
||||
#elif __WINDOWSFORMS__
|
||||
using FluidSharp.Views.WindowsForms.NativeViews;
|
||||
using System.Windows.Forms;
|
||||
namespace FluidSharp.Views.WindowsForms
|
||||
#elif __ANDROID__
|
||||
using FluidSharp.Views.Android.NativeViews;
|
||||
using Android.App;
|
||||
using Android.Views;
|
||||
namespace FluidSharp.Views.Android
|
||||
#elif __IOS__
|
||||
using UIKit;
|
||||
using FluidSharp.Views.iOS.NativeViews;
|
||||
namespace FluidSharp.Views.iOS
|
||||
#elif __UWP__
|
||||
namespace FluidSharp.Views.UWP
|
||||
#endif
|
||||
{
|
||||
|
||||
#if __ANDROID__
|
||||
public class FluidWidgetView : global::Android.Widget.RelativeLayout, IFluidWidgetView
|
||||
#else
|
||||
public class FluidWidgetCanvasView : SkiaCanvasView, IFluidWidgetView
|
||||
#endif
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The source of widgets, either overwrite MakeWidget, or set the WidgetSource to implement a custom view
|
||||
/// </summary>
|
||||
public IWidgetSource WidgetSource { get => widgetSource; set { widgetSource = value; SkiaView.InvalidatePaint(); } }
|
||||
private IWidgetSource widgetSource;
|
||||
|
||||
public Device Device;
|
||||
|
||||
private FluidWidgetViewImplementation implementation;
|
||||
public FluidWidgetViewImplementation Implementation
|
||||
{
|
||||
get => implementation;
|
||||
set
|
||||
{
|
||||
if (implementation != null) implementation.Dispose();
|
||||
implementation = value;
|
||||
}
|
||||
}
|
||||
public NativeViewManager NativeViewManager;
|
||||
|
||||
public VisualState VisualState => Implementation.VisualState;
|
||||
|
||||
/// <summary>
|
||||
/// Set AutoSizeHeight to true if the view should be sized by the (painted) height of the widgets.
|
||||
/// The default is false.
|
||||
/// </summary>
|
||||
public bool AutoSizeHeight { get; set; }
|
||||
private float LastPaintWidth = -1;
|
||||
private float LastHeightRequest = -1;
|
||||
|
||||
#if __ANDROID__
|
||||
|
||||
private SkiaView SkiaView;
|
||||
public SKSize PlatformScale => SkiaView.PlatformScale;
|
||||
|
||||
public FluidWidgetView(global::Android.Content.Context context) : base(context)
|
||||
{
|
||||
|
||||
SkiaView = new SkiaView(context);
|
||||
var fillparams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
|
||||
AddView(SkiaView, fillparams);
|
||||
|
||||
#else
|
||||
|
||||
private SkiaCanvasView SkiaView => this;
|
||||
|
||||
public FluidWidgetCanvasView()
|
||||
{
|
||||
#endif
|
||||
|
||||
Device = new Device();
|
||||
|
||||
#if __FORMS__
|
||||
Device.FlowDirection = Xamarin.Forms.Device.FlowDirection == Xamarin.Forms.FlowDirection.RightToLeft ? SkiaSharp.TextBlocks.Enum.FlowDirection.RightToLeft : SkiaSharp.TextBlocks.Enum.FlowDirection.LeftToRight;
|
||||
#endif
|
||||
|
||||
NativeViewManager = new NativeViewManager(this);
|
||||
|
||||
Implementation = new FluidWidgetViewImplementation(SkiaView, this, Device);
|
||||
|
||||
RegisterNativeViews();
|
||||
|
||||
}
|
||||
|
||||
#if __ANDROID__
|
||||
public FluidWidgetView(global::Android.Content.Context context, bool CreatesOwnImplementation) : base(context)
|
||||
#else
|
||||
protected FluidWidgetCanvasView(bool CreatesOwnImplementation)
|
||||
#endif
|
||||
{
|
||||
if (!CreatesOwnImplementation) throw new ArgumentOutOfRangeException(nameof(CreatesOwnImplementation));
|
||||
}
|
||||
|
||||
protected void RegisterNativeViews()
|
||||
{
|
||||
#if __IOS__
|
||||
NativeViewManager.RegisterNativeView<NativeTextboxWidget, UIView>(
|
||||
(w, c) => ((INativeTextboxImpl)c).Context.Equals(w.Context),
|
||||
(w) => w.Keyboard == Keyboard.MultiLine ? (UIView)
|
||||
new NativeMultiLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context } :
|
||||
new NativeSingleLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
|
||||
#else
|
||||
NativeViewManager.RegisterNativeView<NativeTextboxWidget, NativeTextboxImpl>(
|
||||
(w, c) => c.Context.Equals(w.Context),
|
||||
#if __ANDROID__
|
||||
(w) => new NativeTextboxImpl(Context, VisualState.RequestRedraw) { Context = w.Context }
|
||||
#else
|
||||
(w) => new NativeTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
public INativeViewManager GetNativeViewManager() => NativeViewManager;
|
||||
|
||||
/// <summary>
|
||||
/// Either overwrite this method or set WidgetSource to implement a custom view
|
||||
/// </summary>
|
||||
public virtual Widget MakeWidget(VisualState visualState)
|
||||
{
|
||||
if (WidgetSource == null)
|
||||
return Rectangle.Fill(SKColors.Teal);
|
||||
else
|
||||
return WidgetSource.MakeWidget(visualState);
|
||||
}
|
||||
|
||||
public void SetHeight(float height)
|
||||
{
|
||||
// Maximum height: screen / device size
|
||||
#if __FORMS__
|
||||
#elif __WINDOWSFORMS__
|
||||
height = Math.Min(Height, Screen.FromControl(this).WorkingArea.Height);
|
||||
#elif __ANDROID__
|
||||
#elif __IOS__
|
||||
#elif __UWP__
|
||||
#endif
|
||||
|
||||
// Minimum height: 10
|
||||
height = Math.Max(height, 10);
|
||||
|
||||
// Apply height
|
||||
#if __FORMS__
|
||||
if (this.Height < height - 5 || this.Height > height + 5) InvalidateMeasure();
|
||||
#elif __WINDOWSFORMS__
|
||||
this.Height = (int)height;
|
||||
#elif __ANDROID__
|
||||
#elif __IOS__
|
||||
#elif __UWP__
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual SKColor GetBackgroundColor(VisualState visualState)
|
||||
{
|
||||
if (widgetSource is IBackgroundColorSource backgroundColorSource) return backgroundColorSource.GetBackgroundColor(visualState);
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
#if __FORMS__
|
||||
|
||||
protected override void OnSizeAllocated(double width, double height)
|
||||
{
|
||||
base.OnSizeAllocated(width, height);
|
||||
LastPaintWidth = (float)Width;
|
||||
}
|
||||
|
||||
protected override void InvalidateMeasure()
|
||||
{
|
||||
base.InvalidateMeasure();
|
||||
InvalidatePaint();
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
if (BindingContext == null)
|
||||
Implementation.Dispose();
|
||||
}
|
||||
|
||||
protected override Xamarin.Forms.SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
|
||||
{
|
||||
|
||||
if (Width == LastPaintWidth && LastHeightRequest > -1)
|
||||
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(LastPaintWidth, LastHeightRequest));
|
||||
|
||||
var request = Implementation.Measure(new SKSize((float)widthConstraint, (float)heightConstraint));
|
||||
System.Diagnostics.Debug.WriteLine($"LinkTileView Measured: {request} ({widthConstraint}, {heightConstraint}) ");
|
||||
|
||||
if (float.IsInfinity(request.Width) || float.IsInfinity(request.Height))
|
||||
return base.OnMeasure(widthConstraint, heightConstraint);
|
||||
|
||||
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(request.Width, request.Height));
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if __WINDOWSFORMS__
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
Implementation.Dispose();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __ANDROID__
|
||||
public void AddOnMainThread(View childview)
|
||||
{
|
||||
|
||||
((Activity)Context).RunOnUiThread(() => AddView(childview));
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
using FluidSharp.Engine;
|
||||
using FluidSharp.Interop;
|
||||
using FluidSharp.State;
|
||||
using FluidSharp.Widgets;
|
||||
using FluidSharp.Widgets.Native;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
|
||||
#if __FORMS__
|
||||
using FluidSharp.Views.Forms.NativeViews;
|
||||
namespace FluidSharp.Views.Forms
|
||||
#elif __WINDOWSFORMS__
|
||||
using FluidSharp.Views.WindowsForms.NativeViews;
|
||||
using System.Windows.Forms;
|
||||
namespace FluidSharp.Views.WindowsForms
|
||||
#elif __ANDROID__
|
||||
using FluidSharp.Views.Android.NativeViews;
|
||||
using Android.App;
|
||||
using Android.Views;
|
||||
namespace FluidSharp.Views.Android
|
||||
#elif __IOS__
|
||||
using UIKit;
|
||||
using FluidSharp.Views.iOS.NativeViews;
|
||||
namespace FluidSharp.Views.iOS
|
||||
#elif __UWP__
|
||||
namespace FluidSharp.Views.UWP
|
||||
#endif
|
||||
{
|
||||
|
||||
#if __ANDROID__
|
||||
public class FluidWidgetView : global::Android.Widget.RelativeLayout, IFluidWidgetView
|
||||
#else
|
||||
public class FluidWidgetGLView : SkiaGLView, IFluidWidgetView
|
||||
#endif
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The source of widgets, either overwrite MakeWidget, or set the WidgetSource to implement a custom view
|
||||
/// </summary>
|
||||
public IWidgetSource WidgetSource { get => widgetSource; set { widgetSource = value; SkiaView.InvalidatePaint(); } }
|
||||
private IWidgetSource widgetSource;
|
||||
|
||||
public Device Device;
|
||||
|
||||
private FluidWidgetViewImplementation implementation;
|
||||
public FluidWidgetViewImplementation Implementation
|
||||
{
|
||||
get => implementation;
|
||||
set
|
||||
{
|
||||
if (implementation != null) implementation.Dispose();
|
||||
implementation = value;
|
||||
}
|
||||
}
|
||||
public NativeViewManager NativeViewManager;
|
||||
|
||||
public VisualState VisualState => Implementation.VisualState;
|
||||
|
||||
/// <summary>
|
||||
/// Set AutoSizeHeight to true if the view should be sized by the (painted) height of the widgets.
|
||||
/// The default is false.
|
||||
/// </summary>
|
||||
public bool AutoSizeHeight { get; set; }
|
||||
private float LastPaintWidth = -1;
|
||||
private float LastHeightRequest = -1;
|
||||
|
||||
#if __ANDROID__
|
||||
|
||||
private SkiaView SkiaView;
|
||||
public SKSize PlatformScale => SkiaView.PlatformScale;
|
||||
|
||||
public FluidWidgetView(global::Android.Content.Context context) : base(context)
|
||||
{
|
||||
|
||||
SkiaView = new SkiaView(context);
|
||||
var fillparams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
|
||||
AddView(SkiaView, fillparams);
|
||||
|
||||
#else
|
||||
|
||||
private SkiaGLView SkiaView => this;
|
||||
|
||||
public FluidWidgetGLView()
|
||||
{
|
||||
#endif
|
||||
|
||||
Device = new Device();
|
||||
|
||||
#if __FORMS__
|
||||
Device.FlowDirection = Xamarin.Forms.Device.FlowDirection == Xamarin.Forms.FlowDirection.RightToLeft ? SkiaSharp.TextBlocks.Enum.FlowDirection.RightToLeft : SkiaSharp.TextBlocks.Enum.FlowDirection.LeftToRight;
|
||||
#endif
|
||||
|
||||
NativeViewManager = new NativeViewManager(this);
|
||||
|
||||
Implementation = new FluidWidgetViewImplementation(SkiaView, this, Device);
|
||||
|
||||
RegisterNativeViews();
|
||||
|
||||
}
|
||||
|
||||
#if __ANDROID__
|
||||
public FluidWidgetView(global::Android.Content.Context context, bool CreatesOwnImplementation) : base(context)
|
||||
#else
|
||||
protected FluidWidgetGLView(bool CreatesOwnImplementation)
|
||||
#endif
|
||||
{
|
||||
if (!CreatesOwnImplementation) throw new ArgumentOutOfRangeException(nameof(CreatesOwnImplementation));
|
||||
}
|
||||
|
||||
protected void RegisterNativeViews()
|
||||
{
|
||||
#if __IOS__
|
||||
NativeViewManager.RegisterNativeView<NativeTextboxWidget, UIView>(
|
||||
(w, c) => ((INativeTextboxImpl)c).Context.Equals(w.Context),
|
||||
(w) => w.Keyboard == Keyboard.MultiLine ? (UIView)
|
||||
new NativeMultiLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context } :
|
||||
new NativeSingleLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
|
||||
#else
|
||||
NativeViewManager.RegisterNativeView<NativeTextboxWidget, NativeTextboxImpl>(
|
||||
(w, c) => c.Context.Equals(w.Context),
|
||||
#if __ANDROID__
|
||||
(w) => new NativeTextboxImpl(Context, VisualState.RequestRedraw) { Context = w.Context }
|
||||
#else
|
||||
(w) => new NativeTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
public INativeViewManager GetNativeViewManager() => NativeViewManager;
|
||||
|
||||
/// <summary>
|
||||
/// Either overwrite this method or set WidgetSource to implement a custom view
|
||||
/// </summary>
|
||||
public virtual Widget MakeWidget(VisualState visualState)
|
||||
{
|
||||
if (WidgetSource == null)
|
||||
return Rectangle.Fill(SKColors.Teal);
|
||||
else
|
||||
return WidgetSource.MakeWidget(visualState);
|
||||
}
|
||||
|
||||
public void SetHeight(float height)
|
||||
{
|
||||
// Maximum height: screen / device size
|
||||
#if __FORMS__
|
||||
#elif __WINDOWSFORMS__
|
||||
height = Math.Min(Height, Screen.FromControl(this).WorkingArea.Height);
|
||||
#elif __ANDROID__
|
||||
#elif __IOS__
|
||||
#elif __UWP__
|
||||
#endif
|
||||
|
||||
// Minimum height: 10
|
||||
height = Math.Max(height, 10);
|
||||
|
||||
// Apply height
|
||||
#if __FORMS__
|
||||
if (this.Height < height - 5 || this.Height > height + 5) InvalidateMeasure();
|
||||
#elif __WINDOWSFORMS__
|
||||
this.Height = (int)height;
|
||||
#elif __ANDROID__
|
||||
#elif __IOS__
|
||||
#elif __UWP__
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual SKColor GetBackgroundColor(VisualState visualState)
|
||||
{
|
||||
if (widgetSource is IBackgroundColorSource backgroundColorSource) return backgroundColorSource.GetBackgroundColor(visualState);
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
#if __FORMS__
|
||||
|
||||
protected override void OnSizeAllocated(double width, double height)
|
||||
{
|
||||
base.OnSizeAllocated(width, height);
|
||||
LastPaintWidth = (float)Width;
|
||||
}
|
||||
|
||||
protected override void InvalidateMeasure()
|
||||
{
|
||||
base.InvalidateMeasure();
|
||||
InvalidatePaint();
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
if (BindingContext == null)
|
||||
Implementation.Dispose();
|
||||
}
|
||||
|
||||
protected override Xamarin.Forms.SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
|
||||
{
|
||||
|
||||
if (Width == LastPaintWidth && LastHeightRequest > -1)
|
||||
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(LastPaintWidth, LastHeightRequest));
|
||||
|
||||
var request = Implementation.Measure(new SKSize((float)widthConstraint, (float)heightConstraint));
|
||||
System.Diagnostics.Debug.WriteLine($"LinkTileView Measured: {request} ({widthConstraint}, {heightConstraint}) ");
|
||||
|
||||
if (float.IsInfinity(request.Width) || float.IsInfinity(request.Height))
|
||||
return base.OnMeasure(widthConstraint, heightConstraint);
|
||||
|
||||
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(request.Width, request.Height));
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if __WINDOWSFORMS__
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
Implementation.Dispose();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __ANDROID__
|
||||
public void AddOnMainThread(View childview)
|
||||
{
|
||||
|
||||
((Activity)Context).RunOnUiThread(() => AddView(childview));
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#define USEGL
|
||||
#if false
|
||||
#define USEGL
|
||||
using FluidSharp.Touch;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.iOS;
|
||||
|
@ -97,3 +98,4 @@ namespace FluidSharp.Views.iOS
|
|||
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,129 @@
|
|||
using FluidSharp.Touch;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.iOS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
|
||||
namespace FluidSharp.Views.iOS
|
||||
{
|
||||
public class SkiaGLView : SKGLView, ISkiaView
|
||||
{
|
||||
|
||||
public float Width => (float)Bounds.Width;
|
||||
public float Height => (float)Bounds.Height;
|
||||
|
||||
public event EventHandler<PaintSurfaceEventArgs> PaintViewSurface;
|
||||
public event EventHandler<TouchActionEventArgs> Touch;
|
||||
|
||||
public void InvalidatePaint()
|
||||
{
|
||||
InvokeOnMainThread(() =>
|
||||
{
|
||||
SetNeedsDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
public SkiaGLView()
|
||||
{
|
||||
|
||||
var touchrecognizer = new TouchRecognizer(this);
|
||||
GestureRecognizers = new UIGestureRecognizer[] { touchrecognizer };
|
||||
touchrecognizer.Touch += Touchrecognizer_Touch;
|
||||
|
||||
}
|
||||
|
||||
protected override void OnPaintSurface(SKPaintGLSurfaceEventArgs e)
|
||||
{
|
||||
|
||||
var canvas = e.Surface.Canvas;
|
||||
// Make sure the canvas is drawn using pixel coordinates (but still high res):
|
||||
var factor = (float)Math.Round(e.BackendRenderTarget.Width / Width * 4) / 4;
|
||||
var platformzoom = SKMatrix.CreateScale(factor, factor);
|
||||
canvas.Concat(ref platformzoom);
|
||||
|
||||
PaintViewSurface?.Invoke(this, new PaintSurfaceEventArgs(canvas, Width, Height, e.Surface, default));
|
||||
|
||||
}
|
||||
|
||||
private void Touchrecognizer_Touch(object sender, TouchActionEventArgs e)
|
||||
{
|
||||
Touch?.Invoke(this, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SkiaCanvasView : SKCanvasView, ISkiaView
|
||||
{
|
||||
|
||||
|
||||
public float Width => (float)Bounds.Width;
|
||||
public float Height => (float)Bounds.Height;
|
||||
|
||||
public event EventHandler<PaintSurfaceEventArgs> PaintViewSurface;
|
||||
public event EventHandler<TouchActionEventArgs> Touch;
|
||||
|
||||
public void InvalidatePaint()
|
||||
{
|
||||
InvokeOnMainThread(() =>
|
||||
{
|
||||
SetNeedsDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
public SkiaCanvasView()
|
||||
{
|
||||
|
||||
var touchrecognizer = new TouchRecognizer(this);
|
||||
GestureRecognizers = new UIGestureRecognizer[] { touchrecognizer };
|
||||
touchrecognizer.Touch += Touchrecognizer_Touch;
|
||||
|
||||
PaintSurface += SkiaControl_PaintSurface;
|
||||
}
|
||||
|
||||
private void SkiaControl_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
|
||||
{
|
||||
|
||||
var canvas = e.Surface.Canvas;
|
||||
// Make sure the canvas is drawn using pixel coordinates (but still high res):
|
||||
var factor = (float)Math.Round(e.Info.Width / Width * 4) / 4;
|
||||
var platformzoom = SKMatrix.CreateScale(factor, factor);
|
||||
canvas.Concat(ref platformzoom);
|
||||
|
||||
PaintViewSurface?.Invoke(this, new PaintSurfaceEventArgs(canvas, Width, Height, e.Surface, default));
|
||||
|
||||
}
|
||||
|
||||
private void Touchrecognizer_Touch(object sender, TouchActionEventArgs e)
|
||||
{
|
||||
Touch?.Invoke(this, e);
|
||||
}
|
||||
|
||||
//protected override void Dispose(bool disposing)
|
||||
//{
|
||||
// // detach all events before disposing
|
||||
// var controller = (ISKCanvasViewController)Element;
|
||||
// if (controller != null)
|
||||
// {
|
||||
// controller.SurfaceInvalidated -= OnSurfaceInvalidated;
|
||||
// controller.GetCanvasSize -= OnGetCanvasSize;
|
||||
// }
|
||||
|
||||
// var control = Control;
|
||||
// if (control != null)
|
||||
// {
|
||||
// control.PaintSurface -= OnPaintSurface;
|
||||
// }
|
||||
|
||||
// // detach, regardless of state
|
||||
// touchHandler.Detach(control);
|
||||
|
||||
// base.Dispose(disposing);
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -8,16 +8,16 @@
|
|||
<Description>FluidSharp is a high performance mobile first multi-platform UI layout framework based on Skia.</Description>
|
||||
<Copyright>2020 Wouter Steenbergen</Copyright>
|
||||
<PackageLicenseExpression></PackageLicenseExpression>
|
||||
<Version>1.0.3-alpha</Version>
|
||||
<Version>1.0.5-alpha</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageLicenseFile>License.txt</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -92,34 +92,31 @@ namespace FluidSharp
|
|||
public void DebugRect(SKRect rect, SKColor color)
|
||||
{
|
||||
if (Canvas == null) return;
|
||||
#if DEBUG
|
||||
|
||||
using (var borderpaint = new SKPaint() { Color = color, IsStroke = true })
|
||||
Canvas.DrawRect(rect, borderpaint);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public void DebugGestureRect(SKRect rect, SKColor color)
|
||||
{
|
||||
if (Canvas == null) return;
|
||||
#if DEBUG
|
||||
if (VisualState.ShowTouchRegions)
|
||||
using (var paint = new SKPaint() { Color = color })
|
||||
Canvas.DrawRect(rect, paint);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void DebugLine(float x1, float y1, float x2, float y2, SKColor color)
|
||||
{
|
||||
if (Canvas == null) return;
|
||||
#if DEBUG
|
||||
|
||||
using (var linepaint = new SKPaint() { Color = color, IsStroke = true })
|
||||
Canvas.DrawLine(x1, y1, x2, y2, linepaint);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public void DebugMargin(SKRect inner, Margins margins, SKColor color)
|
||||
{
|
||||
#if DEBUG
|
||||
|
||||
var top = new SKRect(inner.Left, inner.Top - margins.Top, inner.Right, inner.Top);
|
||||
DebugSpacing(top, margins.Top.ToString(), color);
|
||||
|
@ -134,15 +131,12 @@ namespace FluidSharp
|
|||
var far = inner.HorizontalAlign(new SKSize(-margins.Far, inner.Height), HorizontalAlignment.Far, FlowDirection);
|
||||
DebugSpacing(far, margins.Far.ToString(), color);
|
||||
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
public void DebugSpacing(SKRect dest, string text, SKColor color)
|
||||
{
|
||||
|
||||
if (Canvas == null) return;
|
||||
#if DEBUG
|
||||
|
||||
if (!VisualState.ShowSpacing) return;
|
||||
|
||||
|
@ -156,7 +150,6 @@ namespace FluidSharp
|
|||
|
||||
using (var textpaint = new SKPaint() { Color = color, IsAntialias = true })
|
||||
Canvas.DrawText(text, dest.MidX - 4, dest.Bottom - 2, textpaint);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace FluidSharp.Navigation
|
|||
TransitionState.TransitionDuration = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
public Widget MakeWidget(NavigationContainer navigationContainer, VisualState visualState, IWidgetSource from, IWidgetSource to, Func<Task> dismiss)
|
||||
public Widget MakeWidget(NavigationContext context, VisualState visualState, IWidgetSource from, IWidgetSource to, Func<Task> dismiss)
|
||||
{
|
||||
var frame = TransitionState.GetFrame();
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace FluidSharp.Navigation
|
|||
|
||||
public Task OnTransitionCompleted(bool open) => !open ? OnTransitionCompleted2(open) : Task.CompletedTask;
|
||||
|
||||
public Widget MakeWidget(NavigationContainer navigationContainer, VisualState visualState, IWidgetSource from, IWidgetSource to, Func<Task> dismiss)
|
||||
public Widget MakeWidget(NavigationContext context, VisualState visualState, IWidgetSource from, IWidgetSource to, Func<Task> dismiss)
|
||||
{
|
||||
|
||||
var animation = TransitionState.GetAnimation(Easing.CubicOut);
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace FluidSharp.Touch
|
|||
Location = new SKPoint(Location.X / fx, Location.Y / fy);
|
||||
Scale = new SKPoint(Scale.X * fx, Scale.Y * fy);
|
||||
|
||||
if (ClipPathStack != null) throw new Exception("Scaling Clip Paths not supported");
|
||||
if (ClipPathStack != null && ClipPathStack.Count > 0) throw new Exception("Scaling Clip Paths not supported");
|
||||
|
||||
ClipRectStack = new Stack<SKRect>();
|
||||
foreach (var cliprect in originalClipRects.Reverse())
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#if DEBUG
|
||||
#define DEBUGCONTAINER
|
||||
#endif
|
||||
#define SHOWSPACING
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
|
@ -39,7 +37,7 @@ namespace FluidSharp.Widgets
|
|||
childrect = new SKRect(xm - childsize.Width / 2, childrect.Top, xm + childsize.Width / 2, childrect.Top + childsize.Height);
|
||||
layoutsurface.Paint(Child, childrect);
|
||||
|
||||
#if DEBUGCONTAINER
|
||||
#if SHOWSPACING
|
||||
layoutsurface.DebugMargin(childrect, Margin, SKColors.YellowGreen);
|
||||
#endif
|
||||
return rect.WithHeight(childsize.Height + Margin.TotalY);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#if DEBUG
|
||||
#define SHOWSPACING
|
||||
#if DEBUG
|
||||
//#define DEBUGCONTAINER
|
||||
#define SHOWSPACING
|
||||
#endif
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#if DEBUG
|
||||
#define DEBUGCONTAINER
|
||||
#endif
|
||||
#define SHOWSPACING
|
||||
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
|
@ -151,7 +149,7 @@ namespace FluidSharp.Widgets
|
|||
if (child != null)
|
||||
layoutsurface.Paint(child, childrect);
|
||||
|
||||
#if DEBUGCONTAINER
|
||||
#if SHOWSPACING
|
||||
layoutsurface.DebugMargin(childrect, Margin, SKColors.YellowGreen);
|
||||
//layoutsurface.DebugRect(drawrect, SKColors.Blue.WithAlpha(128));
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#if DEBUG
|
||||
#define DEBUGCONTAINER
|
||||
#endif
|
||||
#define SHOWSPACING
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.TextBlocks.Enum;
|
||||
|
@ -90,7 +88,7 @@ namespace FluidSharp.Widgets
|
|||
result = new SKRect(rect.Right - maxlinewidth, rect.Top, rect.Right, y);
|
||||
|
||||
|
||||
#if DEBUGCONTAINER
|
||||
#if SHOWSPACING
|
||||
if (layoutsurface != null)
|
||||
layoutsurface.DebugMargin(result, Margin, SKColors.YellowGreen);
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#if DEBUG
|
||||
#define DEBUGCONTAINER
|
||||
#endif
|
||||
#define SHOWSPACING
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.TextBlocks.Enum;
|
||||
|
@ -243,7 +241,7 @@ namespace FluidSharp.Widgets
|
|||
|
||||
}
|
||||
|
||||
#if DEBUGCONTAINER
|
||||
#if SHOWSPACING
|
||||
if (Debug)
|
||||
{
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#if DEBUG
|
||||
#define DEBUGCONTAINER
|
||||
#endif
|
||||
#define SHOWSPACING
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
|
@ -71,7 +69,7 @@ namespace FluidSharp.Widgets
|
|||
var farrect = childrect.HorizontalAlign(new SKSize(farwidth, childrect.Height), HorizontalAlignment.Far, layoutsurface.FlowDirection);
|
||||
layoutsurface.Paint(Far, farrect);
|
||||
|
||||
#if DEBUGCONTAINER
|
||||
#if SHOWSPACING
|
||||
layoutsurface.DebugMargin(childrect, Margin, SKColors.YellowGreen);
|
||||
//layoutsurface.DebugRect(drawrect, SKColors.Blue.WithAlpha(128));
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#if DEBUG
|
||||
#define DEBUGCONTAINER
|
||||
#endif
|
||||
#define SHOWSPACING
|
||||
using FluidSharp.Layouts;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
|
@ -49,7 +47,7 @@ namespace FluidSharp.Widgets
|
|||
layoutsurface.Paint(Far, farrect);
|
||||
}
|
||||
|
||||
#if DEBUGCONTAINER
|
||||
#if SHOWSPACING
|
||||
layoutsurface.DebugMargin(childrect, Margin, SKColors.YellowGreen);
|
||||
//layoutsurface.DebugRect(drawrect, SKColors.Blue.WithAlpha(128));
|
||||
#endif
|
||||
|
|
|
@ -28,10 +28,20 @@ namespace FluidSharp.Widgets
|
|||
public override SKSize Measure(MeasureCache measureCache, SKSize boundaries) => InnerWidget.Measure(measureCache, boundaries);
|
||||
public override SKRect PaintInternal(LayoutSurface layoutsurface, SKRect rect)
|
||||
{
|
||||
var translated = new SKRect(rect.Left + Translation.X, rect.Top + Translation.Y, rect.Right + Translation.X, rect.Bottom + Translation.Y);
|
||||
var location = layoutsurface.Paint(InnerWidget, translated);
|
||||
var inverse = new SKRect(location.Left - Translation.X, location.Top - Translation.Y, location.Right - Translation.X, location.Bottom - Translation.Y);
|
||||
return inverse;
|
||||
if (layoutsurface.IsRtl)
|
||||
{
|
||||
var translated = new SKRect(rect.Left - Translation.X, rect.Top + Translation.Y, rect.Right - Translation.X, rect.Bottom + Translation.Y);
|
||||
var location = layoutsurface.Paint(InnerWidget, translated);
|
||||
var inverse = new SKRect(location.Left + Translation.X, location.Top - Translation.Y, location.Right + Translation.X, location.Bottom - Translation.Y);
|
||||
return inverse;
|
||||
}
|
||||
else
|
||||
{
|
||||
var translated = new SKRect(rect.Left + Translation.X, rect.Top + Translation.Y, rect.Right + Translation.X, rect.Bottom + Translation.Y);
|
||||
var location = layoutsurface.Paint(InnerWidget, translated);
|
||||
var inverse = new SKRect(location.Left - Translation.X, location.Top - Translation.Y, location.Right - Translation.X, location.Bottom - Translation.Y);
|
||||
return inverse;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче