Browse Source

Android functionality with default Canvas Implementation

pull/327/head
andrey 11 years ago
parent
commit
8e68bd532f
  1. 19
      Perspex.sln
  2. 82
      src/Android/Perspex.Android/AndroidAssetLoader.cs
  3. 221
      src/Android/Perspex.Android/AndroidPlatform.cs
  4. 246
      src/Android/Perspex.Android/CanvasRendering/AndroidGraphicsExtensions.cs
  5. 65
      src/Android/Perspex.Android/CanvasRendering/AndroidPlatformRender.cs
  6. 59
      src/Android/Perspex.Android/CanvasRendering/AndroidTopLevelRenderer.cs
  7. 34
      src/Android/Perspex.Android/CanvasRendering/BitmapImpl.cs
  8. 363
      src/Android/Perspex.Android/CanvasRendering/BrushImpl.cs
  9. 248
      src/Android/Perspex.Android/CanvasRendering/DrawingContext.cs
  10. 205
      src/Android/Perspex.Android/CanvasRendering/FormattedTextImpl.cs
  11. 63
      src/Android/Perspex.Android/CanvasRendering/RenderTarget.cs
  12. 49
      src/Android/Perspex.Android/CanvasRendering/RenderTargetBitmapImpl.cs
  13. 158
      src/Android/Perspex.Android/CanvasRendering/StreamGeometryImpl.cs
  14. 12
      src/Android/Perspex.Android/CursorFactory.cs
  15. 171
      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. 47
      src/Android/Perspex.Android/Platform/ClipboardImpl.cs
  26. 217
      src/Android/Perspex.Android/Platform/Input/AndroidKeyboardDevice.cs
  27. 12
      src/Android/Perspex.Android/Platform/Input/AndroidMouseDevice.cs
  28. 50
      src/Android/Perspex.Android/Platform/Specific/CommonUITimer.cs
  29. 145
      src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  30. 155
      src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  31. 23
      src/Android/Perspex.Android/Platform/Specific/Helpers/ViewDrawType.cs
  32. 12
      src/Android/Perspex.Android/Platform/Specific/IAndroidActivity.cs
  33. 16
      src/Android/Perspex.Android/Platform/Specific/IAndroidView.cs
  34. 54
      src/Android/Perspex.Android/Platform/Specific/PerspexActivity.cs
  35. 30
      src/Android/Perspex.Android/Properties/AssemblyInfo.cs
  36. 50
      src/Android/Perspex.Android/Resources/AboutResources.txt
  37. 60
      src/Android/Perspex.Android/Resources/Resource.Designer.cs
  38. 6
      src/Android/Perspex.Android/Resources/Values/Strings.xml
  39. 16
      src/Android/Perspex.Android/SystemDialogImpl.cs
  40. 9
      src/Android/Perspex.Android/packages.config
  41. 372
      src/Android/Perspex.AndroidTestApplication/App.cs
  42. 19
      src/Android/Perspex.AndroidTestApplication/Assets/AboutAssets.txt
  43. 4
      src/Android/Perspex.AndroidTestApplication/GettingStarted.Xamarin
  44. 67
      src/Android/Perspex.AndroidTestApplication/MainActivity.cs
  45. 215
      src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj
  46. 6
      src/Android/Perspex.AndroidTestApplication/Properties/AndroidManifest.xml
  47. 30
      src/Android/Perspex.AndroidTestApplication/Properties/AssemblyInfo.cs
  48. 50
      src/Android/Perspex.AndroidTestApplication/Resources/AboutResources.txt
  49. 102
      src/Android/Perspex.AndroidTestApplication/Resources/Resource.Designer.cs
  50. BIN
      src/Android/Perspex.AndroidTestApplication/Resources/drawable/Icon.png
  51. 9
      src/Android/Perspex.AndroidTestApplication/Resources/layout/Main.axml
  52. 6
      src/Android/Perspex.AndroidTestApplication/Resources/values/Strings.xml
  53. 162
      src/Android/Perspex.AndroidTestApplication/SetupTestUI.cs
  54. 1659
      src/Android/Perspex.AndroidTestApplication/TestUI.cs
  55. BIN
      src/Android/Perspex.AndroidTestApplication/github_icon.png
  56. 12
      src/Android/Perspex.AndroidTestApplication/packages.config
  57. BIN
      src/Android/Perspex.AndroidTestApplication/pattern.jpg
  58. 2
      src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj
  59. 2
      src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj

19
Perspex.sln

@ -118,9 +118,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Skia.Android", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Skia.Android.TestApp", "src\Skia\Perspex.Skia.Android.TestApp\Perspex.Skia.Android.TestApp.csproj", "{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}"
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.AndroidTestApplication", "src\Android\Perspex.AndroidTestApplication\Perspex.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
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
@ -300,6 +307,16 @@ Global
{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.Build.0 = Release|Any CPU
{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.Deploy.0 = 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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B92AF71-6287-4693-9DCB-BD5B6E927E23}.Release|Any CPU.Build.0 = 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}.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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -335,5 +352,7 @@ Global
{D35A9F3D-8BB0-496E-BF72-444038A7DEBB} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{BD43F7C0-396B-4AA1-BAD9-DFDE54D51298} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{F92E55A5-ED73-4CCB-AB4B-0541B6757F31} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}
{FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}
EndGlobalSection
EndGlobal

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

