Browse Source

Massive android-backend code cleanup and fixes for threading and rendering

pull/327/head
Nikita Tsukanov 11 years ago
parent
commit
cf65cc5ef7
  1. 28
      Perspex.sln
  2. 51
      samples/TestApplicationShared/MainWindow.cs
  3. 82
      src/Android/Perspex.Android/AndroidAssetLoader.cs
  4. 224
      src/Android/Perspex.Android/AndroidPlatform.cs
  5. 84
      src/Android/Perspex.Android/AndroidThreadingInterface.cs
  6. 246
      src/Android/Perspex.Android/CanvasRendering/AndroidGraphicsExtensions.cs
  7. 65
      src/Android/Perspex.Android/CanvasRendering/AndroidPlatformRender.cs
  8. 34
      src/Android/Perspex.Android/CanvasRendering/BitmapImpl.cs
  9. 363
      src/Android/Perspex.Android/CanvasRendering/BrushImpl.cs
  10. 248
      src/Android/Perspex.Android/CanvasRendering/DrawingContext.cs
  11. 205
      src/Android/Perspex.Android/CanvasRendering/FormattedTextImpl.cs
  12. 63
      src/Android/Perspex.Android/CanvasRendering/RenderTarget.cs
  13. 49
      src/Android/Perspex.Android/CanvasRendering/RenderTargetBitmapImpl.cs
  14. 158
      src/Android/Perspex.Android/CanvasRendering/StreamGeometryImpl.cs
  15. 22
      src/Android/Perspex.Android/Perspex.Android.csproj
  16. 14
      src/Android/Perspex.Android/Platform/CanvasPlatform/IAndroidCanvasView.cs
  17. 13
      src/Android/Perspex.Android/Platform/CanvasPlatform/IAndroidViewSupportRender.cs
  18. 71
      src/Android/Perspex.Android/Platform/CanvasPlatform/MainWindowImpl.cs
  19. 184
      src/Android/Perspex.Android/Platform/CanvasPlatform/PointUnitService.cs
  20. 137
      src/Android/Perspex.Android/Platform/CanvasPlatform/PopupImpl.cs
  21. 688
      src/Android/Perspex.Android/Platform/CanvasPlatform/Specific/Helpers/AndroidViewRenderHelpers.cs
  22. 33
      src/Android/Perspex.Android/Platform/CanvasPlatform/SurfaceMainWindowImpl.cs
  23. 194
      src/Android/Perspex.Android/Platform/CanvasPlatform/SurfaceWindowImpl.cs
  24. 207
      src/Android/Perspex.Android/Platform/CanvasPlatform/WindowImpl.cs
  25. 23
      src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs
  26. 50
      src/Android/Perspex.Android/Platform/Specific/CommonUITimer.cs
  27. 28
      src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  28. 23
      src/Android/Perspex.Android/Platform/Specific/Helpers/ViewDrawType.cs
  29. 6
      src/Android/Perspex.Android/Platform/Specific/PerspexActivity.cs
  30. 372
      src/Android/Perspex.AndroidTestApplication/App.cs
  31. 4
      src/Android/Perspex.AndroidTestApplication/GettingStarted.Xamarin
  32. 48
      src/Android/Perspex.AndroidTestApplication/MainActivity.cs
  33. 18
      src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj
  34. 28
      src/Android/Perspex.AndroidTestApplication/Resources/Resource.Designer.cs
  35. 9
      src/Android/Perspex.AndroidTestApplication/Resources/layout/Main.axml
  36. 162
      src/Android/Perspex.AndroidTestApplication/SetupTestUI.cs
  37. 1659
      src/Android/Perspex.AndroidTestApplication/TestUI.cs
  38. BIN
      src/Android/Perspex.AndroidTestApplication/github_icon.png
  39. BIN
      src/Android/Perspex.AndroidTestApplication/pattern.jpg
  40. 9
      src/Perspex.Application/Application.cs
  41. 16
      src/Shared/PlatformSupport/AssetLoader.cs
  42. 5
      src/Shared/PlatformSupport/SharedPlatform.cs
  43. 2
      src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj
  44. 122
      src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj.bak
  45. 2
      src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj
  46. 16
      src/Skia/Perspex.Skia.Android/SkiaView.cs

28
Perspex.sln

@ -123,6 +123,7 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Skia.iOS", "src\Skia\Perspex.Skia.iOS\Perspex.Skia.iOS.csproj", "{47BE08A7-5985-410B-9FFC-2264B8EA595F}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "TestApplicationShared", "samples\TestApplicationShared\TestApplicationShared.shproj", "{78345174-5B52-4A14-B9FD-D5F2428137F0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Android", "src\Android\Perspex.Android\Perspex.Android.csproj", "{7B92AF71-6287-4693-9DCB-BD5B6E927E23}"
@ -136,17 +137,18 @@ Global
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -1157,16 +1159,36 @@ Global
{47BE08A7-5985-410B-9FFC-2264B8EA595F}.Release|iPhone.Build.0 = Release|Any CPU
{47BE08A7-5985-410B-9FFC-2264B8EA595F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{47BE08A7-5985-410B-9FFC-2264B8EA595F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Release|Any CPU.Build.0 = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Release|iPhone.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|Any CPU.Build.0 = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|Any CPU.Deploy.0 = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|iPhone.ActiveCfg = Release|Any CPU
{FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

51
samples/TestApplicationShared/MainWindow.cs

@ -77,45 +77,30 @@ namespace TestApplication
TabControl container;
Window window = new Window
{
Title = "Perspex Test Application",
Width = 900,
Height = 480,
Content = new Grid
//Width = 900,
//Height = 480,
Content = (container = new TabControl
{
ColumnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
new ColumnDefinition(1, GridUnitType.Star),
},
RowDefinitions = new RowDefinitions
Padding = new Thickness(5),
Items = new[]
{
new RowDefinition(GridLength.Auto),
new RowDefinition(1, GridUnitType.Star),
new RowDefinition(GridLength.Auto),
ButtonsTab(),
TextTab(),
HtmlTab(),
ImagesTab(),
ListsTab(),
LayoutTab(),
AnimationsTab(),
},
Children = new Controls
{
(container = new TabControl
{
Padding = new Thickness(5),
Items = new[]
{
ButtonsTab(),
TextTab(),
HtmlTab(),
ImagesTab(),
ListsTab(),
LayoutTab(),
AnimationsTab(),
},
Transition = new CrossFade(TimeSpan.FromSeconds(0.25)),
[Grid.RowProperty] = 1,
[Grid.ColumnSpanProperty] = 2,
})
}
},
Transition = new CrossFade(TimeSpan.FromSeconds(0.25)),
[Grid.RowProperty] = 1,
[Grid.ColumnSpanProperty] = 2,
})
};
container.Classes.Add(":container");

82
src/Android/Perspex.Android/AndroidAssetLoader.cs

@ -1,82 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Android.Platform.Specific;
using Perspex.Platform;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Perspex.Android.PlatformSupport
{
/// <summary>
/// Loads assets compiled into the application binary.
/// </summary>
public class AndroidAssetLoader : IAssetLoader
{
internal Assembly DefaultAssetAssembly { get; set; }
internal string DefaultResourcePrefix { get; set; }
public AndroidAssetLoader()
{
//Assembly.GetEntryAssembly() not working on mono/xamarin for android!!!
//may be this is better way to have at least the application assembly as default one
DefaultAssetAssembly = PerspexLocator.Current.GetService<IAndroidActivity>().Activity.GetType().Assembly;
}
private static readonly Dictionary<string, Assembly> AssemblyNameCache = new Dictionary<string, Assembly>();
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri)
{
return OpenInternal(uri, false) != null;
}
/// <summary>
/// Opens the resource with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>A stream containing the resource contents.</returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Stream Open(Uri uri)
{
return OpenInternal(uri, true);
}
private Stream OpenInternal(Uri uri, bool throwIfNotSuccess)
{
//uri.AbsolutePath is NotFiniteNumberException escaped adn can cause problems in assembly names with spaces
bool isAbsolute = uri.IsAbsoluteUri;
var parts = (isAbsolute ? uri.LocalPath : uri.OriginalString).Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var asm = parts.Length == 1 ? DefaultAssetAssembly : GetAssembly(parts[0]);
var typeName = parts[parts.Length == 1 ? 0 : 1];
if (typeName != null && !isAbsolute && !typeName.StartsWith(DefaultResourcePrefix))
{
typeName = DefaultResourcePrefix + typeName;
}
var rv = asm.GetManifestResourceStream(typeName);
if (rv == null && throwIfNotSuccess)
throw new FileNotFoundException($"The resource {uri} could not be found.");
return rv;
}
private static Assembly GetAssembly(string name)
{
Assembly rv;
if (!AssemblyNameCache.TryGetValue(name, out rv))
AssemblyNameCache[name] = rv =
AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name)
?? Assembly.Load(name);
return rv;
}
}
}

224
src/Android/Perspex.Android/AndroidPlatform.cs

@ -1,11 +1,7 @@
using Android.OS;
using Perspex.Android.CanvasRendering;
using Perspex.Android.Platform;
using Perspex.Android.Platform.Input;
using Perspex.Android.Platform.Specific;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Android.PlatformSupport;
using Perspex.Animation;
using Perspex.Controls.Platform;
using Perspex.Input;
using Perspex.Input.Platform;
@ -14,228 +10,44 @@ using Perspex.Shared.PlatformSupport;
using Perspex.Skia;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Perspex.Android
{
public class AndroidPlatform : IPlatformThreadingInterface, IPlatformSettings
public class AndroidPlatform : IPlatformSettings
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
private HashSet<int> _renderingTask = new HashSet<int>();
public void RegisterRenderingTaskId(int taskId)
{
_renderingTask.Add(taskId);
}
public void UnRegisterRenderingTaskId(int taskId)
{
_renderingTask.Add(taskId);
}
public bool CurrentThreadIsLoopThread
{
get
{
bool res = Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
if (!res)
{
if (Task.CurrentId != null)
{
int id = Task.CurrentId.Value;
res = _renderingTask.Contains(Task.CurrentId.Value);
}
}
return res;
}
}
public void RunLoop(CancellationToken cancellationToken)
{
}
private TimeSpan _animationTick = TimeSpan.FromSeconds(1.0 / Animate.FramesPerSecond);
public IDisposable StartTimer(TimeSpan interval, Action tick)
{
//we should be carefull about the smallest amount of time the timer ticks
//as on android for animations so far there is some issue with 60 fps
//for now we can get set it to 16 fps with observable timer
//and still random native exceptions causing app to stop!!!!
//better not use it
//return Observable.Timer(interval, interval).Subscribe(_ => tick());
//System.Threading.Timer
//int ms = (int)interval.TotalMilliseconds;
////regular timer is working perfect
//return new System.Threading.Timer(_ => tick(), null, ms, ms);
//when interval is 0 normal System.Timers.Timer is not working
//so we ween other strategy for that
if (interval.TotalMilliseconds == 0)
{
if(DefaultViewDrawType == ViewDrawType.Skia)
{
//or start timer with low enough interval 10ms ???
//with skia when animation active if we use RunOnUiThread UI is not handling the events!!! issue or not?
interval = TimeSpan.FromMilliseconds(10);
}
else
{
//android ui thread
PerspexLocator.Current.GetService<IAndroidActivity>().Activity.RunOnUiThread(tick);
return Disposable.Empty;
}
}
else if (OverrideAnimateFramesPerSecond > 0)
{
if (_animationTick >= interval)
{
interval = TimeSpan.FromSeconds(1.0 / OverrideAnimateFramesPerSecond);
}
}
//working not very bad with standard timer
//var timer = new System.Timers.Timer(interval.TotalMilliseconds);
//System.Timers.ElapsedEventHandler elapsed = (s, e) => tick();
//timer.Elapsed += elapsed;
//timer.Start();
//return Disposable.Create(() =>
//{
// timer.Stop();
// timer.Elapsed -= elapsed;
// timer.Dispose();
//});
//common ui timer is optimizing performance
//a lot almost twice, also may be processor usage
//most probably because it optimizes invalidation calls in animations and using less system resources
CommonUITimer timer;
if (!_commonTimers.TryGetValue(interval, out timer))
{
timer = new CommonUITimer(interval);
_commonTimers.Add(interval, timer);
}
timer.AddAction(tick);
return Disposable.Create(() =>
{
timer.RemoveAction(tick);
if (timer.ActiveActionsCount == 0)
{
timer.Dispose();
_commonTimers.Remove(interval);
}
});
}
private Dictionary<TimeSpan, CommonUITimer> _commonTimers = new Dictionary<TimeSpan, CommonUITimer>();
public void Signal()
{
EnsureInvokeOnMainThread(() => Signaled?.Invoke());
}
public event Action Signaled;
public static void Initialize()
AndroidPlatform()
{
var settings = new PlatformSettings();
PerspexLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IMouseDevice>().ToSingleton<AndroidMouseDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformThreadingInterface>().ToConstant(Instance)
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IAssetLoader>().ToSingleton<AndroidAssetLoader>()
.Bind<ITopLevelRenderer>().ToTransient<AndroidTopLevelRenderer>()
.Bind<PlatformSettings>().ToConstant(new PlatformSettings());
;
Instance.RegisterViewDrawType();
//set defaults for simple resources
//Assembly.GetEntryAssembly(); //not working on mono for android
var appType = Application.Current.GetType();
var entryAssembly = appType.Assembly;
AndroidPlatform.Instance.InitializeAssetsLoader(entryAssembly, appType.Namespace + ".");
// SharedPlatform.Register();
//we have custom Assetloader so no need to overwrite it
PerspexLocator.CurrentMutable
.Bind<IPclPlatformWrapper>().ToSingleton<PclPlatformWrapper>();
}
public void RegisterViewDrawType()
{
if (Instance.DefaultViewDrawType == ViewDrawType.Skia)
{
//do the default initialization for skia rendering
SkiaPlatform.Initialize();
PerspexLocator.CurrentMutable.Bind<IWindowImpl>().ToSingleton<Platform.SkiaPlatform.MainWindowImpl>();
}
else
{
AndroidPlatformRender.Initialize();
if (Instance.DefaultViewDrawType == ViewDrawType.SurfaceViewCanvasOnDraw)
{
PerspexLocator.CurrentMutable.Bind<IWindowImpl>().ToSingleton<Platform.CanvasPlatform.SurfaceMainWindowImpl>();
}
else
{
PerspexLocator.CurrentMutable.Bind<IWindowImpl>().ToSingleton<Platform.CanvasPlatform.MainWindowImpl>();
}
PerspexLocator.CurrentMutable.Bind<IPopupImpl>().ToTransient<Platform.CanvasPlatform.PopupImpl>();
}
}
.Bind<PlatformSettings>().ToConstant(settings);
public void RegisterViewPointUnits()
{
PerspexLocator.CurrentMutable.Bind<Platform.CanvasPlatform.IPointUnitService>().ToSingleton<Platform.CanvasPlatform.PointUnitService>();
}
SkiaPlatform.Initialize();
Application.SuppressPlatformInitialization();
PerspexLocator.CurrentMutable.Bind<IWindowImpl>().ToSingleton<Platform.SkiaPlatform.MainWindowImpl>();
public void InitializeAssetsLoader(Assembly assembly, string defaultResourcePrefix)
{
var assetLoder = PerspexLocator.Current.GetService<IAssetLoader>() as AndroidAssetLoader;
if (assetLoder != null)
{
assetLoder.DefaultAssetAssembly = assembly;
assetLoder.DefaultResourcePrefix = defaultResourcePrefix;
}
var scale = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
PerspexLocator.Current.GetService<PlatformSettings>().LayoutScalingFactor = scale;
PerspexLocator.Current.GetService<PlatformSettings>().RenderScalingFactor = scale;
//we have custom Assetloader so no need to overwrite it
}
private void EnsureInvokeOnMainThread(Action action)
public void Init(Type applicationType)
{
var mainHandler = new Handler(global::Android.App.Application.Context.MainLooper);
mainHandler.Post(action);
SharedPlatform.Register(applicationType.Assembly);
}
public bool DrawDebugInfo { get; set; } = false;
/// <summary>
/// Default View DrawType
/// WindowImpl supports:
/// ViewDrawType.CanvasOnDraw
/// ViewDrawType.BitmapOnPreDraw
/// BitmapBackgroundRender
/// by default it is ViewDrawType.CanvasOnDraw simples way of drawing view
/// SurfaceWindowImpl supports:
/// SurfaceViewCanvasOnDraw
/// </summary>
public ViewDrawType DefaultViewDrawType { get; set; } = ViewDrawType.Skia;
public Platform.CanvasPlatform.PointUnit DefaultPointUnit { get; set; } = Platform.CanvasPlatform.PointUnit.DP;
public int OverrideAnimateFramesPerSecond { get; set; } = -1;
}
}

