Browse Source

More iOS platform related improvements. Code style reformatting for PR. ControlCatalog can now run on Skia! AssetLoader hack for iOS to get us running.

pull/506/head
Jason Jarvis 11 years ago
parent
commit
b566401602
  1. 4
      samples/ControlCatalog/ControlCatalog.csproj
  2. 11
      samples/ControlCatalog/Program.cs
  3. 48
      samples/TestApplicationShared/App.cs
  4. 15
      src/Shared/PlatformSupport/AssetLoader.cs
  5. 98
      src/Skia/Perspex.Skia.Desktop.TestApp/Program.cs
  6. 238
      src/Skia/Perspex.Skia.Desktop/MethodTableImpl.cs
  7. 18
      src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj
  8. 32
      src/Skia/Perspex.Skia.iOS/SkiaView.cs
  9. 42
      src/Skia/Perspex.Skia/BitmapImpl.cs
  10. 381
      src/Skia/Perspex.Skia/DrawingContextImpl.cs
  11. 500
      src/Skia/Perspex.Skia/FormattedTextImpl.cs
  12. 258
      src/Skia/Perspex.Skia/RenderTarget.cs
  13. 12
      src/Skia/Perspex.Skia/SkiaPlatform.cs
  14. 110
      src/Skia/Perspex.Skia/SkiaSharpExtensions.cs
  15. 138
      src/Skia/Perspex.Skia/StreamGeometryImpl.cs
  16. 18
      src/Skia/Perspex.Skia/TypefaceCache.cs
  17. 13
      src/Skia/Perspex.Skia/readme.md
  18. 1
      src/iOS/Perspex.iOS/Perspex.iOS.csproj
  19. 2
      src/iOS/Perspex.iOS/PerspexAppDelegate.cs
  20. 12
      src/iOS/Perspex.iOS/PerspexView.cs
  21. 107
      src/iOS/Perspex.iOS/iOSPlatform.cs
  22. 95
      src/iOS/Perspex.iOSTestApplication/AppDelegate.cs
  23. 4
      src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj

4
samples/ControlCatalog/ControlCatalog.csproj

@ -211,6 +211,10 @@
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project> <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Perspex.Themes.Default</Name> <Name>Perspex.Themes.Default</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\src\Skia\Perspex.Skia.Desktop\Perspex.Skia.Desktop.csproj">
<Project>{925dd807-b651-475f-9f7c-cbeb974ce43d}</Project>
<Name>Perspex.Skia.Desktop</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Perspex.Direct2D1\Perspex.Direct2D1.csproj"> <ProjectReference Include="..\..\src\Windows\Perspex.Direct2D1\Perspex.Direct2D1.csproj">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project> <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Perspex.Direct2D1</Name> <Name>Perspex.Direct2D1</Name>

11
samples/ControlCatalog/Program.cs

@ -110,12 +110,11 @@ namespace ControlCatalog
{ {
app.UseWin32(); app.UseWin32();
// not available until we do the SkiaSharp merge if (args.Contains("--skia") || DefaultRenderSystem() == RenderSystem.Skia)
//if (args.Contains("--skia") || DefaultRenderSystem() == RenderSystem.Skia) {
//{ app.UseSkia();
// app.UseSkia(); }
//} else
//else
{ {
app.UseDirect2D(); app.UseDirect2D();
} }

48
samples/TestApplicationShared/App.cs

@ -18,35 +18,35 @@ namespace TestApplication
{ {
public App() public App()
{ {
// TODO: I believe this has to happen before we select sub systems. Can we // TODO: I believe this has to happen before we select sub systems. Can we
// move this safely into Application itself? Is there anything in here // move this safely into Application itself? Is there anything in here
// that is platform specific?? // that is platform specific??
// //
RegisterServices(); RegisterServices();
} }
public void Run() public void Run()
{ {
Styles.Add(new DefaultTheme()); Styles.Add(new DefaultTheme());
var loader = new PerspexXamlLoader(); var loader = new PerspexXamlLoader();
var baseLight = (IStyle)loader.Load( var baseLight = (IStyle)loader.Load(
new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default")); new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default"));
Styles.Add(baseLight); Styles.Add(baseLight);
Styles.Add(new SampleTabStyle()); Styles.Add(new SampleTabStyle());
DataTemplates = new DataTemplates DataTemplates = new DataTemplates
{ {
new FuncTreeDataTemplate<Node>( new FuncTreeDataTemplate<Node>(
x => new TextBlock {Text = x.Name}, x => new TextBlock {Text = x.Name},
x => x.Children), x => x.Children),
}; };
MainWindow.RootNamespace = "TestApplication"; MainWindow.RootNamespace = "TestApplication";
var wnd = MainWindow.Create(); var wnd = MainWindow.Create();
DevTools.Attach(wnd); wnd.AttachDevTools();
Run(wnd); Run(wnd);
} }
} }
} }

15
src/Shared/PlatformSupport/AssetLoader.cs

@ -105,7 +105,20 @@ namespace Perspex.Shared.PlatformSupport
"No defaultAssembly, entry assembly or explicit assembly specified, don't know where to look up for the resource, try specifiyng assembly explicitly"); "No defaultAssembly, entry assembly or explicit assembly specified, don't know where to look up for the resource, try specifiyng assembly explicitly");
IAssetDescriptor rv; IAssetDescriptor rv;
asm.Resources.TryGetValue(uri.AbsolutePath, out rv);
var resourceKey = uri.AbsolutePath;
#if __IOS__
// TODO: HACK: to get iOS up and running. Using Shared projects for resources
// is flawed as this alters the reource key locations across platforms
// I think we need to use Portable libraries from now on to avoid that.
if(asm.Name.Contains("iOS"))
{
resourceKey = resourceKey.Replace("TestApplication", "Perspex.iOSTestApplication");
}
#endif
asm.Resources.TryGetValue(resourceKey, out rv);
return rv; return rv;
} }
throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri)); throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri));

98
src/Skia/Perspex.Skia.Desktop.TestApp/Program.cs

@ -26,66 +26,60 @@ using ReactiveUI;
namespace Perspex.Skia.Desktop.TestApp namespace Perspex.Skia.Desktop.TestApp
{ {
internal class Program internal class Program
{ {
private static void Main(string[] args) private static void Main(string[] args)
{ {
// The version of ReactiveUI currently included is for WPF and so expects a WPF // The version of ReactiveUI currently included is for WPF and so expects a WPF
// dispatcher. This makes sure it's initialized. // dispatcher. This makes sure it's initialized.
System.Windows.Threading.Dispatcher foo = System.Windows.Threading.Dispatcher.CurrentDispatcher; System.Windows.Threading.Dispatcher foo = System.Windows.Threading.Dispatcher.CurrentDispatcher;
new SkiaApp();
//RendererMixin.DrawFpsCounter = true; new App()
.UseWin32()
.UseSkia();
MainWindow.RootNamespace = "Perspex.Skia.Desktop.TestApp"; //RendererMixin.DrawFpsCounter = true;
var wnd = MainWindow.Create();
// let's start small :) MainWindow.RootNamespace = "Perspex.Skia.Desktop.TestApp";
//var wnd = SimpleWindow.Create(); var wnd = MainWindow.Create();
DevTools.Attach(wnd); // let's start small :)
Application.Current.Run(wnd); //var wnd = SimpleWindow.Create();
}
}
internal class SkiaApp : App DevTools.Attach(wnd);
{ Application.Current.Run(wnd);
protected override void PlatformInitialization() }
{ }
SkiaPlatform.Initialize();
InitializeSubsystem("Perspex.Win32");
}
}
internal class SimpleWindow internal class SimpleWindow
{ {
public static Window Create() public static Window Create()
{ {
Border container; Border container;
Window window = new Window Window window = new Window
{ {
Title = "Perspex Test Application", Title = "Perspex Test Application",
Content = (container = new Border Content = (container = new Border
{ {
Background = Brushes.Green, Background = Brushes.Green,
BorderBrush = Brushes.Yellow, BorderBrush = Brushes.Yellow,
BorderThickness = 12, BorderThickness = 12,
Padding = new Thickness(5), Padding = new Thickness(5),
Child = new Rectangle Child = new Rectangle
{ {
Fill = Brushes.Blue, Fill = Brushes.Blue,
Width = 200, Width = 200,
Height = 200, Height = 200,
Margin = new Thickness(50) Margin = new Thickness(50)
} }
}) })
}; };
window.Show(); window.Show();
return window; return window;
} }
} }
} }

238
src/Skia/Perspex.Skia.Desktop/MethodTableImpl.cs