@ -0,0 +1,82 @@
// 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;
}
}
}

221
src/Android/Perspex.Android/AndroidPlatform.cs

@ -0,0 +1,221 @@
using Android.OS;
using Perspex.Android.CanvasRendering;
using Perspex.Android.Platform;
using Perspex.Android.Platform.CanvasPlatform;
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;
using Perspex.Platform;
using Perspex.Shared.PlatformSupport;
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 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);
//System.Timers.Timer
if (interval.TotalMilliseconds == 0)
{
//android ui thread
PerspexLocator.Current.GetService<IAndroidActivity>().Activity.RunOnUiThread(tick);
return Disposable.Empty;
}
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()
{
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<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IAssetLoader>().ToSingleton<AndroidAssetLoader>()
.Bind<ITopLevelRenderer>().ToTransient<AndroidTopLevelRenderer>()
;
AndroidPlatformRender.Initialize();
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.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>();
}
public void RegisterViewPointUnits()
{
PerspexLocator.CurrentMutable.Bind<IPointUnitService>().ToSingleton<PointUnitService>();
}
public void InitializeAssetsLoader(Assembly assembly, string defaultResourcePrefix)
{
var assetLoder = PerspexLocator.Current.GetService<IAssetLoader>() as AndroidAssetLoader;
if (assetLoder != null)
{
assetLoder.DefaultAssetAssembly = assembly;
assetLoder.DefaultResourcePrefix = defaultResourcePrefix;
}
}
private void EnsureInvokeOnMainThread(Action action)
{
var mainHandler = new Handler(global::Android.App.Application.Context.MainLooper);
mainHandler.Post(action);
}
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.SurfaceViewCanvasOnDraw;
public PointUnit DefaultPointUnit { get; set; } = PointUnit.DP;
public int OverrideAnimateFramesPerSecond { get; set; } = -1;
}
}

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

@ -0,0 +1,246 @@
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

@ -0,0 +1,65 @@
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);
}
}
}

59
src/Android/Perspex.Android/CanvasRendering/AndroidTopLevelRenderer.cs

@ -0,0 +1,59 @@
using Perspex.Controls;
using Perspex.Controls.Platform;
using Perspex.Platform;
using Perspex.Rendering;
using Perspex.Threading;
using System;
using System.Collections.Generic;
namespace Perspex.Android.CanvasRendering
{
internal class AndroidTopLevelRenderer : ITopLevelRenderer
{
public void Attach(TopLevel topLevel)
{
var resources = new List<IDisposable>();
var initialClientSize = topLevel.PlatformImpl.ClientSize;
var queueManager = ((IRenderRoot)topLevel).RenderQueueManager;
if (queueManager == null)
return;
var viewport = PlatformManager.CreateRenderTarget(topLevel.PlatformImpl);
resources.Add(viewport);
//resources.Add(queueManager.RenderNeeded.Subscribe(_
// =>
// Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize)))));
Action pendingInvalidation = null;
resources.Add(queueManager.RenderNeeded.Subscribe(_ =>
{
if (pendingInvalidation == null)
{
pendingInvalidation = () =>
{
topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize));
pendingInvalidation = null;
};
Dispatcher.UIThread.InvokeAsync(pendingInvalidation);
}
}
));
topLevel.PlatformImpl.Paint = rect =>
{
viewport.Render(topLevel);
queueManager.RenderFinished();
};
topLevel.Closed += delegate
{
foreach (var disposable in resources)
disposable.Dispose();
resources.Clear();
};
}
}
}

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

@ -0,0 +1,34 @@
// 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

@ -0,0 +1,363 @@
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

@ -0,0 +1,248 @@
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

@ -0,0 +1,205 @@
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

@ -0,0 +1,63 @@
// 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

@ -0,0 +1,49 @@
// 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

@ -0,0 +1,158 @@
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);
}
}
}

12
src/Android/Perspex.Android/CursorFactory.cs

@ -0,0 +1,12 @@
using System;
using Perspex.Input;
using Perspex.Platform;
namespace Perspex.Android
{
internal class CursorFactory : IStandardCursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
=> new PlatformHandle(IntPtr.Zero, "ZeroCursor");
}
}

171
src/Android/Perspex.Android/Perspex.Android.csproj