84
src/Android/Perspex.Android/AndroidThreadingInterface.cs

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Perspex.Platform;
namespace Perspex.Android
{
class AndroidThreadingInterface : IPlatformThreadingInterface
{
private Handler _handler;
public AndroidThreadingInterface()
{
_handler = new Handler(global::Android.App.Application.Context.MainLooper);
}
public void RunLoop(CancellationToken cancellationToken)
{
return;
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
{
if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10);
object l = new object();
var stopped = false;
Timer timer = null;
var scheduled = false;
timer = new Timer(_ =>
{
lock (l)
{
if (stopped)
{
timer.Dispose();
return;
}
if (scheduled)
return;
scheduled = true;
EnsureInvokeOnMainThread(() =>
{
tick();
lock (l)
{
scheduled = false;
}
});
}
}, null, TimeSpan.Zero, interval);
return Disposable.Create(() =>
{
lock (l)
{
stopped = true;
timer.Dispose();
}
});
}
private void EnsureInvokeOnMainThread(Action action) => _handler.Post(action);
public void Signal()
{
EnsureInvokeOnMainThread(() => Signaled?.Invoke());
}
public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
public event Action Signaled;
}
}

246
src/Android/Perspex.Android/CanvasRendering/AndroidGraphicsExtensions.cs

@ -1,246 +0,0 @@
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Android.Platform.Specific;
using Perspex.Media;
using System;
using AAllignment = Android.Text.Layout.Alignment;
using ACap = Android.Graphics.Paint.Cap;
using AColor = Android.Graphics.Color;
using AJoin = Android.Graphics.Paint.Join;
using AMatrix = Android.Graphics.Matrix;
using APoint = Android.Graphics.PointF;
using ARect = Android.Graphics.Rect;
using ARectF = Android.Graphics.RectF;
using AStyle = Android.Graphics.Paint.Style;
using ATextAlign = Android.Graphics.Paint.Align;
using ATileMode = Android.Graphics.Shader.TileMode;
using ATypeface = Android.Graphics.Typeface;
using ATypeStyle = Android.Graphics.TypefaceStyle;
namespace Perspex.Android.CanvasRendering
{
public static class AndroidGraphicsExtensions
{
public static AColor ToAndroidGraphics(this Color c)
{
return new AColor(c.R, c.G, c.B, c.A);
}
public static AMatrix ToAndroidGraphics(this Matrix matrix)
{
var ps = PointUnitService.Instance;
//TODO: !!!
var transformValues = new float[9];
transformValues[0] = (float)matrix.M11;
transformValues[1] = (float)matrix.M21;
transformValues[2] = ps.PerspexToNativeXF(matrix.M31);
//transformValues[2] = (float)matrix.M31;
transformValues[3] = (float)matrix.M12;
transformValues[4] = (float)matrix.M22;
transformValues[5] = ps.PerspexToNativeYF(matrix.M32);
//transformValues[5] = (float)matrix.M32;
transformValues[6] = 0;
transformValues[7] = 0;
transformValues[8] = 1;
var am = new AMatrix();
am.SetValues(transformValues);
return am;
}
//public static Matrix ToPerspex(this AMatrix m)
//{
// var v = new float[9];
// m.GetValues(v);
// return new Matrix(v[0], v[3], v[1], v[4], v[2], v[5]);
//}
public static APoint ToAndroidGraphics(this Point p, bool notransform = false)
{
if (notransform) new APoint((float)p.X, (float)p.Y);
var ps = PointUnitService.Instance;
return new APoint(ps.PerspexToNativeXF(p.X), ps.PerspexToNativeYF(p.Y));
}
//public static Point ToPerspex(this APoint p)
//{
// return new Point(p.X, p.Y);
//}
public static ARect ToAndroidGraphics(this Rect r, bool notransform = false)
{
if (notransform) return new ARect((int)r.X, (int)r.Y, (int)r.Right, (int)r.Bottom);
var ps = PointUnitService.Instance;
return new ARect(ps.PerspexToNativeXInt(r.X), ps.PerspexToNativeYInt(r.Y), ps.PerspexToNativeXInt(r.Right), ps.PerspexToNativeYInt(r.Bottom));
}
public static ARectF ToAndroidGraphicsF(this Rect r)
{
var ps = PointUnitService.Instance;
return new ARectF(ps.PerspexToNativeXF(r.X), ps.PerspexToNativeYF(r.Y), ps.PerspexToNativeXF(r.Right), ps.PerspexToNativeYF(r.Bottom));
}
//public static Rect ToPerspex(this ARect r)
//{
// return new Rect(r.Left, r.Top, r.Width(), r.Height());
//}
public static Rect ToPerspex(this ARectF r)
{
var ps = PointUnitService.Instance;
return new Rect(ps.NativeToPerspexX(r.Left), ps.NativeToPerspexY(r.Top), ps.NativeToPerspexX(r.Width()), ps.NativeToPerspexY(r.Height()));
}
public static AJoin ToAndroidGraphics(this PenLineJoin plj)
{
switch (plj)
{
default:
case PenLineJoin.Bevel:
return AJoin.Bevel;
case PenLineJoin.Miter:
return AJoin.Miter;
case PenLineJoin.Round:
return AJoin.Round;
}
}
public static ACap ToAndroidGraphics(this PenLineCap plc)
{
switch (plc)
{
default:
case PenLineCap.Flat:
return ACap.Butt;
case PenLineCap.Round:
return ACap.Round;
case PenLineCap.Square:
return ACap.Square;
//Triangle not supported
case PenLineCap.Triangle:
goto default;
}
}
public static AStyle ToAndroidGraphics(this BrushUsage bu)
{
switch (bu)
{
default:
case BrushUsage.Fill:
return AStyle.Fill;
case BrushUsage.Stroke:
return AStyle.Stroke;
case BrushUsage.Both:
return AStyle.FillAndStroke;
}
}
public static ATypeface ToAndroidGraphicsTypeface(this FontStyle s, FontWeight fontWeight)
{
ATypeface result = ATypeface.Default;
if (fontWeight >= FontWeight.Bold)
{
result = ATypeface.DefaultBold;
}
return result;
}
public static ATypeStyle ToAndroidGraphicsTypefaceStyle(this FontStyle s, FontWeight fontWeight)
{
ATypeStyle result;
switch (s)
{
default:
case FontStyle.Normal:
result = ATypeStyle.Normal;
break;
case FontStyle.Italic:
result = ATypeStyle.Italic;
break;
//Oblique not supported, return normal
case FontStyle.Oblique:
goto default;
}
if (fontWeight >= FontWeight.Bold)
{
result = result == ATypeStyle.Italic ? ATypeStyle.BoldItalic : ATypeStyle.Bold;
}
return result;
}
public static ATextAlign ToAndroidGraphics(this TextAlignment ta)
{
switch (ta)
{
default:
case TextAlignment.Left:
return ATextAlign.Left;
case TextAlignment.Center:
return ATextAlign.Center;
case TextAlignment.Right:
return ATextAlign.Right;
}
}
public static AAllignment ToAndroidGraphicsLayoutAligment(this TextAlignment ta)
{
switch (ta)
{
default:
case TextAlignment.Left:
return AAllignment.AlignNormal;
case TextAlignment.Center:
return AAllignment.AlignCenter;
case TextAlignment.Right:
return AAllignment.AlignOpposite;
}
}
public static ATileMode ToAndroidGraphicsX(this TileMode tileMode)
{
switch (tileMode)
{
case TileMode.Tile: return ATileMode.Repeat;
case TileMode.FlipX: return ATileMode.Mirror;
case TileMode.FlipXY: return ATileMode.Mirror;
case TileMode.FlipY: return ATileMode.Clamp;
case TileMode.None: return ATileMode.Clamp;
default: return ATileMode.Clamp;
}
}
public static ATileMode ToAndroidGraphicsY(this TileMode tileMode)
{
switch (tileMode)
{
case TileMode.Tile: return ATileMode.Repeat;
case TileMode.FlipX: return ATileMode.Clamp;
case TileMode.FlipXY: return ATileMode.Mirror;
case TileMode.FlipY: return ATileMode.Mirror;
case TileMode.None: return ATileMode.Clamp;
default: return ATileMode.Clamp;
}
}
public static int OpacityToAndroidAlfa(double opacity)
{
return Convert.ToInt32(opacity * 255);
}
}
}

65
src/Android/Perspex.Android/CanvasRendering/AndroidPlatformRender.cs

@ -1,65 +0,0 @@
using Perspex.Android.Platform.Specific;
using Perspex.Media;
using Perspex.Platform;
using System.IO;
using AG = Android.Graphics;
using System;
using Perspex.Android.Platform.CanvasPlatform;
namespace Perspex.Android.CanvasRendering
{
public class AndroidPlatformRender : IPlatformRenderInterface
{
private static readonly AndroidPlatformRender instance = new AndroidPlatformRender();
public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize,
FontStyle fontStyle,
TextAlignment textAlignment, FontWeight fontWeight)
{
return new FormattedTextImpl(text, fontFamilyName, fontSize, fontStyle, textAlignment, fontWeight);
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new StreamGeometryImpl();
}
public IRenderTarget CreateRenderer(IPlatformHandle handle)
{
return new RenderTarget((IAndroidCanvasView)handle);
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int pwidth, int pheight)
{
int width = PointUnitService.Instance.PerspexToNativeXInt(pwidth);
int height = PointUnitService.Instance.PerspexToNativeYInt(pwidth);
return new RenderTargetBitmapImpl(CreateNativeBitmap(width, height));
}
public IBitmapImpl CreateBitmap(int pwidth, int pheight)
{
int width = PointUnitService.Instance.PerspexToNativeXInt(pwidth);
int height = PointUnitService.Instance.PerspexToNativeYInt(pwidth);
return new BitmapImpl(CreateNativeBitmap(width, height));
}
public IBitmapImpl LoadBitmap(string fileName)
{
return new BitmapImpl(AG.BitmapFactory.DecodeFile(fileName));
}
public IBitmapImpl LoadBitmap(Stream stream)
{
return new BitmapImpl(AG.BitmapFactory.DecodeStream(stream));
}
public static void Initialize()
=> PerspexLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(instance);
private AG.Bitmap CreateNativeBitmap(int width, int height)
{
return AG.Bitmap.CreateBitmap(width, height, AG.Bitmap.Config.Argb8888);
}
}
}

34
src/Android/Perspex.Android/CanvasRendering/BitmapImpl.cs

@ -1,34 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Platform;
using System.IO;
using AG = Android.Graphics;
namespace Perspex.Android.CanvasRendering
{
public class BitmapImpl : IBitmapImpl
{
public BitmapImpl(AG.Bitmap bitmap)
{
PlatformBitmap = bitmap;
}
public int PixelWidth => PlatformBitmap.Width;
public int PixelHeight => PlatformBitmap.Height;
public AG.Bitmap PlatformBitmap
{
get;
}
public void Save(string fileName)
{
using (var f = File.OpenWrite(fileName))
{
PlatformBitmap.Compress(AG.Bitmap.CompressFormat.Png, 100, f);
}
}
}
}

363
src/Android/Perspex.Android/CanvasRendering/BrushImpl.cs

@ -1,363 +0,0 @@
using Android.Graphics;
using Android.Util;
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Android.Platform.Specific;
using Perspex.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using AColor = Android.Graphics.Color;
namespace Perspex.Android.CanvasRendering
{
public enum BrushUsage
{
Fill,
Stroke,
Both
};
public interface INativePaintWrapper : IDisposable
{
Paint NativePaint { get; }
void PushOpacity(double value);
void PopOpacity();
}
public abstract class BrushImpl : IDisposable, INativePaintWrapper
{
public BrushImpl(Brush brush)
{
Brush = brush;
Brush.PropertyChanged += Brush_PropertyChanged;
Init();
}
protected virtual void Init()
{
Opacity = Brush.Opacity;
}
private void Brush_PropertyChanged(object sender, PerspexPropertyChangedEventArgs e)
{
_isDirty = true;
}
protected bool _isDirty = false;
public Brush Brush { get; private set; }
private double _oldOpacity = 1;
public double Opacity { get; private set; }
public virtual void Dispose()
{
Brush.PropertyChanged -= Brush_PropertyChanged;
Brush = null;
NativePaint?.Dispose();
}
private Paint _nativeBrush;
public Paint NativePaint
{
get
{
if (_nativeBrush == null)
{
_nativeBrush = new Paint(PaintFlags.AntiAlias);
_isDirty = true;
}
if (_isDirty)
{
Apply();
}
return _nativeBrush;
}
}
public Paint.Style Style { get; set; }
public virtual void ApplyTo(Paint paint)
{
paint.Alpha = AndroidGraphicsExtensions.OpacityToAndroidAlfa(Opacity);
paint.SetStyle(Style);
}
private void Apply()
{
_isDirty = false;
ApplyTo(NativePaint);
}
private static readonly BrushImpl FallbackBrush = new SolidColorBrushImpl(new SolidColorBrush(Colors.Transparent));
public static BrushImpl Create(Brush brush, Size dstRect, Paint.Style style)
{
//Size dstRect = PointService.Instance.PerspexToNative(pdstRect);
BrushImpl impl = null;
if (brush is SolidColorBrush)
{
impl = new SolidColorBrushImpl(brush as SolidColorBrush);
}
else if (brush is LinearGradientBrush)
{
impl = new LinearGradientBrushImpl(brush as LinearGradientBrush, dstRect);
}
else if (brush is RadialGradientBrush)
{
impl = new RadialGradientBrushImpl(brush as RadialGradientBrush, dstRect);
}
else if (brush is ImageBrush)
{
impl = new ImageBrushImpl(brush as ImageBrush);
}
else if (brush is VisualBrush)
{
// TODO: Implement me
Log.Debug("REND", "VisualBrush not implemented");
impl = FallbackBrush;
}
else
{
impl = new SolidColorBrushImpl(null);
}
impl.Style = style;
return impl;
}
private Stack<double> _opacityStack = new Stack<double>();
public void PushOpacity(double value)
{
_opacityStack.Push(this.Opacity);
Opacity = Opacity * value;
_isDirty = value != 1 ? true : false;
}
public void PopOpacity()
{
double newOpacity = _opacityStack.Pop();
if (newOpacity != Opacity)
{
Opacity = newOpacity;
_isDirty = true;
}
}
}
public class PenImpl : IDisposable, INativePaintWrapper
{
public PenImpl(Pen pen, Size dstRect, Paint.Style style)
{
this.Pen = pen;
if (this.Pen.Brush != null)
{
this.Brush = BrushImpl.Create(this.Pen.Brush, dstRect, style);
}
}
private BrushImpl Brush { get; set; }
public Pen Pen { get; }
private Paint _nativePaint;
public Paint NativePaint
{
get
{
if (_nativePaint == null)
{
_nativePaint = new Paint(PaintFlags.AntiAlias);
Apply();
}
return _nativePaint;
}
set
{
this._nativePaint = value;
}
}
public void PushOpacity(double opacity)
{
Brush?.PushOpacity(opacity);
}
public void PopOpacity()
{
Brush?.PopOpacity();
}
private void Apply()
{
if (Pen.DashStyle?.Dashes != null && Pen.DashStyle.Dashes.Count > 0)
{
var cray = Pen.DashStyle.Dashes.Select(d => (float)d).ToArray();
NativePaint.SetPathEffect(new DashPathEffect(cray, (float)Pen.DashStyle.Offset));
}
NativePaint.StrokeWidth = PointUnitService.Instance.PerspexToNativeXF(Pen.Thickness);
NativePaint.StrokeMiter = (float)Pen.MiterLimit;
NativePaint.StrokeJoin = Pen.LineJoin.ToAndroidGraphics();
NativePaint.StrokeCap = Pen.StartLineCap.ToAndroidGraphics();
if (Brush != null)
{
Brush.ApplyTo(NativePaint);
}
}
public void Dispose()
{
Brush?.Dispose();
}
}
public class SolidColorBrushImpl : BrushImpl
{
private AColor _nativeColor;
public SolidColorBrushImpl(SolidColorBrush brush) : base(brush)
{
}
public override void ApplyTo(Paint paint)
{
paint.Color = _nativeColor;
base.ApplyTo(paint);
}
protected override void Init()
{
base.Init();
var brush = Brush as SolidColorBrush;
_nativeColor = brush.Color.ToAndroidGraphics();
}
}
public class ShaderBrushImpl : BrushImpl
{
protected Shader Shader { get; set; }
protected Size DestinationSize { get; }
public ShaderBrushImpl(Brush brush, Size destinationSize) : base(brush)
{
DestinationSize = destinationSize;
}
public override void ApplyTo(Paint paint)
{
base.ApplyTo(paint);
if (Shader != null)
{
paint.SetShader(Shader);
}
else
{
//we had some problem better set something
paint.Color = new AColor(0, 0, 0, 255);
}
}
public override void Dispose()
{
Shader?.Dispose();
base.Dispose();
}
}
public class LinearGradientBrushImpl : ShaderBrushImpl
{
public LinearGradientBrushImpl(LinearGradientBrush brush, Size destinationSize) : base(brush, destinationSize)
{
}
protected override void Init()
{
base.Init();
var brush = Brush as LinearGradientBrush;
int n = brush.GradientStops.Count;
var offsets = new float[n];
var colors = new int[n];
for (var i = 0; i < n; i++)
{
var s = brush.GradientStops[i];
offsets[i] = (float)s.Offset;
colors[i] = (int)s.Color.ToUint32();
}
var startPoint = brush.StartPoint.ToPixels(DestinationSize);
var endPoint = brush.EndPoint.ToPixels(DestinationSize);
this.Shader = new LinearGradient(
(float)startPoint.X, (float)startPoint.Y,
(float)endPoint.X, (float)endPoint.Y,
colors,
offsets,
Shader.TileMode.Clamp);
}
}
public class RadialGradientBrushImpl : ShaderBrushImpl
{
public RadialGradientBrushImpl(RadialGradientBrush brush, Size destinationSize) : base(brush, destinationSize)
{
}
protected override void Init()
{
base.Init();
var brush = Brush as RadialGradientBrush;
int n = brush.GradientStops.Count;
var offsets = new float[n];
var colors = new int[n];
for (var i = 0; i < n; i++)
{
var s = brush.GradientStops[i];
offsets[i] = (float)s.Offset;
colors[i] = (int)s.Color.ToUint32();
}
var centerPoint = brush.Center.ToPixels(DestinationSize);
double radius = brush.Center.Unit == RelativeUnit.Absolute ? brush.Radius : Math.Max(brush.Radius * DestinationSize.Width, brush.Radius * DestinationSize.Height);
Shader = new RadialGradient(
(float)centerPoint.X, (float)centerPoint.Y,
(float)brush.Radius,
colors,
offsets,
Shader.TileMode.Clamp);
}
}
public class ImageBrushImpl : ShaderBrushImpl
{
public ImageBrushImpl(ImageBrush brush) : base(brush, default(Size))
{
}
protected override void Init()
{
base.Init();
var brush = Brush as ImageBrush;
var bmp = brush.Source.PlatformImpl as BitmapImpl;
Shader = new BitmapShader(bmp.PlatformBitmap, brush.TileMode.ToAndroidGraphicsX(), brush.TileMode.ToAndroidGraphicsY());
}
}
}