@ -1,238 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
/* OBSOLETE
//Library loaders were taken from https://github.com/kekekeks/evhttp-sharp/tree/master/EvHttpSharp/Interop (MIT licensed)
namespace Perspex.Skia
{
class MethodTableImpl : MethodTable
{
public MethodTableImpl() : base(GetMethodTable())
{
}
class DetectedPlatformInfo
{
public DetectedPlatformInfo(IDynLoader loader, string name, string cpuArch, string dllExtension)
{
Loader = loader;
Name = name;
CpuArch = cpuArch;
DllExtension = dllExtension;
}
public IDynLoader Loader { get; }
public string Name { get; }
public string CpuArch { get;}
public string DllExtension { get; }
}
static DetectedPlatformInfo DetectPlatform()
{
var macInfo = new DetectedPlatformInfo(new OsxLoader(), "Darwin", IntPtr.Size == 4 ? "i686" : "x86_64", ".dylib");
switch (Environment.OSVersion.Platform)
{
case PlatformID.Unix:
// Well, there are chances MacOSX is reported as Unix instead of MacOSX.
// Instead of platform check, we'll do a feature checks (Mac specific root folders)
if (Directory.Exists("/Applications")
& Directory.Exists("/System")
& Directory.Exists("/Users")
& Directory.Exists("/Volumes"))
return macInfo;
else
{
string cpuArch;
using (var proc =
Process.Start(
(new ProcessStartInfo("uname", "-p")
{
UseShellExecute = false,
RedirectStandardOutput = true
})))
{
proc.WaitForExit();
cpuArch = proc.StandardOutput.ReadToEnd().Trim();
}
return new DetectedPlatformInfo(new LinuxLoader(), "Linux", cpuArch, ".so");
}
case PlatformID.MacOSX:
return macInfo;
default:
return new DetectedPlatformInfo(new Win32Loader(), "Windows", IntPtr.Size == 4 ? "i686" : "x86_64", ".dll");
}
}
static IEnumerable<string> GeneratePossiblePaths()
{
foreach (
var basePath in
new[] {Assembly.GetEntryAssembly(), typeof (MethodTable).Assembly}.Select(
a => Path.GetDirectoryName(a?.GetModules()?[0]?.FullyQualifiedName))
.Concat(new[] { Directory.GetCurrentDirectory()}))
{
if(basePath == null)
continue;
yield return Path.Combine(basePath);
yield return Path.Combine(basePath, "native");
yield return Path.Combine(basePath, "lib");
yield return Path.Combine(basePath, "lib", "native");
}
}
static IntPtr GetMethodTable()
{
var plat = DetectPlatform();
var name = "libperspesk" + plat.DllExtension;
var paths = GeneratePossiblePaths().Select(p => Path.Combine(p, plat.Name, plat.CpuArch, name)).ToList();
var dll = paths.FirstOrDefault(File.Exists);
var msg = "Unable to find native library in following paths: '" +
string.Join("', '", paths) + "'";
Console.Error.WriteLine(msg);
if (dll == null)
throw new FileNotFoundException(msg);
IntPtr hLib = plat.Loader.LoadLibrary(dll);
IntPtr pGetMethodTable = plat.Loader.GetProcAddress(hLib, "GetPerspexMethodTable");
var getTable = (GetPerspexMethodTableDelegate)
Marshal.GetDelegateForFunctionPointer(pGetMethodTable, typeof (GetPerspexMethodTableDelegate));
return getTable();
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate IntPtr GetPerspexMethodTableDelegate();
internal interface IDynLoader
{
IntPtr LoadLibrary(string dll);
IntPtr GetProcAddress(IntPtr dll, string proc);
}
class LinuxLoader : IDynLoader
{
// ReSharper disable InconsistentNaming
[DllImport("libdl.so.2")]
private static extern IntPtr dlopen(string path, int flags);
[DllImport("libdl.so.2")]
private static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport("libdl.so.2")]
private static extern IntPtr dlerror();
// ReSharper restore InconsistentNaming
static string DlError()
{
return Marshal.PtrToStringAuto(dlerror());
}
public IntPtr LoadLibrary(string dll)
{
var handle = dlopen(dll, 1);
if (handle == IntPtr.Zero)
throw new Win32Exception(DlError());
return handle;
}
public IntPtr GetProcAddress(IntPtr dll, string proc)
{
var ptr = dlsym(dll, proc);
if (ptr == IntPtr.Zero)
throw new Win32Exception(DlError());
return ptr;
}
}
internal class Win32Loader : IDynLoader
{
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpszLib);
[DllImport("kernel32", EntryPoint = "SetDllDirectoryW", SetLastError = true, CharSet = CharSet.Unicode)]
extern static bool SetDllDirectory(string lpPathName);
[DllImport("kernel32", EntryPoint = "GetDllDirectoryW", SetLastError = true, CharSet = CharSet.Unicode)]
extern static int GetDllDirectory(int nBufferLength, char[] lpBuffer);
IntPtr IDynLoader.LoadLibrary(string dll)
{
var buffer = new char[2048];
int oldLen = GetDllDirectory(buffer.Length, buffer);
var oldDir = new string(buffer, 0, oldLen);
SetDllDirectory(Path.GetDirectoryName(dll));
var handle = LoadLibrary(dll);
if (handle == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
SetDllDirectory(oldDir);
return handle;
}
IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc)
{
var ptr = GetProcAddress(dll, proc);
if (ptr == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
return ptr;
}
}
class OsxLoader : IDynLoader
{
// ReSharper disable InconsistentNaming
[DllImport("/usr/lib/libSystem.dylib")]
public static extern IntPtr dlopen(string path, int mode);
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlerror();
// ReSharper restore InconsistentNaming
static string DlError()
{
return Marshal.PtrToStringAuto(dlerror());
}
public IntPtr LoadLibrary(string dll)
{
var handle = dlopen(dll, 1);
if (handle == IntPtr.Zero)
throw new Win32Exception(DlError());
return handle;
}
public IntPtr GetProcAddress(IntPtr dll, string proc)
{
var ptr = dlsym(dll, proc);
if (ptr == IntPtr.Zero)
throw new Win32Exception(DlError());
return ptr;
}
}
}
}
*/

18
src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj

@ -70,7 +70,6 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="MethodTableImpl.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\..\Windows\Perspex.Win32\Interop\UnmanagedMethods.cs"> <Compile Include="..\..\Windows\Perspex.Win32\Interop\UnmanagedMethods.cs">
<Link>UnmanagedMethods.cs</Link> <Link>UnmanagedMethods.cs</Link>
@ -111,23 +110,6 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\native\Windows\i686\libEGL.dll">
<Link>native\Windows\i686\libEGL.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\native\Windows\i686\libGLESv2.dll">
<Link>native\Windows\i686\libGLESv2.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\native\Windows\i686\libperspesk.dll">
<Link>native\Windows\i686\libperspesk.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="..\native\Linux\x86_64\libperspesk.so">
<Link>native\Linux\x86_64\libperspesk.so</Link>
</None>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" /> <Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" />

32
src/Skia/Perspex.Skia.iOS/SkiaView.cs

@ -15,8 +15,8 @@ using UIKit;
namespace Perspex.Skia.iOS namespace Perspex.Skia.iOS
{ {
public abstract class SkiaView : UIView //GLKView public abstract class SkiaView : UIView //GLKView
{ {
//[DllImport("__Internal")] //[DllImport("__Internal")]
//static extern IntPtr GetPerspexEAGLContext(); //static extern IntPtr GetPerspexEAGLContext();
@ -24,15 +24,15 @@ namespace Perspex.Skia.iOS
//CADisplayLink _link; //CADisplayLink _link;
static EAGLContext GetContext() static EAGLContext GetContext()
{ {
/* No longer needed with SkiaSharp /* No longer needed with SkiaSharp, but require a variant for HW accel
//Ensure initialization //Ensure initialization
MethodTable.Instance.SetOption((MethodTable.Option)0x10009999, IntPtr.Zero); MethodTable.Instance.SetOption((MethodTable.Option)0x10009999, IntPtr.Zero);
var ctx = GetPerspexEAGLContext(); var ctx = GetPerspexEAGLContext();
var rv = Runtime.GetNSObject<EAGLContext>(ctx); var rv = Runtime.GetNSObject<EAGLContext>(ctx);
rv.DangerousRetain(); rv.DangerousRetain();
return rv; return rv;
*/ */
return null; return null;
} }
@ -52,10 +52,10 @@ namespace Perspex.Skia.iOS
{ {
_drawQueued = false; _drawQueued = false;
// GLKView // GLKView
//Display(); //Display();
this.SetNeedsDisplay(); this.SetNeedsDisplay();
} }
} }
@ -64,9 +64,9 @@ namespace Perspex.Skia.iOS
_drawQueued = true; _drawQueued = true;
} }
protected IPlatformHandle PerspexPlatformHandle { get; } protected IPlatformHandle PerspexPlatformHandle { get; }
= new PlatformHandle(IntPtr.Zero, "Null (iOS-specific)"); = new PlatformHandle(IntPtr.Zero, "Null (iOS-specific)");
protected abstract void Draw(); protected abstract void Draw();

42
src/Skia/Perspex.Skia/BitmapImpl.cs