@ -0,0 +1,171 @@
<?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>{7B92AF71-6287-4693-9DCB-BD5B6E927E23}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Android</RootNamespace>
<AssemblyName>Perspex.Android</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
</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>
</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>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Collections" />
<Reference Include="System.Core" />
<Reference Include="System.IO" />
<Reference Include="System.Reflection" />
<Reference Include="System.ObjectModel" />
<Reference Include="System.Threading.Tasks" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AndroidPlatform.cs" />
<Compile Include="AndroidAssetLoader.cs" />
<Compile Include="Platform\CanvasPlatform\IAndroidCanvasView.cs" />
<Compile Include="Platform\CanvasPlatform\IAndroidViewSupportRender.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\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="CanvasRendering\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" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Resources\AboutResources.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\Strings.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Markup\Perspex.Markup.Xaml\Perspex.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Perspex.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Animation\Perspex.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</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.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</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.ReactiveUI\Perspex.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Perspex.ReactiveUI</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>
</ItemGroup>
<ItemGroup />
<Import Project="..\..\Shared\PlatformSupport\PlatformSupport.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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

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

@ -0,0 +1,14 @@
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

@ -0,0 +1,13 @@
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

@ -0,0 +1,71 @@
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

@ -0,0 +1,184 @@
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

@ -0,0 +1,137 @@
// 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

@ -0,0 +1,688 @@
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

@ -0,0 +1,33 @@
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

@ -0,0 +1,194 @@
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

@ -0,0 +1,207 @@
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);
}
}
}

47
src/Android/Perspex.Android/Platform/ClipboardImpl.cs

@ -0,0 +1,47 @@
using Android.Content;
using Android.Runtime;
using Android.Views;
using Perspex.Input.Platform;
using Perspex.Platform;
using System.Threading.Tasks;
namespace Perspex.Android.Platform
{
internal class ClipboardImpl : IClipboard
{
private Context context = (PerspexLocator.Current.GetService<IWindowImpl>() as View).Context;
private ClipboardManager ClipboardManager
{
get
{
return this.context.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>();
}
}
public Task<string> GetTextAsync()
{
if (ClipboardManager.HasPrimaryClip)
{
return Task.FromResult<string>(ClipboardManager.PrimaryClip.GetItemAt(0).Text);
}
return Task.FromResult<string>(null);
}
public Task SetTextAsync(string text)
{
ClipData clip = ClipData.NewPlainText("text", text);
ClipboardManager.PrimaryClip = clip;
return Task.FromResult<object>(null);
}
public Task ClearAsync()
{
ClipboardManager.PrimaryClip = null;
return Task.FromResult<object>(null);
}
}
}

217
src/Android/Perspex.Android/Platform/Input/AndroidKeyboardDevice.cs