248
src/Android/Perspex.Android/CanvasRendering/DrawingContext.cs

@ -1,248 +0,0 @@
using Android.Graphics;
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Android.Platform.Specific;
using Perspex.Media;
using Perspex.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
namespace Perspex.Android.CanvasRendering
{
public class DrawingContextImpl : IDrawingContextImpl
{
private double _currentOpacity = 1.0f;
private double CurrentOpacity
{
get { return _currentOpacity; }
set
{
_currentOpacity = value;
}
}
public Canvas _canvas;
private Dictionary<object, IDisposable> _visualCaches;
private Dictionary<object, IDisposable> _cache;
public DrawingContextImpl(Canvas canvas, Dictionary<object, IDisposable> visualCaches)
{
_canvas = canvas;
_visualCaches = visualCaches;
_cache = visualCaches ?? new Dictionary<object, IDisposable>();
}
public void Dispose()
{
_canvas = null;
if (_visualCaches == null)
{
DisposeCaches();
}
}
private void DisposeCaches()
{
foreach (var d in _cache)
{
d.Value.Dispose();
}
_cache.Clear();
}
private Matrix _currentTransform = Matrix.Identity;
public Matrix Transform
{
get { return _currentTransform; }
set
{
if (_currentTransform == value)
return;
_currentTransform = value;
_canvas.Matrix = value.ToAndroidGraphics();
}
}
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
{
var nativeBitmap = (source.PlatformImpl as BitmapImpl).PlatformBitmap;
_canvas.DrawBitmap(nativeBitmap, sourceRect.ToAndroidGraphics(true), destRect.ToAndroidGraphicsF(), new Paint() { Alpha = AndroidGraphicsExtensions.OpacityToAndroidAlfa(opacity * CurrentOpacity) });
}
public void DrawLine(Pen pen, Point pp1, Point pp2)
{
var p1 = PointUnitService.Instance.PerspexToNative(pp1);
var p2 = PointUnitService.Instance.PerspexToNative(pp2);
var Rect = new Rect(p1.X, p1.Y, p2.X, p2.Y);
PenImpl np;
using ((GetPen(pen, new Size(Rect.Width, Rect.Height), out np)))
{
_canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, np.NativePaint);
}
}
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
{
var impl = geometry.PlatformImpl as StreamGeometryImpl;
if (brush != null)
{
BrushImpl nb;
using (var b = GetBrush(brush, geometry.Bounds.Size, out nb))
{
_canvas.DrawPath(impl.Path, nb.NativePaint);
}
}
//if the pen brush is null and thickness null on android
//the shape is filled with black !! better not draw the stroke!!!
if (pen?.Brush != null && pen?.Thickness > 0)
{
PenImpl np;
using (var p = GetPen(pen, geometry.Bounds.Size, out np))
{
_canvas.DrawPath(impl.Path, np.NativePaint);
}
}
}
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
{
PenImpl np;
using (GetPen(pen, rect.Size, out np))
{
if (cornerRadius == 0)
{
_canvas.DrawRect(rect.ToAndroidGraphicsF(), np.NativePaint);
}
else
{
_canvas.DrawRoundRect(rect.ToAndroidGraphicsF(), cornerRadius, cornerRadius, np.NativePaint);
}
}
}
public void DrawText(Brush foreground, Point origin, FormattedText text)
{
if (!string.IsNullOrEmpty(text.Text))
{
var ap = origin.ToAndroidGraphics();
var impl = (FormattedTextImpl)text.PlatformImpl;
_canvas.Save();
BrushImpl nb;
using (var b = GetBrush(foreground, impl.Measure(), out nb))
{
nb.ApplyTo(impl.TextPaint);
_canvas.Translate((float)ap.X, (float)ap.Y);
impl.TextLayout.Draw(_canvas);
}
_canvas.Restore();
}
}
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0)
{
BrushImpl np;
using (var b = GetBrush(brush, rect.Size, out np))
{
var ar = rect.ToAndroidGraphicsF();
if (cornerRadius == 0)
{
_canvas.DrawRect(ar, np.NativePaint);
}
else
{
var cr = PointUnitService.Instance.PerspexToNativeXF(cornerRadius);
_canvas.DrawRoundRect(ar, cr, cr, np.NativePaint);
//Canvas.DrawPath(RoundRectPath(rect, cornerRadius), nb.NativePaint);
}
}
}
private Rect _clip;
public void PushClip(Rect clip)
{
_canvas.Save();
_canvas.ClipRect(clip.ToAndroidGraphics());
_clip = clip;
}
public void PopClip() => _canvas.Restore();
private Stack<double> _opacityStack = new Stack<double>();
public void PushOpacity(double opacity)
{
_opacityStack.Push(CurrentOpacity);
CurrentOpacity = CurrentOpacity * opacity;
}
public void PopOpacity() => CurrentOpacity = _opacityStack.Pop();
//private Path RoundRectPath(Rect rc, float radius)
//{
// var x = (float)rc.TopLeft.X;
// var y = (float)rc.TopLeft.Y;
// var width = (float)rc.Width;
// var height = (float)rc.Height;
// var rx = radius;
// var ry = radius;
// var path = new Path();
// path.MoveTo(x + rx, y);
// path.LineTo(x + width - rx, y + 0);
// path.QuadTo(x + width, y, x + width, y + ry);
// path.LineTo(x + width, y + height - ry);
// path.QuadTo(x + width, y + height, x + width - rx, y + height);
// path.LineTo(x + rx, y + height);
// path.QuadTo(x, y + height, x, y + height - ry);
// path.LineTo(x, y + ry);
// path.QuadTo(x, y, x + rx, y);
// return path;
//}
private IDisposable GetPen(Pen pen, Size dstRect, out PenImpl result)
{
var penImpl = GetOrCreatePaintWrapper<PenImpl>(pen, () => new PenImpl(pen, dstRect, Paint.Style.Stroke));
penImpl.PushOpacity(CurrentOpacity);
result = penImpl;
return Disposable.Create(() => penImpl.PopOpacity());
}
private IDisposable GetBrush(Brush brush, Size dstRect, out BrushImpl result)
{
var brushImpl = GetOrCreatePaintWrapper<BrushImpl>(brush, () => BrushImpl.Create(brush, dstRect, Paint.Style.Fill));
brushImpl.PushOpacity(CurrentOpacity);
result = brushImpl;
return Disposable.Create(() => brushImpl.PopOpacity());
}
private T GetOrCreatePaintWrapper<T>(object key, Func<T> createFunc) where T : INativePaintWrapper
{
IDisposable cachedVisual;
T result;
if (!_cache.TryGetValue(key, out cachedVisual))
{
result = createFunc();
_cache[key] = result;
}
else
{
result = (T)cachedVisual;
}
return result;
}
}
}

205
src/Android/Perspex.Android/CanvasRendering/FormattedTextImpl.cs

@ -1,205 +0,0 @@
using Android.Graphics;
using Android.Text;
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Android.Platform.Specific;
using Perspex.Media;
using Perspex.Platform;
using System;
using System.Collections.Generic;
using AAllignment = Android.Text.Layout.Alignment;
using ARect = Android.Graphics.Rect;
using ATextPaint = Android.Text.TextPaint;
namespace Perspex.Android.CanvasRendering
{
public class FormattedTextImpl : IFormattedTextImpl
{
public FormattedTextImpl(
string text,
string fontFamily,
double fontSize,
FontStyle fontStyle,
TextAlignment textAlignment,
FontWeight fontWeight)
{
String = text;
TextPaint = new ATextPaint { TextAlign = textAlignment.ToAndroidGraphics() };
var typefaceStyle = fontStyle.ToAndroidGraphicsTypefaceStyle(fontWeight);
var typeface = fontStyle.ToAndroidGraphicsTypeface(fontWeight);
TextPaint.SetTypeface(typeface);
TextPaint.TextSize = PointUnitService.Instance.PerspexToNativeFontSize(fontSize);
Aligment = textAlignment.ToAndroidGraphicsLayoutAligment();
var rect = new ARect();
TextPaint.GetTextBounds(String, 0, String.Length, rect);
//or use measure text to give us other opinion for the text ???
//double tw = TextFormatting.MeasureText(String, 0, String.Length);
//give few pixels in advance just in case
_initialSize = PointUnitService.Instance.NativeToPerspex(new Size(rect.Right, rect.Bottom));
Constraint = _initialSize;
}
private AAllignment Aligment;
public string String { get; }
public ATextPaint TextPaint { get; }
private Size _initialSize;
public Size Constraint { get; set; }
public void Dispose()
{
}
private StaticLayout _layout = null;
private double _layoutBuildWidth = 0;
private Dictionary<string, KeyValuePair<object, object>> _localCachedValues = new Dictionary<string, KeyValuePair<object, object>>();
private T GetLocalCachedValue<T>(string name, object forKey, Func<T> getFunc)
{
KeyValuePair<object, object> kvp = default(KeyValuePair<object, object>);
bool create = true;
if (_localCachedValues.ContainsKey(name))
{
kvp = _localCachedValues[name];
create = kvp.Key != forKey;
}
if (create)
{
kvp = new KeyValuePair<object, object>(forKey, getFunc());
_localCachedValues[name] = kvp;
}
return (T)kvp.Value;
}
public StaticLayout TextLayout
{
get
{
if (_layout == null || Math.Abs(_layoutBuildWidth - Constraint.Width) > 1)
{
var newWidth = (Constraint.Width > int.MaxValue ? _initialSize.Width : Constraint.Width) + 10;
double widthDiff = _layoutBuildWidth - newWidth;
if (_layout == null || widthDiff < 0)
{
_layoutBuildWidth = newWidth;
_layout = new StaticLayout(String, TextPaint, PointUnitService.Instance.PerspexToNativeXInt(_layoutBuildWidth), Aligment, 1.0f, 0.0f, false);
_localCachedValues.Clear();
}
}
return _layout;
}
}
public IEnumerable<FormattedTextLine> GetLines()
{
return GetLocalCachedValue("GetLines", "GetLines", () =>
{
List<FormattedTextLine> result = new List<FormattedTextLine>();
for (int i = 0; i < TextLayout.LineCount; i++)
{
int start = TextLayout.GetLineStart(i);
int end = TextLayout.GetLineEnd(i);
//string lineContent = String.Substring(start, end - start);
result.Add(new FormattedTextLine(end - start, PointUnitService.Instance.NativeToPerspexY(TextLayout.GetLineTop(i) - TextLayout.GetLineBottom(i))));
}
return result;
});
}
public TextHitTestResult HitTestPoint(Point ppoint)
{
return GetLocalCachedValue("HitTestPoint", ppoint, () =>
{
var point = PointUnitService.Instance.PerspexToNative(ppoint);
bool inside = point.X >= 0 && point.X <= TextLayout.Width && point.Y >= 0 && point.Y <= TextLayout.Height;
int index = -1;
if (inside)
{
int line = this.TextLayout.GetLineForVertical(Convert.ToInt32(point.Y));
index = this.TextLayout.GetOffsetForHorizontal(line, (float)point.X);
}
return new TextHitTestResult { IsInside = inside, IsTrailing = false, TextPosition = index };
});
}
public Rect HitTestTextPosition(int index)
{
return
GetLocalCachedValue("HitTestTextPosition", index, () =>
{
int line = TextLayout.GetLineForOffset(index);
int top = TextLayout.GetLineTop(line);
int bottom = TextLayout.GetLineBottom(line);
double left = TextLayout.GetPrimaryHorizontal(index);
double right = String.Length > index + 1 ? TextLayout.GetPrimaryHorizontal(index + 1) : left;
return PointUnitService.Instance.NativeToPerspex(new Rect(left, top, right - left, bottom - top));
});
}
public IEnumerable<Rect> HitTestTextRange(int index, int length)
{
int startLine = TextLayout.GetLineForOffset(index);
int endIndex = index + length;
int endLine = TextLayout.GetLineForOffset(endIndex);
for (int i = startLine; i <= endLine; i++)
{
int top = TextLayout.GetLineTop(i);
int bottom = TextLayout.GetLineBottom(i);
double left = 0, right = 0;
if (startLine == i)
{
left = TextLayout.GetPrimaryHorizontal(index);
}
else
{
left = TextLayout.GetLineLeft(i);
}
if (endLine == i)
{
right = TextLayout.GetPrimaryHorizontal(endIndex);
}
else
{
right = TextLayout.GetLineRight(i);
}
yield return PointUnitService.Instance.NativeToPerspex(new Rect(left, top, right - left, bottom - top));
}
}
public Size Measure()
{
return PointUnitService.Instance.NativeToPerspex(new Size(this.TextLayout.Width, this.TextLayout.Height));
}
public void SetForegroundBrush(Brush brush, int startIndex, int length)
{
var scb = brush as SolidColorBrush;
if (scb != null)
{
TextPaint.Color = scb.Color.ToAndroidGraphics();
}
}
}
}