@ -11,28 +11,28 @@ namespace Perspex.Skia
{ {
class BitmapImpl : IRenderTargetBitmapImpl class BitmapImpl : IRenderTargetBitmapImpl
{ {
public SKBitmap Bitmap { get; private set; } public SKBitmap Bitmap { get; private set; }
public BitmapImpl(SKBitmap bm) public BitmapImpl(SKBitmap bm)
{ {
Bitmap = bm; Bitmap = bm;
PixelHeight = bm.Width; PixelHeight = bm.Width;
PixelWidth = bm.Height; PixelWidth = bm.Height;
} }
public BitmapImpl(int width, int height) public BitmapImpl(int width, int height)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void Dispose() public void Dispose()
{ {
} }
public void Save(string fileName) public void Save(string fileName)
{ {
// TODO: Implement this for SkiaSharp // TODO: Implement this for SkiaSharp
throw new NotImplementedException(); throw new NotImplementedException();
//var ext = Path.GetExtension(fileName)?.ToLower(); //var ext = Path.GetExtension(fileName)?.ToLower();
//var type = MethodTable.SkiaImageType.Png; //var type = MethodTable.SkiaImageType.Png;
@ -49,12 +49,12 @@ namespace Perspex.Skia
public int PixelWidth { get; private set; } public int PixelWidth { get; private set; }
public int PixelHeight { get; private set; } public int PixelHeight { get; private set; }
public DrawingContext CreateDrawingContext() public DrawingContext CreateDrawingContext()
{ {
return return
new DrawingContext( new DrawingContext(
new DrawingContextImpl(null)); // MethodTable.Instance.RenderTargetCreateRenderingContext(Handle))); new DrawingContextImpl(null)); // MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
} }
} }

381
src/Skia/Perspex.Skia/DrawingContextImpl.cs

@ -10,250 +10,251 @@ namespace Perspex.Skia
{ {
unsafe class DrawingContextImpl : IDrawingContextImpl unsafe class DrawingContextImpl : IDrawingContextImpl
{ {
public SKCanvas Canvas { get; private set; } public SKCanvas Canvas { get; private set; }
public DrawingContextImpl(SKCanvas canvas) public DrawingContextImpl(SKCanvas canvas)
{ {
Canvas = canvas; Canvas = canvas;
} }
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
{ {
var impl = (BitmapImpl)source.PlatformImpl; var impl = (BitmapImpl)source.PlatformImpl;
var s = sourceRect.ToSKRect(); var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect(); var d = destRect.ToSKRect();
Canvas.DrawBitmap(impl.Bitmap, s, d); Canvas.DrawBitmap(impl.Bitmap, s, d);
} }
public void DrawLine(Pen pen, Point p1, Point p2) public void DrawLine(Pen pen, Point p1, Point p2)
{ {
using (var paint = CreatePaint(pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) using (var paint = CreatePaint(pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{ {
Canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, paint); Canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, paint);
}
} }
}
public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry) public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry)
{ {
var impl = ((StreamGeometryImpl)geometry.PlatformImpl); var impl = ((StreamGeometryImpl)geometry.PlatformImpl);
var size = geometry.Bounds.Size; var size = geometry.Bounds.Size;
using (var fill = brush != null ? CreatePaint(brush, size) : null) using (var fill = brush != null ? CreatePaint(brush, size) : null)
using (var stroke = pen?.Brush != null ? CreatePaint(pen, size) : null) using (var stroke = pen?.Brush != null ? CreatePaint(pen, size) : null)
{ {
if (fill != null) if (fill != null)
{ {
Canvas.DrawPath(impl.EffectivePath, fill); Canvas.DrawPath(impl.EffectivePath, fill);
} }
if (stroke != null) if (stroke != null)
{ {
Canvas.DrawPath(impl.EffectivePath, stroke); Canvas.DrawPath(impl.EffectivePath, stroke);
} }
} }
} }
private SKPaint CreatePaint(IBrush brush, Size targetSize) private SKPaint CreatePaint(IBrush brush, Size targetSize)
{ {
SKPaint paint = new SKPaint(); SKPaint paint = new SKPaint();
paint.IsStroke = false; paint.IsStroke = false;
// TODO: SkiaSharp does not contain alpha yet! // TODO: SkiaSharp does not contain alpha yet!
//double opacity = brush.Opacity * _currentOpacity; //double opacity = brush.Opacity * _currentOpacity;
//paint.SetAlpha(paint.GetAlpha() * opacity); //paint.SetAlpha(paint.GetAlpha() * opacity);
paint.IsAntialias = true; paint.IsAntialias = true;
var solid = brush as SolidColorBrush; var solid = brush as SolidColorBrush;
if (solid != null) if (solid != null)
{ {
paint.Color = solid.Color.ToSKColor(); paint.Color = solid.Color.ToSKColor();
return paint; return paint;
} }
var gradient = brush as GradientBrush; var gradient = brush as GradientBrush;
if (gradient != null) if (gradient != null)
{ {
var tileMode = gradient.SpreadMethod.ToSKShaderTileMode(); var tileMode = gradient.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradient.GradientStops.Select(s => s.Color.ToSKColor()).ToArray(); var stopColors = gradient.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradient.GradientStops.Select(s => (float)s.Offset).ToArray(); var stopOffsets = gradient.GradientStops.Select(s => (float)s.Offset).ToArray();
var linearGradient = brush as LinearGradientBrush; var linearGradient = brush as LinearGradientBrush;
if (linearGradient != null) if (linearGradient != null)
{ {
var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint(); var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint(); var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
// would be nice to cache these shaders possibly? // would be nice to cache these shaders possibly?
var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode); var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode);
paint.Shader = shader; paint.Shader = shader;
} }
else else
{ {
var radialGradient = brush as RadialGradientBrush; var radialGradient = brush as RadialGradientBrush;
if (radialGradient != null) if (radialGradient != null)
{ {
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint(); var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)radialGradient.Radius; var radius = (float)radialGradient.Radius;
// TODO: There is no SetAlpha in SkiaSharp // TODO: There is no SetAlpha in SkiaSharp
//paint.setAlpha(128); //paint.setAlpha(128);
// would be nice to cache these shaders possibly? // would be nice to cache these shaders possibly?
var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode); var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode);
paint.Shader = shader; paint.Shader = shader;
}
}
return paint;
} }
}
return paint;
}
var tileBrush = brush as TileBrush; var tileBrush = brush as TileBrush;
if (tileBrush != null) if (tileBrush != null)
{ {
// TODO: Get Tile Brushes working!!! // TODO: Get Tile Brushes working!!!
// //
throw new NotImplementedException(); //throw new NotImplementedException();
// rv.Brush->Type = NativeBrushType.Image; // rv.Brush->Type = NativeBrushType.Image;
// var helper = new TileBrushImplHelper(tileBrush, targetSize); // var helper = new TileBrushImplHelper(tileBrush, targetSize);
// var bitmap = new BitmapImpl((int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height); // var bitmap = new BitmapImpl((int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height);
// rv.AddDisposable(bitmap); // rv.AddDisposable(bitmap);
// using (var ctx = bitmap.CreateDrawingContext()) // using (var ctx = bitmap.CreateDrawingContext())
// helper.DrawIntermediate(ctx); // helper.DrawIntermediate(ctx);
// rv.Brush->Bitmap = bitmap.Handle; // rv.Brush->Bitmap = bitmap.Handle;
// rv.Brush->BitmapTileMode = tileBrush.TileMode; // rv.Brush->BitmapTileMode = tileBrush.TileMode;
// rv.Brush->BitmapTranslation = new SkiaPoint(-helper.DestinationRect.X, -helper.DestinationRect.Y); // rv.Brush->BitmapTranslation = new SkiaPoint(-helper.DestinationRect.X, -helper.DestinationRect.Y);
// SkMatrix matrix; // SkMatrix matrix;
// matrix.setTranslate(brush->BitmapTranslation); // matrix.setTranslate(brush->BitmapTranslation);
// SkShader::TileMode tileX = brush->BitmapTileMode == ptmNone ? SkShader::kClamp_TileMode // SkShader::TileMode tileX = brush->BitmapTileMode == ptmNone ? SkShader::kClamp_TileMode
// : (brush->BitmapTileMode == ptmFlipX || brush->BitmapTileMode == ptmFlipXY) ? SkShader::kMirror_TileMode : SkShader::kRepeat_TileMode; // : (brush->BitmapTileMode == ptmFlipX || brush->BitmapTileMode == ptmFlipXY) ? SkShader::kMirror_TileMode : SkShader::kRepeat_TileMode;
// SkShader::TileMode tileY = brush->BitmapTileMode == ptmNone ? SkShader::kClamp_TileMode // SkShader::TileMode tileY = brush->BitmapTileMode == ptmNone ? SkShader::kClamp_TileMode
// : (brush->BitmapTileMode == ptmFlipY || brush->BitmapTileMode == ptmFlipXY) ? SkShader::kMirror_TileMode : SkShader::kRepeat_TileMode; // : (brush->BitmapTileMode == ptmFlipY || brush->BitmapTileMode == ptmFlipXY) ? SkShader::kMirror_TileMode : SkShader::kRepeat_TileMode;
// paint.setShader(SkShader::CreateBitmapShader(brush->Bitmap->Bitmap, tileX, tileY, &matrix))->unref(); // paint.setShader(SkShader::CreateBitmapShader(brush->Bitmap->Bitmap, tileX, tileY, &matrix))->unref();
} }
return paint; return paint;
} }
private SKPaint CreatePaint(Pen pen, Size targetSize) private SKPaint CreatePaint(Pen pen, Size targetSize)
{ {
var paint = CreatePaint(pen.Brush, targetSize); var paint = CreatePaint(pen.Brush, targetSize);
paint.IsStroke = true; paint.IsStroke = true;
paint.StrokeWidth = (float) pen.Thickness; paint.StrokeWidth = (float)pen.Thickness;
if (pen.StartLineCap == PenLineCap.Round) if (pen.StartLineCap == PenLineCap.Round)
paint.StrokeCap = SKStrokeCap.Round; paint.StrokeCap = SKStrokeCap.Round;
else if (pen.StartLineCap == PenLineCap.Square) else if (pen.StartLineCap == PenLineCap.Square)
paint.StrokeCap = SKStrokeCap.Square; paint.StrokeCap = SKStrokeCap.Square;
else else
paint.StrokeCap = SKStrokeCap.Butt; paint.StrokeCap = SKStrokeCap.Butt;
if (pen.LineJoin == PenLineJoin.Miter) if (pen.LineJoin == PenLineJoin.Miter)
paint.StrokeJoin = SKStrokeJoin.Mitter; paint.StrokeJoin = SKStrokeJoin.Mitter;
else if (pen.LineJoin == PenLineJoin.Round) else if (pen.LineJoin == PenLineJoin.Round)
paint.StrokeJoin = SKStrokeJoin.Round; paint.StrokeJoin = SKStrokeJoin.Round;
else else
paint.StrokeJoin = SKStrokeJoin.Bevel; paint.StrokeJoin = SKStrokeJoin.Bevel;
paint.StrokeMiter = (float) pen.MiterLimit; paint.StrokeMiter = (float)pen.MiterLimit;
//if (pen.DashStyle?.Dashes != null) //if (pen.DashStyle?.Dashes != null)
//{ //{
// var dashes = pen.DashStyle.Dashes; // var dashes = pen.DashStyle.Dashes;
// if (dashes.Count > NativeBrush.MaxDashCount) // if (dashes.Count > NativeBrush.MaxDashCount)
// throw new NotSupportedException("Maximum supported dash count is " + NativeBrush.MaxDashCount); // throw new NotSupportedException("Maximum supported dash count is " + NativeBrush.MaxDashCount);
// brush.Brush->StrokeDashCount = dashes.Count; // brush.Brush->StrokeDashCount = dashes.Count;
// for (int c = 0; c < dashes.Count; c++) // for (int c = 0; c < dashes.Count; c++)
// brush.Brush->StrokeDashes[c] = (float)dashes[c]; // brush.Brush->StrokeDashes[c] = (float)dashes[c];
// brush.Brush->StrokeDashOffset = (float)pen.DashStyle.Offset; // brush.Brush->StrokeDashOffset = (float)pen.DashStyle.Offset;
//} //}
//if (brush->StrokeDashCount != 0) //if (brush->StrokeDashCount != 0)
//{ //{
// paint.setPathEffect(SkDashPathEffect::Create(brush->StrokeDashes, brush->StrokeDashCount, brush->StrokeDashOffset))->unref(); // paint.setPathEffect(SkDashPathEffect::Create(brush->StrokeDashes, brush->StrokeDashCount, brush->StrokeDashOffset))->unref();
//} //}
return paint; return paint;
} }
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0) public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
{ {
using (var paint = CreatePaint(pen, rect.Size)) using (var paint = CreatePaint(pen, rect.Size))
{ {
var rc = rect.ToSKRect(); var rc = rect.ToSKRect();
if (cornerRadius == 0) if (cornerRadius == 0)
{ {
Canvas.DrawRect(rc, paint); Canvas.DrawRect(rc, paint);
}
else
{
// DrawRRect is not accesible in SkiaSharp yet. We should add that
// to SkiaSharp and initiate a PR....
Canvas.DrawRect(rc, paint);
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
}
} }
else
{
// this does not appear to exist in SkiaSharp?
throw new NotImplementedException();
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
} }
}
}
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0) public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0)
{ {
using (var paint = CreatePaint(brush, rect.Size)) using (var paint = CreatePaint(brush, rect.Size))
{ {
var rc = rect.ToSKRect(); var rc = rect.ToSKRect();
if (cornerRadius == 0) if (cornerRadius == 0)
{ {
Canvas.DrawRect(rc, paint); Canvas.DrawRect(rc, paint);
}
else
{
// this does not appear to exist in SkiaSharp?
//throw new NotImplementedException();
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
Canvas.DrawRect(rc, paint);
}
} }
else
{
// this does not appear to exist in SkiaSharp?
//throw new NotImplementedException();
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
Canvas.DrawRect(rc, paint);
} }
}
}
public void DrawText(IBrush foreground, Point origin, FormattedText text) public void DrawText(IBrush foreground, Point origin, FormattedText text)
{ {
using (var paint = CreatePaint(foreground, text.Measure())) using (var paint = CreatePaint(foreground, text.Measure()))
{ {
var textImpl = text.PlatformImpl as FormattedTextImpl; var textImpl = text.PlatformImpl as FormattedTextImpl;
textImpl.Draw(this.Canvas, origin.ToSKPoint()); textImpl.Draw(this.Canvas, origin.ToSKPoint());
}
} }
}
public void PushClip(Rect clip) public void PushClip(Rect clip)
{ {
Canvas.Save(); Canvas.Save();
Canvas.ClipRect(clip.ToSKRect()); Canvas.ClipRect(clip.ToSKRect());
} }
public void PopClip() public void PopClip()
{ {
Canvas.Restore(); Canvas.Restore();
} }
double _currentOpacity = 1.0f; double _currentOpacity = 1.0f;
private readonly Stack<double> _opacityStack = new Stack<double>(); private readonly Stack<double> _opacityStack = new Stack<double>();
public void PushOpacity(double opacity) public void PushOpacity(double opacity)
{ {
_opacityStack.Push(_currentOpacity); _opacityStack.Push(_currentOpacity);
_currentOpacity *= opacity; _currentOpacity *= opacity;
} }
public void PopOpacity() public void PopOpacity()
{ {
_currentOpacity = _opacityStack.Pop(); _currentOpacity = _opacityStack.Pop();
} }
public virtual void Dispose() public virtual void Dispose()
{ {
} }
private Matrix _currentTransform = Matrix.Identity; private Matrix _currentTransform = Matrix.Identity;
@ -262,30 +263,30 @@ namespace Perspex.Skia
get { return _currentTransform; } get { return _currentTransform; }
set set
{ {
if(_currentTransform == value) if (_currentTransform == value)
return; return;
_currentTransform = value; _currentTransform = value;
Canvas.SetMatrix(value.ToSKMatrix()); Canvas.SetMatrix(value.ToSKMatrix());
}
} }
}
} }
// not sure we need this yet // not sure we need this yet
internal class WindowDrawingContextImpl : DrawingContextImpl internal class WindowDrawingContextImpl : DrawingContextImpl
{ {
WindowRenderTarget _target; WindowRenderTarget _target;
public WindowDrawingContextImpl(WindowRenderTarget target) public WindowDrawingContextImpl(WindowRenderTarget target)
: base(target.Surface.Canvas) : base(target.Surface.Canvas)
{ {
_target = target; _target = target;
} }
public override void Dispose() public override void Dispose()
{ {
base.Dispose(); base.Dispose();
_target.Present(); _target.Present();
} }
} }
} }