@ -0,0 +1,217 @@
using System;
using Perspex.Input;
//using Android.InputMethodServices;
using System.Collections.Generic;
using Android.Views;
namespace Perspex.Android.Platform.Input
{
public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
private static readonly Dictionary<Keycode, Key> KeyDic = new Dictionary<Keycode, Key>
{
// { Keycode.Cancel?, Key.Cancel },
{ Keycode.Del, Key.Back },
{ Keycode.Tab, Key.Tab },
// { Keycode.Linefeed?, Key.LineFeed },
{ Keycode.Clear, Key.Clear },
{ Keycode.Enter, Key.Return },
{ Keycode.MediaPause, Key.Pause },
//{ Keycode.?, Key.CapsLock }
//{ Keycode.?, Key.HangulMode }
//{ Keycode.?, Key.JunjaMode }
//{ Keycode.?, Key.FinalMode }
//{ Keycode.?, Key.KanjiMode }
{ Keycode.Escape, Key.Escape },
//{ Keycode.?, Key.ImeConvert }
//{ Keycode.?, Key.ImeNonConvert }
//{ Keycode.?, Key.ImeAccept }
//{ Keycode.?, Key.ImeModeChange }
{ Keycode.Space, Key.Space },
{ Keycode.PageUp, Key.Prior },
{ Keycode.PageDown, Key.PageDown },
// { Keycode.end?, Key.End },
{ Keycode.Home, Key.Home },
{ Keycode.DpadLeft, Key.Left },
{ Keycode.DpadUp, Key.Up },
{ Keycode.DpadRight, Key.Right },
{ Keycode.DpadDown, Key.Down },
// { Keycode.ButtonSelect?, Key.Select },
// { Keycode.print?, Key.Print },
//{ Keycode.execute?, Key.Execute },
// { Keycode.snap, Key.Snapshot }
{ Keycode.Insert, Key.Insert },
{ Keycode.ForwardDel, Key.Delete },
//{ Keycode.help, Key.Help },
//{ Keycode.?, Key.D0 }
//{ Keycode.?, Key.D1 }
//{ Keycode.?, Key.D2 }
//{ Keycode.?, Key.D3 }
//{ Keycode.?, Key.D4 }
//{ Keycode.?, Key.D5 }
//{ Keycode.?, Key.D6 }
//{ Keycode.?, Key.D7 }
//{ Keycode.?, Key.D8 }
//{ Keycode.?, Key.D9 }
{ Keycode.A, Key.A },
{ Keycode.B, Key.B },
{ Keycode.C, Key.C },
{ Keycode.D, Key.D },
{ Keycode.E, Key.E },
{ Keycode.F, Key.F },
{ Keycode.G, Key.G },
{ Keycode.H, Key.H },
{ Keycode.I, Key.I },
{ Keycode.J, Key.J },
{ Keycode.K, Key.K },
{ Keycode.L, Key.L },
{ Keycode.M, Key.M },
{ Keycode.N, Key.N },
{ Keycode.O, Key.O },
{ Keycode.P, Key.P },
{ Keycode.Q, Key.Q },
{ Keycode.R, Key.R },
{ Keycode.S, Key.S },
{ Keycode.T, Key.T },
{ Keycode.U, Key.U },
{ Keycode.V, Key.V },
{ Keycode.W, Key.W },
{ Keycode.X, Key.X },
{ Keycode.Y, Key.Y },
{ Keycode.Z, Key.Z },
//{ Keycode.a, Key.A },
//{ Keycode.b, Key.B },
//{ Keycode.c, Key.C },
//{ Keycode.d, Key.D },
//{ Keycode.e, Key.E },
//{ Keycode.f, Key.F },
//{ Keycode.g, Key.G },
//{ Keycode.h, Key.H },
//{ Keycode.i, Key.I },
//{ Keycode.j, Key.J },
//{ Keycode.k, Key.K },
//{ Keycode.l, Key.L },
//{ Keycode.m, Key.M },
//{ Keycode.n, Key.N },
//{ Keycode.o, Key.O },
//{ Keycode.p, Key.P },
//{ Keycode.q, Key.Q },
//{ Keycode.r, Key.R },
//{ Keycode.s, Key.S },
//{ Keycode.t, Key.T },
//{ Keycode.u, Key.U },
//{ Keycode.v, Key.V },
//{ Keycode.w, Key.W },
//{ Keycode.x, Key.X },
//{ Keycode.y, Key.Y },
//{ Keycode.z, Key.Z },
//{ Keycode.?, Key.LWin }
//{ Keycode.?, Key.RWin }
//{ Keycode.?, Key.Apps }
//{ Keycode.?, Key.Sleep }
//{ Keycode.?, Key.NumPad0 }
//{ Keycode.?, Key.NumPad1 }
//{ Keycode.?, Key.NumPad2 }
//{ Keycode.?, Key.NumPad3 }
//{ Keycode.?, Key.NumPad4 }
//{ Keycode.?, Key.NumPad5 }
//{ Keycode.?, Key.NumPad6 }
//{ Keycode.?, Key.NumPad7 }
//{ Keycode.?, Key.NumPad8 }
//{ Keycode.?, Key.NumPad9 }
{ Keycode.NumpadMultiply, Key.Multiply },
{ Keycode.NumpadAdd, Key.Add },
{ Keycode.NumpadComma, Key.Separator },
{ Keycode.NumpadSubtract, Key.Subtract },
//{ Keycode.numpaddecimal?, Key.Decimal }
{ Keycode.NumpadDivide, Key.Divide },
{ Keycode.F1, Key.F1 },
{ Keycode.F2, Key.F2 },
{ Keycode.F3, Key.F3 },
{ Keycode.F4, Key.F4 },
{ Keycode.F5, Key.F5 },
{ Keycode.F6, Key.F6 },
{ Keycode.F7, Key.F7 },
{ Keycode.F8, Key.F8 },
{ Keycode.F9, Key.F9 },
{ Keycode.F10, Key.F10 },
{ Keycode.F11, Key.F11 },
{ Keycode.F12, Key.F12 },
//{ Keycode.f13, Key.F13 },
//{ Keycode.F14, Key.F14 },
//{ Keycode.L5, Key.F15 },
//{ Keycode.F16, Key.F16 },
//{ Keycode.F17, Key.F17 },
//{ Keycode.L8, Key.F18 },
//{ Keycode.L9, Key.F19 },
//{ Keycode.L10, Key.F20 },
//{ Keycode.R1, Key.F21 },
//{ Keycode.R2, Key.F22 },
//{ Keycode.F23, Key.F23 },
//{ Keycode.R4, Key.F24 },
// { Keycode.numpad, Key.NumLock }
{ Keycode.ScrollLock, Key.Scroll },
{ Keycode.ShiftLeft, Key.LeftShift },
//{ Keycode.?, Key.RightShift }
//{ Keycode.?, Key.LeftCtrl }
//{ Keycode.?, Key.RightCtrl }
//{ Keycode.?, Key.LeftAlt }
//{ Keycode.?, Key.RightAlt }
//{ Keycode.?, Key.BrowserBack }
//{ Keycode.?, Key.BrowserForward }
//{ Keycode.?, Key.BrowserRefresh }
//{ Keycode.?, Key.BrowserStop }
//{ Keycode.?, Key.BrowserSearch }
//{ Keycode.?, Key.BrowserFavorites }
//{ Keycode.?, Key.BrowserHome }
//{ Keycode.?, Key.VolumeMute }
//{ Keycode.?, Key.VolumeDown }
//{ Keycode.?, Key.VolumeUp }
//{ Keycode.?, Key.MediaNextTrack }
//{ Keycode.?, Key.MediaPreviousTrack }
//{ Keycode.?, Key.MediaStop }
//{ Keycode.?, Key.MediaPlayPause }
//{ Keycode.?, Key.LaunchMail }
//{ Keycode.?, Key.SelectMedia }
//{ Keycode.?, Key.LaunchApplication1 }
//{ Keycode.?, Key.LaunchApplication2 }
//{ Keycode.?, Key.OemSemicolon }
//{ Keycode.?, Key.OemPlus }
//{ Keycode.?, Key.OemComma }
//{ Keycode.?, Key.OemMinus }
//{ Keycode.?, Key.OemPeriod }
//{ Keycode.?, Key.Oem2 }
//{ Keycode.?, Key.OemTilde }
//{ Keycode.?, Key.AbntC1 }
//{ Keycode.?, Key.AbntC2 }
//{ Keycode.?, Key.Oem4 }
//{ Keycode.?, Key.OemPipe }
//{ Keycode.?, Key.OemCloseBrackets }
//{ Keycode.?, Key.Oem7 }
//{ Keycode.?, Key.Oem8 }
//{ Keycode.?, Key.Oem102 }
//{ Keycode.?, Key.ImeProcessed }
//{ Keycode.?, Key.System }
//{ Keycode.?, Key.OemAttn }
//{ Keycode.?, Key.OemFinish }
//{ Keycode.?, Key.DbeHiragana }
//{ Keycode.?, Key.OemAuto }
//{ Keycode.?, Key.DbeDbcsChar }
//{ Keycode.?, Key.OemBackTab }
//{ Keycode.?, Key.Attn }
//{ Keycode.?, Key.DbeEnterWordRegisterMode }
//{ Keycode.?, Key.DbeEnterImeConfigureMode }
//{ Keycode.?, Key.EraseEof }
//{ Keycode.?, Key.Play }
//{ Keycode.?, Key.Zoom }
//{ Keycode.?, Key.NoName }
//{ Keycode.?, Key.DbeEnterDialogConversionMode }
//{ Keycode.?, Key.OemClear }
//{ Keycode.?, Key.DeadCharProcessed }
};
internal static Key ConvertKey(Keycode key) {
Key result;
return KeyDic.TryGetValue(key, out result) ? result : Key.None;
}
}
}