63
src/Android/Perspex.Android/CanvasRendering/RenderTarget.cs

@ -1,63 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Android.Platform.Specific;
using Perspex.Platform;
using AG = Android.Graphics;
namespace Perspex.Android.CanvasRendering
{
/// <summary>
/// A android render target.
/// </summary>
public class RenderTarget : IRenderTarget
{
private readonly AG.Bitmap _nativeBitmap;
private IAndroidCanvasView _window;
/// <summary>
/// Initializes a new instance of the <see cref="RenderTarget"/> class.
/// </summary>
/// <param name="window">The window.</param>
/// <param name="width">The width of the window.</param>
/// <param name="height">The height of the window.</param>
public RenderTarget(IAndroidCanvasView window)
{
_window = window;
}
public RenderTarget(AG.Bitmap surface)
{
_nativeBitmap = surface;
}
/// <summary>
/// Resizes the renderer.
/// </summary>
/// <param name="width">The new width.</param>
/// <param name="height">The new height.</param>
public void Resize(int width, int height)
{
if (_nativeBitmap != null)
{
this._nativeBitmap.Reconfigure(width, height, AG.Bitmap.Config.Argb8888);
}
}
/// <summary>
/// Creates a DrawingContext that targets a platform-specific resource.
/// </summary>
/// <returns>A surface wrapped in an <see cref="Perspex.Media.DrawingContext"/>.</returns>
public Perspex.Media.DrawingContext CreateDrawingContext()
{
var canvas = _nativeBitmap != null ? new AG.Canvas(this._nativeBitmap) : _window.Canvas;
return new Perspex.Media.DrawingContext(new DrawingContextImpl(canvas, _window != null ? _window.VisualCaches : null));
}
public void Dispose()
{
this._nativeBitmap?.Dispose();
}
}
}

49
src/Android/Perspex.Android/CanvasRendering/RenderTargetBitmapImpl.cs

@ -1,49 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Platform;
using System.IO;
using AG = Android.Graphics;
namespace Perspex.Android.CanvasRendering
{
public class RenderTargetBitmapImpl : IRenderTargetBitmapImpl
{
private readonly RenderTarget _renderTarget;
public RenderTargetBitmapImpl(AG.Bitmap surface)
{
Surface = surface;
_renderTarget = new RenderTarget(Surface);
}
public int PixelWidth => Surface.Width;
public int PixelHeight => Surface.Height;
public void Dispose()
{
_renderTarget.Dispose();
}
public AG.Bitmap Surface { get; }
public void Save(string fileName)
{
using (var f = File.OpenWrite(fileName))
{
Surface.Compress(AG.Bitmap.CompressFormat.Png, 100, f);
}
}
public Perspex.Media.DrawingContext CreateDrawingContext()
{
return _renderTarget.CreateDrawingContext();
}
//void IRenderTarget.Resize(int width, int height)
//{
// this._renderTarget.Resize(width, height);
//}
}
}

158
src/Android/Perspex.Android/CanvasRendering/StreamGeometryImpl.cs

@ -1,158 +0,0 @@
using Android.Graphics;
using Perspex.Media;
using Perspex.Platform;
using System;
using System.Linq;
using APath = Android.Graphics.Path;
using ARect = Android.Graphics.RectF;
namespace Perspex.Android.CanvasRendering
{
public class StreamGeometryImpl : IStreamGeometryImpl
{
private readonly StreamGeometryContextImpl _impl;
private Matrix _transform = Matrix.Identity;
public StreamGeometryImpl()
{
_impl = new StreamGeometryContextImpl(null);
}
public StreamGeometryImpl(StreamGeometryContextImpl impl)
{
_impl = impl;
}
public APath Path => _impl.Path;
public Rect Bounds => _impl.CalculatedBounds;
public Matrix Transform
{
get { return _transform; }
set
{
if (value == Transform) return;
if (!value.IsIdentity)
{
_transform = value;
Path.Transform(_transform.ToAndroidGraphics());
}
}
}
public Rect GetRenderBounds(double strokeThickness)
{
return Bounds.Inflate(strokeThickness);
}
public IStreamGeometryImpl Clone()
{
return new StreamGeometryImpl(_impl);
}
public IStreamGeometryContextImpl Open()
{
return _impl;
}
}
public class StreamGeometryContextImpl : IStreamGeometryContextImpl
{
private double _top = double.MaxValue;
private double _left = double.MaxValue;
private double _bottom = 1;
private double _right = 1;
public StreamGeometryContextImpl(APath path = null)
{
Path = path != null ? new APath(path) : new APath();
Path.AddRect(new ARect(), APath.Direction.Cw);
}
public APath Path { get; }
public Rect PathBounds
{
get
{
var _bounds = new ARect();
Path.ComputeBounds(_bounds, true);
return _bounds.ToPerspex();
}
}
public Rect CalculatedBounds
{
get
{
return new Rect(_left, _top, _right - _left, _bottom - _top);
}
}
public void Dispose()
{
}
private void ProcessBounds(params Point[] points)
{
_top = Math.Min(_top, points.Min(p => p.Y));
_left = Math.Min(_left, points.Min(p => p.X));
_bottom = Math.Max(_bottom, points.Max(p => p.Y));
_right = Math.Max(_right, points.Max(p => p.X));
}
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
ProcessBounds(point);
Path.ArcTo(new Rect(point, size).ToAndroidGraphicsF(), 0, (float)rotationAngle);
}
public void BeginFigure(Point pstartPoint, bool isFilled)
{
ProcessBounds(pstartPoint);
var sp = pstartPoint.ToAndroidGraphics();
Path.MoveTo(sp.X, sp.Y);
}
public void BezierTo(Point ppoint1, Point ppoint2, Point ppoint3)
{
ProcessBounds(ppoint1, ppoint2, ppoint3);
var point1 = ppoint1.ToAndroidGraphics();
var point2 = ppoint2.ToAndroidGraphics();
var point3 = ppoint3.ToAndroidGraphics();
Path.CubicTo(point1.X, point1.Y,
point2.X, point2.Y,
point3.X, point3.Y);
}
public void LineTo(Point ppoint)
{
ProcessBounds(ppoint);
var point = ppoint.ToAndroidGraphics();
Path.LineTo(point.X, point.Y);
}
public void EndFigure(bool isClosed)
{
if (Path != null)
if (isClosed)
Path.Close();
}
public void QuadTo(Point ppoint1, Point ppoint2)
{
ProcessBounds(ppoint1, ppoint2);
var point1 = ppoint1.ToAndroidGraphics();
var point2 = ppoint2.ToAndroidGraphics();
Path.QuadTo(point1.X, point1.Y, point2.X, point2.Y);
}
}
}

22
src/Android/Perspex.Android/Perspex.Android.csproj

@ -66,39 +66,19 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AndroidPlatform.cs" />
<Compile Include="AndroidAssetLoader.cs" />
<Compile Include="Platform\CanvasPlatform\IAndroidCanvasView.cs" />
<Compile Include="Platform\CanvasPlatform\IAndroidViewSupportRender.cs" />
<Compile Include="AndroidThreadingInterface.cs" />
<Compile Include="Platform\ClipboardImpl.cs" />
<Compile Include="CursorFactory.cs" />
<Compile Include="Platform\Input\AndroidKeyboardDevice.cs" />
<Compile Include="Platform\Input\AndroidMouseDevice.cs" />
<Compile Include="Platform\SkiaPlatform\MainWindowImpl.cs" />
<Compile Include="Platform\SkiaPlatform\WindowImpl.cs" />
<Compile Include="Platform\Specific\CommonUITimer.cs" />
<Compile Include="Platform\CanvasPlatform\SurfaceMainWindowImpl.cs" />
<Compile Include="Platform\CanvasPlatform\Specific\Helpers\AndroidViewRenderHelpers.cs" />
<Compile Include="Platform\Specific\Helpers\ViewDrawType.cs" />
<Compile Include="Platform\Specific\IAndroidView.cs" />
<Compile Include="Platform\Specific\IAndroidActivity.cs" />
<Compile Include="Platform\CanvasPlatform\PointUnitService.cs" />
<Compile Include="Platform\Specific\PerspexActivity.cs" />
<Compile Include="CanvasRendering\AndroidPlatformRender.cs" />
<Compile Include="Platform\Specific\Helpers\AndroidTouchEventsHelper.cs" />
<Compile Include="Platform\Specific\Helpers\AndroidKeyboardEventsHelper.cs" />
<Compile Include="CanvasRendering\BitmapImpl.cs" />
<Compile Include="CanvasRendering\BrushImpl.cs" />
<Compile Include="CanvasRendering\DrawingContext.cs" />
<Compile Include="CanvasRendering\FormattedTextImpl.cs" />
<Compile Include="Platform\AndroidTopLevelRenderer.cs" />
<Compile Include="Platform\CanvasPlatform\MainWindowImpl.cs" />
<Compile Include="Platform\CanvasPlatform\SurfaceWindowImpl.cs" />
<Compile Include="Platform\CanvasPlatform\WindowImpl.cs" />
<Compile Include="CanvasRendering\AndroidGraphicsExtensions.cs" />
<Compile Include="Platform\CanvasPlatform\PopupImpl.cs" />
<Compile Include="CanvasRendering\RenderTarget.cs" />
<Compile Include="CanvasRendering\RenderTargetBitmapImpl.cs" />
<Compile Include="CanvasRendering\StreamGeometryImpl.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SystemDialogImpl.cs" />

14
src/Android/Perspex.Android/Platform/CanvasPlatform/IAndroidCanvasView.cs

@ -1,14 +0,0 @@
using Android.Graphics;
using Perspex.Android.Platform.Specific;
using System;
using System.Collections.Generic;
namespace Perspex.Android.Platform.CanvasPlatform
{
public interface IAndroidCanvasView : IAndroidView
{
Canvas Canvas { get; }
Dictionary<object, IDisposable> VisualCaches { get; }
}
}

13
src/Android/Perspex.Android/Platform/CanvasPlatform/IAndroidViewSupportRender.cs

@ -1,13 +0,0 @@
using Android.Graphics;
namespace Perspex.Android.Platform.CanvasPlatform
{
public interface IAndroidViewSupportRender
{
void PreRender(Canvas canvas, Rect rect);
void Render(Canvas canvas, Rect rect);
void PostRender(Canvas canvas, Rect rect);
}
}

71
src/Android/Perspex.Android/Platform/CanvasPlatform/MainWindowImpl.cs

@ -1,71 +0,0 @@
using Android.Views;
using Perspex.Android.Platform.Specific;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Input;
using Perspex.Platform;
using AG = Android.Graphics;
namespace Perspex.Android.Platform.CanvasPlatform
{
public class MainWindowImpl :
//SurfaceWindowImpl
WindowImpl
, IWindowImpl
{
private IPointUnitService _pointService = PointUnitService.Instance;
protected override void Init()
{
base.Init();
HandleEvents = true;
_keyboardHelper.ActivateAutoShowKeybord();
}
private int _statusbarHeight = -1;
private int StatusBarHeight
{
get
{
if (_statusbarHeight < 0)
{
var a = PerspexLocator.Current.GetService<IAndroidActivity>().Activity;
AG.Rect rectangle = new AG.Rect();
Window window = a.Window;
window.DecorView.GetWindowVisibleDisplayFrame(rectangle);
int statusBarHeight = rectangle.Top;
//int contentViewTop =
// window.FindViewById(Window.IdAndroidContent).Top;
//int titleBarHeight = contentViewTop - statusBarHeight;
_statusbarHeight = statusBarHeight;
}
return _statusbarHeight;
}
}
public override Point GetPerspexPointFromEvent(MotionEvent e)
{
//toolbar is on top of android screen and it's height is 50 ???
//may be dependent from dpi ???
//return base.GetPerspexPointFromEvent(e);
//return new Point(e.GetX(0), e.GetY(0) + StatusBarHeight);
var point = new Point(e.GetX(0), e.GetY(0) + (DrawType == ViewDrawType.CanvasOnDraw ? StatusBarHeight : 0));
return _pointService.NativeToPerspex(point);
}
void ITopLevelImpl.Show()
{
(Parent as ViewGroup)?.RemoveAllViews();
PerspexLocator.Current.GetService<IAndroidActivity>().ContentView = this;
this.Visibility = ViewStates.Visible;
}
void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot)
{
base.SetInputRoot(inputRoot);
_keyboardHelper.UpdateKeyboardState(inputRoot);
}
}
}

184
src/Android/Perspex.Android/Platform/CanvasPlatform/PointUnitService.cs