500
src/Skia/Perspex.Skia/FormattedTextImpl.cs

@ -10,59 +10,59 @@ namespace Perspex.Skia
{ {
unsafe class FormattedTextImpl : IFormattedTextImpl unsafe class FormattedTextImpl : IFormattedTextImpl
{ {
public SKPaint Paint { get; private set; } public SKPaint Paint { get; private set; }
public FormattedTextImpl(string text) public FormattedTextImpl(string text)
{ {
_text = text; _text = text;
Paint = new SKPaint(); Paint = new SKPaint();
Paint.TextEncoding = SKTextEncoding.Utf16; Paint.TextEncoding = SKTextEncoding.Utf16;
Paint.IsStroke = false; Paint.IsStroke = false;
Paint.IsAntialias = true; Paint.IsAntialias = true;
LineOffset = 0; LineOffset = 0;
//Replace 0 characters with zero-width spaces (200B) //Replace 0 characters with zero-width spaces (200B)
_text = _text.Replace((char)0, (char) 0x200B); _text = _text.Replace((char)0, (char)0x200B);
} }
public static FormattedTextImpl Create(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, public static FormattedTextImpl Create(string text, string fontFamilyName, double fontSize, FontStyle fontStyle,
TextAlignment textAlignment, FontWeight fontWeight) TextAlignment textAlignment, FontWeight fontWeight)
{ {
var typeface = TypefaceCache.GetTypeface(fontFamilyName, fontStyle, fontWeight); var typeface = TypefaceCache.GetTypeface(fontFamilyName, fontStyle, fontWeight);
FormattedTextImpl instance = new FormattedTextImpl(text); FormattedTextImpl instance = new FormattedTextImpl(text);
instance.Paint.Typeface = typeface; instance.Paint.Typeface = typeface;
instance.Paint.TextSize = (float) fontSize; instance.Paint.TextSize = (float)fontSize;
instance.Paint.TextAlign = textAlignment.ToSKTextAlign(); instance.Paint.TextAlign = textAlignment.ToSKTextAlign();
instance.Rebuild(); instance.Rebuild();
return instance; return instance;
} }
private readonly string _text; private readonly string _text;
readonly List<FormattedTextLine> _lines = new List<FormattedTextLine>(); readonly List<FormattedTextLine> _lines = new List<FormattedTextLine>();
readonly List<Rect> _rects = new List<Rect>(); readonly List<Rect> _rects = new List<Rect>();
List<PerspexFormattedTextLine> _skiaLines; List<PerspexFormattedTextLine> _skiaLines;
SKRect[] _skiaRects; SKRect[] _skiaRects;
Size _size; Size _size;
const float MAX_LINE_WIDTH = 10000; const float MAX_LINE_WIDTH = 10000;
float LineOffset; float LineOffset;
float WidthConstraint = -1; float WidthConstraint = -1;
struct PerspexFormattedTextLine struct PerspexFormattedTextLine
{ {
public float Top; public float Top;
public int Start; public int Start;
public int Length; public int Length;
public float Height; public float Height;
public float Width; public float Width;
}; };
public IEnumerable<FormattedTextLine> GetLines() public IEnumerable<FormattedTextLine> GetLines()
{ {
return _lines; return _lines;
} }
@ -79,13 +79,13 @@ namespace Perspex.Skia
{ {
IsInside = true, IsInside = true,
TextPosition = c, TextPosition = c,
IsTrailing = (point.X - rc.X) > rc.Width/2 IsTrailing = (point.X - rc.X) > rc.Width / 2
}; };
} }
} }
bool end = point.X > _size.Width || point.Y > _size.Height; bool end = point.X > _size.Width || point.Y > _size.Height;
return new TextHitTestResult() {IsTrailing = end, TextPosition = end ? _text.Length - 1 : 0}; return new TextHitTestResult() { IsTrailing = end, TextPosition = end ? _text.Length - 1 : 0 };
} }
public Rect HitTestTextPosition(int index) public Rect HitTestTextPosition(int index)
@ -108,224 +108,224 @@ namespace Perspex.Skia
public void SetForegroundBrush(IBrush brush, int startIndex, int length) public void SetForegroundBrush(IBrush brush, int startIndex, int length)
{ {
// Is this method even relevant now??? // Is this method even relevant now???
// //
//var scb = brush as SolidColorBrush; //var scb = brush as SolidColorBrush;
//if (scb != null) //if (scb != null)
//{ //{
// Layout.Attributes.Insert(brushAttr); // Layout.Attributes.Insert(brushAttr);
//} //}
} }
void Rebuild() void Rebuild()
{
var length = _text.Length;
_lines.Clear();
_skiaRects = new SKRect[length];
_skiaLines = new List<PerspexFormattedTextLine>();
int curOff = 0;
float curY = 0;
var metrics = Paint.FontMetrics;
var mTop = metrics.Top; // The greatest distance above the baseline for any glyph (will be <= 0).
var mBottom = metrics.Bottom; // The greatest distance below the baseline for any glyph (will be >= 0).
var mLeading = metrics.Leading; // The recommended distance to add between lines of text (will be >= 0).
// This seems like the best measure of full vertical extent
float lineHeight = mBottom - mTop;
// Rendering is relative to baseline
LineOffset = -metrics.Top;
string subString;
byte[] bytes;
GCHandle pinnedArray;
IntPtr pointer;
for (int c = 0; curOff < length; c++)
{
float lineWidth = -1;
int measured;
int extraSkip = 0;
if (WidthConstraint <= 0)
{
measured = length;
}
else
{
float constraint = WidthConstraint;
if (constraint > MAX_LINE_WIDTH)
constraint = MAX_LINE_WIDTH;
subString = _text.Substring(curOff);
// TODO: This method is not linking into SkiaSharp so we must use the RAW buffer version
//measured = (int)Paint.BreakText(subString, constraint, out lineWidth) / 2;
bytes = Encoding.UTF8.GetBytes(subString);
pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pointer = pinnedArray.AddrOfPinnedObject();
// for some reason I have to pass nBytes * 2
measured = (int)Paint.BreakText(pointer, (IntPtr)(bytes.Length * 2), constraint, out lineWidth) / 2;
// some weird unicode byte issue again
//if(subString.Length % 2 == 1)
//{
// measured -= 1;
//}
pinnedArray.Free();
if (measured == 0)
{
measured = 1;
lineWidth = -1;
}
char nextChar = ' ';
if (curOff + measured < length)
nextChar = _text[curOff + measured];
if (nextChar != ' ')
{
//Perform scan for the last space and end the line there
for (int si = curOff + measured - 1; si > curOff; si--)
{
if (_text[si] == ' ')
{
measured = si - curOff;
extraSkip = 1;
//Rects[si] = SkRect();
break;
}
}
}
}
PerspexFormattedTextLine line = new PerspexFormattedTextLine();
line.Start = curOff;
line.Length = measured;
line.Width = lineWidth;
line.Height = lineHeight;
line.Top = curY;
if (line.Width < 0)
line.Width = _skiaRects[line.Start + line.Length - 1].Right;
//Build character rects
for (int i = line.Start; i < line.Start + line.Length; i++)
{
float prevRight = 0;
if (i != line.Start)
prevRight = _skiaRects[i - 1].Right;
subString = _text.Substring(line.Start, i - line.Start + 1);
float w = Paint.MeasureText(subString);
SKRect rc;
rc.Left = prevRight;
rc.Right = w;
rc.Top = line.Top;
rc.Bottom = line.Top + line.Height;
_skiaRects[i] = rc;
}
subString = _text.Substring(line.Start, line.Length);
line.Width = Paint.MeasureText(subString);
_skiaLines.Add(line);
curY += lineHeight; // + mLeading;
curOff += measured + extraSkip;
}
// Now convert to Perspex data formats
_lines.Clear();
_rects.Clear();
float maxX = 0;
for (var c = 0; c < _skiaLines.Count; c++)
{
var w = _skiaLines[c].Width;
if (maxX < w)
maxX = w;
_lines.Add(new FormattedTextLine(_skiaLines[c].Length, _skiaLines[c].Height));
}
for (var c = 0; c < _text.Length; c++)
_rects.Add(_skiaRects[c].ToPerspexRect());
if (_skiaLines.Count == 0)
_size = new Size();
else
{
var lastLine = _skiaLines[_skiaLines.Count - 1];
_size = new Size(maxX, lastLine.Top + lastLine.Height);
}
}
internal void Draw(SKCanvas canvas, SKPoint origin)
{ {
var length = _text.Length; SKPaint paint = Paint;
_lines.Clear(); /* This originated from Native code, it might be useful
//Debugging code for character positions
_skiaRects = new SKRect[length]; SkPaint dpaint;
_skiaLines = new List<PerspexFormattedTextLine>(); ctx->Canvas->save();
ctx->Canvas->translate(origin.fX, origin.fY);
int curOff = 0; for (int c = 0; c < Lines.size(); c++)
float curY = 0; {
dpaint.setARGB(255, 0, 0, 0);
var metrics = Paint.FontMetrics; SkRect rc;
var mTop = metrics.Top; // The greatest distance above the baseline for any glyph (will be <= 0). rc.fLeft = 0;
var mBottom = metrics.Bottom; // The greatest distance below the baseline for any glyph (will be >= 0). rc.fTop = Lines[c].Top;
var mLeading = metrics.Leading; // The recommended distance to add between lines of text (will be >= 0). rc.fRight = Lines[c].Width;
rc.fBottom = rc.fTop + LineOffset;
// This seems like the best measure of full vertical extent ctx->Canvas->drawRect(rc, dpaint);
float lineHeight = mBottom - mTop; }
for (int c = 0; c < Length; c++)
// Rendering is relative to baseline {
LineOffset = -metrics.Top; dpaint.setARGB(255, c % 10 * 125 / 10 + 125, (c * 7) % 10 * 250 / 10, (c * 13) % 10 * 250 / 10);
dpaint.setStyle(SkPaint::kFill_Style);
string subString; ctx->Canvas->drawRect(Rects[c], dpaint);
}
byte[] bytes; ctx->Canvas->restore();
GCHandle pinnedArray; */
IntPtr pointer;
// These seems to vertically align the text properly
for (int c = 0; curOff < length; c++) var yOffset = (LineOffset); // + 1) / 2;
{
float lineWidth = -1; for (int c = 0; c < _skiaLines.Count; c++)
int measured; {
int extraSkip = 0; PerspexFormattedTextLine line = _skiaLines[c];
if (WidthConstraint <= 0) var subString = _text.Substring(line.Start, line.Length);
{ canvas.DrawText(subString, origin.X, origin.Y + line.Top + yOffset, paint);
measured = length; }
} }
else
{ Size _constraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
float constraint = WidthConstraint;
if (constraint > MAX_LINE_WIDTH)
constraint = MAX_LINE_WIDTH;
subString = _text.Substring(curOff);
// TODO: This method is not linking into SkiaSharp so we must use the RAW buffer version
//measured = (int)Paint.BreakText(subString, constraint, out lineWidth) / 2;
bytes = Encoding.UTF8.GetBytes(subString);
pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pointer = pinnedArray.AddrOfPinnedObject();
// for some reason I have to pass nBytes * 2
measured = (int)Paint.BreakText(pointer, (IntPtr)(bytes.Length * 2), constraint, out lineWidth) / 2;
// some weird unicode byte issue again
//if(subString.Length % 2 == 1)
//{
// measured -= 1;
//}
pinnedArray.Free();
if (measured == 0)
{
measured = 1;
lineWidth = -1;
}
char nextChar = ' ';
if (curOff + measured < length)
nextChar = _text[curOff + measured];
if (nextChar != ' ')
{
//Perform scan for the last space and end the line there
for (int si = curOff + measured - 1; si > curOff; si--)
{
if (_text[si] == ' ')
{
measured = si - curOff;
extraSkip = 1;
//Rects[si] = SkRect();
break;
}
}
}
}
PerspexFormattedTextLine line = new PerspexFormattedTextLine();
line.Start = curOff;
line.Length = measured;
line.Width = lineWidth;
line.Height = lineHeight;
line.Top = curY;
if (line.Width < 0)
line.Width = _skiaRects[line.Start + line.Length - 1].Right;
//Build character rects
for (int i = line.Start; i < line.Start + line.Length; i++)
{
float prevRight = 0;
if (i != line.Start)
prevRight = _skiaRects[i - 1].Right;
subString = _text.Substring(line.Start, i - line.Start + 1);
float w = Paint.MeasureText(subString);
SKRect rc;
rc.Left = prevRight;
rc.Right = w;
rc.Top = line.Top;
rc.Bottom = line.Top + line.Height;
_skiaRects[i] = rc;
}
subString = _text.Substring(line.Start, line.Length);
line.Width = Paint.MeasureText(subString);
_skiaLines.Add(line);
curY += lineHeight; // + mLeading;
curOff += measured + extraSkip;
}
// Now convert to Perspex data formats
_lines.Clear();
_rects.Clear();
float maxX = 0;
for (var c = 0; c < _skiaLines.Count; c++)
{
var w = _skiaLines[c].Width;
if (maxX < w)
maxX = w;
_lines.Add(new FormattedTextLine(_skiaLines[c].Length, _skiaLines[c].Height));
}
for (var c = 0; c < _text.Length; c++)
_rects.Add(_skiaRects[c].ToPerspexRect());
if (_skiaLines.Count == 0)
_size = new Size();
else
{
var lastLine = _skiaLines[_skiaLines.Count - 1];
_size = new Size(maxX, lastLine.Top + lastLine.Height);
}
}
internal void Draw(SKCanvas canvas, SKPoint origin)
{
SKPaint paint = Paint;
/* This originated from Native code, it might be useful
//Debugging code for character positions
SkPaint dpaint;
ctx->Canvas->save();
ctx->Canvas->translate(origin.fX, origin.fY);
for (int c = 0; c < Lines.size(); c++)
{
dpaint.setARGB(255, 0, 0, 0);
SkRect rc;
rc.fLeft = 0;
rc.fTop = Lines[c].Top;
rc.fRight = Lines[c].Width;
rc.fBottom = rc.fTop + LineOffset;
ctx->Canvas->drawRect(rc, dpaint);
}
for (int c = 0; c < Length; c++)
{
dpaint.setARGB(255, c % 10 * 125 / 10 + 125, (c * 7) % 10 * 250 / 10, (c * 13) % 10 * 250 / 10);
dpaint.setStyle(SkPaint::kFill_Style);
ctx->Canvas->drawRect(Rects[c], dpaint);
}
ctx->Canvas->restore();
*/
// These seems to vertically align the text properly
var yOffset = (LineOffset); // + 1) / 2;
for (int c = 0; c < _skiaLines.Count; c++)
{
PerspexFormattedTextLine line = _skiaLines[c];
var subString = _text.Substring(line.Start, line.Length);
canvas.DrawText(subString, origin.X, origin.Y + line.Top + yOffset, paint);
}
}
Size _constraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
public Size Constraint public Size Constraint
{ {
get { return _constraint; } get { return _constraint; }
set set
{ {
if(_constraint == value) if (_constraint == value)
return; return;
_constraint = value; _constraint = value;
WidthConstraint = (_constraint.Width != double.PositiveInfinity) WidthConstraint = (_constraint.Width != double.PositiveInfinity)
? (float)_constraint.Width ? (float)_constraint.Width
: -1; : -1;
Rebuild(); Rebuild();
} }
} }
@ -334,8 +334,8 @@ namespace Perspex.Skia
return _text; return _text;
} }
public void Dispose() public void Dispose()
{ {
} }
} }
} }