12
src/Android/Perspex.Android/Platform/Input/AndroidMouseDevice.cs

@ -0,0 +1,12 @@
using Perspex.Input;
namespace Perspex.Android.Platform.Input
{
public class AndroidMouseDevice : MouseDevice
{
public AndroidMouseDevice()
{
}
}
}

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

@ -0,0 +1,50 @@
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;
}
}

145
src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -0,0 +1,145 @@
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Perspex.Android.Platform.Input;
using Perspex.Controls;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Platform;
using System;
using System.ComponentModel;
namespace Perspex.Android.Platform.Specific.Helpers
{
public class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : View, IWindowImpl, IAndroidView
{
private TView _view;
private IInputElement _lastFocusedElement;
public bool HandleEvents { get; set; }
public AndroidKeyboardEventsHelper(TView view)
{
this._view = view;
HandleEvents = true;
}
public bool? DispatchKeyEvent(KeyEvent e, out bool callBase)
{
if (!HandleEvents)
{
callBase = true;
return null;
}
return DispatchKeyEventInternal(e, out callBase);
}
private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase)
{
if (e.Action == KeyEventActions.Multiple)
{
callBase = true;
return null;
}
var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e));
_view.Input(rawKeyEvent);
if (e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
{
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
Convert.ToChar(e.UnicodeChar).ToString()
);
_view.Input(rawTextEvent);
}
if (e.Action == KeyEventActions.Up)
{
//nothing to do here more call base no need of more events
callBase = true;
return null;
}
callBase = false;
return false;
}
private static InputModifiers GetModifierKeys(KeyEvent e)
{
var rv = InputModifiers.None;
if (e.IsCtrlPressed) rv |= InputModifiers.Control;
if (e.IsShiftPressed) rv |= InputModifiers.Shift;
return rv;
}
private bool NeedsKeyboard(IInputElement element)
{
//may be some other elements
return element is TextBox;
}
private void TryShowHideKeyboard(IInputElement element, bool value)
{
var input = _view.Context.GetSystemService(Context.InputMethodService).JavaCast<InputMethodManager>();
if (value)
{
//show keyboard
//may be in the future different keyboards support e.g. normal, only digits etc.
//Android.Text.InputTypes
input.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
}
else
{
//hide keyboard
input.HideSoftInputFromWindow(_view.WindowToken, HideSoftInputFlags.None);
}
}
public void UpdateKeyboardState(IInputElement element)
{
var focusedElement = element;
bool oldValue = NeedsKeyboard(_lastFocusedElement);
bool newValue = NeedsKeyboard(focusedElement);
if (newValue != oldValue || newValue)
{
TryShowHideKeyboard(focusedElement, newValue);
}
_lastFocusedElement = element;
}
public void ActivateAutoShowKeybord()
{
var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged);
//just in case we've called more than once the method
kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged;
kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged;
}
private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement))
{
UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement);
}
}
public void Dispose()
{
HandleEvents = false;
}
}
}

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

