59 changed files with 7153 additions and 2 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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);
|
|||
//}
|
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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"); |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Perspex.Input; |
|||
|
|||
namespace Perspex.Android.Platform.Input |
|||
{ |
|||
public class AndroidMouseDevice : MouseDevice |
|||
{ |
|||
public AndroidMouseDevice() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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")] |
|||
@ -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. |
|||
@ -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
|
|||
@ -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> |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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"); |
|||
@ -0,0 +1,4 @@ |
|||
<GettingStarted> |
|||
<LocalContent>GS\Android\CS\AndroidApp\GettingStarted.html</LocalContent> |
|||
<EmbeddedNavigation>false</EmbeddedNavigation> |
|||
</GettingStarted> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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")] |
|||
@ -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. |
|||
@ -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
|
|||
|
After Width: | Height: | Size: 4.0 KiB |
@ -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> |
|||
@ -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> |
|||
@ -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; } |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
|
After Width: | Height: | Size: 17 KiB |
@ -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> |
|||
|
After Width: | Height: | Size: 133 KiB |
Loading…
Reference in new issue