258
src/Skia/Perspex.Skia/RenderTarget.cs

@ -17,174 +17,174 @@ using Perspex.Win32.Interop;
namespace Perspex.Skia namespace Perspex.Skia
{ {
internal class RenderTarget : IRenderTarget internal class RenderTarget : IRenderTarget
{ {
public SKSurface Surface { get; protected set; } public SKSurface Surface { get; protected set; }
//protected override void Delete(IntPtr handle) => MethodTable.Instance.DisposeRenderTarget(handle); //protected override void Delete(IntPtr handle) => MethodTable.Instance.DisposeRenderTarget(handle);
public virtual DrawingContext CreateDrawingContext() public virtual DrawingContext CreateDrawingContext()
{ {
return return
new DrawingContext( new DrawingContext(
new DrawingContextImpl(Surface.Canvas)); // MethodTable.Instance.RenderTargetCreateRenderingContext(Handle))); new DrawingContextImpl(Surface.Canvas)); // MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
} }
public void Dispose() public void Dispose()
{ {
//throw new NotImplementedException(); //throw new NotImplementedException();
} }
} }
internal class WindowRenderTarget : RenderTarget internal class WindowRenderTarget : RenderTarget
{ {
private readonly IntPtr _hwnd; private readonly IntPtr _hwnd;
SKBitmap _bitmap; SKBitmap _bitmap;
int Width { get; set; } int Width { get; set; }
int Height { get; set; } int Height { get; set; }
public WindowRenderTarget(IntPtr hwnd) public WindowRenderTarget(IntPtr hwnd)
{ {
_hwnd = hwnd; _hwnd = hwnd;
FixSize(); FixSize();
} }
#if __IOS__ #if __IOS__
private CGRect GetApplicationFrame() private CGRect GetApplicationFrame()
{ {
// if we are excluding Status Bar then we use ApplicationFrame // if we are excluding Status Bar then we use ApplicationFrame
// otherwise we use full screen bounds. Note that this must also match // otherwise we use full screen bounds. Note that this must also match
// the Skia/PerspexView!!! // the Skia/PerspexView!!!
// //
bool excludeStatusArea = false; // TODO: make this configurable later bool excludeStatusArea = false; // TODO: make this configurable later
if (excludeStatusArea) if (excludeStatusArea)
{ {
return UIScreen.MainScreen.ApplicationFrame; return UIScreen.MainScreen.ApplicationFrame;
} }
else else
{ {
return UIScreen.MainScreen.Bounds; return UIScreen.MainScreen.Bounds;
} }
} }
#endif #endif
void FixSize() void FixSize()
{ {
int width, height; int width, height;
GetPlatformWindowSize(_hwnd, out width, out height); GetPlatformWindowSize(_hwnd, out width, out height);
if (Width == width && Height == height) if (Width == width && Height == height)
return; return;
Width = width; Width = width;
Height = height; Height = height;
if(Surface != null) if (Surface != null)
{ {
Surface.Dispose(); Surface.Dispose();
} }
if(_bitmap != null) if (_bitmap != null)
{ {
_bitmap.Dispose(); _bitmap.Dispose();
} }
_bitmap = new SKBitmap(width, height, SKColorType.N_32, SKAlphaType.Premul); _bitmap = new SKBitmap(width, height, SKColorType.N_32, SKAlphaType.Premul);
//bitmap.LockPixels(); //bitmap.LockPixels();
IntPtr length; IntPtr length;
var pixels = _bitmap.GetPixels(out length); var pixels = _bitmap.GetPixels(out length);
// Wrap the bitmap in a Surface and keep it cached // Wrap the bitmap in a Surface and keep it cached
Surface = SKSurface.Create(_bitmap.Info, pixels, _bitmap.RowBytes); Surface = SKSurface.Create(_bitmap.Info, pixels, _bitmap.RowBytes);
} }
void GetPlatformWindowSize(IntPtr hwnd, out int w, out int h) void GetPlatformWindowSize(IntPtr hwnd, out int w, out int h)
{ {
#if __IOS__ #if __IOS__
var bounds = GetApplicationFrame(); var bounds = GetApplicationFrame();
w = (int) bounds.Width; w = (int)bounds.Width;
h = (int)bounds.Height; h = (int)bounds.Height;
#elif WIN32 #elif WIN32
UnmanagedMethods.RECT rc; UnmanagedMethods.RECT rc;
UnmanagedMethods.GetClientRect(_hwnd, out rc); UnmanagedMethods.GetClientRect(_hwnd, out rc);
w = rc.right - rc.left; w = rc.right - rc.left;
h = rc.bottom - rc.top; h = rc.bottom - rc.top;
#else #else
throw new NotImplementedException(); throw new NotImplementedException();
#endif #endif
} }
public override DrawingContext CreateDrawingContext() public override DrawingContext CreateDrawingContext()
{ {
FixSize(); FixSize();
var canvas = Surface.Canvas; var canvas = Surface.Canvas;
canvas.RestoreToCount(0); canvas.RestoreToCount(0);
canvas.Save(); canvas.Save();
#if __IOS__ #if __IOS__
var screenScale = UIScreen.MainScreen.Scale; var screenScale = UIScreen.MainScreen.Scale;
canvas.Scale((float)screenScale, (float)screenScale); canvas.Scale((float)screenScale, (float)screenScale);
#endif #endif
canvas.Clear(SKColors.Red); canvas.Clear(SKColors.Red);
canvas.ResetMatrix(); canvas.ResetMatrix();
return return
new DrawingContext( new DrawingContext(
new WindowDrawingContextImpl(this)); new WindowDrawingContextImpl(this));
} }
public void Present() public void Present()
{ {
_bitmap.LockPixels(); _bitmap.LockPixels();
IntPtr length; IntPtr length;
var pixels = _bitmap.GetPixels(out length); var pixels = _bitmap.GetPixels(out length);
#if __IOS__ #if __IOS__
const int bitmapInfo = ((int)CGBitmapFlags.ByteOrder32Big) | ((int)CGImageAlphaInfo.PremultipliedLast); const int bitmapInfo = ((int)CGBitmapFlags.ByteOrder32Big) | ((int)CGImageAlphaInfo.PremultipliedLast);
var bounds = GetApplicationFrame(); var bounds = GetApplicationFrame();
var statusBarOffset = UIScreen.MainScreen.Bounds.Height - bounds.Height; var statusBarOffset = UIScreen.MainScreen.Bounds.Height - bounds.Height;
using (var colorSpace = CGColorSpace.CreateDeviceRGB()) using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(pixels, _bitmap.Width, _bitmap.Height, 8, _bitmap.Width * 4, colorSpace, (CGImageAlphaInfo)bitmapInfo)) using (var bContext = new CGBitmapContext(pixels, _bitmap.Width, _bitmap.Height, 8, _bitmap.Width * 4, colorSpace, (CGImageAlphaInfo)bitmapInfo))
using (var image = bContext.ToImage()) using (var image = bContext.ToImage())
using (var context = UIGraphics.GetCurrentContext()) using (var context = UIGraphics.GetCurrentContext())
{ {
// flip the image for CGContext.DrawImage // flip the image for CGContext.DrawImage
context.TranslateCTM(0, bounds.Height + statusBarOffset); context.TranslateCTM(0, bounds.Height + statusBarOffset);
context.ScaleCTM(1, -1); context.ScaleCTM(1, -1);
context.DrawImage(bounds, image); context.DrawImage(bounds, image);
} }
#elif WIN32 #elif WIN32
UnmanagedMethods.BITMAPINFO bmi = new UnmanagedMethods.BITMAPINFO(); UnmanagedMethods.BITMAPINFO bmi = new UnmanagedMethods.BITMAPINFO();
bmi.biSize = UnmanagedMethods.SizeOf_BITMAPINFOHEADER; bmi.biSize = UnmanagedMethods.SizeOf_BITMAPINFOHEADER;
bmi.biWidth = _bitmap.Width; bmi.biWidth = _bitmap.Width;
bmi.biHeight = -_bitmap.Height; // top-down image bmi.biHeight = -_bitmap.Height; // top-down image
bmi.biPlanes = 1; bmi.biPlanes = 1;
bmi.biBitCount = 32; bmi.biBitCount = 32;
bmi.biCompression = (uint)UnmanagedMethods.BitmapCompressionMode.BI_RGB; bmi.biCompression = (uint)UnmanagedMethods.BitmapCompressionMode.BI_RGB;
bmi.biSizeImage = 0; bmi.biSizeImage = 0;
IntPtr hdc = UnmanagedMethods.GetDC(_hwnd); IntPtr hdc = UnmanagedMethods.GetDC(_hwnd);
int ret = UnmanagedMethods.SetDIBitsToDevice(hdc, int ret = UnmanagedMethods.SetDIBitsToDevice(hdc,
0, 0, 0, 0,
(uint) _bitmap.Width, (uint) _bitmap.Height, (uint)_bitmap.Width, (uint)_bitmap.Height,
0, 0, 0, 0,
0, (uint) _bitmap.Height, 0, (uint)_bitmap.Height,
pixels, pixels,
ref bmi, ref bmi,
(uint)UnmanagedMethods.DIBColorTable.DIB_RGB_COLORS); (uint)UnmanagedMethods.DIBColorTable.DIB_RGB_COLORS);
UnmanagedMethods.ReleaseDC(_hwnd, hdc); UnmanagedMethods.ReleaseDC(_hwnd, hdc);
#endif #endif
_bitmap.UnlockPixels(); _bitmap.UnlockPixels();
} }
} }
} }