@ -0,0 +1,155 @@
using Android.Graphics;
using Android.Views;
using Perspex.Android.Platform.CanvasPlatform;
using Perspex.Input;
using Perspex.Input.Raw;
using Perspex.Platform;
using System;
namespace Perspex.Android.Platform.Specific.Helpers
{
public class AndroidTouchEventsHelper<TView> : IDisposable where TView : View, IWindowImpl, IAndroidView
{
private TView _view;
public bool HandleEvents { get; set; }
public AndroidTouchEventsHelper(TView view, Func<IInputRoot> getInputRoot, Func<MotionEvent, Point> getPointfunc)
{
this._view = view;
HandleEvents = true;
_getPointFunc = getPointfunc;
_getInputRoot = getInputRoot;
}
private DateTime _lastTouchMoveEventTime = DateTime.Now;
private Point? _lastTouchMovePoint;
private Func<MotionEvent, Point> _getPointFunc;
private Func<IInputRoot> _getInputRoot;
private Point _point;
public bool? DispatchTouchEvent(MotionEvent e, out bool callBase)
{
if (!HandleEvents)
{
callBase = true;
return null;
}
RawMouseEventType? mouseEventType = null;
var eventTime = DateTime.Now;
//Basic touch support
switch (e.Action)
{
case MotionEventActions.Move:
//may be bot flood the evnt system with too many event especially on not so powerfull mobile devices
if ((eventTime - _lastTouchMoveEventTime).TotalMilliseconds > 10)
{
mouseEventType = RawMouseEventType.Move;
}
break;
case MotionEventActions.Down:
mouseEventType = RawMouseEventType.LeftButtonDown;
break;
case MotionEventActions.Up:
mouseEventType = RawMouseEventType.LeftButtonUp;
break;
}
if (mouseEventType != null)
{
//if point is in view otherwise it's possible perspex not to find the proper window to dispatch the event
_point = _getPointFunc(e);
double x = _view.GetX();
double y = _view.GetY();
double r = x + _view.Width;
double b = y + _view.Height;
if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
{
var inputRoot = _getInputRoot();
var mouseDevice = MouseDevice.Instance;
//in order the controls to work in a predictable way
//we need to generate mouse move before first mouse down event
//as this is the way buttons are working every time
//otherwise there is a problem sometimes
if (mouseEventType == RawMouseEventType.LeftButtonDown)
{
var me = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
RawMouseEventType.Move, _point, InputModifiers.None);
_view.Input(me);
}
var mouseEvent = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
mouseEventType.Value, _point, InputModifiers.LeftMouseButton);
_view.Input(mouseEvent);
if (e.Action == MotionEventActions.Move && mouseDevice.Captured == null)
{
if (_lastTouchMovePoint != null)
{
//raise mouse scroll event so the scrollers
//are moving with the cursor
double vectorX = _point.X - _lastTouchMovePoint.Value.X;
double vectorY = _point.Y - _lastTouchMovePoint.Value.Y;
//based on test correction of 0.02 is working perfect
double correction = 0.02;
var mouseWheelEvent = new RawMouseWheelEventArgs(
mouseDevice,
(uint)eventTime.Ticks,
inputRoot,
_point,
new Vector(vectorX * correction, vectorY * correction), InputModifiers.LeftMouseButton);
_view.Input(mouseWheelEvent);
}
_lastTouchMovePoint = _point;
_lastTouchMoveEventTime = eventTime;
}
else if (e.Action == MotionEventActions.Down)
{
_lastTouchMovePoint = _point;
}
else
{
_lastTouchMovePoint = null;
}
}
}
callBase = true;
//if return false events for move and up are not received!!!
return e.Action != MotionEventActions.Up;
}
private Paint _paint;
public void DrawLastMousePoint(Canvas canvas)
{
if (!AndroidPlatform.Instance.DrawDebugInfo) return;
if (_paint == null)
{
_paint = new Paint() { Color = 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

@ -0,0 +1,23 @@
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
}
}

12
src/Android/Perspex.Android/Platform/Specific/IAndroidActivity.cs

@ -0,0 +1,12 @@
using Android.App;
using Android.Views;
namespace Perspex.Android.Platform.Specific
{
public interface IAndroidActivity
{
Activity Activity { get; }
IAndroidView ContentView { get; set; }
}
}

16
src/Android/Perspex.Android/Platform/Specific/IAndroidView.cs

@ -0,0 +1,16 @@
using Android.Graphics;
using Android.Views;
using System;
using System.Collections.Generic;
namespace Perspex.Android.Platform.Specific
{
public interface IAndroidView
{
View View { get; }
}
}

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

@ -0,0 +1,54 @@
using Android.App;
using Android.OS;
using Android.Views;
using Android.Widget;
namespace Perspex.Android.Platform.Specific
{
public class PerspexActivity : Activity, IAndroidActivity
{
private IAndroidView _contentView;
public Activity Activity => this;
public IAndroidView ContentView
{
get
{
return this._contentView;
}
set
{
this._contentView = value;
var fl = new FrameLayout(this);
fl.AddView(this._contentView.View);
//this.SetContentView(value.View);
this.SetContentView(fl);
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
PerspexLocator.CurrentMutable.Bind<IAndroidActivity>().ToConstant(this);
RequestWindowFeature(WindowFeatures.NoTitle);
base.OnCreate(savedInstanceState);
}
public override void SetContentView(View view)
{
base.SetContentView(view);
TakeKeyEvents(true);
}
public override bool DispatchKeyEvent(KeyEvent e)
{
if (_contentView != null)
{
_contentView.View.DispatchKeyEvent(e);
}
return base.DispatchKeyEvent(e);
}
}
}

30
src/Android/Perspex.Android/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.Android")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Android")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

50
src/Android/Perspex.Android/Resources/AboutResources.txt

@ -0,0 +1,50 @@
Images, layout descriptions, binary blobs and string dictionaries can be included
in your application as resource files. Various Android APIs are designed to
operate on the resource IDs instead of dealing with images, strings or binary blobs
directly.
For example, a sample Android app that contains a user interface layout (main.xml),
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
would keep its resources in the "Resources" directory of the application:
Resources/
drawable-hdpi/
icon.png
drawable-ldpi/
icon.png
drawable-mdpi/
icon.png
layout/
main.xml
values/
strings.xml
In order to get the build system to recognize Android resources, set the build action to
"AndroidResource". The native Android APIs do not operate directly with filenames, but
instead operate on resource IDs. When you compile an Android application that uses resources,
the build system will package the resources for distribution and generate a class called
"Resource" that contains the tokens for each one of the resources included. For example,
for the above Resources layout, this is what the Resource class would expose:
public class Resource {
public class drawable {
public const int icon = 0x123;
}
public class layout {
public const int main = 0x456;
}
public class strings {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
string in the dictionary file values/strings.xml.

60
src/Android/Perspex.Android/Resources/Resource.Designer.cs

@ -0,0 +1,60 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("Perspex.Android.Resource", IsApplication=false)]
namespace Perspex.Android
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class String
{
// aapt resource value: 0x7f020001
public static int ApplicationName = 2130837505;
// aapt resource value: 0x7f020000
public static int Hello = 2130837504;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

6
src/Android/Perspex.Android/Resources/Values/Strings.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Hello">Hello World, Click Me!</string>
<string name="ApplicationName">$projectname$</string>
</resources>

16
src/Android/Perspex.Android/SystemDialogImpl.cs

@ -0,0 +1,16 @@
using System;
using System.Threading.Tasks;
using Perspex.Controls;
using Perspex.Controls.Platform;
using Perspex.Platform;
namespace Perspex.Android
{
internal class SystemDialogImpl : ISystemDialogImpl
{
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{
throw new NotImplementedException();
}
}
}

9
src/Android/Perspex.Android/packages.config

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-Linq" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-Main" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="monoandroid51" />
</packages>

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

@ -0,0 +1,372 @@
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;
}
}
}

