From 3de3dd42447d74a7199d6a33558d47136f2ede78 Mon Sep 17 00:00:00 2001 From: andrey Date: Fri, 13 Nov 2015 00:40:04 +0200 Subject: [PATCH] initial support for skia in android --- .../Perspex.Android/AndroidPlatform.cs | 54 ++++-- .../Perspex.Android/Perspex.Android.csproj | 8 +- .../AndroidTopLevelRenderer.cs | 0 .../Platform/SkiaPlatform/MainWindowImpl.cs | 37 ++++ .../Platform/SkiaPlatform/WindowImpl.cs | 171 ++++++++++++++++++ .../Helpers/AndroidTouchEventsHelper.cs | 14 +- .../MainActivity.cs | 31 ++-- .../Perspex.AndroidTestApplication.csproj | 5 + 8 files changed, 286 insertions(+), 34 deletions(-) rename src/Android/Perspex.Android/{CanvasRendering => Platform}/AndroidTopLevelRenderer.cs (100%) create mode 100644 src/Android/Perspex.Android/Platform/SkiaPlatform/MainWindowImpl.cs create mode 100644 src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs diff --git a/src/Android/Perspex.Android/AndroidPlatform.cs b/src/Android/Perspex.Android/AndroidPlatform.cs index 4781cecead..6e6b061d8c 100644 --- a/src/Android/Perspex.Android/AndroidPlatform.cs +++ b/src/Android/Perspex.Android/AndroidPlatform.cs @@ -1,7 +1,6 @@ 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; @@ -12,6 +11,7 @@ using Perspex.Input; using Perspex.Input.Platform; using Perspex.Platform; using Perspex.Shared.PlatformSupport; +using Perspex.Skia; using System; using System.Collections.Generic; using System.Reactive.Disposables; @@ -77,15 +77,24 @@ namespace Perspex.Android ////regular timer is working perfect //return new System.Threading.Timer(_ => tick(), null, ms, ms); - //System.Timers.Timer + //when interval is 0 normal System.Timers.Timer is not working + //so we ween other strategy for that if (interval.TotalMilliseconds == 0) { - //android ui thread - PerspexLocator.Current.GetService().Activity.RunOnUiThread(tick); - return Disposable.Empty; + if(DefaultViewDrawType == ViewDrawType.Skia) + { + //or start timer with low enough interval 10ms ??? + //with skia when animation active if we use RunOnUiThread UI is not handling the events!!! issue or not? + interval = TimeSpan.FromMilliseconds(10); + } + else + { + //android ui thread + PerspexLocator.Current.GetService().Activity.RunOnUiThread(tick); + return Disposable.Empty; + } } - - if (OverrideAnimateFramesPerSecond > 0) + else if (OverrideAnimateFramesPerSecond > 0) { if (_animationTick >= interval) { @@ -150,8 +159,8 @@ namespace Perspex.Android .Bind().ToTransient() .Bind().ToSingleton() .Bind().ToTransient() - ; - AndroidPlatformRender.Initialize(); + .Bind().ToConstant(new PlatformSettings()); + ; Instance.RegisterViewDrawType(); //set defaults for simple resources @@ -167,21 +176,32 @@ namespace Perspex.Android public void RegisterViewDrawType() { - if (Instance.DefaultViewDrawType == ViewDrawType.SurfaceViewCanvasOnDraw) + if (Instance.DefaultViewDrawType == ViewDrawType.Skia) { - PerspexLocator.CurrentMutable.Bind().ToSingleton(); + //do the default initialization for skia rendering + SkiaPlatform.Initialize(); + PerspexLocator.CurrentMutable.Bind().ToSingleton(); } else { - PerspexLocator.CurrentMutable.Bind().ToSingleton(); - } + AndroidPlatformRender.Initialize(); - PerspexLocator.CurrentMutable.Bind().ToTransient(); + if (Instance.DefaultViewDrawType == ViewDrawType.SurfaceViewCanvasOnDraw) + { + PerspexLocator.CurrentMutable.Bind().ToSingleton(); + } + else + { + PerspexLocator.CurrentMutable.Bind().ToSingleton(); + } + + PerspexLocator.CurrentMutable.Bind().ToTransient(); + } } public void RegisterViewPointUnits() { - PerspexLocator.CurrentMutable.Bind().ToSingleton(); + PerspexLocator.CurrentMutable.Bind().ToSingleton(); } public void InitializeAssetsLoader(Assembly assembly, string defaultResourcePrefix) @@ -212,9 +232,9 @@ namespace Perspex.Android /// SurfaceWindowImpl supports: /// SurfaceViewCanvasOnDraw /// - public ViewDrawType DefaultViewDrawType { get; set; } = ViewDrawType.SurfaceViewCanvasOnDraw; + public ViewDrawType DefaultViewDrawType { get; set; } = ViewDrawType.Skia; - public PointUnit DefaultPointUnit { get; set; } = PointUnit.DP; + public Platform.CanvasPlatform.PointUnit DefaultPointUnit { get; set; } = Platform.CanvasPlatform.PointUnit.DP; public int OverrideAnimateFramesPerSecond { get; set; } = -1; } diff --git a/src/Android/Perspex.Android/Perspex.Android.csproj b/src/Android/Perspex.Android/Perspex.Android.csproj index 4b39caf2d7..8b06bae59d 100644 --- a/src/Android/Perspex.Android/Perspex.Android.csproj +++ b/src/Android/Perspex.Android/Perspex.Android.csproj @@ -73,6 +73,8 @@ + + @@ -88,7 +90,7 @@ - + @@ -157,6 +159,10 @@ {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} Perspex.Styling + + {bd43f7c0-396b-4aa1-bad9-dfde54d51298} + Perspex.Skia.Android + diff --git a/src/Android/Perspex.Android/CanvasRendering/AndroidTopLevelRenderer.cs b/src/Android/Perspex.Android/Platform/AndroidTopLevelRenderer.cs similarity index 100% rename from src/Android/Perspex.Android/CanvasRendering/AndroidTopLevelRenderer.cs rename to src/Android/Perspex.Android/Platform/AndroidTopLevelRenderer.cs diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/MainWindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/MainWindowImpl.cs new file mode 100644 index 0000000000..330cd45d50 --- /dev/null +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/MainWindowImpl.cs @@ -0,0 +1,37 @@ +using Android.Views; +using Perspex.Android.Platform.Specific; +using Perspex.Input; +using Perspex.Platform; + +namespace Perspex.Android.Platform.SkiaPlatform +{ + public class MainWindowImpl : + WindowImpl + , IWindowImpl + { + public MainWindowImpl() + { + } + + protected override void Init() + { + base.Init(); + + HandleEvents = true; + _keyboardHelper.ActivateAutoShowKeybord(); + } + + void ITopLevelImpl.Show() + { + (Parent as ViewGroup)?.RemoveAllViews(); + PerspexLocator.Current.GetService().ContentView = this; + //this.Visibility = ViewStates.Visible; + } + + void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) + { + base.SetInputRoot(inputRoot); + _keyboardHelper.UpdateKeyboardState(inputRoot); + } + } +} \ No newline at end of file diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs new file mode 100644 index 0000000000..4e1233e909 --- /dev/null +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -0,0 +1,171 @@ +using Android.App; +using Android.Content; +using Android.Util; +using Android.Views; +using Perspex.Android.Platform.Specific; +using Perspex.Android.Platform.Specific.Helpers; +using Perspex.Input; +using Perspex.Input.Raw; +using Perspex.Platform; +using Perspex.Skia.Android; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Perspex.Android.Platform.SkiaPlatform +{ + public class WindowImpl : SkiaView, IAndroidView, IWindowImpl + { + protected AndroidKeyboardEventsHelper _keyboardHelper; + + private AndroidTouchEventsHelper _touchHelper; + + public WindowImpl(Context context) : base((Activity)context) + { + _keyboardHelper = new AndroidKeyboardEventsHelper(this); + _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetPerspexPointFromEvent(p)); + + ClientSize = MaxClientSize; + Resized = size => Invalidate(new Rect(size)); + Init(); + } + + public WindowImpl() : this(PerspexLocator.Current.GetService().Activity) + { + } + + protected virtual void Init() + { + } + + private bool _handleEvents; + + public bool HandleEvents + { + get { return _handleEvents; } + set + { + _handleEvents = value; + _keyboardHelper.HandleEvents = _handleEvents; + } + } + + public virtual Point GetPerspexPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY()); + + public IInputRoot InputRoot { get; private set; } + + public Size ClientSize { get; set; } + + public Action Closed { get; set; } + + public Action Deactivated { get; set; } + + public Action Input { get; set; } + + public Size MaxClientSize => new Size(Resources.DisplayMetrics.WidthPixels, Resources.DisplayMetrics.HeightPixels); + + public Action Paint { get; set; } + + public Action Resized { get; set; } + + public View View => this; + + Action ITopLevelImpl.Activated { get; set; } + + IPlatformHandle ITopLevelImpl.Handle => this; + + public void Activate() + { + } + + public void Hide() + { + this.Visibility = ViewStates.Invisible; + } + + public void Invalidate(Rect rect) + { + //not working if invalidate is called before Draw app is crashing !!!???? + //TODO: investigate and make better workaround also this solution is ok so far + //if (this.Holder?.Surface != null) + if(Holder?.Surface?.IsValid == true) base.Invalidate(); + // if (_drawInitialized) base.Invalidate(); + } + + public Point PointToScreen(Point point) + { + return point; + } + + public void SetCursor(IPlatformHandle cursor) + { + //still not implemented + } + + public void SetInputRoot(IInputRoot inputRoot) + { + InputRoot = inputRoot; + } + + public void SetTitle(string title) + { + } + + public void Show() + { + this.Visibility = ViewStates.Visible; + } + + public IDisposable ShowDialog() + { + throw new NotImplementedException(); + } + + 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; + } + + private Queue _avgDrawQueue = new Queue(); + + protected override void Draw() + { + _drawInitialized = true; + + DateTime begin = DateTime.Now; + + Paint?.Invoke(new Rect(new Point(0, 0), ClientSize)); + + TimeSpan duration = DateTime.Now - begin; + + if (AndroidPlatform.Instance.DrawDebugInfo) + { + //draw some basic debug info about rendering + //we can't create drawing context so push some info to std out + //double ms = duration.TotalMilliseconds; + //_avgDrawQueue.Enqueue(ms); + //if (_avgDrawQueue.Count > 50) _avgDrawQueue.Dequeue(); + + //double msAvg = _avgDrawQueue.Average(); + + //string text = $"DrawType={AndroidPlatform.Instance.DefaultViewDrawType}, OnDraw={ms.ToString("0.00")}, aOnDraw={msAvg.ToString("0.00")}"; + //Log.Debug("render", text); + } + } + + private bool _drawInitialized = false; + } +} \ No newline at end of file diff --git a/src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index b504415e18..96a7c2cadb 100644 --- a/src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/src/Android/Perspex.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs @@ -3,6 +3,7 @@ using Android.Views; using Perspex.Android.Platform.CanvasPlatform; using Perspex.Input; using Perspex.Input.Raw; +using Perspex.Media; using Perspex.Platform; using System; @@ -127,24 +128,31 @@ namespace Perspex.Android.Platform.Specific.Helpers private Paint _paint; + public void DrawLastMousePoint(DrawingContext context) + { + if (!AndroidPlatform.Instance.DrawDebugInfo) return; + + double w = AndroidPlatform.Instance.DoubleClickSize.Width; + context.FillRectangle(Brushes.Red, new Rect(_point.X - w / 2, _point.Y - w / 2, w, w)); + } + public void DrawLastMousePoint(Canvas canvas) { if (!AndroidPlatform.Instance.DrawDebugInfo) return; if (_paint == null) { - _paint = new Paint() { Color = Color.Red, }; + _paint = new Paint() { Color = global::Android.Graphics.Color.Red, }; _paint.SetStyle(Paint.Style.Fill); } - if(AndroidPlatform.Instance.DefaultViewDrawType != ViewDrawType.Skia) + 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() diff --git a/src/Android/Perspex.AndroidTestApplication/MainActivity.cs b/src/Android/Perspex.AndroidTestApplication/MainActivity.cs index 994b77dc32..9d98b23cab 100644 --- a/src/Android/Perspex.AndroidTestApplication/MainActivity.cs +++ b/src/Android/Perspex.AndroidTestApplication/MainActivity.cs @@ -4,17 +4,8 @@ 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.Controls.Platform; using Perspex.Platform; -using Perspex.Styling; -using ReactiveUI; -using System; -using System.Reactive.Linq; -using System.Windows.Input; namespace Perspex.AndroidTestApplication { @@ -31,12 +22,16 @@ namespace Perspex.AndroidTestApplication //set some parameters to android platform AndroidPlatform.Instance.DrawDebugInfo = true; + //skia is the default rendering method on android so no need to set it + AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.Skia; //AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.BitmapOnPreDraw; - AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.CanvasOnDraw; + //AndroidPlatform.Instance.DefaultViewDrawType = ViewDrawType.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.DP; //AndroidPlatform.Instance.DefaultPointUnit = Android.Platform.CanvasPlatform.PointUnit.Pixel; + + //60 fps animation are causing user interface in animations to stop responding AndroidPlatform.Instance.OverrideAnimateFramesPerSecond = 16; App app; @@ -44,13 +39,23 @@ namespace Perspex.AndroidTestApplication app = (App)Perspex.Application.Current; else app = new App(); - + + if (AndroidPlatform.Instance.DefaultPointUnit == Android.Platform.CanvasPlatform.PointUnit.DP && + AndroidPlatform.Instance.DefaultViewDrawType == ViewDrawType.Skia) + { + double scale = Resources.DisplayMetrics.ScaledDensity; + //make it DiP in skia + PerspexLocator.Current.GetService().LayoutScalingFactor = scale; + PerspexLocator.Current.GetService().RenderScalingFactor = scale; + } + var window = TestUI.TestUIBuilder.BuildTestUI(); //var window = BuildPlatforSetup(); window.Show(); app.Run(window); } + public static void StartAppFromSetup() { PerspexLocator.Current.GetService().Dispose(); diff --git a/src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj b/src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj index 89fd0ed151..ecdb79d773 100644 --- a/src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj +++ b/src/Android/Perspex.AndroidTestApplication/Perspex.AndroidTestApplication.csproj @@ -79,6 +79,7 @@ True + @@ -193,6 +194,10 @@ {5fb2b005-0a7f-4dad-add4-3ed01444e63d} Perspex.HtmlRenderer + + {bd43f7c0-396b-4aa1-bad9-dfde54d51298} + Perspex.Skia.Android +