12
src/Skia/Perspex.Skia/SkiaPlatform.cs

@ -24,17 +24,17 @@ namespace Perspex.Skia
public static void Initialize() public static void Initialize()
=> PerspexLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(new PlatformRenderInterface()); => PerspexLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(new PlatformRenderInterface());
public static bool ForceSoftwareRendering public static bool ForceSoftwareRendering
{ {
get { return s_forceSoftwareRendering; } get { return s_forceSoftwareRendering; }
set set
{ {
s_forceSoftwareRendering = value; s_forceSoftwareRendering = value;
// Do we still need this with SkiaSharp?? // Do we still need this with SkiaSharp??
//MethodTable.Instance.SetOption(MethodTable.Option.ForceSoftware, new IntPtr(value ? 1 : 0)); //MethodTable.Instance.SetOption(MethodTable.Option.ForceSoftware, new IntPtr(value ? 1 : 0));
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
} }
} }

110
src/Skia/Perspex.Skia/SkiaSharpExtensions.cs

@ -4,66 +4,66 @@ using SkiaSharp;
namespace Perspex.Skia namespace Perspex.Skia
{ {
public static class SkiaSharpExtensions public static class SkiaSharpExtensions
{ {
public static SKPoint ToSKPoint(this Point p) public static SKPoint ToSKPoint(this Point p)
{ {
return new SKPoint((float)p.X, (float)p.Y); return new SKPoint((float)p.X, (float)p.Y);
} }
public static SKRect ToSKRect(this Rect r) public static SKRect ToSKRect(this Rect r)
{ {
return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom); return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);
} }
public static Rect ToPerspexRect(this SKRect r) public static Rect ToPerspexRect(this SKRect r)
{ {
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
} }
public static SKMatrix ToSKMatrix(this Matrix m) public static SKMatrix ToSKMatrix(this Matrix m)
{ {
var sm = new SKMatrix var sm = new SKMatrix
{ {
ScaleX = (float)m.M11, ScaleX = (float)m.M11,
SkewX = (float)m.M21, SkewX = (float)m.M21,
TransX = (float)m.M31, TransX = (float)m.M31,
SkewY = (float)m.M12, SkewY = (float)m.M12,
ScaleY = (float)m.M22, ScaleY = (float)m.M22,
TransY = (float)m.M32, TransY = (float)m.M32,
Persp0 = 0, Persp0 = 0,
Persp1 = 0, Persp1 = 0,
Persp2 = 1 Persp2 = 1
}; };
return sm; return sm;
} }
public static SKColor ToSKColor(this Media.Color c) public static SKColor ToSKColor(this Media.Color c)
{ {
return new SKColor(c.R, c.G, c.B, c.A); return new SKColor(c.R, c.G, c.B, c.A);
} }
public static SKShaderTileMode ToSKShaderTileMode(this Media.GradientSpreadMethod m) public static SKShaderTileMode ToSKShaderTileMode(this Media.GradientSpreadMethod m)
{ {
switch (m) switch (m)
{ {
default: default:
case Media.GradientSpreadMethod.Pad: return SKShaderTileMode.Clamp; case Media.GradientSpreadMethod.Pad: return SKShaderTileMode.Clamp;
case Media.GradientSpreadMethod.Reflect: return SKShaderTileMode.Mirror; case Media.GradientSpreadMethod.Reflect: return SKShaderTileMode.Mirror;
case Media.GradientSpreadMethod.Repeat: return SKShaderTileMode.Repeat; case Media.GradientSpreadMethod.Repeat: return SKShaderTileMode.Repeat;
} }
} }
public static SKTextAlign ToSKTextAlign(this TextAlignment a) public static SKTextAlign ToSKTextAlign(this TextAlignment a)
{ {
switch (a) switch (a)
{ {
default: default:
case TextAlignment.Left: return SKTextAlign.Left; case TextAlignment.Left: return SKTextAlign.Left;
case TextAlignment.Center: return SKTextAlign.Center; case TextAlignment.Center: return SKTextAlign.Center;
case TextAlignment.Right: return SKTextAlign.Right; case TextAlignment.Right: return SKTextAlign.Right;
} }
} }
} }
} }