19
src/Android/Perspex.AndroidTestApplication/Assets/AboutAssets.txt

@ -0,0 +1,19 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".
These files will be deployed with you package and will be accessible using Android's
AssetManager, like this:
public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
InputStream input = Assets.Open ("my_asset.txt");
}
}
Additionally, some Android functions will automatically load asset files:
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

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

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

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

@ -0,0 +1,67 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
using Perspex.Android;
using Perspex.Android.Platform.Specific;
using Perspex.Android.Platform.Specific.Helpers;
using Perspex.Controls;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Markup.Xaml.Data;
using Perspex.Platform;
using Perspex.Styling;
using ReactiveUI;
using System;
using System.Reactive.Linq;
using System.Windows.Input;
namespace Perspex.AndroidTestApplication
{
[Activity(Label = "Main",
MainLauncher = true,
Icon = "@drawable/icon",
LaunchMode = LaunchMode.SingleInstance/*,
ScreenOrientation = ScreenOrientation.Landscape*/)]
public class MainBaseActivity : PerspexActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//set some parameters to android platform
AndroidPlatform.Instance.DrawDebugInfo = true;
//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;
AndroidPlatform.Instance.OverrideAnimateFramesPerSecond = 16;
App app;
if (Perspex.Application.Current != null)
app = (App)Perspex.Application.Current;
else
app = new App();
var window = TestUI.TestUIBuilder.BuildTestUI();
//var window = BuildPlatforSetup();
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);
}
}
}