@ -1,184 +0,0 @@
using Perspex.Android.Platform.Specific;
using System;
namespace Perspex.Android.Platform.CanvasPlatform
{
public enum PointUnit
{
Pixel,
DP, //device independent pixel
Custom // CustomScale
}
public interface IPointUnitService
{
double ScaleX { get; }
double ScaleY { get; }
double FontScale { get; }
double NativeToPerspexX(double x);
double NativeToPerspexY(double y);
double PerspexToNativeX(double x);
double PerspexToNativeY(double y);
float PerspexToNativeFontSize(double fontSize);
}
public class PointUnitService : IPointUnitService
{
public static IPointUnitService Instance => PerspexLocator.Current.GetService<IPointUnitService>() ?? new PointUnitService();
private PointUnit _pointUnit;
public PointUnit PointUnit
{
get { return _pointUnit; }
set
{
_pointUnit = value;
OnPointUnitChanged();
}
}
public double ScaleX { get; set; }
public double ScaleY { get; set; }
public double FontScale { get; set; }
public PointUnitService()
{
PointUnit = AndroidPlatform.Instance.DefaultPointUnit;
//ScaleX = 1;
//ScaleY = 1;
////FontScale = 1;
//ScaleX = 2;
//ScaleY = 2;
//FontScale = 2;
}
private void OnPointUnitChanged()
{
switch (PointUnit)
{
case PointUnit.Pixel:
ScaleX = 1;
ScaleY = 1;
FontScale = 1;
break;
case PointUnit.DP:
{
//get android metrics for device pixel format
var activity = PerspexLocator.Current.GetService<IAndroidActivity>().Activity;
var dm = activity.Resources.DisplayMetrics;
double scale = Math.Round((double)dm.ScaledDensity, 2);
ScaleX = scale;
ScaleY = scale;
FontScale = scale;
}
break;
case PointUnit.Custom:
{
double scale = 2;
ScaleX = scale;
ScaleY = scale;
FontScale = scale;
}
break;
}
}
public double NativeToPerspexX(double x) => ScaleX == 1 ? x : RoundToPerspex(x / ScaleX);
public double NativeToPerspexY(double y) => ScaleY == 1 ? y : RoundToPerspex(y / ScaleY);
public double PerspexToNativeX(double x) => ScaleX == 1 ? x : RoundToNative(ScaleX * x);
public double PerspexToNativeY(double y) => ScaleY == 1 ? y : RoundToNative(ScaleY * y);
public float PerspexToNativeFontSize(double fontSize)
{
if (FontScale == 1) return (float)fontSize;
return (float)Math.Round(FontScale * fontSize, 1);
}
private static double RoundToPerspex(double v)
{
// return Math.Ceiling(v);
return Math.Round(v, 5);
}
private static double RoundToNative(double v)
{
//return Math.Ceiling(v);
return Math.Round(v, 2);
}
}
public static class PointServiceExtensions
{
//public static Matrix PerspexToNative(this IPointService ps, Matrix m)
//{
// if (ps.ScaleX == ps.ScaleY && ps.ScaleX == 1) return m;
// return new Matrix(m.M11, m.M12, m.M21, m.M22, m.M31 * ps.ScaleX, m.M32 * ps.ScaleY);
//}
//public static double PerspexToNative(this IPointService service, double x)
//{
// if (service.ScaleX == 1) return x;
// return x * service.ScaleX;
//}
private static int RoundToInt(double v)
{
return (int)Math.Round(v, 0);
}
public static double NativeToPerspex(this IPointUnitService ps, double x) => ps.NativeToPerspexX(x);
public static float PerspexToNativeXF(this IPointUnitService ps, double x) => (float)ps.PerspexToNativeX(x);
public static float PerspexToNativeYF(this IPointUnitService ps, double y) => (float)ps.PerspexToNativeY(y);
public static int PerspexToNativeXInt(this IPointUnitService ps, double x) => RoundToInt(ps.PerspexToNativeX(x));
public static int PerspexToNativeYInt(this IPointUnitService ps, double y) => RoundToInt(ps.PerspexToNativeX(y));
public static Point NativeToPerspex(this IPointUnitService ps, Point point) => (ps.ScaleY == 1 && ps.ScaleX == 1) ? point : new Point(ps.NativeToPerspexX(point.X), ps.NativeToPerspexY(point.Y));
public static Point PerspexToNative(this IPointUnitService ps, Point point) => (ps.ScaleY == 1 && ps.ScaleX == 1) ? point : new Point(ps.PerspexToNativeX(point.X), ps.PerspexToNativeY(point.Y));
public static Size NativeToPerspex(this IPointUnitService service, Size size)
{
if (service.ScaleX == service.ScaleY && service.ScaleX == 1) return size;
return new Size(size.Width / service.ScaleX, size.Height / service.ScaleY);
}
public static Size PerspexToNative(this IPointUnitService service, Size size)
{
if (service.ScaleX == service.ScaleY && service.ScaleX == 1) return size;
return new Size(size.Width * service.ScaleX, size.Height * service.ScaleY);
}
public static Rect NativeToPerspex(this IPointUnitService service, Rect rect)
{
if (service.ScaleX == service.ScaleY && service.ScaleX == 1) return rect;
return new Rect(rect.X / service.ScaleX, rect.Y / service.ScaleY, rect.Width / service.ScaleX, rect.Height / service.ScaleY);
}
public static Rect PerspexToNative(this IPointUnitService service, Rect rect)
{
if (service.ScaleX == service.ScaleY && service.ScaleX == 1) return rect;
return new Rect(rect.X * service.ScaleX, rect.Y * service.ScaleY, rect.Width * service.ScaleX, rect.Height * service.ScaleY);
}
}
}

137
src/Android/Perspex.Android/Platform/CanvasPlatform/PopupImpl.cs

@ -1,137 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Android.Content;
using Android.Views;
using Android.Widget;
using Perspex.Android.Platform.Specific;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Platform;
using System;
using AG = Android.Graphics;
namespace Perspex.Android.Platform.CanvasPlatform
{
public class PopupImpl : PopupWindow, IPopupImpl, IPlatformHandle
{
private class PopupPerspexViewContent : WindowImpl
{
private Func<MotionEvent, bool?> _funcDispatchTouchEvent;
public PopupPerspexViewContent(Context context, Func<MotionEvent, bool?> funcDispatchTouchEvent) : base(context)
{
this._funcDispatchTouchEvent = funcDispatchTouchEvent;
}
public override bool DispatchTouchEvent(MotionEvent e)
{
bool? res = _funcDispatchTouchEvent(e);
return res == null ? base.DispatchTouchEvent(e) : res.Value;
}
}
private PopupPerspexViewContent content;
private View mainView;
//public PopupImpl(Context context) : base(context)
public PopupImpl()
{
ContentView = content = new PopupPerspexViewContent(PerspexLocator.Current.GetService<IAndroidActivity>().Activity, DispatchTouchEvent);
ContentView.Background = new AG.Drawables.ColorDrawable(AG.Color.Transparent);
mainView = PerspexLocator.Current.GetService<IAndroidActivity>().ContentView.View;
//Background = new AG.Drawables.ColorDrawable(AG.Color.);
}
//public PopupImpl() : this(PerspexLocator.Current.GetService<IAndroidActivity>().Activity)
//{
//}
private bool? DispatchTouchEvent(MotionEvent e)
{
var p = content.GetPerspexPointFromEvent(e);
Rect clientBounds = new Rect(ClientSize);
if (clientBounds.Contains(p))
{
return null;
}
return mainView.DispatchTouchEvent(e);
}
public Size MaxClientSize => content.MaxClientSize;
public Action Activated { get { return (content as ITopLevelImpl).Activated; } set { (content as ITopLevelImpl).Activated = value; } }
public Size ClientSize { get { return content.ClientSize; } set { content.ClientSize = value; } }
public Action Closed { get { return content.Closed; } set { content.Closed = value; } }
public Action Deactivated { get { return content.Deactivated; } set { content.Deactivated = value; } }
public Action<RawInputEventArgs> Input { get { return content.Input; } set { content.Input = value; } }
public Action<Rect> Paint { get { return content.Paint; } set { content.Paint = value; } }
public Action<Size> Resized { get { return content.Resized; } set { content.Resized = value; } }
IntPtr IPlatformHandle.Handle => content.Handle;
IPlatformHandle ITopLevelImpl.Handle => content;
public string HandleDescriptor => "PopupImpl";
public void Activate() => content.Activate();
public void Invalidate(Rect rect) => content.Invalidate(rect);
public Point PointToScreen(Point point)
{
return content.PointToScreen(point);
}
public void SetCursor(IPlatformHandle cursor)
{
content.SetCursor(cursor);
}
public void SetInputRoot(IInputRoot inputRoot)
{
content.SetInputRoot(inputRoot);
}
private Point position;
public void SetPosition(Point p)
{
position = p;
}
public void Show()
{
content.HandleEvents = true;
var ps = PointUnitService.Instance;
//content.Activate();
Width = ps.PerspexToNativeXInt(MaxClientSize.Width - position.X);
Height = ps.PerspexToNativeYInt(MaxClientSize.Height - position.Y);
content.LayoutParameters = new ViewGroup.LayoutParams(Width, Height);
// content.ClientSize = new Size(MaxClientSize.Width - position.X, MaxClientSize.Height - position.Y);
//Width = Convert.ToInt32(ClientSize.Width);
//Height = Convert.ToInt32(ClientSize.Height);
base.ShowAtLocation(mainView, GravityFlags.Top | GravityFlags.Left, ps.PerspexToNativeXInt(position.X), ps.PerspexToNativeYInt(position.Y));
content.Invalidate();
}
public void Hide()
{
content.HandleEvents = false;
//content.Hide();
base.Dismiss();
}
}
}

688
src/Android/Perspex.Android/Platform/CanvasPlatform/Specific/Helpers/AndroidViewRenderHelpers.cs