138
src/Skia/Perspex.Skia/StreamGeometryImpl.cs

@ -12,11 +12,11 @@ namespace Perspex.Skia
{ {
class StreamGeometryImpl : IStreamGeometryImpl class StreamGeometryImpl : IStreamGeometryImpl
{ {
SKPath _path; SKPath _path;
SKPath _transformedPath; SKPath _transformedPath;
private Matrix _transform = Matrix.Identity;
private Matrix _transform = Matrix.Identity;
public SKPath EffectivePath => (_transformedPath ?? _path); public SKPath EffectivePath => (_transformedPath ?? _path);
public Rect GetRenderBounds(double strokeThickness) public Rect GetRenderBounds(double strokeThickness)
@ -32,7 +32,7 @@ namespace Perspex.Skia
get { return _transform; } get { return _transform; }
set set
{ {
if(_transform == value) if (_transform == value)
return; return;
_transform = value; _transform = value;
@ -42,80 +42,80 @@ namespace Perspex.Skia
void ApplyTransform() void ApplyTransform()
{ {
if (_path == null) if (_path == null)
return; return;
if (_transformedPath != null) if (_transformedPath != null)
{ {
_transformedPath.Dispose(); _transformedPath.Dispose();
_transformedPath = null; _transformedPath = null;
} }
// TODO: SkiaSharp does not expose Transform yet!!! // TODO: SkiaSharp does not expose Transform yet!!!
//if (!Transform.IsIdentity) //if (!Transform.IsIdentity)
//{ //{
// _transformedPath = new SKPath(); // _transformedPath = new SKPath();
// _path.Transform(Transform.ToSKMatrix(), _transformedPath); // _path.Transform(Transform.ToSKMatrix(), _transformedPath);
//} //}
} }
public IStreamGeometryImpl Clone() public IStreamGeometryImpl Clone()
{ {
// TODO: there is no SKPath.Clone yet!!!!!!!!!!!!! // TODO: there is no SKPath.Clone yet!!!!!!!!!!!!!
// //
//return new StreamGeometryImpl //return new StreamGeometryImpl
//{ //{
// _path = _path?.Clone(), // _path = _path?.Clone(),
// _transformedPath = _transformedPath?.Clone(), // _transformedPath = _transformedPath?.Clone(),
// _transform = Transform, // _transform = Transform,
// Bounds = Bounds // Bounds = Bounds
//}; //};
return this; // this will probably end bad!! return this; // this will probably end bad!!
} }
public IStreamGeometryContextImpl Open() public IStreamGeometryContextImpl Open()
{ {
_path = new SKPath(); _path = new SKPath();
return new StreamContext(this); return new StreamContext(this);
} }
class StreamContext : IStreamGeometryContextImpl class StreamContext : IStreamGeometryContextImpl
{ {
private readonly StreamGeometryImpl _geometryImpl; private readonly StreamGeometryImpl _geometryImpl;
private SKPath _path; private SKPath _path;
//readonly List<SkiaGeometryElement> _elements = new List<SkiaGeometryElement>(); //readonly List<SkiaGeometryElement> _elements = new List<SkiaGeometryElement>();
Point _currentPoint; Point _currentPoint;
public StreamContext(StreamGeometryImpl geometryImpl) public StreamContext(StreamGeometryImpl geometryImpl)
{ {
_geometryImpl = geometryImpl; _geometryImpl = geometryImpl;
_path = _geometryImpl._path; _path = _geometryImpl._path;
} }
public void Dispose() public void Dispose()
{ {
// Not sure what we need to do here.... // Not sure what we need to do here....
// //throw new NotImplementedException(); // //throw new NotImplementedException();
// var arr = _elements.ToArray(); // var arr = _elements.ToArray();
// SkRect rc; // SkRect rc;
// _path?.Dispose(); // _path?.Dispose();
// _path = new SKPath(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc))); // _path = new SKPath(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc)));
// _geometryImpl.ApplyTransform(); // _geometryImpl.ApplyTransform();
// _geometryImpl.Bounds = rc.ToRect(); // _geometryImpl.Bounds = rc.ToRect();
SKRect rc; SKRect rc;
_path.GetBounds(out rc); _path.GetBounds(out rc);
_geometryImpl.ApplyTransform(); _geometryImpl.ApplyTransform();
_geometryImpl.Bounds = rc.ToPerspexRect(); _geometryImpl.Bounds = rc.ToPerspexRect();
} }
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
//ArcToHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection); //ArcToHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection);
//_currentPoint = point; //_currentPoint = point;
@ -123,34 +123,34 @@ namespace Perspex.Skia
public void BeginFigure(Point startPoint, bool isFilled) public void BeginFigure(Point startPoint, bool isFilled)
{ {
_path.MoveTo((float)startPoint.X, (float)startPoint.Y); _path.MoveTo((float)startPoint.X, (float)startPoint.Y);
_currentPoint = startPoint; _currentPoint = startPoint;
} }
public void CubicBezierTo(Point point1, Point point2, Point point3) public void CubicBezierTo(Point point1, Point point2, Point point3)
{ {
_path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y); _path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
_currentPoint = point3; _currentPoint = point3;
} }
public void QuadraticBezierTo(Point point1, Point point2) public void QuadraticBezierTo(Point point1, Point point2)
{ {
_path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y); _path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
_currentPoint = point2; _currentPoint = point2;
} }
public void LineTo(Point point) public void LineTo(Point point)
{ {
_path.LineTo((float)point.X, (float)point.Y); _path.LineTo((float)point.X, (float)point.Y);
_currentPoint = point; _currentPoint = point;
} }
public void EndFigure(bool isClosed) public void EndFigure(bool isClosed)
{ {
if(isClosed) if (isClosed)
{ {
_path.Close(); _path.Close();
} }
} }
public void SetFillRule(FillRule fillRule) public void SetFillRule(FillRule fillRule)

18
src/Skia/Perspex.Skia/TypefaceCache.cs

@ -22,13 +22,13 @@ namespace Perspex.Skia
SKTypeface typeface = null; SKTypeface typeface = null;
if (!entry.TryGetValue(style, out typeface)) if (!entry.TryGetValue(style, out typeface))
{ {
typeface = SKTypeface.FromFamilyName(name, style); typeface = SKTypeface.FromFamilyName(name, style);
if(typeface == null) if (typeface == null)
{ {
typeface = SKTypeface.FromFamilyName(null, style); typeface = SKTypeface.FromFamilyName(null, style);
} }
entry[style] = typeface; entry[style] = typeface;
} }
return typeface; return typeface;
} }
@ -38,8 +38,8 @@ namespace Perspex.Skia
if (style != FontStyle.Normal) if (style != FontStyle.Normal)
sstyle |= SKTypefaceStyle.Italic; sstyle |= SKTypefaceStyle.Italic;
if(weight>FontWeight.Normal) if (weight > FontWeight.Normal)
sstyle |= SKTypefaceStyle.Bold; sstyle |= SKTypefaceStyle.Bold;
return GetTypeface(name, sstyle); return GetTypeface(name, sstyle);
} }

13
src/Skia/Perspex.Skia/readme.md

@ -5,18 +5,18 @@ BitmapImpl
- Save - Save
DrawingContextImpl DrawingContextImpl
- DrawRoundRect is not properly implemented due to lack of support in Skia - DrawRoundRect is not properly implemented due to lack of support in SkiaSharp
- Alpha support missing as SkiaSharp does not expose this - Alpha support missing as SkiaSharp does not expose this
- Gradient Shader caching? - Gradient Shader caching?
- TileBrushes - TileBrushes
- Pen Dash styles - Pen Dash styles
Formatted Text Rendering Formatted Text Rendering
- still needs a lot of work once SkiaSharp implementation is better - minor polish
RenderTarget RenderTarget
- Figure out a cleaner implementation across all platforms - Figure out a cleaner implementation across all platforms
- HW accelerated working? - HW acceleration
StreamGeometry StreamGeometry
- Paths within Paths may not work right - Paths within Paths may not work right
@ -30,13 +30,12 @@ App Bootstrapping
- ie. app.UseDirect2D() (via platform specific extension methods) - ie. app.UseDirect2D() (via platform specific extension methods)
Android Android
- Testing & fixes - Not tested at all yet
iOS iOS
- Get GLView working again? - Get GLView working again. See HW above
General General
- Cleanup/eliminate obsolete files - Cleanup/eliminate obsolete files
- Cleanup the many Test Applications - Finish cleanup of the many Test Applications
- Get Skia Unit Tests passing - Get Skia Unit Tests passing

1
src/iOS/Perspex.iOS/Perspex.iOS.csproj

@ -37,6 +37,7 @@
<Compile Include="Clipboard.cs" /> <Compile Include="Clipboard.cs" />
<Compile Include="CursorFactory.cs" /> <Compile Include="CursorFactory.cs" />
<Compile Include="Extensions.cs" /> <Compile Include="Extensions.cs" />
<Compile Include="iOSPlatform.cs" />
<Compile Include="PerspexView.cs" /> <Compile Include="PerspexView.cs" />
<Compile Include="PerspexAppDelegate.cs" /> <Compile Include="PerspexAppDelegate.cs" />
<Compile Include="PlatformSettings.cs" /> <Compile Include="PlatformSettings.cs" />

2
src/iOS/Perspex.iOS/PerspexAppDelegate.cs

@ -10,6 +10,7 @@ using Perspex.Shared.PlatformSupport;
using Perspex.Skia; using Perspex.Skia;
using UIKit; using UIKit;
/* No Longer needed
namespace Perspex.iOS namespace Perspex.iOS
{ {
public class PerspexAppDelegate : UIApplicationDelegate public class PerspexAppDelegate : UIApplicationDelegate
@ -73,3 +74,4 @@ namespace Perspex.iOS
} }
} }
} }
*/

12
src/iOS/Perspex.iOS/PerspexView.cs

@ -168,7 +168,7 @@ namespace Perspex.iOS
var location = touch.LocationInView(this).ToPerspex(); var location = touch.LocationInView(this).ToPerspex();
Input?.Invoke(new RawMouseEventArgs( Input?.Invoke(new RawMouseEventArgs(
PerspexAppDelegate.MouseDevice, iOSPlatform.MouseDevice,
(uint) touch.Timestamp, (uint) touch.Timestamp,
_inputRoot, _inputRoot,
RawMouseEventType.LeftButtonUp, RawMouseEventType.LeftButtonUp,
@ -185,10 +185,10 @@ namespace Perspex.iOS
{ {
var location = touch.LocationInView(this).ToPerspex(); var location = touch.LocationInView(this).ToPerspex();
_touchLastPoint = location; _touchLastPoint = location;
Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot, Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint) touch.Timestamp, _inputRoot,
RawMouseEventType.Move, location, InputModifiers.None)); RawMouseEventType.Move, location, InputModifiers.None));
Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot, Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint) touch.Timestamp, _inputRoot,
RawMouseEventType.LeftButtonDown, location, InputModifiers.None)); RawMouseEventType.LeftButtonDown, location, InputModifiers.None));
} }
} }
@ -199,15 +199,15 @@ namespace Perspex.iOS
if (touch != null) if (touch != null)
{ {
var location = touch.LocationInView(this).ToPerspex(); var location = touch.LocationInView(this).ToPerspex();
if (PerspexAppDelegate.MouseDevice.Captured != null) if (iOSPlatform.MouseDevice.Captured != null)
Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot, Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint) touch.Timestamp, _inputRoot,
RawMouseEventType.Move, location, InputModifiers.LeftMouseButton)); RawMouseEventType.Move, location, InputModifiers.LeftMouseButton));
else else
{ {
//magic number based on test - correction of 0.02 is working perfect //magic number based on test - correction of 0.02 is working perfect
double correction = 0.02; double correction = 0.02;
Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint)touch.Timestamp, Input?.Invoke(new RawMouseWheelEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp,
_inputRoot, location, (location - _touchLastPoint)* correction, InputModifiers.LeftMouseButton)); _inputRoot, location, (location - _touchLastPoint)* correction, InputModifiers.LeftMouseButton));
} }
_touchLastPoint = location; _touchLastPoint = location;