215
src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj

@ -0,0 +1,215 @@
<?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>{FF69B927-C545-49AE-8E16-3D14D621AA12}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.AndroidTestApplication</RootNamespace>
<AssemblyName>Perspex Test Application</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidApplication>true</AndroidApplication>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v4.4</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>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
<AndroidLinkSkip />
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi,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>
<AndroidLinkSkip />
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<Debugger>Xamarin</Debugger>
<AotAssemblies>False</AotAssemblies>
<EnableLLVM>False</EnableLLVM>
<AndroidEnableMultiDex>False</AndroidEnableMultiDex>
<EnableProguard>False</EnableProguard>
<DevInstrumentationEnabled>False</DevInstrumentationEnabled>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Serilog.1.5.9\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+MonoTouch10\Serilog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Splat.1.6.2\lib\monoandroid\Splat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Sprache, Version=2.0.0.47, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Sprache.2.0.0.47\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.IO" />
<Reference Include="System.Linq.Expressions" />
<Reference Include="System.ObjectModel" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
<Reference Include="System.Xml.Linq" />
<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>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Icon.png" />
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Android\Perspex.Android\Perspex.Android.csproj">
<Project>{7b92af71-6287-4693-9dcb-bd5b6e927e23}</Project>
<Name>Perspex.Android</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Markup\Perspex.Markup.Xaml\Perspex.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Perspex.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Animation\Perspex.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.ReactiveUI\Perspex.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Perspex.ReactiveUI</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Perspex.Themes.Default\Perspex.Themes.Default.csproj">
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Perspex.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.HtmlRenderer\Perspex.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Perspex.HtmlRenderer</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="$(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>

6
src/Android/Perspex.AndroidTestApplication/Properties/AndroidManifest.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Perspex.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk />
<application android:label="Perspex.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

30
src/Android/Perspex.AndroidTestApplication/Properties/AssemblyInfo.cs

@ -0,0 +1,30 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Perspex.AndroidTestApplication")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.AndroidTestApplication")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

50
src/Android/Perspex.AndroidTestApplication/Resources/AboutResources.txt

@ -0,0 +1,50 @@
Images, layout descriptions, binary blobs and string dictionaries can be included
in your application as resource files. Various Android APIs are designed to
operate on the resource IDs instead of dealing with images, strings or binary blobs
directly.
For example, a sample Android app that contains a user interface layout (main.xml),
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
would keep its resources in the "Resources" directory of the application:
Resources/
drawable-hdpi/
icon.png
drawable-ldpi/
icon.png
drawable-mdpi/
icon.png
layout/
main.xml
values/
strings.xml
In order to get the build system to recognize Android resources, set the build action to
"AndroidResource". The native Android APIs do not operate directly with filenames, but
instead operate on resource IDs. When you compile an Android application that uses resources,
the build system will package the resources for distribution and generate a class called
"Resource" that contains the tokens for each one of the resources included. For example,
for the above Resources layout, this is what the Resource class would expose:
public class Resource {
public class drawable {
public const int icon = 0x123;
}
public class layout {
public const int main = 0x456;
}
public class strings {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
string in the dictionary file values/strings.xml.

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

@ -0,0 +1,102 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("Perspex.AndroidTestApplication.Resource", IsApplication=true)]
namespace Perspex.AndroidTestApplication
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public static void UpdateIdValues()
{
global::Perspex.Android.Resource.String.ApplicationName = global::Perspex.AndroidTestApplication.Resource.String.ApplicationName;
global::Perspex.Android.Resource.String.Hello = global::Perspex.AndroidTestApplication.Resource.String.Hello;
global::Splat.Resource.String.library_name = global::Perspex.AndroidTestApplication.Resource.String.library_name;
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class Drawable
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
static Drawable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Drawable()
{
}
}
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: 0x7f040001
public const int Hello = 2130968577;
// aapt resource value: 0x7f040000
public const int library_name = 2130968576;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

BIN
src/Android/Perspex.AndroidTestApplication/Resources/drawable/Icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

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

@ -0,0 +1,9 @@
<?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>

6
src/Android/Perspex.AndroidTestApplication/Resources/values/Strings.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Hello">Hello World, Click Me!</string>
<string name="ApplicationName">Perspex.AndroidTestApplication</string>
</resources>

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

@ -0,0 +1,162 @@
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.

After

Width:  |  Height:  |  Size: 17 KiB

12
src/Android/Perspex.AndroidTestApplication/packages.config

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-Linq" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-Main" version="2.2.5" targetFramework="monoandroid51" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="monoandroid51" />
<package id="Serilog" version="1.5.9" targetFramework="monoandroid51" />
<package id="Splat" version="1.6.2" targetFramework="monoandroid51" />
<package id="Sprache" version="2.0.0.47" targetFramework="monoandroid51" />
</packages>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

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>v5.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

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>v5.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>

Loading…
Cancel
Save