@ -1,688 +0,0 @@
using Android.Graphics;
using Android.Runtime;
using Android.Text;
using Android.Util;
using Android.Views;
using Perspex.Android.CanvasRendering;
using Perspex.Android.Platform.Specific.Helpers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using P = Perspex.Platform;
namespace Perspex.Android.Platform.CanvasPlatform.Specific.Helpers
{
public abstract class AndroidViewRenderHelper<TView> : Java.Lang.Object, IDisposable where TView : View, P.IWindowImpl, IAndroidViewSupportRender
{
private static bool _debug = false;
protected AndroidViewRenderHelper(TView view)
{
this.View = view;
VisualCaches = null;
}
public Dictionary<object, IDisposable> VisualCaches { get; protected set; }
public abstract void OnDraw(Canvas canvas);
public abstract void Invalidate(Rect rect);
public TView View { get; private set; }
public ViewDrawType DrawType { get; private set; }
public static AndroidViewRenderHelper<TView> Create(TView view, ViewDrawType drawType)
{
AndroidViewRenderHelper<TView> result;
switch (drawType)
{
case ViewDrawType.BitmapBackgroundRender:
result = new BitmapBackgroundRenderHelper<TView>(view);
break;
case ViewDrawType.BitmapOnPreDraw:
result = new BitmapPreDrawRenderHelper<TView>(view);
break;
case ViewDrawType.CanvasOnDraw:
result = new CanvasOnDrawAndroidViewRenderHelper<TView>(view);
break;
default:
throw new NotSupportedException();
}
Init(result, drawType);
return result;
}
protected static void Init(AndroidViewRenderHelper<TView> helper, ViewDrawType drawType)
{
helper.DrawType = drawType;
helper.SetDebugInfo("DrawType", drawType.ToString());
}
public virtual new void Dispose()
{
View = null;
_debugInfoPaint?.Dispose();
}
public Dictionary<string, string> DebugInfo { get; } = new Dictionary<string, string>();
protected void SetDebugInfo(string key, string value) => DebugInfo[key] = value;
private const int _avgValuesForStat = 50;
private Dictionary<string, Queue<double>> _renderStats = new Dictionary<string, Queue<double>>();
protected void UpdateRenderStat(string key, double ms)
{
Queue<double> statsQueue;
if (!_renderStats.TryGetValue(key, out statsQueue))
{
statsQueue = new Queue<double>();
_renderStats[key] = statsQueue;
}
statsQueue.Enqueue(ms);
if (statsQueue.Count > _avgValuesForStat) statsQueue.Dequeue();
SetDebugInfo(key, ms.ToString("0.0"));
SetDebugInfo($"a{key}", (statsQueue.Average()).ToString("0.0"));
}
protected void RenderView(Canvas canvas, Rect rect)
{
DateTime begin = DateTime.Now;
View.PreRender(canvas, rect);
View.Render(canvas, rect);
View.PostRender(canvas, rect);
TimeSpan duration = DateTime.Now - begin;
if (_debug) Log.Debug("AndroidViewRenderHelper", $"RenderView {duration}");
UpdateRenderStat("RenderView", duration.TotalMilliseconds);
UpdateRenderStat("mfps", 1000 / duration.TotalMilliseconds);
}
private Paint _debugInfoPaint = null;
public void DrawDebugInfoToCanvas(Canvas canvas)
{
if (!AndroidPlatform.Instance.DrawDebugInfo) return;
if (DebugInfo.Count == 0) return;
if (_debugInfoPaint == null) _debugInfoPaint = new TextPaint(PaintFlags.AntiAlias) { Color = Color.Red, TextAlign = Paint.Align.Left, TextSize = 20 };
var sb = new StringBuilder();
foreach (var o in DebugInfo)
{
sb.Append($"{o.Key} = {o.Value}; ");
}
string sdb = sb.ToString();
var r = new global::Android.Graphics.Rect();
_debugInfoPaint.GetTextBounds(sdb, 0, sdb.Length, r);
canvas.DrawText(sdb, View.Width - r.Right, View.Height - r.Bottom, _debugInfoPaint);
if (_debug) Log.Debug("AndroidViewRenderHelper", sdb);
}
}
public class CanvasOnDrawAndroidViewRenderHelper<TView> : AndroidViewRenderHelper<TView> where TView : View, P.IWindowImpl, IAndroidViewSupportRender
{
private Rect _rect;
public CanvasOnDrawAndroidViewRenderHelper(TView view) : base(view)
{
}
public override void Invalidate(Rect rect)
{
_rect = rect;
View.Invalidate(rect.ToAndroidGraphics());
}
public override void OnDraw(Canvas canvas)
{
RenderView(canvas, _rect);
}
public override void Dispose()
{
base.Dispose();
}
}
public class BitmapBackgroundRenderHelper<TView> : AndroidViewRenderHelper<TView> where TView : View, P.IWindowImpl, IAndroidViewSupportRender
{
private bool _debug = WindowImpl._debug;
private Bitmap _bitmap;
private Rect _rect;
private Paint _paint = new Paint();
private ConcurrentStack<Rect> _invalidateRects = new ConcurrentStack<Rect>();
private object _invalidateRectsSync = new object();
private object _drawSync = new object();
private Stack<Bitmap> _renderingBitmapsCache = new Stack<Bitmap>();
private Dictionary<Bitmap, Canvas> _bmCanvases = new Dictionary<Bitmap, Canvas>();
private CancellationTokenSource _cancelationTokenSource;
public BitmapBackgroundRenderHelper(TView view) : base(view)
{
_cancelationTokenSource = new CancellationTokenSource();
VisualCaches = new Dictionary<object, IDisposable>();
Task.Factory.StartNew(RenderingLoop, _cancelationTokenSource.Token, _cancelationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
public override void Invalidate(Rect rect)
{
_rect = rect;
_invalidateRects.Push(rect);
lock (_invalidateRectsSync)
{
Monitor.Pulse(_invalidateRectsSync);
}
}
public override void OnDraw(Canvas canvas)
{
var begin = DateTime.Now;
lock (_drawSync)
{
if (_bitmap != null)
{
canvas.DrawBitmap(_bitmap, 0, 0, _paint);
}
}
UpdateRenderStat("OnDraw", (DateTime.Now - begin).TotalMilliseconds);
}
private void RenderingLoop(object obj)
{
CancellationToken token = (CancellationToken)obj;
if (_debug) Log.Debug("render", "RenderingLoop Start ...");
AndroidPlatform.Instance.RegisterRenderingTaskId(Task.CurrentId.Value);
while (!token.IsCancellationRequested)
{
try
{
Rect rect = default(Rect);
bool hasRect = false;
hasRect = _invalidateRects.Count > 0; ;
if (!hasRect)
{
lock (_invalidateRectsSync)
{
hasRect = Monitor.Wait(_invalidateRectsSync, 1000);
}
}
if (!hasRect)
{
DisposeVisualCaches();
continue;
}
if (_invalidateRects.Count < 1) continue;
int rCnt = _invalidateRects.Count;
Rect[] rects = new Rect[rCnt > 1 ? (rCnt - 1) : 1];
int cnt = _invalidateRects.TryPopRange(rects, 0, rects.Length);
if (_debug) Log.Debug("render", $"RenderingLoop processing {cnt} rects");
if (cnt == 1)
{
rect = rects[0];
//assuming this is the last frame so we dispose the caches
DisposeVisualCaches();
}
else if (cnt > 1)
{
//for now all the rects are the same but may be in the future this can change
var minX = rects.Min(r => r.X);
var minY = rects.Min(r => r.Y);
var maxRight = rects.Max(r => r.Right);
var maxBottom = rects.Max(r => r.Bottom);
rect = new Rect(minX, minY, maxRight - minX, maxBottom - minY);
}
else
{
//Should never happend
hasRect = false;
}
if (hasRect)
{
Render(rect);
}
}
catch (Exception e)
{
Log.Debug("render", "RenderingLoop Exception {0} ...", e);
}
}
AndroidPlatform.Instance.UnRegisterRenderingTaskId(Task.CurrentId.Value);
Log.Debug("render", "RenderingLoop End ...");
}
private void Render(Rect rect)
{
DateTime begin = DateTime.Now;
if (_debug) Log.Debug("render", $"RenderingLoop new invalidation ... {rect} {begin}");
Bitmap bitmap;
int w = View.Width;
int h = View.Height;
if (w == 0)
{
var s = PointUnitService.Instance.PerspexToNative(View.ClientSize);
w = (int)s.Width;
h = (int)s.Height;
}
if (_renderingBitmapsCache.Count > 0)
{
// _renderingBitmapsCache.TryPop(out bitmap);
bitmap = _renderingBitmapsCache.Pop();
}
else
{
bitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
}
if (bitmap.Width != w || bitmap.Height != h)
{
bitmap.Dispose();
bitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
foreach (var c in _bmCanvases)
{
c.Value.Dispose();
}
_bmCanvases.Clear();
}
Canvas canvas;
if (!_bmCanvases.TryGetValue(bitmap, out canvas))
{
canvas = new Canvas(bitmap);
_bmCanvases.Add(bitmap, canvas);
}
RenderView(canvas, _rect);
//???no need dispose canvas is cached
//canvas.Dispose();
lock (_drawSync)
{
if (_bitmap != null) _renderingBitmapsCache.Push(_bitmap);
_bitmap = bitmap;
}
var nr = rect.ToAndroidGraphics();
View.PostInvalidate(nr.Left, nr.Top, nr.Right, nr.Bottom);
//View.PostInvalidateDelayed(1, nr.Left, nr.Top, nr.Right, nr.Bottom);
}
private void DisposeVisualCaches()
{
lock (_drawSync)
{
if (_debug) Log.Debug("render", $"RenderingLoop disposing {VisualCaches.Count} visual caches");
foreach (var vc in VisualCaches)
{
vc.Value.Dispose();
}
VisualCaches.Clear();
}
}
public override void Dispose()
{
base.Dispose();
_cancelationTokenSource.Cancel();
_cancelationTokenSource = null;
}
}
public class BitmapPreDrawRenderHelper<TView> : AndroidViewRenderHelper<TView> where TView : View, P.IWindowImpl, IAndroidViewSupportRender
{
private class OnPreDrawListener : Java.Lang.Object, ViewTreeObserver.IOnPreDrawListener
{
private Func<bool> _preDraw;
public OnPreDrawListener(Func<bool> preDraw)
{
_preDraw = preDraw;
}
public bool OnPreDraw()
{
return _preDraw();
}
}
private Bitmap _bitmap;
private Rect _rect;
private Paint _paint = new Paint();
private OnPreDrawListener _listener;
public BitmapPreDrawRenderHelper(TView view) : base(view)
{
_listener = new OnPreDrawListener(OnPreDraw);
View.ViewTreeObserver.AddOnPreDrawListener(_listener);
}
public override void Dispose()
{
base.Dispose();
View.ViewTreeObserver.RemoveOnPreDrawListener(_listener);
_listener.Dispose();
_bitmap.Dispose();
_paint.Dispose();
}
public override void Invalidate(Rect rect)
{
_rect = rect;
View.Invalidate(rect.ToAndroidGraphics());
}
public override void OnDraw(Canvas canvas)
{
var begin = DateTime.Now;
if (_bitmap != null)
{
canvas.DrawBitmap(_bitmap, 0, 0, _paint);
}
UpdateRenderStat("OnDraw", (DateTime.Now - begin).TotalMilliseconds);
}
public bool OnPreDraw()
{
int w = View.Width;
int h = View.Height;
Bitmap bitmap = _bitmap;
if (bitmap == null)
{
bitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
}
else if (bitmap.Width != w || bitmap.Height != h)
{
bitmap.Dispose();
bitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
}
using (var canvas = new Canvas(bitmap))
{
RenderView(canvas, _rect);
}
_bitmap = bitmap;
return true;
}
}
public class SurfaceViewRenderHelper<TView> : AndroidViewRenderHelper<TView> where TView : SurfaceView, P.IWindowImpl, IAndroidViewSupportRender
{
private class SurfaceHolderCallback : Java.Lang.Object, ISurfaceHolderCallback//, ISurfaceHolderCallback2
{
private SurfaceViewRenderHelper<TView> _parent;
public SurfaceHolderCallback(SurfaceViewRenderHelper<TView> parent)
{
_parent = parent;
}
public void SurfaceChanged(ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height)
{
_parent.SurfaceChanged(holder, format, width, height);
}
public void SurfaceCreated(ISurfaceHolder holder)
{
_parent.SurfaceCreated(holder);
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
_parent.SurfaceDestroyed(holder);
}
public void SurfaceRedrawNeeded(ISurfaceHolder holder)
{
_parent.SurfaceRedrawNeeded(holder);
}
}
public int DelayBetweenDrawms { get; set; }
protected ISurfaceHolder _holder;
private Task _task;
private CancellationTokenSource _cancelationTokenSource;
private CancellationToken _cancelationToken;
private ConcurrentStack<Rect> _pendingInvalidations = new ConcurrentStack<Rect>();
private object _invalidationRectsSync = new object();
private Format _surfaceFormat = Format.Rgba8888;
private Bitmap.Config _bitmapFormat = Bitmap.Config.Argb8888;
public static new SurfaceViewRenderHelper<TView> Create(TView view, ViewDrawType drawType)
{
SurfaceViewRenderHelper<TView> result;
switch (drawType)
{
case ViewDrawType.SurfaceViewCanvasOnDraw:
result = new SurfaceViewRenderHelper<TView>(view);
break;
default: throw new NotSupportedException();
}
Init(result, drawType);
return result;
}
public SurfaceViewRenderHelper(TView view) : base(view)
{
//max 16fps -60ms, 25fps - 40ms, max 30fps 30ms, max 60 fps-17ms
DelayBetweenDrawms = 40;
view.Holder.AddCallback(new SurfaceHolderCallback(this));
}
public override void Invalidate(Rect rect)
{
lock (_invalidationRectsSync)
{
_pendingInvalidations.Push(rect);
Monitor.Pulse(_invalidationRectsSync);
}
}
public override void OnDraw(Canvas canvas)
{
//do nothing here we are drawing on another thread
}
public void SurfaceChanged(ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height)
{
Log.Debug("SurfaceViewRenderAdapter", $"SurfaceChanged format={format} width ={width} height={height}...");
var ps = PointUnitService.Instance;
Size newSize = new Size(ps.NativeToPerspexX(width), ps.NativeToPerspexY(height));
if (View.ClientSize != newSize)
{
View.ClientSize = newSize;
View.Resized?.Invoke(View.ClientSize);
}
ScheduleRender();
}
public void SurfaceCreated(ISurfaceHolder holder)
{
Log.Debug("SurfaceViewRenderAdapter", "SurfaceCreated ...");
_holder = holder;
_holder.SetFormat(_surfaceFormat);
if (_task != null)
{
StopTask();
}
if (_task == null) StartTask();
ScheduleRender();
}
private void SurfaceRedrawNeeded(ISurfaceHolder holder)
{
Log.Debug("SurfaceViewRenderAdapter", "SurfaceRedrawNeeded ...");
ScheduleRender();
}
private void ScheduleRender()
{
if (_pendingInvalidations.Count > 0)
{
Invalidate(new Rect(0, 0, View.ClientSize.Width, View.ClientSize.Height));
}
}
private void RenderingLoop(object obj)
{
AndroidPlatform.Instance.RegisterRenderingTaskId(Task.CurrentId.Value);
Log.Debug("SurfaceViewRenderAdapter", $"DrawingTask Enter Id={Task.CurrentId.Value}... ");
//sometime the ui is not ready for other thread rendering
Thread.Sleep(10 * DelayBetweenDrawms);
var cancelationToken = (CancellationToken)obj;
while (!cancelationToken.IsCancellationRequested)
{
try
{
bool waitResult = false;
//wait few miliseconds so we are not heating the processor too much
Thread.Sleep(DelayBetweenDrawms);
if (_pendingInvalidations.Count < 1)
{
lock (_invalidationRectsSync)
{
waitResult = Monitor.Wait(_invalidationRectsSync, 1000);
Thread.Sleep(0);
}
}
if (cancelationToken.IsCancellationRequested) break;
//if invalidations are more than one render one time after this
//so we are sure we have correct state
int requestCnt = _pendingInvalidations.Count > 1 ? _pendingInvalidations.Count - 1 : _pendingInvalidations.Count;
if (requestCnt < 1)
{
//we can not continue with drawing
continue;
}
var rects = new Rect[requestCnt];
int rectsCount = _pendingInvalidations.TryPopRange(rects);
if (rectsCount < 1)
{
//should never happen but who knows
continue;
}
Rect rect;
if (rectsCount == 1)
{
rect = rects[0];
}
else
{
double minX = rects.Min(r => r.X);
double minY = rects.Min(r => r.Y);
double maxRight = rects.Max(r => r.Right);
double maxBottom = rects.Max(r => r.Bottom);
rect = new Rect(minX, minY, maxRight - minX, maxBottom - minY);
}
RenderRect(rect);
}
catch (Exception e)
{
Log.Debug("SurfaceViewRenderAdapter", $"Exception in DrawingTask {e}");
}
}
Log.Debug("SurfaceViewRenderAdapter", $"DrawingTask Exit Id={Task.CurrentId.Value}...");
}
private void RenderRect(Rect rect)
{
//Log.Debug("SurfaceViewRenderAdapter", $"RenderRect Enter rect={rect}... ");
var surfaceCanvas = _holder.LockCanvas();
try
{
lock (_holder)
{
RenderView(surfaceCanvas, rect);
}
}
finally
{
_holder.UnlockCanvasAndPost(surfaceCanvas);
}
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
StopTask();
}
private void StartTask()
{
_cancelationTokenSource = new CancellationTokenSource();
_cancelationToken = _cancelationTokenSource.Token;
_task = Task.Factory.StartNew(RenderingLoop, _cancelationToken, _cancelationToken);
}
private void StopTask()
{
try
{
Log.Debug("SurfaceViewRenderAdapter", $"StopTask _cancelationTokenSource != null {_cancelationTokenSource != null}");
if (_cancelationTokenSource != null)
{
_cancelationTokenSource.Cancel();
Task.WaitAll(_task);
_cancelationTokenSource = null;
_task = null;
}
}
catch (Exception e)
{
Log.Debug("SurfaceViewRenderAdapter", $"Exception in StopTask DrawingTask.Cancel {e}");
}
}
}
}

33
src/Android/Perspex.Android/Platform/CanvasPlatform/SurfaceMainWindowImpl.cs

@ -1,33 +0,0 @@
using Android.Views;
using Perspex.Android.Platform.Specific;
using Perspex.Input;
using Perspex.Platform;
namespace Perspex.Android.Platform.CanvasPlatform
{
public class SurfaceMainWindowImpl : SurfaceWindowImpl, IWindowImpl
{
private IPointUnitService _pointService = PointUnitService.Instance;
protected override void Init()
{
base.Init();
HandleEvents = true;
_keyboardHelper.ActivateAutoShowKeybord();
}
void ITopLevelImpl.Show()
{
(Parent as ViewGroup)?.RemoveAllViews();
PerspexLocator.Current.GetService<IAndroidActivity>().ContentView = this;
Visibility = ViewStates.Visible;
}
void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot)
{
base.SetInputRoot(inputRoot);
_keyboardHelper.UpdateKeyboardState(inputRoot);
}
}
}

194
src/Android/Perspex.Android/Platform/CanvasPlatform/SurfaceWindowImpl.cs

@ -1,194 +0,0 @@
using Android.Content;
using Android.Graphics;
using Android.Util;
using Android.Views;
using Perspex.Android.Platform.CanvasPlatform.Specific.Helpers;
using Perspex.Android.Platform.Specific;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Platform;
using System;
using System.Collections.Generic;
using AG = Android.Graphics;
namespace Perspex.Android.Platform.CanvasPlatform
{
public class SurfaceWindowImpl : SurfaceView, IWindowImpl, IPlatformHandle, IAndroidCanvasView, IAndroidViewSupportRender
{
public SurfaceWindowImpl(Context context) : base(context)
{
DrawType = ViewDrawType.SurfaceViewCanvasOnDraw;
_keyboardHelper = new AndroidKeyboardEventsHelper<SurfaceWindowImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<SurfaceWindowImpl>(this, () => InputRoot, GetPerspexPointFromEvent);
ClientSize = MaxClientSize;
Resized = size => Invalidate(new Rect(size));
Init();
Background = new AG.Drawables.ColorDrawable(AG.Color.Transparent);
}
public SurfaceWindowImpl() : this(PerspexLocator.Current.GetService<IAndroidActivity>().Activity)
{
}
public void SurfaceRedrawNeeded(ISurfaceHolder holder)
{
Log.Debug("SurfaceView", "SurfaceRedrawNeeded...");
//TryDraw(holder);
}
protected override void OnDraw(Canvas canvas)
{
//base.OnDraw(canvas);
this._renderHelper.OnDraw(canvas);
}
private IPointUnitService _pointService = PointUnitService.Instance;
public ViewDrawType DrawType { get; }
private SurfaceViewRenderHelper<SurfaceWindowImpl> _renderHelper;
protected AndroidKeyboardEventsHelper<SurfaceWindowImpl> _keyboardHelper;
protected AndroidTouchEventsHelper<SurfaceWindowImpl> _touchHelper;
private bool _handleEvents;
public bool HandleEvents
{
get { return _handleEvents; }
set
{
_handleEvents = value;
_keyboardHelper.HandleEvents = _handleEvents;
}
}
protected virtual void Init()
{
//SetLayerType(LayerType.Hardware, null);
_renderHelper = SurfaceViewRenderHelper<SurfaceWindowImpl>.Create(this, DrawType);
HandleEvents = false;
//if the background is not transparent nothing is visible!!!
//this is the case when inherit from GridLayout, not from View
//this.Background = new AG.Drawables.ColorDrawable(AG.Color.Transparent);
}
public Rect Bounds { get; set; }
public IInputRoot InputRoot { get; private set; }
IntPtr IPlatformHandle.Handle => Handle;
string IPlatformHandle.HandleDescriptor => "Perspex SurfaceView";
public void Resize(double width, double height)
{
if (ClientSize == new Size(width, height)) return;
ClientSize = new Size(width, height);
Resized(ClientSize);
}
public Size ClientSize { get; set; }
IPlatformHandle ITopLevelImpl.Handle => this;
Action ITopLevelImpl.Activated { get; set; }
public Action Closed { get; set; }
public Action Deactivated { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public void Activate()
{
}
public void SetInputRoot(IInputRoot inputRoot)
{
InputRoot = inputRoot;
}
public Point PointToScreen(Point point)
{
return point;
}
public void SetCursor(IPlatformHandle cursor)
{
}
public Size MaxClientSize => _pointService.NativeToPerspex(new Size(Resources.DisplayMetrics.WidthPixels, Resources.DisplayMetrics.HeightPixels));
public View View => this;
public void SetTitle(string title)
{
}
void ITopLevelImpl.Show()
{
this.Visibility = ViewStates.Visible;
}
public IDisposable ShowDialog()
{
throw new NotImplementedException();
}
public void Hide()
{
}
public virtual Point GetPerspexPointFromEvent(MotionEvent e) => _pointService.NativeToPerspex(new Point(e.GetX(), e.GetY()));
public override bool DispatchTouchEvent(MotionEvent e)
{
bool callBase;
bool? result = _touchHelper.DispatchTouchEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
return result != null ? result.Value : baseResult;
}
public override bool DispatchKeyEvent(KeyEvent e)
{
bool callBase;
bool? res = _keyboardHelper.DispatchKeyEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchKeyEvent(e) : false;
return res != null ? res.Value : baseResult;
}
public void Invalidate(Rect rect)
{
this._renderHelper.Invalidate(rect);
}
public virtual void PreRender(Canvas canvas, Rect rect)
{
}
public virtual void Render(Canvas canvas, Rect rect)
{
Canvas = canvas;
Paint?.Invoke(rect);
}
public virtual void PostRender(Canvas canvas, Rect rect)
{
_renderHelper.DrawDebugInfoToCanvas(canvas);
_touchHelper.DrawLastMousePoint(canvas);
}
public AG.Canvas Canvas { get; private set; }
public Dictionary<object, IDisposable> VisualCaches => _renderHelper.VisualCaches;
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
Resize(MaxClientSize.Width, MaxClientSize.Height);
}
}
}

207
src/Android/Perspex.Android/Platform/CanvasPlatform/WindowImpl.cs

@ -1,207 +0,0 @@
using Android.Content;
using Android.Views;
using Perspex.Android.Platform.CanvasPlatform.Specific.Helpers;
using Perspex.Android.Platform.Specific;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Platform;
using System;
using System.Collections.Generic;
using AG = Android.Graphics;
namespace Perspex.Android.Platform.CanvasPlatform
{
public class WindowImpl : View,
//GridLayout, //may be GridLayout can be better if int he future we want to embed native views or controls
IWindowImpl, IPlatformHandle, IAndroidCanvasView, IAndroidViewSupportRender, IDisposable
{
private IPointUnitService _pointService = PointUnitService.Instance;
public ViewDrawType DrawType { get; }
private AndroidViewRenderHelper<WindowImpl> _renderHelper;
protected AndroidKeyboardEventsHelper<WindowImpl> _keyboardHelper;
protected AndroidTouchEventsHelper<WindowImpl> _touchHelper;
private bool _handleEvents;
public bool HandleEvents
{
get { return _handleEvents; }
set
{
_handleEvents = value;
_keyboardHelper.HandleEvents = _handleEvents;
}
}
internal static bool _debug = false;
public WindowImpl(Context context) : base(context)
{
DrawType = AndroidPlatform.Instance.DefaultViewDrawType == ViewDrawType.SurfaceViewCanvasOnDraw ? ViewDrawType.CanvasOnDraw : AndroidPlatform.Instance.DefaultViewDrawType;
_keyboardHelper = new AndroidKeyboardEventsHelper<WindowImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<WindowImpl>(this, () => InputRoot, GetPerspexPointFromEvent);
_renderHelper = AndroidViewRenderHelper<WindowImpl>.Create(this, DrawType);
//LayerType = LayerType.Hardware;
//SetLayerType(LayerType.Hardware, null);
ClientSize = MaxClientSize;
Resized = size => Invalidate(new Rect(size));
this.Init();
}
public WindowImpl() : this(PerspexLocator.Current.GetService<IAndroidActivity>().Activity)
{
}
protected virtual void Init()
{
HandleEvents = false;
//if the background is not transparent nothing is visible!!!
//this is the case when inherit from GridLayout, not from View
//this.Background = new AG.Drawables.ColorDrawable(AG.Color.Transparent);
}
public Rect Bounds { get; set; }
public IInputRoot InputRoot { get; private set; }
IntPtr IPlatformHandle.Handle => Handle;
string IPlatformHandle.HandleDescriptor => "Perspex View";
public void Resize(double width, double height)
{
if (ClientSize == new Size(width, height)) return;
ClientSize = new Size(width, height);
Resized(ClientSize);
}
public Size ClientSize { get; set; }
IPlatformHandle ITopLevelImpl.Handle => this;
Action ITopLevelImpl.Activated { get; set; }
public Action Closed { get; set; }
public Action Deactivated { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public void Activate()
{
}
public void SetInputRoot(IInputRoot inputRoot)
{
InputRoot = inputRoot;
}
public Point PointToScreen(Point point)
{
return point;
}
public void SetCursor(IPlatformHandle cursor)
{
}
public Size MaxClientSize => _pointService.NativeToPerspex(new Size(Resources.DisplayMetrics.WidthPixels, Resources.DisplayMetrics.HeightPixels));
public View View => this;
public void SetTitle(string title)
{
}
void ITopLevelImpl.Show()
{
this.Visibility = ViewStates.Visible;
}
public IDisposable ShowDialog()
{
throw new NotImplementedException();
}
public void Hide()
{
this.Visibility = ViewStates.Invisible;
}
public virtual Point GetPerspexPointFromEvent(MotionEvent e) => _pointService.NativeToPerspex(new Point(e.GetX(), e.GetY()));
public override bool DispatchTouchEvent(MotionEvent e)
{
bool callBase;
bool? result = _touchHelper.DispatchTouchEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
return result != null ? result.Value : baseResult;
}
public override bool DispatchKeyEvent(KeyEvent e)
{
bool callBase;
bool? res = _keyboardHelper.DispatchKeyEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchKeyEvent(e) : false;
return res != null ? res.Value : baseResult;
}
public void Invalidate(Rect rect)
{
this._renderHelper.Invalidate(rect);
}
public AG.Canvas Canvas { get; private set; }
public Dictionary<object, IDisposable> VisualCaches
{
get
{
return _renderHelper.VisualCaches;
}
}
protected TimeSpan _onDrawDuration;
private DateTime _beginDraw;
public virtual void PreRender(AG.Canvas canvas, Rect rect)
{
_beginDraw = DateTime.Now;
}
public virtual void Render(AG.Canvas canvas, Rect rect)
{
Canvas = canvas;
Paint?.Invoke(rect);
}
public virtual void PostRender(AG.Canvas canvas, Rect rect)
{
_renderHelper.DrawDebugInfoToCanvas(canvas);
_touchHelper.DrawLastMousePoint(canvas);
}
protected override void OnDraw(AG.Canvas canvas)
{
_renderHelper.OnDraw(canvas);
}
void IDisposable.Dispose()
{
_renderHelper.Dispose();
_touchHelper.Dispose();
_keyboardHelper.Dispose();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
Resize(MaxClientSize.Width, MaxClientSize.Height);
}
}
}

23
src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs

@ -9,7 +9,6 @@ using Perspex.Input.Raw;
using Perspex.Platform;
using Perspex.Skia.Android;
using System;
using System.Collections.Generic;
namespace Perspex.Android.Platform.SkiaPlatform
{
@ -146,30 +145,10 @@ namespace Perspex.Android.Platform.SkiaPlatform
return res != null ? res.Value : baseResult;
}
private Queue<double> _avgDrawQueue = new Queue<double>();
protected override void Draw()
{
DateTime begin = DateTime.Now;
Paint?.Invoke(new Rect(new Point(0, 0), ClientSize));
TimeSpan duration = DateTime.Now - begin;
if (AndroidPlatform.Instance.DrawDebugInfo)
{
//draw some basic debug info about rendering
//we can't create drawing context so push some info to std out
//double ms = duration.TotalMilliseconds;
//_avgDrawQueue.Enqueue(ms);
//if (_avgDrawQueue.Count > 50) _avgDrawQueue.Dequeue();
//double msAvg = _avgDrawQueue.Average();
//string text = $"DrawType={AndroidPlatform.Instance.DefaultViewDrawType}, OnDraw={ms.ToString("0.00")}, aOnDraw={msAvg.ToString("0.00")}";
//Log.Debug("render", text);
}
}
}
}