107
src/iOS/Perspex.iOS/iOSPlatform.cs

@ -0,0 +1,107 @@
using System;
using System.Reflection;
using Perspex.Input;
using Perspex.Input.Platform;
using Perspex.iOS;
using Perspex.Platform;
using Perspex.Shared.PlatformSupport;
using Perspex.Skia;
using UIKit;
namespace Perspex
{
public static class iOSApplicationExtensions
{
public static AppT UseiOS<AppT>(this AppT app) where AppT : Application
{
Perspex.iOS.iOSPlatform.Initialize();
return app;
}
// I wish I could merge this with the SkiaPlatform itself. Might be possible
// once we switch to SkiaSharp
public static AppT UseSkiaViewHost<AppT>(this AppT app) where AppT : Application
{
var window = new UIWindow(UIScreen.MainScreen.Bounds);
var controller = new PerspexViewController(window);
window.RootViewController = controller;
window.MakeKeyAndVisible();
PerspexLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatform(controller.PerspexView));
SkiaPlatform.Initialize();
return app;
}
public static AppT UseAssetAssembly<AppT>(this AppT app, Assembly assembly) where AppT : Application
{
// Asset loading searches our own assembly?
var loader = new AssetLoader(assembly);
PerspexLocator.CurrentMutable.Bind<IAssetLoader>().ToConstant(loader);
return app;
}
// This is somewhat generic, could probably put this elsewhere. But I don't think
// it should part of the iOS App Delegate
//
class WindowingPlatform : IWindowingPlatform
{
private readonly IWindowImpl _window;
public WindowingPlatform(IWindowImpl window)
{
_window = window;
}
public IWindowImpl CreateWindow()
{
return _window;
}
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public IPopupImpl CreatePopup()
{
throw new NotImplementedException();
}
}
}
}
namespace Perspex.iOS
{
// TODO: Perhaps we should make this class handle all these interfaces directly, like we
// do for Win32 and Gtk
//
public class iOSPlatform //: IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform
{
internal static MouseDevice MouseDevice;
internal static KeyboardDevice KeyboardDevice;
public static void Initialize()
{
MouseDevice = new MouseDevice();
KeyboardDevice = new KeyboardDevice();
// refactored
//SharedPlatform.Register(appType.Assembly);
PerspexLocator.CurrentMutable
.Bind<IPclPlatformWrapper>().ToSingleton<PclPlatformWrapper>()
.Bind<IClipboard>().ToTransient<Clipboard>()
//.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
}
}
}

95
src/iOS/Perspex.iOSTestApplication/AppDelegate.cs

@ -16,7 +16,7 @@ namespace Perspex.iOSTestApplication
// User Interface of the application, as well as listening (and optionally responding) to // User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS. // application events from iOS.
[Register("AppDelegate")] [Register("AppDelegate")]
public partial class AppDelegate : PerspexAppDelegate public partial class AppDelegate : UIApplicationDelegate
{ {
// //
// This method is invoked when the application has loaded and is ready to run. In this // This method is invoked when the application has loaded and is ready to run. In this
@ -27,58 +27,65 @@ namespace Perspex.iOSTestApplication
// //
public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options) public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
{ {
InitPerspex (typeof (App));
var app = new App(); var app = new App();
MainWindow.RootNamespace = "Perspex.iOSTestApplication"; app.UseiOS();
var window = MainWindow.Create(); app.UseSkiaViewHost();
//var window = Create(); app.UseSkia();
window.Show();
app.Run(window); // looking for this URI fails: "TestApplication.github_icon.png"
var asm = typeof(App).Assembly;
app.UseAssetAssembly(asm);
//MainWindow.RootNamespace = "Perspex.iOSTestApplication";
//var window = MainWindow.Create();
////var window = Create();
//window.Show();
//app.Run(window);
app.Run();
return true; return true;
} }
// This provides a simple UI tree for testing input handling // This provides a simple UI tree for testing input handling
public static Window Create() public static Window Create()
{ {
Window window = new Window Window window = new Window
{ {
Title = "Perspex Test Application", Title = "Perspex Test Application",
//Width = 900, //Width = 900,
//Height = 480, //Height = 480,
Background = Brushes.Red, Background = Brushes.Red,
Content = new StackPanel Content = new StackPanel
{ {
Margin = new Thickness(30), Margin = new Thickness(30),
Background = Brushes.Yellow, Background = Brushes.Yellow,
Children = new Controls.Controls Children = new Controls.Controls
{ {
new TextBlock new TextBlock
{ {
Text = "TEXT BLOCK", Text = "TEXT BLOCK",
Width = 300, Width = 300,
Height = 40, Height = 40,
Background = Brushes.White, Background = Brushes.White,
Foreground = Brushes.Black Foreground = Brushes.Black
}, },
new Button new Button
{ {
Content = "BUTTON", Content = "BUTTON",
Width = 150, Width = 150,
Height = 40, Height = 40,
Background = Brushes.LightGreen, Background = Brushes.LightGreen,
Foreground = Brushes.Black Foreground = Brushes.Black
} }
} }
} }
}; };
return window; return window;
} }
} }

4
src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj

@ -147,6 +147,10 @@
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project> <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name> <Name>Perspex.Controls</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.HtmlRenderer\Perspex.HtmlRenderer.csproj"> <ProjectReference Include="..\..\Perspex.HtmlRenderer\Perspex.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project> <Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Perspex.HtmlRenderer</Name> <Name>Perspex.HtmlRenderer</Name>

Loading…
Cancel
Save