50
src/Android/Perspex.Android/Platform/Specific/CommonUITimer.cs

@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
namespace Perspex.Android.Platform.Specific
{
public class CommonUITimer : IDisposable
{
private System.Timers.Timer _timer;
private TimeSpan _interval;
private Dictionary<Action, DateTime> _actions = new Dictionary<Action, DateTime>();
public CommonUITimer(TimeSpan interval)
{
_timer = new System.Timers.Timer(interval.TotalMilliseconds);
_interval = interval;
_timer.Elapsed += _timer_Elapsed;
_timer.Start();
}
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DateTime now = DateTime.Now;
foreach (var kvp in _actions)
{
if ((now - kvp.Value) >= _interval)
{
kvp.Key();
}
}
}
public void AddAction(Action action)
{
_actions[action] = DateTime.Now;
}
public void Dispose()
{
_timer.Elapsed -= _timer_Elapsed;
_timer?.Stop();
}
public void RemoveAction(Action action)
{
_actions.Remove(action);
}
public int ActiveActionsCount => _actions.Count;
}
}

28
src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -1,6 +1,5 @@
using Android.Graphics;
using Android.Views;
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Media;
@ -128,33 +127,6 @@ namespace Perspex.Android.Platform.Specific.Helpers
private Paint _paint;
public void DrawLastMousePoint(DrawingContext context)
{
if (!AndroidPlatform.Instance.DrawDebugInfo) return;
double w = AndroidPlatform.Instance.DoubleClickSize.Width;
context.FillRectangle(Brushes.Red, new Rect(_point.X - w / 2, _point.Y - w / 2, w, w));
}
public void DrawLastMousePoint(Canvas canvas)
{
if (!AndroidPlatform.Instance.DrawDebugInfo) return;
if (_paint == null)
{
_paint = new Paint() { Color = global::Android.Graphics.Color.Red, };
_paint.SetStyle(Paint.Style.Fill);
}
if (AndroidPlatform.Instance.DefaultViewDrawType != ViewDrawType.Skia)
{
canvas.DrawCircle(PointUnitService.Instance.PerspexToNativeXF(_point.X),
PointUnitService.Instance.PerspexToNativeYF(_point.Y),
(float)AndroidPlatform.Instance.DoubleClickSize.Width,
_paint);
}
}
public void Dispose()
{
HandleEvents = false;

23
src/Android/Perspex.Android/Platform/Specific/Helpers/ViewDrawType.cs

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace Perspex.Android.Platform.Specific.Helpers
{
public enum ViewDrawType
{
Skia,
CanvasOnDraw,
BitmapOnPreDraw,
BitmapBackgroundRender,
SurfaceViewCanvasOnDraw
}
}

6
src/Android/Perspex.Android/Platform/Specific/PerspexActivity.cs

@ -1,3 +1,4 @@
using System;
using Android.App;
using Android.OS;
using Android.Views;
@ -9,6 +10,11 @@ namespace Perspex.Android.Platform.Specific
{
private IAndroidView _contentView;
public PerspexActivity(Type applicationType)
{
AndroidPlatform.Instance.Init(applicationType);
}
public Activity Activity => this;
public IAndroidView ContentView

372
src/Android/Perspex.AndroidTestApplication/App.cs

@ -1,372 +0,0 @@
using Perspex.Android;
using Perspex.Controls;
using Perspex.Controls.Shapes;
using Perspex.Interactivity;
using Perspex.Layout;
using Perspex.Media;
using Perspex.Media.Imaging;
using Perspex.Platform;
using Perspex.Themes.Default;
using System;
namespace Perspex.AndroidTestApplication
{
public class App : Application
{
public App()
{
RegisterServices();
InitializePlatform();
Styles = new DefaultTheme();
}
protected void InitializePlatform()
{
AndroidPlatform.Initialize();
//not needed already default assembly is set to Activity that inherits PerspexActivity
//AndroidPlatform.Instance.SetDefaultAssetAssembly(typeof(App).Assembly);
}
public Window BuildSimpleTextUI()
{
var window = new Window
{
Title = "Perspex Test Application",
Background = Brushes.Green,
Content = new Grid
{
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(10, 10, 0, 0),
Children = new Controls.Controls
{
new TextBlock
{
Foreground = Brushes.GhostWhite,
Background = Brushes.BlueViolet,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
Text =
"Hello! This is a test of text wrapping and me actually getting something to render for the first time in life. Im and happy to say this was by no means all me. I want to thank everyone who helped me this far.",
FontSize = 80,
Margin = new Thickness(10),
TextWrapping = TextWrapping.Wrap
}
}
}
};
return window;
}
public Window BuildStackPanelUI()
{
var window = new Window
{
Title = "Perspex Test Application",
Background = Brushes.Green,
Content = new Grid
{
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
Margin = new Thickness(10, 10, 0, 0),
Children = new Controls.Controls
{
new StackPanel
{
Orientation = Orientation.Horizontal,
[Grid.RowProperty] = 1,
[Grid.ColumnSpanProperty] = 2,
// Background = SolidColorBrush.Parse("#000000"),
Children = new Controls.Controls
{
new Button
{
Content = "Button 1",
Width = 100,
Foreground = Brushes.Blue,
Margin = new Thickness(5)
},
new Button
{
Content = "Button 2",
Width = 100,
Margin = new Thickness(5)
},
new Button
{
Content = "Button 3",
Width = 100,
Margin = new Thickness(5)
}
}
}
}
}
};
return window;
}
public Window BuildGridWithSomeButtonsAndStuff()
{
var buttonToClick = new Button
{
Content = "Tap Me",
Width = 200,
Foreground = Brushes.White,
Margin = new Thickness(5),
Background = Brushes.Blue,
FontSize = 50
};
var clickCount = 0;
buttonToClick.Click += (object sender, RoutedEventArgs e) =>
{
Console.WriteLine("You clicked it :)");
buttonToClick.Content = $"Tap x{clickCount}";
clickCount++;
};
var window = new Window
{
Title = "Perspex Test Application",
Background = Brushes.DarkTurquoise,
Content = new Grid
{
Margin = new Thickness(0, 0, 0, 0), // skip the status bar area on iOS
ColumnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
new ColumnDefinition(1, GridUnitType.Star)
},
RowDefinitions = new RowDefinitions
{
new RowDefinition(80, GridUnitType.Pixel),
new RowDefinition(160, GridUnitType.Pixel),
new RowDefinition(1, GridUnitType.Star),
new RowDefinition(1, GridUnitType.Star)
},
Children = new Controls.Controls
{
new TextBlock
{
[Grid.RowProperty] = 0,
[Grid.ColumnSpanProperty] = 2,
Text = "Welcome to Perspex on Android !!!",
Foreground = Brushes.SteelBlue,
Background = Brushes.White,
FontSize = 50,
Margin = new Thickness(5, 7, 5, 5),
TextAlignment = TextAlignment.Left
},
////
new StackPanel
{
Orientation = Orientation.Horizontal,
[Grid.RowProperty] = 1,
[Grid.ColumnSpanProperty] = 2,
Background = Brush.Parse("#000000"),
Children = new Controls.Controls
{
buttonToClick,
new Button
{
Content = "Button 2",
Width = 250,
FontSize = 50,
Margin = new Thickness(5)
},
new Button
{
Content = "Button 3",
Width = 250,
FontSize = 50,
Margin = new Thickness(5)
}
}
},
////
new Image
{
[Grid.RowProperty] = 2,
[Grid.ColumnProperty] = 0,
Margin = new Thickness(20),
// Source = new Bitmap("github_icon.png"),
Opacity = 0.4
},
////
new Ellipse
{
[Grid.RowProperty] = 3,
[Grid.ColumnProperty] = 0,
Margin = new Thickness(20),
Fill = Brushes.Blue
},
////
new TextBlock
{
[Grid.RowProperty] = 2,
[Grid.ColumnProperty] = 1,
Text =
"How does this look? Is this text going to wrap and then look nice within the bounds of this widget? If not I will be extremely disappointed!\n\nWill we start a new paragraph here? If not there will be hell to pay!!!!",
Foreground = Brushes.Purple,
Background = Brushes.Transparent,
FontSize = 28,
Margin = new Thickness(10, 30, 10, 10)
},
new Path
{
Data =
StreamGeometry.Parse(
"M 50,50 l 15,0 l 5,-15 l 5,15 l 15,0 l -10,10 l 4,15 l -15,-9 l -15,9 l 7,-15 Z"),
[Grid.RowProperty] = 3,
[Grid.ColumnProperty] = 1,
Margin = new Thickness(20),
Fill = Brushes.White,
Stroke = Brushes.Blue,
StrokeThickness = 4
}
}
}
};
return window;
}
public Window BuildGridWithSomeButtonsAndStuff2()
{
var buttonToClick = new Button
{
Content = "Tap Me",
Width = 200,
Foreground = Brushes.White,
Margin = new Thickness(5),
Background = Brushes.Blue,
FontSize = 50
};
var clickCount = 0;
buttonToClick.Click += (object sender, RoutedEventArgs e) =>
{
Console.WriteLine("You clicked it :)");
buttonToClick.Content = $"Tap x{clickCount}";
clickCount++;
};
var window = new Window
{
Title = "Perspex Test Application",
Background = Brushes.DarkTurquoise,
Content = new Grid
{
Margin = new Thickness(0, 50, 0, 0), // skip the status bar area on iOS
ColumnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
new ColumnDefinition(1, GridUnitType.Star)
},
RowDefinitions = new RowDefinitions
{
new RowDefinition(80, GridUnitType.Pixel),
new RowDefinition(160, GridUnitType.Pixel),
new RowDefinition(1, GridUnitType.Star),
new RowDefinition(1, GridUnitType.Star)
},
Children = new Controls.Controls
{
new TextBox
{
[Grid.RowProperty] = 0,
[Grid.ColumnSpanProperty] = 2,
Text = "Welcome to Perspex on Android !!!",
Foreground = Brushes.SteelBlue,
Background = Brushes.White,
FontSize = 50,
//Margin = new Thickness(5, 7, 5, 5),
Margin = new Thickness(5, 7, 5, 5),
//VerticalAlignment = VerticalAlignment.Bottom
//TextAlignment = TextAlignment.Left
},
////
new StackPanel
{
Orientation = Orientation.Horizontal,
[Grid.RowProperty] = 1,
[Grid.ColumnSpanProperty] = 2,
Background = Brush.Parse("#000000"),
Children = new Controls.Controls
{
buttonToClick,
new Button
{
Content = "Button 2",
Width = 250,
FontSize = 50,
Margin = new Thickness(5)
},
new Button
{
Content = "Button 3",
Width = 250,
FontSize = 50,
Margin = new Thickness(5)
}
}
},
////
new Image
{
[Grid.RowProperty] = 2,
[Grid.ColumnProperty] = 0,
Margin = new Thickness(20),
//Stretch=Stretch.Uniform,
Width=400,
Height=400,
// Source = new Bitmap("github_icon.png"),
Source = new Bitmap(PerspexLocator.Current.GetService<IAssetLoader>().Open(new Uri("res://component/Perspex Test Application/Perspex.AndroidTestApplication.github_icon.png"))),
Opacity = 0.4
},
////
new Ellipse
{
[Grid.RowProperty] = 3,
[Grid.ColumnProperty] = 0,
Margin = new Thickness(20),
Fill = Brushes.Blue,
//Stroke = Brushes.Red,
//StrokeThickness = 4
},
////
new TextBlock
{
[Grid.RowProperty] = 2,
[Grid.ColumnProperty] = 1,
Text =
"How does this look? Is this text going to wrap and then look nice within the bounds of this widget? If not I will be extremely disappointed!\n\nWill we start a new paragraph here? If not there will be hell to pay!!!!",
Foreground = Brushes.Purple,
Background = Brushes.Transparent,
FontSize = 28,
Margin = new Thickness(10, 30, 10, 10)
},
new Path
{
Data =
StreamGeometry.Parse(
"M 50,50 l 15,0 l 5,-15 l 5,15 l 15,0 l -10,10 l 4,15 l -15,-9 l -15,9 l 7,-15 Z"),
[Grid.RowProperty] = 3,
[Grid.ColumnProperty] = 1,
Margin = new Thickness(20),
Fill = Brushes.White,
Stroke = Brushes.Blue,
StrokeThickness = 4
}
}
}
};
return window;
}
}
}

4
src/Android/Perspex.AndroidTestApplication/GettingStarted.Xamarin

@ -1,4 +0,0 @@
<GettingStarted>
<LocalContent>GS\Android\CS\AndroidApp\GettingStarted.html</LocalContent>
<EmbeddedNavigation>false</EmbeddedNavigation>
</GettingStarted>

48
src/Android/Perspex.AndroidTestApplication/MainActivity.cs

@ -6,6 +6,7 @@ using Perspex.Android.Platform.Specific;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Controls.Platform;
using Perspex.Platform;
using TestApplication;
namespace Perspex.AndroidTestApplication
{
@ -16,57 +17,28 @@ namespace Perspex.AndroidTestApplication
ScreenOrientation = ScreenOrientation.Landscape*/)]
public class MainBaseActivity : PerspexActivity
{
protected override void OnCreate(Bundle savedInstanceState)
public MainBaseActivity() : base(typeof (App))
{
base.OnCreate(savedInstanceState);
//set some parameters to android platform
AndroidPlatform.Instance.DrawDebugInfo = true;
//skia is the default rendering method on android so no need to set it
AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.Skia;
//AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.BitmapOnPreDraw;
//AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.CanvasOnDraw;
//AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.BitmapBackgroundRender;
//AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.SurfaceViewCanvasOnDraw;
//AndroidPlatform.Instance.DefaultPointUnit = Android.Platform.CanvasPlatform.PointUnit.DP;
//AndroidPlatform.Instance.DefaultPointUnit = Android.Platform.CanvasPlatform.PointUnit.Pixel;
}
//60 fps animation are causing user interface in animations to stop responding
AndroidPlatform.Instance.OverrideAnimateFramesPerSecond = 16;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
App app;
if (Perspex.Application.Current != null)
app = (App)Perspex.Application.Current;
else
app = new App();
if (AndroidPlatform.Instance.DefaultPointUnit == Android.Platform.CanvasPlatform.PointUnit.DP &&
AndroidPlatform.Instance.DefaultViewDrawType == ViewDrawType.Skia)
{
double scale = Resources.DisplayMetrics.ScaledDensity;
//make it DiP in skia
PerspexLocator.Current.GetService<PlatformSettings>().LayoutScalingFactor = scale;
PerspexLocator.Current.GetService<PlatformSettings>().RenderScalingFactor = scale;
}
var window = TestUI.TestUIBuilder.BuildTestUI();
//var window = BuildPlatforSetup();
MainWindow.RootNamespace = "Perspex.AndroidTestApplication";
var window = MainWindow.Create();
window.Show();
app.Run(window);
}
public static void StartAppFromSetup()
{
PerspexLocator.Current.GetService<IWindowImpl>().Dispose();
AndroidPlatform.Instance.RegisterViewDrawType();
AndroidPlatform.Instance.RegisterViewPointUnits();
var window = TestUI.TestUIBuilder.BuildTestUI();
window.Show();
Perspex.Application.Current.Run(window);
}
}
}

18
src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj

@ -106,22 +106,15 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.cs" />
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SetupTestUI.cs" />
<Compile Include="TestUI.cs" />
</ItemGroup>
<ItemGroup>
<None Include="GettingStarted.Xamarin" />
<None Include="packages.config" />
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Main.axml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values\Strings.xml">
<SubType>Designer</SubType>
@ -199,16 +192,7 @@
<Name>Perspex.Skia.Android</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="github_icon.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="pattern.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\..\samples\TestApplicationShared\TestApplicationShared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

28
src/Android/Perspex.AndroidTestApplication/Resources/Resource.Designer.cs

@ -60,33 +60,17 @@ namespace Perspex.AndroidTestApplication
}
}
public partial class Layout
{
// aapt resource value: 0x7f030000
public const int Main = 2130903040;
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
{
}
}
public partial class String
{
// aapt resource value: 0x7f040002
public const int ApplicationName = 2130968578;
// aapt resource value: 0x7f030002
public const int ApplicationName = 2130903042;
// aapt resource value: 0x7f040001
public const int Hello = 2130968577;
// aapt resource value: 0x7f030001
public const int Hello = 2130903041;
// aapt resource value: 0x7f040000
public const int library_name = 2130968576;
// aapt resource value: 0x7f030000
public const int library_name = 2130903040;
static String()
{

9
src/Android/Perspex.AndroidTestApplication/Resources/layout/Main.axml

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<SurfaceView
android:layout_height="fill_parent"
android:layout_width="fill_parent" />
</LinearLayout>

162
src/Android/Perspex.AndroidTestApplication/SetupTestUI.cs

@ -1,162 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using ReactiveUI;
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Android;
using Perspex.Styling;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Controls;
using Perspex.Markup.Xaml.Data;
using Perspex.Controls.Presenters;
namespace Perspex.AndroidTestApplication
{
public class SetupTestUI
{
private static Style HeaderedContentControlStyle()
{
return new Style(x => x.OfType<HeaderedContentControl>())
{
Setters = new[]
{
new Setter(TemplatedControl.TemplateProperty, new FuncControlTemplate<HeaderedContentControl>(control=> {
return new Border()
{
[~Border.BackgroundProperty] = control[~TemplatedControl.BackgroundProperty],
Child = new StackPanel()
{
Children = new Controls.Controls()
{
new ContentPresenter
{
Name = "headercontentPresenter",
[~ContentPresenter.ContentProperty] = control[~HeaderedContentControl.HeaderProperty],
},
new ContentPresenter
{
Name = "contentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
}
}
}
};
})),
},
};
}
private Window BuildPlatforSetup()
{
Perspex.Application.Current.Styles.Add(HeaderedContentControlStyle());
var vm = new PlatformSetupViewModel();
vm.PointUnit = AndroidPlatform.Instance.DefaultPointUnit.ToString();
vm.ViewDrawType = AndroidPlatform.Instance.DefaultViewDrawType.ToString();
var lbPointUnit = new ListBox()
{
Name = "PointUnit",
Items = vm.PointUnits,
[ListBox.SelectedItemProperty] = vm.PointUnit
};
var lbViewDrawType = new ListBox()
{
Name = "ViewDrawType",
Items = vm.ViewDrawTypes,
[ListBox.SelectedItemProperty] = vm.ViewDrawType
};
var window = new Window()
{
DataContext = vm,
Content = new Grid()
{
Margin = new Thickness(20, 100, 0, 0),
RowDefinitions = new RowDefinitions() {
new RowDefinition() { Height = GridLength.Auto },
new RowDefinition()
},
Children = new Controls.Controls()
{
new Button()
{
[Grid.RowProperty] = 1,
HorizontalAlignment = Layout.HorizontalAlignment.Center,
VerticalAlignment = Layout.VerticalAlignment.Center,
Content = "Start application",
Command = vm.Command
},
new StackPanel()
{
[Grid.RowProperty] = 0,
Orientation = Orientation.Horizontal,
Gap=10,
Children= new Controls.Controls
{
new HeaderedContentControl() {Content = lbPointUnit, Header="Point Units Type" },
new HeaderedContentControl() { Content = lbViewDrawType, Header ="Draw View type" },
}
}
}
}
};
var b = new Binding();
b.SourcePropertyPath = "PointUnit";
b.Mode = BindingMode.TwoWay;
b.Bind(lbPointUnit, ListBox.SelectedItemProperty);
b = new Binding();
b.SourcePropertyPath = "ViewDrawType";
b.Mode = BindingMode.TwoWay;
b.Bind(lbViewDrawType, ListBox.SelectedItemProperty);
return window;
}
public class PlatformSetupViewModel : ReactiveObject
{
public PlatformSetupViewModel()
{
ViewDrawTypes = Enum.GetNames(typeof(ViewDrawType));
PointUnits = Enum.GetNames(typeof(PointUnit));
var cmd = ReactiveCommand.Create();
Command = cmd;
cmd.Subscribe(onNext: o =>
{
var pu = (PointUnit)Enum.Parse(typeof(PointUnit), PointUnit);
var vdt = (ViewDrawType)Enum.Parse(typeof(ViewDrawType), ViewDrawType);
AndroidPlatform.Instance.DefaultPointUnit = pu;
AndroidPlatform.Instance.DefaultViewDrawType = vdt;
MainBaseActivity.StartAppFromSetup();
//restart app ;
});
}
public string[] ViewDrawTypes { get; set; }
public string[] PointUnits { get; set; }
private string _viewDrawType;
public string ViewDrawType
{
get { return _viewDrawType; }
set { this.RaiseAndSetIfChanged(ref _viewDrawType, value); }
}
private string _pointUnit;
public string PointUnit
{
get { return _pointUnit; }
set { this.RaiseAndSetIfChanged(ref _pointUnit, value); }
}
public ICommand Command { get; set; }
}
}
}

1659
src/Android/Perspex.AndroidTestApplication/TestUI.cs

File diff suppressed because it is too large

BIN
src/Android/Perspex.AndroidTestApplication/github_icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

BIN
src/Android/Perspex.AndroidTestApplication/pattern.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

9
src/Perspex.Application/Application.cs

@ -34,6 +34,8 @@ namespace Perspex
/// </remarks>
public class Application : IGlobalDataTemplates, IGlobalStyles
{
static bool _suppressPlatformInitialization;
/// <summary>
/// The application-global data templates.
/// </summary>
@ -60,6 +62,11 @@ namespace Perspex
Current = this;
}
public static void SuppressPlatformInitialization()
{
_suppressPlatformInitialization = true;
}
/// <summary>
/// Gets the current instance of the <see cref="Application"/> class.
/// </summary>
@ -179,6 +186,8 @@ namespace Perspex
/// <param name="platformID">The value of Environment.OSVersion.Platform.</param>
protected void InitializeSubsystems(int platformID)
{
if(_suppressPlatformInitialization)
return;
if (platformID == 4 || platformID == 6)
{
InitializeSubsystem("Perspex.Cairo");

16
src/Shared/PlatformSupport/AssetLoader.cs

@ -18,6 +18,13 @@ namespace Perspex.Shared.PlatformSupport
private static readonly Dictionary<string, Assembly> AssemblyNameCache
= new Dictionary<string, Assembly>();
private Assembly _defaultAssembly;
public AssetLoader(Assembly assembly = null)
{
_defaultAssembly = assembly;
}
static Assembly GetAssembly(string name)
{
Assembly rv;
@ -36,7 +43,7 @@ namespace Perspex.Shared.PlatformSupport
public bool Exists(Uri uri)
{
var parts = uri.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var asm = parts.Length == 1 ? Assembly.GetEntryAssembly() : GetAssembly(parts[0]);
var asm = parts.Length == 1 ? (_defaultAssembly ?? Assembly.GetEntryAssembly()) : GetAssembly(parts[0]);
var typeName = parts[parts.Length == 1 ? 0 : 1];
var rv = asm.GetManifestResourceStream(typeName);
return rv != null;
@ -53,11 +60,16 @@ namespace Perspex.Shared.PlatformSupport
public Stream Open(Uri uri)
{
var parts = uri.AbsolutePath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
var asm = parts.Length == 1 ? Assembly.GetEntryAssembly() : GetAssembly(parts[0]);
var asm = parts.Length == 1 ? (_defaultAssembly ?? Assembly.GetEntryAssembly()) : GetAssembly(parts[0]);
var typeName = parts[parts.Length == 1 ? 0 : 1];
var rv = asm.GetManifestResourceStream(typeName);
if (rv == null)
{
#if DEBUG
var names = asm.GetManifestResourceNames().ToList();
#endif
throw new FileNotFoundException($"The resource {uri} could not be found.");
}
return rv;
}
}

5
src/Shared/PlatformSupport/SharedPlatform.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Perspex.Platform;
@ -7,11 +8,11 @@ namespace Perspex.Shared.PlatformSupport
{
static class SharedPlatform
{
public static void Register()
public static void Register(Assembly assembly = null)
{
PerspexLocator.CurrentMutable
.Bind<IPclPlatformWrapper>().ToSingleton<PclPlatformWrapper>()
.Bind<IAssetLoader>().ToSingleton<AssetLoader>();
.Bind<IAssetLoader>().ToConstant(new AssetLoader(assembly));
}
}
}

2
src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v5.1</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

122
src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj.bak

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Skia.Android.TestApp</RootNamespace>
<AssemblyName>Perspex.Skia.Android.TestApp</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidApplication>true</AndroidApplication>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<AndroidLinkSkip />
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<Debugger>Xamarin</Debugger>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<DevInstrumentationEnabled>True</DevInstrumentationEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Collections" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />
<AndroidResource Include="Resources\layout\Main.axml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Perspex.Animation\Perspex.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Skia.Android\Perspex.Skia.Android.csproj">
<Project>{bd43f7c0-396b-4aa1-bad9-dfde54d51298}</Project>
<Name>Perspex.Skia.Android</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Icon.png" />
<AndroidResource Include="Resources\values\Strings.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

2
src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj

@ -15,7 +15,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>

16
src/Skia/Perspex.Skia.Android/SkiaView.cs

@ -20,12 +20,15 @@ namespace Perspex.Skia.Android
{
private readonly Activity _context;
bool _invalidateQueued;
object _lock = new object();
readonly object _lock = new object();
private readonly Handler _handler;
public SkiaView(Activity context) : base(context)
{
_context = context;
SkiaPlatform.Initialize();
Holder.AddCallback(this);
_handler = new Handler(context.MainLooper);
}
public override void Invalidate()
@ -34,13 +37,20 @@ namespace Perspex.Skia.Android
{
if(_invalidateQueued)
return;
_context.RunOnUiThread(() =>
_handler.Post(() =>
{
lock (_lock)
{
_invalidateQueued = false;
}
Draw();
try
{
Draw();
}
catch (Exception e)
{
Log.WriteLine(LogPriority.Error, "Perspex", e.ToString());
}
});
}
}

Loading…
Cancel
Save