From 8f442b7578fc8f13619911e9173a2e31a0ef1088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 18 Jul 2025 10:25:43 -0600 Subject: [PATCH 01/94] improve log and control over the drawing engine --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 2ce92c6af..e32a211bb 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -36,13 +36,15 @@ public class CoreMotionCanvas : IDisposable { private static readonly Stopwatch s_clock = new(); internal HashSet _paintTasks = []; - private object _sync = new(); - private int _frames = 0; private Stopwatch? _fspSw; - private double _lastKnowFps = 0; + private double _totalDrawTime = 0; + private double _lastDrawTime = 0; private double _totalFrames = 0; private double _totalSeconds = 0; + private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; + private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); + private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; static CoreMotionCanvas() { @@ -94,13 +96,15 @@ public static long ElapsedMilliseconds /// /// The synchronize. /// - public object Sync { get => _sync; internal set => _sync = value ?? new object(); } + public object Sync { get; internal set => field = value ?? new object(); } = new(); /// /// Gets the animatables collection. /// public HashSet Trackers { get; } = []; + internal TimeSpan _nextFrameDelay = s_baseFrameDelay; + /// /// Draws the frame. /// @@ -117,6 +121,7 @@ public void DrawFrame(TDrawingContext context) #endif var showFps = LiveCharts.ShowFPS; + var drawStartTime = s_clock.ElapsedTicks; lock (Sync) { @@ -177,11 +182,12 @@ public void DrawFrame(TDrawingContext context) if (showFps) { - MeasureFPS(); + MeasureFPS(drawStartTime); if (_totalSeconds > 0) context.LogOnCanvas( - $"[fps] last {_lastKnowFps:N2}, average {_totalFrames / _totalSeconds:N2}"); + $"FSP [{_totalFrames / _totalSeconds:N2}] " + + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ]"); } IsValid = isValid; @@ -199,6 +205,15 @@ public void DrawFrame(TDrawingContext context) _fspSw = null; } } + + var timeInDrawOperation = s_clock.ElapsedTicks - drawStartTime; + var delay = s_baseFrameDelay.Ticks - timeInDrawOperation; + + var frameDelay = delay <= s_jitterThreshold + ? s_baseFrameDelay + : new TimeSpan(delay); + + _nextFrameDelay = frameDelay; } /// @@ -299,8 +314,13 @@ public void Dispose() IsValid = true; } - private void MeasureFPS() + private void MeasureFPS(long drawStartTime) { + if (s_clock.ElapsedMilliseconds < 3000) + return; // we only start measuring after 3 seconds, to improve accuracy. + + _totalDrawTime += (s_clock.ElapsedTicks - drawStartTime) / s_ticksPerMillisecond; + if (_fspSw is null) { _fspSw = new(); @@ -313,11 +333,14 @@ private void MeasureFPS() if (_frames % logEach == 0) { var elapsedSeconds = _fspSw.ElapsedMilliseconds / 1000d; - _lastKnowFps = logEach / elapsedSeconds; _totalFrames += logEach; _totalSeconds += elapsedSeconds; + // it not exactly the last frame time, is the 20th frame time + // so we can actually read the time in the log. + _lastDrawTime = (s_clock.ElapsedTicks - drawStartTime) / s_ticksPerMillisecond; + _fspSw.Restart(); } } From 907519a032619f55054b2c93a96bb059700eaf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 19 Jul 2025 07:25:29 -0600 Subject: [PATCH 02/94] move render loop to core --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index e32a211bb..33732b914 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using LiveChartsCore.Drawing; using LiveChartsCore.Painting; @@ -42,6 +43,8 @@ public class CoreMotionCanvas : IDisposable private double _lastDrawTime = 0; private double _totalFrames = 0; private double _totalSeconds = 0; + private bool _isDrawingLoopRunning = false; + private TimeSpan _nextFrameDelay = s_baseFrameDelay; private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; @@ -103,8 +106,6 @@ public static long ElapsedMilliseconds /// public HashSet Trackers { get; } = []; - internal TimeSpan _nextFrameDelay = s_baseFrameDelay; - /// /// Draws the frame. /// @@ -186,7 +187,7 @@ public void DrawFrame(TDrawingContext context) if (_totalSeconds > 0) context.LogOnCanvas( - $"FSP [{_totalFrames / _totalSeconds:N2}] " + + $"FSP [{_totalFrames / _totalSeconds:N2}]" + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ]"); } @@ -302,6 +303,25 @@ public int CountGeometries() return count; } + /// + /// Runs the drawing loop asynchronously, a custom alternative when the + /// target platform does not support CompositionTarget or equivalent. + /// + /// An action that invalidates the control. + public async void RunDrawingLoop(Action invalidator) + { + if (_isDrawingLoopRunning) return; + _isDrawingLoopRunning = true; + + while (!IsValid) + { + invalidator(); + await Task.Delay(_nextFrameDelay); + } + + _isDrawingLoopRunning = false; + } + /// /// Releases the resources. /// From 089fa40de87a4a42130d64a6878f67be19d41318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:25:34 -0600 Subject: [PATCH 03/94] add livecharts render settings --- .../Kernel/LiveChartsSettings.cs | 51 ++++++++++++++++--- src/LiveChartsCore/LiveCharts.cs | 48 ++++++++++------- .../ThemesExtensions.cs | 2 +- 3 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs index 660e2c2c0..b61ee6c87 100644 --- a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs +++ b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Ignore Spelling: Tooltip - using System; using System.Collections.Generic; using LiveChartsCore.Drawing; @@ -386,7 +384,7 @@ public LiveChartsSettings WithTooltipTextSize(double size) /// Removes a map from the settings. /// /// The type of the model. - /// + /// The current settings. public LiveChartsSettings RemoveMap() { _ = _mappers.Remove(typeof(TModel)); @@ -397,7 +395,7 @@ public LiveChartsSettings RemoveMap() /// Adds the default styles. /// /// The builder. - /// + /// The current settings. public LiveChartsSettings HasTheme(Action builder) { Theme t; @@ -411,7 +409,6 @@ public LiveChartsSettings HasTheme(Action builder) /// /// Gets the styles builder. /// - /// public Theme GetTheme() => (Theme?)_theme ?? throw new Exception("A theme is required."); @@ -429,10 +426,10 @@ public Theme GetTheme() /// /// Enables LiveCharts to be able to plot short, int, long, float, double, decimal, short?, int?, long?, float?, double?, decimal?. /// - /// + /// The current settings. public LiveChartsSettings AddDefaultMappers() { - LiveCharts.HasDefaultMappers = true; + LiveCharts.s_hasDefaultMappers = true; return HasMap((model, index) => new(index, model)) @@ -448,4 +445,44 @@ public LiveChartsSettings AddDefaultMappers() .HasMap((model, index) => new(index, model!.Value)) .HasMap((model, index) => new(index, (double)model!.Value)); } + + /// + /// Indicates whether hardware acceleration is used to render the charts, this will only work if + /// the current platform and device supports it. See also. . + /// + /// + /// Indicates whether hardware acceleration is used, this will only work + /// if the platform and device support it, default is true. This is ignored in Avalonia, in avalonia the + /// frame rate and rendering cadence is determined by the Avalonia rendering loop. + /// + /// + /// Indicates whether the rendering cadence should be aligned with the display refresh rate, + /// gpu acceleration is required for this to work. This is ignored in Avalonia, in avalonia the frame rate + /// and rendering cadence is determined by the Avalonia rendering loop. + /// + /// + /// The target frames per second for the rendering engine, this property is ignored when + /// is true and GPU acceleration is enabled, + /// This is ignored in Avalonia, in avalonia the frame rate and rendering cadence is determined by the + /// Avalonia rendering loop. + /// + /// + /// When true, The chart will also draw the frames per second in the top left corner of the chart. + /// + /// The current settings. + public LiveChartsSettings RenderingSettings( + bool useHardwareAcceleration, + bool tryUseVSync, + double targetFps = 60, + bool showFps = false) + { + LiveCharts.s_hasDefaultHardwareAcceleration = true; + + LiveCharts.UseGPU = useHardwareAcceleration; + LiveCharts.TryUseVSync = tryUseVSync; + LiveCharts.TargetFps = targetFps; + LiveCharts.ShowFPS = showFps; + + return this; + } } diff --git a/src/LiveChartsCore/LiveCharts.cs b/src/LiveChartsCore/LiveCharts.cs index 3e18d02ac..de09c28f0 100644 --- a/src/LiveChartsCore/LiveCharts.cs +++ b/src/LiveChartsCore/LiveCharts.cs @@ -30,8 +30,12 @@ namespace LiveChartsCore; /// public static class LiveCharts { - private static bool s_useGPU = false; + private static bool s_useGPU = true; private static bool s_gpuSetByUser = false; + internal static bool s_hasBackend = false; + internal static bool s_hasDefaultTheme = false; + internal static bool s_hasDefaultMappers = false; + internal static bool s_hasDefaultHardwareAcceleration = false; /// /// A constant that indicates that the tool tip should not add the current label. @@ -57,32 +61,40 @@ public static class LiveCharts /// /// Gets or sets the maximum fps requested. /// - public static double MaxFps { get; set; } = 65; + [Obsolete($"Renamed to {nameof(TargetFps)}")] + public static double MaxFps { get => TargetFps; set => TargetFps = value; } /// - /// Gets or sets a value indicating whether LiveCharts should use a hardware graphics API - /// to render the charts. + /// Gets or sets the target frames per second for the rendering engine, + /// this property is ignored when is true and + /// GPU acceleration is enabled, default is 60 fps. /// - public static bool UseGPU - { - get => s_useGPU; - set { s_useGPU = value; s_gpuSetByUser = true; } - } + public static double TargetFps { get; set; } = 60; /// - /// Gets a value indicating whether LiveCharts has a backend registered. + /// Attempts to align rendering cadence with display refresh rate (VSync) when supported. + /// Requires GPU acceleration. May be ignored in software-mode or virtual environments. + /// In WPF and WinUI the rendering cadence is regulated by the CompositionTarget.Rendering event, + /// which dispatches frame updates synchronized with the display refresh cycle. + /// In Avalonia, this value is ignored as the chart is rendered based on Avalonia's rendering loop. /// - public static bool HasBackend { get; internal set; } = false; + public static bool TryUseVSync { get; set; } = true; /// - /// Gets a value indicating whether LiveCharts has a theme registered. + /// Gets or sets a value indicating whether LiveCharts should use the GPU for rendering. + /// When set to true, the library will attempt to use GPU acceleration. + /// This has no effect on Avalonia since Avalonia determines the rendering backend. + /// When GPU rendering is not available, it will fallback to software rendering. /// - public static bool HasDefaultTheme { get; set; } = false; - - /// - /// Gets a value indicating whether LiveCharts has the default mappers registered. - /// - public static bool HasDefaultMappers { get; set; } = false; + public static bool UseGPU + { + get => s_useGPU; + set + { + s_useGPU = value; + s_gpuSetByUser = true; + } + } /// /// Gets the current settings. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs index 20c65c9c7..2f6e491f0 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs @@ -53,7 +53,7 @@ public static LiveChartsSettings AddDefaultTheme( Action? themeSettings = null, LvcThemeKind requestedTheme = LvcThemeKind.Unknown) { - LiveCharts.HasDefaultTheme = true; + LiveCharts.s_hasDefaultTheme = true; return settings .HasTheme(theme => From 541dfc64f718b3b3c5fdb209ef51834a27595f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:26:48 -0600 Subject: [PATCH 04/94] IFrameTicker and IRenderMode concepts Abstractions that helps to use the native VSync of each target OS --- src/LiveChartsCore/AssemblyInfo.cs | 1 + src/LiveChartsCore/Motion/AsyncLoopTicker.cs | 65 ++++++++++++++ .../Motion/CanvasRenderSettings.cs | 84 +++++++++++++++++++ src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 36 +++----- src/LiveChartsCore/Motion/IFrameTicker.cs | 30 +++++++ src/LiveChartsCore/Motion/IRenderMode.cs | 34 ++++++++ 6 files changed, 226 insertions(+), 24 deletions(-) create mode 100644 src/LiveChartsCore/Motion/AsyncLoopTicker.cs create mode 100644 src/LiveChartsCore/Motion/CanvasRenderSettings.cs create mode 100644 src/LiveChartsCore/Motion/IFrameTicker.cs create mode 100644 src/LiveChartsCore/Motion/IRenderMode.cs diff --git a/src/LiveChartsCore/AssemblyInfo.cs b/src/LiveChartsCore/AssemblyInfo.cs index 25b92af29..efeae6990 100644 --- a/src/LiveChartsCore/AssemblyInfo.cs +++ b/src/LiveChartsCore/AssemblyInfo.cs @@ -33,6 +33,7 @@ #else [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView")] +[assembly: InternalsVisibleTo("LiveChartsCore.Behaviours")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WinForms")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WPF")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.Avalonia")] diff --git a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs new file mode 100644 index 000000000..723d50447 --- /dev/null +++ b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs @@ -0,0 +1,65 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Threading.Tasks; + +namespace LiveChartsCore.Motion; + +internal class AsyncLoopTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + private bool _isDrawingLoopRunning = false; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + + _canvas.Invalidated += OnCoreInvalidated; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _ = RunDrawingLoop(); + + private async Task RunDrawingLoop() + { + if (_isDrawingLoopRunning) return; + _isDrawingLoopRunning = true; + + while (!_canvas.IsValid) + { + _renderMode.InvalidateRenderer(); + await Task.Delay(_canvas._nextFrameDelay); + } + + _isDrawingLoopRunning = false; + } + + public void DisposeTicker() + { + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + } +} diff --git a/src/LiveChartsCore/Motion/CanvasRenderSettings.cs b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs new file mode 100644 index 000000000..214c4e641 --- /dev/null +++ b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs @@ -0,0 +1,84 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Motion; + +internal class CanvasRenderSettings + where TCPURenderMode : IRenderMode, new() + where TGPURenderMode : IRenderMode, new() + where TVSyncTicker : IFrameTicker, new() +{ + private static bool? s_canUseGPU; + + public CanvasRenderSettings() + { + RenderMode = LiveCharts.UseGPU && CanUseGPU() + ? new TGPURenderMode() + : new TCPURenderMode(); + + Ticker = LiveCharts.TryUseVSync + ? new TVSyncTicker() + : new AsyncLoopTicker(); + } + + public IRenderMode RenderMode { get; } + + public IFrameTicker Ticker { get; } + + public void Initialize(CoreMotionCanvas canvas) + { + RenderMode.InitializeRenderMode(canvas); + Ticker.InitializeTicker(canvas, RenderMode); + RenderMode.FrameRequest += canvas.DrawFrame; + } + + public void Dispose(CoreMotionCanvas canvas) + { + RenderMode.DisposeRenderMode(); + Ticker.DisposeTicker(); + RenderMode.FrameRequest -= canvas.DrawFrame; + canvas.Dispose(); + } + + internal static bool CanUseGPU() + { + if (s_canUseGPU.HasValue) + return s_canUseGPU.Value; + + try + { + var renderer = new TGPURenderMode(); + renderer.DisposeRenderMode(); + + s_canUseGPU = true; + + return true; + } + catch + { + s_canUseGPU = false; + LiveCharts.UseGPU = false; + + return false; + } + } +} diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 33732b914..439f13ed3 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -24,7 +24,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using LiveChartsCore.Drawing; using LiveChartsCore.Painting; @@ -43,8 +42,7 @@ public class CoreMotionCanvas : IDisposable private double _lastDrawTime = 0; private double _totalFrames = 0; private double _totalSeconds = 0; - private bool _isDrawingLoopRunning = false; - private TimeSpan _nextFrameDelay = s_baseFrameDelay; + internal TimeSpan _nextFrameDelay = s_baseFrameDelay; private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; @@ -54,6 +52,8 @@ static CoreMotionCanvas() s_clock.Start(); } + internal delegate void FrameRequestHandler(DrawingContext context); + /// /// Gets the clock elapsed time in milliseconds. /// @@ -187,8 +187,10 @@ public void DrawFrame(TDrawingContext context) if (_totalSeconds > 0) context.LogOnCanvas( - $"FSP [{_totalFrames / _totalSeconds:N2}]" + - $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ]"); + $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + + $"GPU [ {LiveCharts.UseGPU} ] " + + $"VSync [ {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]"); } IsValid = isValid; @@ -202,8 +204,13 @@ public void DrawFrame(TDrawingContext context) if (showFps) { + // restart the count when the canvas is valid. _frames = 0; _fspSw = null; + _totalDrawTime = 0; + _lastDrawTime = 0; + _totalFrames = 0; + _totalSeconds = 0; } } @@ -303,25 +310,6 @@ public int CountGeometries() return count; } - /// - /// Runs the drawing loop asynchronously, a custom alternative when the - /// target platform does not support CompositionTarget or equivalent. - /// - /// An action that invalidates the control. - public async void RunDrawingLoop(Action invalidator) - { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - while (!IsValid) - { - invalidator(); - await Task.Delay(_nextFrameDelay); - } - - _isDrawingLoopRunning = false; - } - /// /// Releases the resources. /// diff --git a/src/LiveChartsCore/Motion/IFrameTicker.cs b/src/LiveChartsCore/Motion/IFrameTicker.cs new file mode 100644 index 000000000..4fedfca6d --- /dev/null +++ b/src/LiveChartsCore/Motion/IFrameTicker.cs @@ -0,0 +1,30 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Motion; + +internal interface IFrameTicker +{ + void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode); + + void DisposeTicker(); +} diff --git a/src/LiveChartsCore/Motion/IRenderMode.cs b/src/LiveChartsCore/Motion/IRenderMode.cs new file mode 100644 index 000000000..bfd907103 --- /dev/null +++ b/src/LiveChartsCore/Motion/IRenderMode.cs @@ -0,0 +1,34 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Motion; + +internal interface IRenderMode +{ + event CoreMotionCanvas.FrameRequestHandler FrameRequest; + + void InitializeRenderMode(CoreMotionCanvas canvas); + + void InvalidateRenderer(); + + void DisposeRenderMode(); +} From 425f50f6980c48de5fb227ccbd3a425facafaf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:28:58 -0600 Subject: [PATCH 05/94] add native vsyncers --- .../NativeTicker.Android.cs | 86 +++++++++++++++++++ .../NativeTicker.Mac.cs | 68 +++++++++++++++ .../NativeTicker.Windows.cs | 63 ++++++++++++++ .../NativeTicker._shared.cs | 36 ++++++++ 4 files changed, 253 insertions(+) create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker.Android.cs create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker._shared.cs diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Android.cs b/src/LiveChartsCore.Behaviours/NativeTicker.Android.cs new file mode 100644 index 000000000..17a27d900 --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker.Android.cs @@ -0,0 +1,86 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if ANDROID + +using System; +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + private VSyncTicker _vsyncTicker = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + _vsyncTicker = new VSyncTicker(OnFrameTick); + _vsyncTicker.Start(); + + _canvas.Invalidated += OnCoreInvalidated; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnFrameTick() + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + _vsyncTicker.Stop(); + _vsyncTicker = null!; + } + + private class VSyncTicker(Action onFrameTick) + : Java.Lang.Object, Android.Views.Choreographer.IFrameCallback + { + private readonly Android.Views.Choreographer _chor = Android.Views.Choreographer.Instance!; + + public void Start() => + _chor.PostFrameCallback(this); + + // frameTimeNanos: + // the absolute timestamp (in nanoseconds) that the system’s choreographer assigns to this frame. + public void DoFrame(long frameTimeNanos) + { + onFrameTick(); + _chor.PostFrameCallback(this); + } + + public void Stop() => + _chor.RemoveFrameCallback(this); + } +} + +#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs b/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs new file mode 100644 index 000000000..4169c6bac --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs @@ -0,0 +1,68 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if IOS || MACCATALYST + +using CoreAnimation; +using Foundation; +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + private CADisplayLink _displayLink = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + _displayLink = CADisplayLink.Create(OnFrameTick); + _displayLink.AddToRunLoop(NSRunLoop.Main, NSRunLoopMode.Common); + + _canvas.Invalidated += OnCoreInvalidated; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnFrameTick() + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + _displayLink.Invalidate(); + _displayLink.Dispose(); + _displayLink = null!; + } +} + +#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs b/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs new file mode 100644 index 000000000..b155876e2 --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs @@ -0,0 +1,63 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if WINDOWS + +using LiveChartsCore.Motion; +using Microsoft.UI.Xaml.Media; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + + _canvas.Invalidated += OnCoreInvalidated; + CompositionTarget.Rendering += OnCompositonTargetRendering; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnCompositonTargetRendering(object? sender, object e) + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + CompositionTarget.Rendering -= OnCompositonTargetRendering; + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + } +} + +#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs b/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs new file mode 100644 index 000000000..c07a24c8e --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs @@ -0,0 +1,36 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if !WINDOWS && !ANDROID && !IOS && !MACCATALYST +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + void IFrameTicker.InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => + throw new System.NotImplementedException(); + + void IFrameTicker.DisposeTicker() => + throw new System.NotImplementedException(); +} +#endif From 14c2b8a78a2d97d1d8b6928027f62f779b7a621c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:30:07 -0600 Subject: [PATCH 06/94] update wpf to latests skiasharp version --- .../LiveChartsCore.SkiaSharpView.WPF.csproj | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj index 1bd57170f..8dd70d50f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj @@ -43,35 +43,8 @@ - - - - - - - + + From 6bc006318d05ec33792831cb59f738a7b7b04cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:30:50 -0600 Subject: [PATCH 07/94] use compositionTarget.renderion in wpf --- .../MotionCanvas.cs | 127 +++--------------- .../Rendering/CPURenderMode.cs | 75 +++++++++++ .../Rendering/CompositionTargetTicker.cs | 60 +++++++++ .../Rendering/GPURenderMode.cs | 84 ++++++++++++ 4 files changed, 234 insertions(+), 112 deletions(-) create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs index 0ce09d070..156d38e91 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs @@ -20,17 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using LiveChartsCore.Motion; -using LiveChartsCore.SkiaSharpView.Drawing; -using SkiaSharp; -using SkiaSharp.Views.Desktop; -using SkiaSharp.Views.WPF; - +using LiveChartsCore.SkiaSharpView.WPF.Rendering; namespace LiveChartsCore.SkiaSharpView.WPF; /// @@ -39,123 +32,33 @@ namespace LiveChartsCore.SkiaSharpView.WPF; /// public class MotionCanvas : UserControl { - private SKElement? _skiaElement; -#if NET6_0_OR_GREATER - // workaround #250115 - private SKGLElement? _skiaGlElement; -#endif - private bool _isDrawingLoopRunning = false; + private readonly CanvasRenderSettings _settings; /// /// Initializes a new instance of the class. /// public MotionCanvas() { + _settings = new(); + + Content = _settings.RenderMode; + Loaded += OnLoaded; Unloaded += OnUnloaded; } - /// - /// Gets the canvas core. - /// - /// - /// The canvas core. - /// + /// public CoreMotionCanvas CanvasCore { get; } = new(); - internal void AddLogicalChild(DependencyObject child) => base.AddLogicalChild(child); - internal void RemoveLogicalChild(DependencyObject child) => base.RemoveLogicalChild(child); - - /// - protected virtual void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) - { - var density = GetPixelDensity(); - args.Surface.Canvas.Scale(density.dpix, density.dpiy); - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); - } - - /// - protected virtual void OnPaintGlSurface(object? sender, SKPaintGLSurfaceEventArgs args) - { - var density = GetPixelDensity(); - args.Surface.Canvas.Scale(density.dpix, density.dpiy); - - var c = (((Control)Parent).Background is not SolidColorBrush bg) - ? Colors.White - : bg.Color; - - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas) - { - Background = new SKColor(c.R, c.G, c.B) - }); - } - - private void InitializeElement() - { - if (LiveCharts.UseGPU) - { -#if NET6_0_OR_GREATER - // workaround #250115 - Content = _skiaGlElement = new SKGLElement(); - _skiaGlElement.PaintSurface += OnPaintGlSurface; -#else - throw new PlatformNotSupportedException( - "GPU rendering is only supported in .NET 6.0 or greater, " + - "because https://github.com/mono/SkiaSharp/issues/3111 needs to be fixed."); -#endif - } - else - { - Content = _skiaElement = new SKElement(); - _skiaElement.PaintSurface += OnPaintSurface; - } - } - - private ResolutionHelper GetPixelDensity() - { - var presentationSource = PresentationSource.FromVisual(this); - if (presentationSource is null) return new(1f, 1f); - var compositionTarget = presentationSource.CompositionTarget; - if (compositionTarget is null) return new(1f, 1f); - - var matrix = compositionTarget.TransformToDevice; - return new((float)matrix.M11, (float)matrix.M22); - } + internal void AddLogicalChild(DependencyObject child) => + base.AddLogicalChild(child); - private void OnCanvasCoreInvalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); + internal void RemoveLogicalChild(DependencyObject child) => + base.RemoveLogicalChild(child); - private void OnLoaded(object sender, RoutedEventArgs e) - { - InitializeElement(); - CanvasCore.Invalidated += OnCanvasCoreInvalidated; - } + private void OnLoaded(object sender, RoutedEventArgs e) => + _settings.Initialize(CanvasCore); - private void OnUnloaded(object sender, RoutedEventArgs e) - { - CanvasCore.Invalidated -= OnCanvasCoreInvalidated; - CanvasCore.Dispose(); - } - - private async void RunDrawingLoop() - { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); - - while (!CanvasCore.IsValid) - { - _skiaElement?.InvalidateVisual(); -#if NET6_0_OR_GREATER - // workaround #250115 - _skiaGlElement?.InvalidateVisual(); -#endif - await Task.Delay(ts); - } - - _isDrawingLoopRunning = false; - } + private void OnUnloaded(object sender, RoutedEventArgs e) => + _settings.Dispose(CanvasCore); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs new file mode 100644 index 000000000..6931919b0 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs @@ -0,0 +1,75 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Windows; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp.Views.Desktop; +using SkiaSharp.Views.WPF; + +namespace LiveChartsCore.SkiaSharpView.WPF.Rendering; + +internal class CPURenderMode : SKElement, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + } + + public void InvalidateRenderer() => + InvalidateVisual(); + + private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) + { + var density = GetPixelDensity(); + if (density.dpix != 1 || density.dpiy != 1) + args.Surface.Canvas.Scale(density.dpix, density.dpiy); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface)); + } + + private ResolutionHelper GetPixelDensity() + { + var presentationSource = PresentationSource.FromVisual(this); + if (presentationSource is null) return new(1f, 1f); + var compositionTarget = presentationSource.CompositionTarget; + if (compositionTarget is null) return new(1f, 1f); + + var matrix = compositionTarget.TransformToDevice; + return new((float)matrix.M11, (float)matrix.M22); + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs new file mode 100644 index 000000000..e6ace58ea --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs @@ -0,0 +1,60 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Windows.Media; +using LiveChartsCore.Motion; + +namespace LiveChartsCore.SkiaSharpView.WPF.Rendering; + +internal class CompositionTargetTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + + _canvas.Invalidated += OnCoreInvalidated; + CompositionTarget.Rendering += OnCompositonTargetRendering; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnCompositonTargetRendering(object? sender, EventArgs e) + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + CompositionTarget.Rendering -= OnCompositonTargetRendering; + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs new file mode 100644 index 000000000..f961e1fd8 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs @@ -0,0 +1,84 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Desktop; +using SkiaSharp.Views.WPF; + +namespace LiveChartsCore.SkiaSharpView.WPF.Rendering; + +internal class GPURenderMode : SKGLElement, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + Dispose(); + } + + public void InvalidateRenderer() => + InvalidateVisual(); + + private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) + { + var density = GetPixelDensity(); + if (density.dpix != 1 || density.dpiy != 1) + args.Surface.Canvas.Scale(density.dpix, density.dpiy); + + var c = ((Control)Parent).Background is not SolidColorBrush bg + ? Colors.White + : bg.Color; + + FrameRequest?.Invoke( + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, new SKColor(c.R, c.G, c.B))); + } + + private ResolutionHelper GetPixelDensity() + { + var presentationSource = PresentationSource.FromVisual(this); + if (presentationSource is null) return new(1f, 1f); + var compositionTarget = presentationSource.CompositionTarget; + if (compositionTarget is null) return new(1f, 1f); + + var matrix = compositionTarget.TransformToDevice; + return new((float)matrix.M11, (float)matrix.M22); + } +} From d2512c936f42256ab59d7e3a5c03bea17a2dbb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:33:34 -0600 Subject: [PATCH 08/94] WinUI/UNO add render modes --- src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 83 ++++--------------- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 80 ++++++++++++++++++ .../_Shared.WinUI/Rendering/GPURenderMode.cs | 80 ++++++++++++++++++ .../_Shared.WinUI/_Shared.WinUI.projitems | 2 + 4 files changed, 180 insertions(+), 65 deletions(-) create mode 100644 src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs create mode 100644 src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index 1d61cfbbd..7f3f5a2f7 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -20,13 +20,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.Threading.Tasks; +using LiveChartsCore.Behaviours; using LiveChartsCore.Motion; -using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.WinUI.Rendering; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using SkiaSharp.Views.Windows; + +#pragma warning disable IDE0028 // Simplify collection initialization namespace LiveChartsCore.SkiaSharpView.WinUI; @@ -35,84 +35,37 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// public partial class MotionCanvas : Canvas { - private readonly SKXamlCanvas? _skiaElement; - private bool _isDrawingLoopRunning; + private readonly CanvasRenderSettings _settings; /// /// Initializes a new instance of the class. /// public MotionCanvas() { + _settings = new(); + + Children.Add((UIElement)_settings.RenderMode); + Loaded += OnLoaded; Unloaded += OnUnloaded; SizeChanged += OnSizeChanged; - -#pragma warning disable IDE0028 // Simplify collection initialization - _skiaElement = new(); -#pragma warning restore IDE0028 // Simplify collection initialization - Children.Add(_skiaElement); - SetLeft(_skiaElement, 0); - SetTop(_skiaElement, 0); - - _skiaElement.PaintSurface += OnPaintSurface; } - /// - /// Gets the canvas core. - /// - /// - /// The canvas core. - /// + /// public CoreMotionCanvas CanvasCore { get; } = new(); - private void OnLoaded(object sender, RoutedEventArgs e) => - CanvasCore.Invalidated += OnCanvasCoreInvalidated; - - private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) - { -#if HAS_UNO_WINUI - var scale = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f; - args.Surface.Canvas.Scale((float)scale, (float)scale); - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); -#else - var scaleFactor = XamlRoot.RasterizationScale; - args.Surface.Canvas.Scale((float)scaleFactor, (float)scaleFactor); - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); -#endif - } - - private async void RunDrawingLoop() + private void OnSizeChanged(object sender, SizeChangedEventArgs e) { - if (_isDrawingLoopRunning || _skiaElement == null) return; - _isDrawingLoopRunning = true; + var fe = (FrameworkElement)_settings.RenderMode; - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); - - while (!CanvasCore.IsValid) - { - _skiaElement?.Invalidate(); - await Task.Delay(ts); - } - - _isDrawingLoopRunning = false; + fe.Width = e.NewSize.Width; + fe.Height = e.NewSize.Height; } - private void OnCanvasCoreInvalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - if (_skiaElement == null) return; - _skiaElement.Width = e.NewSize.Width; - _skiaElement.Height = e.NewSize.Height; - } + private void OnLoaded(object sender, RoutedEventArgs e) => + _settings.Initialize(CanvasCore); - private void OnUnloaded(object sender, RoutedEventArgs e) - { - CanvasCore.Invalidated -= OnCanvasCoreInvalidated; - CanvasCore.Dispose(); - } + private void OnUnloaded(object sender, RoutedEventArgs e) => + _settings.Dispose(CanvasCore); } diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs new file mode 100644 index 000000000..c8f83c075 --- /dev/null +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -0,0 +1,80 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Windows; + +namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; + +internal partial class CPURenderMode : SKXamlCanvas, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + } + + private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) + { + var density = GetPixelDensity(); + if (density.DpiX != 1 || density.DpiY != 1) + e.Surface.Canvas.Scale(density.DpiX, density.DpiY); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, SKColor.Empty)); + } + + public void InvalidateRenderer() => + Invalidate(); + + private PixelDensity GetPixelDensity() + { +#if HAS_UNO_WINUI + var d = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f; + return new(d, d); +#else + var scaleFactor = (float)XamlRoot.RasterizationScale; + return new(scaleFactor, scaleFactor); +#endif + } + + private readonly struct PixelDensity(float dpiX, float dpiY) + { + public float DpiX { get; } = dpiX; + public float DpiY { get; } = dpiY; + } +} diff --git a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs new file mode 100644 index 000000000..970cc2529 --- /dev/null +++ b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs @@ -0,0 +1,80 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Windows; + +namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; + +internal partial class GPURenderMode : SKSwapChainPanel, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + } + + private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) + { + var density = GetPixelDensity(); + if (density.DpiX != 1 || density.DpiY != 1) + e.Surface.Canvas.Scale(density.DpiX, density.DpiY); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, SKColor.Empty)); + } + + public void InvalidateRenderer() => + Invalidate(); + + private PixelDensity GetPixelDensity() + { +#if HAS_UNO_WINUI + var d = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f; + return new(d, d); +#else + var scaleFactor = (float)XamlRoot.RasterizationScale; + return new(scaleFactor, scaleFactor); +#endif + } + + private readonly struct PixelDensity(float dpiX, float dpiY) + { + public float DpiX { get; } = dpiX; + public float DpiY { get; } = dpiY; + } +} diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index 26f8018dc..957a87a09 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -15,6 +15,8 @@ + + From 106b1febfc6912b8698fa874b0bf0ae0aea3d4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:34:55 -0600 Subject: [PATCH 09/94] maui add render modes --- .../MotionCanvas.cs | 216 +++++++++++++++--- .../Rendering/CPURenderMode.cs | 72 ++++++ .../Rendering/GPURenderMode.cs | 72 ++++++ 3 files changed, 322 insertions(+), 38 deletions(-) create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs index 6d7443230..ccddb8bbc 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs @@ -21,12 +21,12 @@ // SOFTWARE. using System; -using System.Threading.Tasks; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using Microsoft.Maui.Controls; using Microsoft.Maui.Devices; using Microsoft.Maui.Layouts; +using SkiaSharp; using SkiaSharp.Views.Maui; using SkiaSharp.Views.Maui.Controls; @@ -37,9 +37,7 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// public class MotionCanvas : AbsoluteLayout { - private bool _isDrawingLoopRunning = false; - private bool _isLoaded = true; - private double _density = 1; + private readonly Renderer _renderer; private SKCanvasView? _canvasView; private SKGLView? _glView; @@ -50,11 +48,11 @@ public MotionCanvas() { InitializeView(); + CanvasCore = new(); + _renderer = new(CanvasCore, InvalidateChart); + Loaded += OnLoaded; Unloaded += OnUnloaded; - - _density = DeviceDisplay.MainDisplayInfo.Density; - DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; } /// @@ -65,30 +63,32 @@ public MotionCanvas() /// public CoreMotionCanvas CanvasCore { get; } = new(); - /// - /// Invalidates this instance. - /// - /// - public void Invalidate() => - RunDrawingLoop(); + private void InvalidateChart() + { + _canvasView?.InvalidateSurface(); + _glView?.InvalidateSurface(); + } private void OnCanvasViewPaintSurface(object? sender, SKPaintSurfaceEventArgs args) { - args.Surface.Canvas.Scale((float)_density, (float)_density); + if (_renderer.Density != 1) + args.Surface.Canvas.Scale(_renderer.Density, _renderer.Density); + CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); + new SkiaSharpDrawingContext( + CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); } private void OnGlViewPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) { - args.Surface.Canvas.Scale((float)_density, (float)_density); + if (_renderer.Density != 1) + args.Surface.Canvas.Scale(_renderer.Density, _renderer.Density); + CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, new SkiaSharp.SKImageInfo((int)Width, (int)Height), args.Surface, args.Surface.Canvas)); + new SkiaSharpDrawingContext( + CanvasCore, new SKImageInfo((int)Width, (int)Height), args.Surface, args.Surface.Canvas)); } - private void OnCanvasCoreInvalidated(CoreMotionCanvas sender) => - Invalidate(); - private void InitializeView() { if (LiveCharts.UseGPU) @@ -113,36 +113,176 @@ private void InitializeView() } } - private async void RunDrawingLoop() + private void OnLoaded(object? sender, EventArgs e) => + _renderer.Start(); + + private void OnUnloaded(object? sender, EventArgs e) + { + _renderer.Stop(); + CanvasCore.Dispose(); + } +} + +/// +/// Defines the renderer class for Maui. +/// +public partial class Renderer +{ + private readonly CoreMotionCanvas _canvas; + private readonly Action _invalidator; + + /// + /// Gets the screen density of the device. + /// + public float Density { get; private set; } = 1; + + /// + /// Initializes a new instance of the class. + /// + /// The livecharts canvas. + /// The action to invalidate the canvas. + public Renderer(CoreMotionCanvas canvas, Action invalidator) { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; + Density = (float)DeviceDisplay.MainDisplayInfo.Density; + DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); + _canvas = canvas; + _invalidator = invalidator; + } - while (!CanvasCore.IsValid && _isLoaded) + /// + /// Starts the rendering loop for the canvas. + /// + public void Start() + { + if (!LiveCharts.UseVSync) { - _canvasView?.InvalidateSurface(); - _glView?.InvalidateSurface(); - await Task.Delay(ts); + _canvas.Invalidated += StartLiveChartsDrawingLoop; + return; } - _isDrawingLoopRunning = false; +#if WINDOWS + StartWindowsRenderer(); +#elif ANDROID + StartAndroidRenderer(); +#else + // if no platform-specific renderer is available, + // then use the livecharts drawing loop, + // slower but works on all platforms + + _canvas.Invalidated += StartLiveChartsDrawingLoop; +#endif } - private void OnLoaded(object? sender, EventArgs e) + /// + /// Ends the rendering loop for the canvas. + /// + public void Stop() { - _isLoaded = true; - CanvasCore.Invalidated += OnCanvasCoreInvalidated; + if (!LiveCharts.UseVSync) + { + _canvas.Invalidated -= StartLiveChartsDrawingLoop; + return; + } + +#if WINDOWS + StopWindowsRenderer(); +#elif ANDROID + StopAndroidRenderer(); +#else + _canvas.Invalidated -= StartLiveChartsDrawingLoop; +#endif } - private void OnUnloaded(object? sender, EventArgs e) + private void StartLiveChartsDrawingLoop(CoreMotionCanvas canvas) => + _canvas.RunDrawingLoop(_invalidator); + + private void MainDisplayInfoChanged(object? sender, EventArgs e) => + Density = (float)DeviceDisplay.MainDisplayInfo.Density; +} + +#if WINDOWS + +public partial class Renderer +{ + private void StartWindowsRenderer() { - _isLoaded = false; - CanvasCore.Invalidated -= OnCanvasCoreInvalidated; - CanvasCore.Dispose(); + Microsoft.UI.Xaml.Media.CompositionTarget.Rendering += OnRendering; + _canvas.Invalidated += OnLiveChartsCanvasInvalidated; } - private void MainDisplayInfoChanged(object? sender, EventArgs e) => - _density = DeviceDisplay.MainDisplayInfo.Density; + private void StopWindowsRenderer() + { + Microsoft.UI.Xaml.Media.CompositionTarget.Rendering -= OnRendering; + _canvas.Invalidated -= OnLiveChartsCanvasInvalidated; + } + + private void OnRendering(object? sender, object e) + { + // this is called on every vsync tick + if (_canvas.IsValid) return; + _invalidator(); + } + + private void OnLiveChartsCanvasInvalidated(CoreMotionCanvas obj) => + // this is the first call to invalidate the canvas + // when livecharts detect a change in the data/properties + _invalidator(); +} + +#endif + +#if ANDROID + +public class VSyncTicker(Action onFrameTick) + : Java.Lang.Object, Android.Views.Choreographer.IFrameCallback +{ + private readonly Android.Views.Choreographer _chor = Android.Views.Choreographer.Instance!; + + public void Start() => + _chor.PostFrameCallback(this); + + // frameTimeNanos: + // the absolute timestamp (in nanoseconds) that the system’s choreographer assigns to this frame. + public void DoFrame(long frameTimeNanos) + { + onFrameTick(); + _chor.PostFrameCallback(this); + } + + public void Stop() => + _chor.RemoveFrameCallback(this); } + +public partial class Renderer +{ + private VSyncTicker _vsyncTicker = null!; + + private void StartAndroidRenderer() + { + _vsyncTicker = new VSyncTicker(OnRendering); + _vsyncTicker.Start(); + _canvas.Invalidated += OnLiveChartsCanvasInvalidated; + } + + private void StopAndroidRenderer() + { + _vsyncTicker.Stop(); + _vsyncTicker = null!; + _canvas.Invalidated -= OnLiveChartsCanvasInvalidated; + } + + private void OnRendering() + { + // this is called on every vsync tick + if (_canvas.IsValid) return; + _invalidator(); + } + + private void OnLiveChartsCanvasInvalidated(CoreMotionCanvas obj) => + // this is the first call to invalidate the canvas + // when livecharts detect a change in the data/properties + _invalidator(); +} + +#endif diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs new file mode 100644 index 000000000..acbc8fa6b --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs @@ -0,0 +1,72 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.Maui.Devices; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace LiveChartsCore.SkiaSharpView.Maui.Rendering; + +internal class CPURenderMode : SKCanvasView, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + private float _pixelDensity = 1; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; + DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + DeviceDisplay.MainDisplayInfoChanged -= MainDisplayInfoChanged; + } + + public void InvalidateRenderer() => + InvalidateSurface(); + + private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) + { + if (_pixelDensity != 1) + args.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface)); + } + + private void MainDisplayInfoChanged(object? sender, EventArgs e) => + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs new file mode 100644 index 000000000..73cfce411 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs @@ -0,0 +1,72 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.Maui.Devices; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace LiveChartsCore.SkiaSharpView.Maui.Rendering; + +internal class GPURenderMode : SKGLView, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + private float _pixelDensity = 1; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; + DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + DeviceDisplay.MainDisplayInfoChanged -= MainDisplayInfoChanged; + } + + public void InvalidateRenderer() => + InvalidateSurface(); + + private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) + { + if (_pixelDensity != 1) + e.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface)); + } + + private void MainDisplayInfoChanged(object? sender, EventArgs e) => + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; +} From 265739c3adae66b3b0eb2b804e97b5edda6ab9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:36:29 -0600 Subject: [PATCH 10/94] simplify skiasharpdrawingcontext --- .../Drawing/SkiaSharpDrawingContext.cs | 14 +++++--------- .../SKCharts/InMemorySkiaSharpChart.cs | 17 +++++++---------- .../SKCharts/SKGeoMap.cs | 7 +++---- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs index 0400e7957..193e211c2 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs @@ -36,13 +36,11 @@ namespace LiveChartsCore.SkiaSharpView.Drawing; /// The motion canvas. /// The information. /// The surface. -/// The canvas. /// Indicates whether the canvas is cleared on frame draw. public class SkiaSharpDrawingContext( CoreMotionCanvas motionCanvas, SKImageInfo info, - SKSurface? surface, - SKCanvas canvas, + SKSurface surface, bool clearOnBeginDraw = true) : DrawingContext { @@ -52,17 +50,15 @@ public class SkiaSharpDrawingContext( /// The motion canvas. /// The information. /// The surface. - /// The canvas. /// The background. /// Indicates whether the canvas is cleared on frame draw. public SkiaSharpDrawingContext( CoreMotionCanvas motionCanvas, SKImageInfo info, - SKSurface? surface, - SKCanvas canvas, + SKSurface surface, SKColor background, bool clearOnBeginDraw = true) - : this(motionCanvas, info, surface, canvas, clearOnBeginDraw) + : this(motionCanvas, info, surface, clearOnBeginDraw) { Background = background; } @@ -89,7 +85,7 @@ public SkiaSharpDrawingContext( /// /// The surface. /// - public SKSurface? Surface { get; set; } = surface; + public SKSurface Surface { get; set; } = surface; /// /// Gets or sets the canvas. @@ -97,7 +93,7 @@ public SkiaSharpDrawingContext( /// /// The canvas. /// - public SKCanvas Canvas { get; set; } = canvas; + public SKCanvas Canvas => Surface.Canvas; /// /// Gets or sets the paint. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs index 99d7f80cd..661062e58 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs @@ -94,7 +94,7 @@ public virtual SKImage GetImage() using var surface = SKSurface.Create(new SKImageInfo(Width, Height)); using var canvas = surface.Canvas; - DrawOnCanvas(canvas, surface); + DrawOnCanvas(surface); return surface.Snapshot(); } @@ -129,19 +129,18 @@ public virtual void SaveImage(string path, SKEncodedImageFormat format = SKEncod /// /// Draws the image to the specified canvas. /// - /// The canvas + /// The surface. /// Indicates whether the canvas should be cleared when the draw starts, default is false. - public virtual void SaveImage(SKCanvas canvas, bool clearCanvasOnBeginDraw = false) => - DrawOnCanvas(canvas, null, clearCanvasOnBeginDraw); + public virtual void SaveImage(SKSurface surface, bool clearCanvasOnBeginDraw = false) => + DrawOnCanvas(surface, clearCanvasOnBeginDraw); /// /// Draws the chart to the specified canvas. /// - /// The canvas. /// The surface. /// [probably an obsolete param] Indicates whether the canvas should be cleared when the draw starts, default is false. /// - public virtual void DrawOnCanvas(SKCanvas canvas, SKSurface? surface = null, bool clearCanvasOnBeginDraw = false) + public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw = false) { if (CoreChart is null || CoreChart is not Chart skiaChart) throw new Exception("Something is missing :("); @@ -152,8 +151,7 @@ public virtual void DrawOnCanvas(SKCanvas canvas, SKSurface? surface = null, boo new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface!, - canvas, + surface, Background, clearCanvasOnBeginDraw)); @@ -172,8 +170,7 @@ public virtual void DrawOnCanvas(SKCanvas canvas, SKSurface? surface = null, boo new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface!, - canvas, + surface, Background, clearCanvasOnBeginDraw)); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs index 2fe9f2de9..cfd243648 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs @@ -120,8 +120,8 @@ public object? ViewCommand } } - /// - public override void DrawOnCanvas(SKCanvas canvas, SKSurface? surface, bool clearCanvasOnBeginDraw = false) + /// + public override void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw = false) { Canvas.DisableAnimations = true; @@ -131,8 +131,7 @@ public override void DrawOnCanvas(SKCanvas canvas, SKSurface? surface, bool clea new SkiaSharpDrawingContext( Canvas, new SKImageInfo(Width, Height), - surface!, - canvas, + surface, Background, clearCanvasOnBeginDraw)); From 94a39fe4bdb04f2bd8fb1c8fa527754e93f36350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:37:41 -0600 Subject: [PATCH 11/94] add default render settings --- .../LiveChartsSkiaSharp.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index 2902433d5..5d1a20f5b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Ignore Spelling: Skia Lvc - using System; using LiveChartsCore.Drawing; using LiveChartsCore.Kernel; @@ -52,9 +50,21 @@ public static class LiveChartsSkiaSharp /// The settings. public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) { - if (!LiveCharts.HasBackend) _ = settings.AddSkiaSharp(); - if (!LiveCharts.HasDefaultTheme) _ = settings.AddDefaultTheme(); - if (!LiveCharts.HasDefaultMappers) _ = settings.AddDefaultMappers(); + if (!LiveCharts.s_hasBackend) + _ = settings.AddSkiaSharp(); + + if (!LiveCharts.s_hasDefaultTheme) + _ = settings.AddDefaultTheme(); + + if (!LiveCharts.s_hasDefaultMappers) + _ = settings.AddDefaultMappers(); + + if (!LiveCharts.s_hasDefaultHardwareAcceleration) + _ = settings.RenderingSettings( + useHardwareAcceleration: true, + tryUseVSync: true, + targetFps: 60, // 60 as a fallback when VSync is not available + showFps: false); return settings; } @@ -66,7 +76,7 @@ public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) /// public static LiveChartsSettings AddSkiaSharp(this LiveChartsSettings settings) { - LiveCharts.HasBackend = true; + LiveCharts.s_hasBackend = true; PropertyDefinition.Parsers[typeof(Paint)] = HexToPaintTypeConverter.Parse; PropertyDefinition.Parsers[typeof(LvcColor)] = HexToLvcColorTypeConverter.Parse; From 246c7f36dca2e9f97fd28a3f53c71dd38335e439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 09:45:50 -0600 Subject: [PATCH 12/94] make updater properties regular properties --- src/skiasharp/_Shared/ChartControl.cs | 6 ++++++ src/skiasharp/_Shared/ChartControl.sgp.cs | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/skiasharp/_Shared/ChartControl.cs b/src/skiasharp/_Shared/ChartControl.cs index 8dd7c06bd..9bbf0484d 100644 --- a/src/skiasharp/_Shared/ChartControl.cs +++ b/src/skiasharp/_Shared/ChartControl.cs @@ -73,6 +73,12 @@ public abstract partial class ChartControl /// public CoreMotionCanvas CoreCanvas => CanvasView.CanvasCore; + /// + public TimeSpan UpdaterThrottler { get; set; } = LiveCharts.DefaultSettings.UpdateThrottlingTimeout; + + /// + public bool AutoUpdateEnabled { get; set; } = true; + #if XAML_LVC private bool HasValidSource => SeriesSource is not null && SeriesTemplate is not null; diff --git a/src/skiasharp/_Shared/ChartControl.sgp.cs b/src/skiasharp/_Shared/ChartControl.sgp.cs index d50cf1e1a..df5e2cafe 100644 --- a/src/skiasharp/_Shared/ChartControl.sgp.cs +++ b/src/skiasharp/_Shared/ChartControl.sgp.cs @@ -127,10 +127,6 @@ public partial class ChartControl static UIProperty animationsSpeed = new(d.AnimationsSpeed); /// static UIProperty?> easingFunction = new(d.EasingFunction); - /// - static UIProperty updaterThrottler = new(d.UpdateThrottlingTimeout); - /// - static UIProperty autoUpdateEnabled = new(true); /// static UIProperty drawMargin = new(null, OnChartPropertyChanged); From 9de77dd6088b8d18f2c5875c0ee29c4225edb388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 09:48:49 -0600 Subject: [PATCH 13/94] remove obsolete notes --- src/LiveChartsCore/Kernel/LiveChartsSettings.cs | 2 -- src/LiveChartsCore/Themes/LvcThemeKind.cs | 2 -- .../LiveChartsCore.SkiaSharp/Extensions/GaugeOptions.cs | 2 -- src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs | 2 -- 4 files changed, 8 deletions(-) diff --git a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs index 660e2c2c0..adb0ade6c 100644 --- a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs +++ b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Ignore Spelling: Tooltip - using System; using System.Collections.Generic; using LiveChartsCore.Drawing; diff --git a/src/LiveChartsCore/Themes/LvcThemeKind.cs b/src/LiveChartsCore/Themes/LvcThemeKind.cs index ff2bcfc94..e060a8a52 100644 --- a/src/LiveChartsCore/Themes/LvcThemeKind.cs +++ b/src/LiveChartsCore/Themes/LvcThemeKind.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Ignore Spelling: Gauge - namespace LiveChartsCore.Themes; /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Extensions/GaugeOptions.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Extensions/GaugeOptions.cs index a55d38bfe..43c9132e1 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Extensions/GaugeOptions.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Extensions/GaugeOptions.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Ignore Spelling: Skia Lvc - namespace LiveChartsCore.SkiaSharpView.Extensions; /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index 2902433d5..c45c6c702 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Ignore Spelling: Skia Lvc - using System; using LiveChartsCore.Drawing; using LiveChartsCore.Kernel; From e1dd7d461405c6286a595aef878956b6cfc6c869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 18 Jul 2025 10:25:43 -0600 Subject: [PATCH 14/94] improve log and control over the drawing engine --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 2ce92c6af..e32a211bb 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -36,13 +36,15 @@ public class CoreMotionCanvas : IDisposable { private static readonly Stopwatch s_clock = new(); internal HashSet _paintTasks = []; - private object _sync = new(); - private int _frames = 0; private Stopwatch? _fspSw; - private double _lastKnowFps = 0; + private double _totalDrawTime = 0; + private double _lastDrawTime = 0; private double _totalFrames = 0; private double _totalSeconds = 0; + private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; + private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); + private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; static CoreMotionCanvas() { @@ -94,13 +96,15 @@ public static long ElapsedMilliseconds /// /// The synchronize. /// - public object Sync { get => _sync; internal set => _sync = value ?? new object(); } + public object Sync { get; internal set => field = value ?? new object(); } = new(); /// /// Gets the animatables collection. /// public HashSet Trackers { get; } = []; + internal TimeSpan _nextFrameDelay = s_baseFrameDelay; + /// /// Draws the frame. /// @@ -117,6 +121,7 @@ public void DrawFrame(TDrawingContext context) #endif var showFps = LiveCharts.ShowFPS; + var drawStartTime = s_clock.ElapsedTicks; lock (Sync) { @@ -177,11 +182,12 @@ public void DrawFrame(TDrawingContext context) if (showFps) { - MeasureFPS(); + MeasureFPS(drawStartTime); if (_totalSeconds > 0) context.LogOnCanvas( - $"[fps] last {_lastKnowFps:N2}, average {_totalFrames / _totalSeconds:N2}"); + $"FSP [{_totalFrames / _totalSeconds:N2}] " + + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ]"); } IsValid = isValid; @@ -199,6 +205,15 @@ public void DrawFrame(TDrawingContext context) _fspSw = null; } } + + var timeInDrawOperation = s_clock.ElapsedTicks - drawStartTime; + var delay = s_baseFrameDelay.Ticks - timeInDrawOperation; + + var frameDelay = delay <= s_jitterThreshold + ? s_baseFrameDelay + : new TimeSpan(delay); + + _nextFrameDelay = frameDelay; } /// @@ -299,8 +314,13 @@ public void Dispose() IsValid = true; } - private void MeasureFPS() + private void MeasureFPS(long drawStartTime) { + if (s_clock.ElapsedMilliseconds < 3000) + return; // we only start measuring after 3 seconds, to improve accuracy. + + _totalDrawTime += (s_clock.ElapsedTicks - drawStartTime) / s_ticksPerMillisecond; + if (_fspSw is null) { _fspSw = new(); @@ -313,11 +333,14 @@ private void MeasureFPS() if (_frames % logEach == 0) { var elapsedSeconds = _fspSw.ElapsedMilliseconds / 1000d; - _lastKnowFps = logEach / elapsedSeconds; _totalFrames += logEach; _totalSeconds += elapsedSeconds; + // it not exactly the last frame time, is the 20th frame time + // so we can actually read the time in the log. + _lastDrawTime = (s_clock.ElapsedTicks - drawStartTime) / s_ticksPerMillisecond; + _fspSw.Restart(); } } From 1b66a02c7bbd27167df82c088a67fce595b4ab80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 19 Jul 2025 07:25:29 -0600 Subject: [PATCH 15/94] move render loop to core --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index e32a211bb..33732b914 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using LiveChartsCore.Drawing; using LiveChartsCore.Painting; @@ -42,6 +43,8 @@ public class CoreMotionCanvas : IDisposable private double _lastDrawTime = 0; private double _totalFrames = 0; private double _totalSeconds = 0; + private bool _isDrawingLoopRunning = false; + private TimeSpan _nextFrameDelay = s_baseFrameDelay; private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; @@ -103,8 +106,6 @@ public static long ElapsedMilliseconds /// public HashSet Trackers { get; } = []; - internal TimeSpan _nextFrameDelay = s_baseFrameDelay; - /// /// Draws the frame. /// @@ -186,7 +187,7 @@ public void DrawFrame(TDrawingContext context) if (_totalSeconds > 0) context.LogOnCanvas( - $"FSP [{_totalFrames / _totalSeconds:N2}] " + + $"FSP [{_totalFrames / _totalSeconds:N2}]" + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ]"); } @@ -302,6 +303,25 @@ public int CountGeometries() return count; } + /// + /// Runs the drawing loop asynchronously, a custom alternative when the + /// target platform does not support CompositionTarget or equivalent. + /// + /// An action that invalidates the control. + public async void RunDrawingLoop(Action invalidator) + { + if (_isDrawingLoopRunning) return; + _isDrawingLoopRunning = true; + + while (!IsValid) + { + invalidator(); + await Task.Delay(_nextFrameDelay); + } + + _isDrawingLoopRunning = false; + } + /// /// Releases the resources. /// From b89b61b3b278f3ceb0393487f281d70df03e8593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:25:34 -0600 Subject: [PATCH 16/94] add livecharts render settings --- .../Kernel/LiveChartsSettings.cs | 49 +++++++++++++++++-- src/LiveChartsCore/LiveCharts.cs | 48 +++++++++++------- .../ThemesExtensions.cs | 2 +- 3 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs index adb0ade6c..b61ee6c87 100644 --- a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs +++ b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs @@ -384,7 +384,7 @@ public LiveChartsSettings WithTooltipTextSize(double size) /// Removes a map from the settings. /// /// The type of the model. - /// + /// The current settings. public LiveChartsSettings RemoveMap() { _ = _mappers.Remove(typeof(TModel)); @@ -395,7 +395,7 @@ public LiveChartsSettings RemoveMap() /// Adds the default styles. /// /// The builder. - /// + /// The current settings. public LiveChartsSettings HasTheme(Action builder) { Theme t; @@ -409,7 +409,6 @@ public LiveChartsSettings HasTheme(Action builder) /// /// Gets the styles builder. /// - /// public Theme GetTheme() => (Theme?)_theme ?? throw new Exception("A theme is required."); @@ -427,10 +426,10 @@ public Theme GetTheme() /// /// Enables LiveCharts to be able to plot short, int, long, float, double, decimal, short?, int?, long?, float?, double?, decimal?. /// - /// + /// The current settings. public LiveChartsSettings AddDefaultMappers() { - LiveCharts.HasDefaultMappers = true; + LiveCharts.s_hasDefaultMappers = true; return HasMap((model, index) => new(index, model)) @@ -446,4 +445,44 @@ public LiveChartsSettings AddDefaultMappers() .HasMap((model, index) => new(index, model!.Value)) .HasMap((model, index) => new(index, (double)model!.Value)); } + + /// + /// Indicates whether hardware acceleration is used to render the charts, this will only work if + /// the current platform and device supports it. See also. . + /// + /// + /// Indicates whether hardware acceleration is used, this will only work + /// if the platform and device support it, default is true. This is ignored in Avalonia, in avalonia the + /// frame rate and rendering cadence is determined by the Avalonia rendering loop. + /// + /// + /// Indicates whether the rendering cadence should be aligned with the display refresh rate, + /// gpu acceleration is required for this to work. This is ignored in Avalonia, in avalonia the frame rate + /// and rendering cadence is determined by the Avalonia rendering loop. + /// + /// + /// The target frames per second for the rendering engine, this property is ignored when + /// is true and GPU acceleration is enabled, + /// This is ignored in Avalonia, in avalonia the frame rate and rendering cadence is determined by the + /// Avalonia rendering loop. + /// + /// + /// When true, The chart will also draw the frames per second in the top left corner of the chart. + /// + /// The current settings. + public LiveChartsSettings RenderingSettings( + bool useHardwareAcceleration, + bool tryUseVSync, + double targetFps = 60, + bool showFps = false) + { + LiveCharts.s_hasDefaultHardwareAcceleration = true; + + LiveCharts.UseGPU = useHardwareAcceleration; + LiveCharts.TryUseVSync = tryUseVSync; + LiveCharts.TargetFps = targetFps; + LiveCharts.ShowFPS = showFps; + + return this; + } } diff --git a/src/LiveChartsCore/LiveCharts.cs b/src/LiveChartsCore/LiveCharts.cs index 3e18d02ac..de09c28f0 100644 --- a/src/LiveChartsCore/LiveCharts.cs +++ b/src/LiveChartsCore/LiveCharts.cs @@ -30,8 +30,12 @@ namespace LiveChartsCore; /// public static class LiveCharts { - private static bool s_useGPU = false; + private static bool s_useGPU = true; private static bool s_gpuSetByUser = false; + internal static bool s_hasBackend = false; + internal static bool s_hasDefaultTheme = false; + internal static bool s_hasDefaultMappers = false; + internal static bool s_hasDefaultHardwareAcceleration = false; /// /// A constant that indicates that the tool tip should not add the current label. @@ -57,32 +61,40 @@ public static class LiveCharts /// /// Gets or sets the maximum fps requested. /// - public static double MaxFps { get; set; } = 65; + [Obsolete($"Renamed to {nameof(TargetFps)}")] + public static double MaxFps { get => TargetFps; set => TargetFps = value; } /// - /// Gets or sets a value indicating whether LiveCharts should use a hardware graphics API - /// to render the charts. + /// Gets or sets the target frames per second for the rendering engine, + /// this property is ignored when is true and + /// GPU acceleration is enabled, default is 60 fps. /// - public static bool UseGPU - { - get => s_useGPU; - set { s_useGPU = value; s_gpuSetByUser = true; } - } + public static double TargetFps { get; set; } = 60; /// - /// Gets a value indicating whether LiveCharts has a backend registered. + /// Attempts to align rendering cadence with display refresh rate (VSync) when supported. + /// Requires GPU acceleration. May be ignored in software-mode or virtual environments. + /// In WPF and WinUI the rendering cadence is regulated by the CompositionTarget.Rendering event, + /// which dispatches frame updates synchronized with the display refresh cycle. + /// In Avalonia, this value is ignored as the chart is rendered based on Avalonia's rendering loop. /// - public static bool HasBackend { get; internal set; } = false; + public static bool TryUseVSync { get; set; } = true; /// - /// Gets a value indicating whether LiveCharts has a theme registered. + /// Gets or sets a value indicating whether LiveCharts should use the GPU for rendering. + /// When set to true, the library will attempt to use GPU acceleration. + /// This has no effect on Avalonia since Avalonia determines the rendering backend. + /// When GPU rendering is not available, it will fallback to software rendering. /// - public static bool HasDefaultTheme { get; set; } = false; - - /// - /// Gets a value indicating whether LiveCharts has the default mappers registered. - /// - public static bool HasDefaultMappers { get; set; } = false; + public static bool UseGPU + { + get => s_useGPU; + set + { + s_useGPU = value; + s_gpuSetByUser = true; + } + } /// /// Gets the current settings. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs index 20c65c9c7..2f6e491f0 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs @@ -53,7 +53,7 @@ public static LiveChartsSettings AddDefaultTheme( Action? themeSettings = null, LvcThemeKind requestedTheme = LvcThemeKind.Unknown) { - LiveCharts.HasDefaultTheme = true; + LiveCharts.s_hasDefaultTheme = true; return settings .HasTheme(theme => From 0edde2e510bc609dd2b1e7ef6c85596009812028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:26:48 -0600 Subject: [PATCH 17/94] IFrameTicker and IRenderMode concepts Abstractions that helps to use the native VSync of each target OS --- src/LiveChartsCore/AssemblyInfo.cs | 1 + src/LiveChartsCore/Motion/AsyncLoopTicker.cs | 65 ++++++++++++++ .../Motion/CanvasRenderSettings.cs | 84 +++++++++++++++++++ src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 36 +++----- src/LiveChartsCore/Motion/IFrameTicker.cs | 30 +++++++ src/LiveChartsCore/Motion/IRenderMode.cs | 34 ++++++++ 6 files changed, 226 insertions(+), 24 deletions(-) create mode 100644 src/LiveChartsCore/Motion/AsyncLoopTicker.cs create mode 100644 src/LiveChartsCore/Motion/CanvasRenderSettings.cs create mode 100644 src/LiveChartsCore/Motion/IFrameTicker.cs create mode 100644 src/LiveChartsCore/Motion/IRenderMode.cs diff --git a/src/LiveChartsCore/AssemblyInfo.cs b/src/LiveChartsCore/AssemblyInfo.cs index 25b92af29..efeae6990 100644 --- a/src/LiveChartsCore/AssemblyInfo.cs +++ b/src/LiveChartsCore/AssemblyInfo.cs @@ -33,6 +33,7 @@ #else [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView")] +[assembly: InternalsVisibleTo("LiveChartsCore.Behaviours")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WinForms")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WPF")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.Avalonia")] diff --git a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs new file mode 100644 index 000000000..723d50447 --- /dev/null +++ b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs @@ -0,0 +1,65 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Threading.Tasks; + +namespace LiveChartsCore.Motion; + +internal class AsyncLoopTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + private bool _isDrawingLoopRunning = false; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + + _canvas.Invalidated += OnCoreInvalidated; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _ = RunDrawingLoop(); + + private async Task RunDrawingLoop() + { + if (_isDrawingLoopRunning) return; + _isDrawingLoopRunning = true; + + while (!_canvas.IsValid) + { + _renderMode.InvalidateRenderer(); + await Task.Delay(_canvas._nextFrameDelay); + } + + _isDrawingLoopRunning = false; + } + + public void DisposeTicker() + { + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + } +} diff --git a/src/LiveChartsCore/Motion/CanvasRenderSettings.cs b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs new file mode 100644 index 000000000..214c4e641 --- /dev/null +++ b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs @@ -0,0 +1,84 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Motion; + +internal class CanvasRenderSettings + where TCPURenderMode : IRenderMode, new() + where TGPURenderMode : IRenderMode, new() + where TVSyncTicker : IFrameTicker, new() +{ + private static bool? s_canUseGPU; + + public CanvasRenderSettings() + { + RenderMode = LiveCharts.UseGPU && CanUseGPU() + ? new TGPURenderMode() + : new TCPURenderMode(); + + Ticker = LiveCharts.TryUseVSync + ? new TVSyncTicker() + : new AsyncLoopTicker(); + } + + public IRenderMode RenderMode { get; } + + public IFrameTicker Ticker { get; } + + public void Initialize(CoreMotionCanvas canvas) + { + RenderMode.InitializeRenderMode(canvas); + Ticker.InitializeTicker(canvas, RenderMode); + RenderMode.FrameRequest += canvas.DrawFrame; + } + + public void Dispose(CoreMotionCanvas canvas) + { + RenderMode.DisposeRenderMode(); + Ticker.DisposeTicker(); + RenderMode.FrameRequest -= canvas.DrawFrame; + canvas.Dispose(); + } + + internal static bool CanUseGPU() + { + if (s_canUseGPU.HasValue) + return s_canUseGPU.Value; + + try + { + var renderer = new TGPURenderMode(); + renderer.DisposeRenderMode(); + + s_canUseGPU = true; + + return true; + } + catch + { + s_canUseGPU = false; + LiveCharts.UseGPU = false; + + return false; + } + } +} diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 33732b914..439f13ed3 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -24,7 +24,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using LiveChartsCore.Drawing; using LiveChartsCore.Painting; @@ -43,8 +42,7 @@ public class CoreMotionCanvas : IDisposable private double _lastDrawTime = 0; private double _totalFrames = 0; private double _totalSeconds = 0; - private bool _isDrawingLoopRunning = false; - private TimeSpan _nextFrameDelay = s_baseFrameDelay; + internal TimeSpan _nextFrameDelay = s_baseFrameDelay; private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; @@ -54,6 +52,8 @@ static CoreMotionCanvas() s_clock.Start(); } + internal delegate void FrameRequestHandler(DrawingContext context); + /// /// Gets the clock elapsed time in milliseconds. /// @@ -187,8 +187,10 @@ public void DrawFrame(TDrawingContext context) if (_totalSeconds > 0) context.LogOnCanvas( - $"FSP [{_totalFrames / _totalSeconds:N2}]" + - $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ]"); + $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + + $"GPU [ {LiveCharts.UseGPU} ] " + + $"VSync [ {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]"); } IsValid = isValid; @@ -202,8 +204,13 @@ public void DrawFrame(TDrawingContext context) if (showFps) { + // restart the count when the canvas is valid. _frames = 0; _fspSw = null; + _totalDrawTime = 0; + _lastDrawTime = 0; + _totalFrames = 0; + _totalSeconds = 0; } } @@ -303,25 +310,6 @@ public int CountGeometries() return count; } - /// - /// Runs the drawing loop asynchronously, a custom alternative when the - /// target platform does not support CompositionTarget or equivalent. - /// - /// An action that invalidates the control. - public async void RunDrawingLoop(Action invalidator) - { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - while (!IsValid) - { - invalidator(); - await Task.Delay(_nextFrameDelay); - } - - _isDrawingLoopRunning = false; - } - /// /// Releases the resources. /// diff --git a/src/LiveChartsCore/Motion/IFrameTicker.cs b/src/LiveChartsCore/Motion/IFrameTicker.cs new file mode 100644 index 000000000..4fedfca6d --- /dev/null +++ b/src/LiveChartsCore/Motion/IFrameTicker.cs @@ -0,0 +1,30 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Motion; + +internal interface IFrameTicker +{ + void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode); + + void DisposeTicker(); +} diff --git a/src/LiveChartsCore/Motion/IRenderMode.cs b/src/LiveChartsCore/Motion/IRenderMode.cs new file mode 100644 index 000000000..bfd907103 --- /dev/null +++ b/src/LiveChartsCore/Motion/IRenderMode.cs @@ -0,0 +1,34 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Motion; + +internal interface IRenderMode +{ + event CoreMotionCanvas.FrameRequestHandler FrameRequest; + + void InitializeRenderMode(CoreMotionCanvas canvas); + + void InvalidateRenderer(); + + void DisposeRenderMode(); +} From 8f9cf7debc91c5117d8f8c9d3aa9910f58dacdc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:28:58 -0600 Subject: [PATCH 18/94] add native vsyncers --- .../NativeTicker.Android.cs | 86 +++++++++++++++++++ .../NativeTicker.Mac.cs | 68 +++++++++++++++ .../NativeTicker.Windows.cs | 63 ++++++++++++++ .../NativeTicker._shared.cs | 36 ++++++++ 4 files changed, 253 insertions(+) create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker.Android.cs create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs create mode 100644 src/LiveChartsCore.Behaviours/NativeTicker._shared.cs diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Android.cs b/src/LiveChartsCore.Behaviours/NativeTicker.Android.cs new file mode 100644 index 000000000..17a27d900 --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker.Android.cs @@ -0,0 +1,86 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if ANDROID + +using System; +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + private VSyncTicker _vsyncTicker = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + _vsyncTicker = new VSyncTicker(OnFrameTick); + _vsyncTicker.Start(); + + _canvas.Invalidated += OnCoreInvalidated; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnFrameTick() + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + _vsyncTicker.Stop(); + _vsyncTicker = null!; + } + + private class VSyncTicker(Action onFrameTick) + : Java.Lang.Object, Android.Views.Choreographer.IFrameCallback + { + private readonly Android.Views.Choreographer _chor = Android.Views.Choreographer.Instance!; + + public void Start() => + _chor.PostFrameCallback(this); + + // frameTimeNanos: + // the absolute timestamp (in nanoseconds) that the system’s choreographer assigns to this frame. + public void DoFrame(long frameTimeNanos) + { + onFrameTick(); + _chor.PostFrameCallback(this); + } + + public void Stop() => + _chor.RemoveFrameCallback(this); + } +} + +#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs b/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs new file mode 100644 index 000000000..4169c6bac --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs @@ -0,0 +1,68 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if IOS || MACCATALYST + +using CoreAnimation; +using Foundation; +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + private CADisplayLink _displayLink = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + _displayLink = CADisplayLink.Create(OnFrameTick); + _displayLink.AddToRunLoop(NSRunLoop.Main, NSRunLoopMode.Common); + + _canvas.Invalidated += OnCoreInvalidated; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnFrameTick() + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + _displayLink.Invalidate(); + _displayLink.Dispose(); + _displayLink = null!; + } +} + +#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs b/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs new file mode 100644 index 000000000..b155876e2 --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs @@ -0,0 +1,63 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if WINDOWS + +using LiveChartsCore.Motion; +using Microsoft.UI.Xaml.Media; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + + _canvas.Invalidated += OnCoreInvalidated; + CompositionTarget.Rendering += OnCompositonTargetRendering; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnCompositonTargetRendering(object? sender, object e) + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + CompositionTarget.Rendering -= OnCompositonTargetRendering; + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + } +} + +#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs b/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs new file mode 100644 index 000000000..c07a24c8e --- /dev/null +++ b/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs @@ -0,0 +1,36 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if !WINDOWS && !ANDROID && !IOS && !MACCATALYST +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + void IFrameTicker.InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => + throw new System.NotImplementedException(); + + void IFrameTicker.DisposeTicker() => + throw new System.NotImplementedException(); +} +#endif From fadeebd3759017b0a2c282c0e4dbfae3c89cde78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:30:07 -0600 Subject: [PATCH 19/94] update wpf to latests skiasharp version --- .../LiveChartsCore.SkiaSharpView.WPF.csproj | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj index 1bd57170f..8dd70d50f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj @@ -43,35 +43,8 @@ - - - - - - - + + From e94b8190e2991c4b83c9eba1b37c53c48626d9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:30:50 -0600 Subject: [PATCH 20/94] use compositionTarget.renderion in wpf --- .../MotionCanvas.cs | 127 +++--------------- .../Rendering/CPURenderMode.cs | 75 +++++++++++ .../Rendering/CompositionTargetTicker.cs | 60 +++++++++ .../Rendering/GPURenderMode.cs | 84 ++++++++++++ 4 files changed, 234 insertions(+), 112 deletions(-) create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs index 0ce09d070..156d38e91 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs @@ -20,17 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using LiveChartsCore.Motion; -using LiveChartsCore.SkiaSharpView.Drawing; -using SkiaSharp; -using SkiaSharp.Views.Desktop; -using SkiaSharp.Views.WPF; - +using LiveChartsCore.SkiaSharpView.WPF.Rendering; namespace LiveChartsCore.SkiaSharpView.WPF; /// @@ -39,123 +32,33 @@ namespace LiveChartsCore.SkiaSharpView.WPF; /// public class MotionCanvas : UserControl { - private SKElement? _skiaElement; -#if NET6_0_OR_GREATER - // workaround #250115 - private SKGLElement? _skiaGlElement; -#endif - private bool _isDrawingLoopRunning = false; + private readonly CanvasRenderSettings _settings; /// /// Initializes a new instance of the class. /// public MotionCanvas() { + _settings = new(); + + Content = _settings.RenderMode; + Loaded += OnLoaded; Unloaded += OnUnloaded; } - /// - /// Gets the canvas core. - /// - /// - /// The canvas core. - /// + /// public CoreMotionCanvas CanvasCore { get; } = new(); - internal void AddLogicalChild(DependencyObject child) => base.AddLogicalChild(child); - internal void RemoveLogicalChild(DependencyObject child) => base.RemoveLogicalChild(child); - - /// - protected virtual void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) - { - var density = GetPixelDensity(); - args.Surface.Canvas.Scale(density.dpix, density.dpiy); - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); - } - - /// - protected virtual void OnPaintGlSurface(object? sender, SKPaintGLSurfaceEventArgs args) - { - var density = GetPixelDensity(); - args.Surface.Canvas.Scale(density.dpix, density.dpiy); - - var c = (((Control)Parent).Background is not SolidColorBrush bg) - ? Colors.White - : bg.Color; - - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas) - { - Background = new SKColor(c.R, c.G, c.B) - }); - } - - private void InitializeElement() - { - if (LiveCharts.UseGPU) - { -#if NET6_0_OR_GREATER - // workaround #250115 - Content = _skiaGlElement = new SKGLElement(); - _skiaGlElement.PaintSurface += OnPaintGlSurface; -#else - throw new PlatformNotSupportedException( - "GPU rendering is only supported in .NET 6.0 or greater, " + - "because https://github.com/mono/SkiaSharp/issues/3111 needs to be fixed."); -#endif - } - else - { - Content = _skiaElement = new SKElement(); - _skiaElement.PaintSurface += OnPaintSurface; - } - } - - private ResolutionHelper GetPixelDensity() - { - var presentationSource = PresentationSource.FromVisual(this); - if (presentationSource is null) return new(1f, 1f); - var compositionTarget = presentationSource.CompositionTarget; - if (compositionTarget is null) return new(1f, 1f); - - var matrix = compositionTarget.TransformToDevice; - return new((float)matrix.M11, (float)matrix.M22); - } + internal void AddLogicalChild(DependencyObject child) => + base.AddLogicalChild(child); - private void OnCanvasCoreInvalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); + internal void RemoveLogicalChild(DependencyObject child) => + base.RemoveLogicalChild(child); - private void OnLoaded(object sender, RoutedEventArgs e) - { - InitializeElement(); - CanvasCore.Invalidated += OnCanvasCoreInvalidated; - } + private void OnLoaded(object sender, RoutedEventArgs e) => + _settings.Initialize(CanvasCore); - private void OnUnloaded(object sender, RoutedEventArgs e) - { - CanvasCore.Invalidated -= OnCanvasCoreInvalidated; - CanvasCore.Dispose(); - } - - private async void RunDrawingLoop() - { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); - - while (!CanvasCore.IsValid) - { - _skiaElement?.InvalidateVisual(); -#if NET6_0_OR_GREATER - // workaround #250115 - _skiaGlElement?.InvalidateVisual(); -#endif - await Task.Delay(ts); - } - - _isDrawingLoopRunning = false; - } + private void OnUnloaded(object sender, RoutedEventArgs e) => + _settings.Dispose(CanvasCore); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs new file mode 100644 index 000000000..6931919b0 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs @@ -0,0 +1,75 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Windows; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp.Views.Desktop; +using SkiaSharp.Views.WPF; + +namespace LiveChartsCore.SkiaSharpView.WPF.Rendering; + +internal class CPURenderMode : SKElement, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + } + + public void InvalidateRenderer() => + InvalidateVisual(); + + private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) + { + var density = GetPixelDensity(); + if (density.dpix != 1 || density.dpiy != 1) + args.Surface.Canvas.Scale(density.dpix, density.dpiy); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface)); + } + + private ResolutionHelper GetPixelDensity() + { + var presentationSource = PresentationSource.FromVisual(this); + if (presentationSource is null) return new(1f, 1f); + var compositionTarget = presentationSource.CompositionTarget; + if (compositionTarget is null) return new(1f, 1f); + + var matrix = compositionTarget.TransformToDevice; + return new((float)matrix.M11, (float)matrix.M22); + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs new file mode 100644 index 000000000..e6ace58ea --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs @@ -0,0 +1,60 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Windows.Media; +using LiveChartsCore.Motion; + +namespace LiveChartsCore.SkiaSharpView.WPF.Rendering; + +internal class CompositionTargetTicker : IFrameTicker +{ + private IRenderMode _renderMode = null!; + private CoreMotionCanvas _canvas = null!; + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + _canvas = canvas; + _renderMode = renderMode; + + _canvas.Invalidated += OnCoreInvalidated; + CompositionTarget.Rendering += OnCompositonTargetRendering; + } + + private void OnCoreInvalidated(CoreMotionCanvas obj) => + _renderMode.InvalidateRenderer(); + + private void OnCompositonTargetRendering(object? sender, EventArgs e) + { + if (_canvas.IsValid) return; + _renderMode.InvalidateRenderer(); + } + + public void DisposeTicker() + { + CompositionTarget.Rendering -= OnCompositonTargetRendering; + _canvas.Invalidated -= OnCoreInvalidated; + + _canvas = null!; + _renderMode = null!; + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs new file mode 100644 index 000000000..f961e1fd8 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs @@ -0,0 +1,84 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Desktop; +using SkiaSharp.Views.WPF; + +namespace LiveChartsCore.SkiaSharpView.WPF.Rendering; + +internal class GPURenderMode : SKGLElement, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + Dispose(); + } + + public void InvalidateRenderer() => + InvalidateVisual(); + + private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) + { + var density = GetPixelDensity(); + if (density.dpix != 1 || density.dpiy != 1) + args.Surface.Canvas.Scale(density.dpix, density.dpiy); + + var c = ((Control)Parent).Background is not SolidColorBrush bg + ? Colors.White + : bg.Color; + + FrameRequest?.Invoke( + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, new SKColor(c.R, c.G, c.B))); + } + + private ResolutionHelper GetPixelDensity() + { + var presentationSource = PresentationSource.FromVisual(this); + if (presentationSource is null) return new(1f, 1f); + var compositionTarget = presentationSource.CompositionTarget; + if (compositionTarget is null) return new(1f, 1f); + + var matrix = compositionTarget.TransformToDevice; + return new((float)matrix.M11, (float)matrix.M22); + } +} From 548663195b878d2d328f31aa571703ea004f6f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:33:34 -0600 Subject: [PATCH 21/94] WinUI/UNO add render modes --- src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 83 ++++--------------- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 80 ++++++++++++++++++ .../_Shared.WinUI/Rendering/GPURenderMode.cs | 80 ++++++++++++++++++ .../_Shared.WinUI/_Shared.WinUI.projitems | 2 + 4 files changed, 180 insertions(+), 65 deletions(-) create mode 100644 src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs create mode 100644 src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index 1d61cfbbd..7f3f5a2f7 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -20,13 +20,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; -using System.Threading.Tasks; +using LiveChartsCore.Behaviours; using LiveChartsCore.Motion; -using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.WinUI.Rendering; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using SkiaSharp.Views.Windows; + +#pragma warning disable IDE0028 // Simplify collection initialization namespace LiveChartsCore.SkiaSharpView.WinUI; @@ -35,84 +35,37 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// public partial class MotionCanvas : Canvas { - private readonly SKXamlCanvas? _skiaElement; - private bool _isDrawingLoopRunning; + private readonly CanvasRenderSettings _settings; /// /// Initializes a new instance of the class. /// public MotionCanvas() { + _settings = new(); + + Children.Add((UIElement)_settings.RenderMode); + Loaded += OnLoaded; Unloaded += OnUnloaded; SizeChanged += OnSizeChanged; - -#pragma warning disable IDE0028 // Simplify collection initialization - _skiaElement = new(); -#pragma warning restore IDE0028 // Simplify collection initialization - Children.Add(_skiaElement); - SetLeft(_skiaElement, 0); - SetTop(_skiaElement, 0); - - _skiaElement.PaintSurface += OnPaintSurface; } - /// - /// Gets the canvas core. - /// - /// - /// The canvas core. - /// + /// public CoreMotionCanvas CanvasCore { get; } = new(); - private void OnLoaded(object sender, RoutedEventArgs e) => - CanvasCore.Invalidated += OnCanvasCoreInvalidated; - - private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) - { -#if HAS_UNO_WINUI - var scale = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f; - args.Surface.Canvas.Scale((float)scale, (float)scale); - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); -#else - var scaleFactor = XamlRoot.RasterizationScale; - args.Surface.Canvas.Scale((float)scaleFactor, (float)scaleFactor); - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); -#endif - } - - private async void RunDrawingLoop() + private void OnSizeChanged(object sender, SizeChangedEventArgs e) { - if (_isDrawingLoopRunning || _skiaElement == null) return; - _isDrawingLoopRunning = true; + var fe = (FrameworkElement)_settings.RenderMode; - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); - - while (!CanvasCore.IsValid) - { - _skiaElement?.Invalidate(); - await Task.Delay(ts); - } - - _isDrawingLoopRunning = false; + fe.Width = e.NewSize.Width; + fe.Height = e.NewSize.Height; } - private void OnCanvasCoreInvalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - if (_skiaElement == null) return; - _skiaElement.Width = e.NewSize.Width; - _skiaElement.Height = e.NewSize.Height; - } + private void OnLoaded(object sender, RoutedEventArgs e) => + _settings.Initialize(CanvasCore); - private void OnUnloaded(object sender, RoutedEventArgs e) - { - CanvasCore.Invalidated -= OnCanvasCoreInvalidated; - CanvasCore.Dispose(); - } + private void OnUnloaded(object sender, RoutedEventArgs e) => + _settings.Dispose(CanvasCore); } diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs new file mode 100644 index 000000000..c8f83c075 --- /dev/null +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -0,0 +1,80 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Windows; + +namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; + +internal partial class CPURenderMode : SKXamlCanvas, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + } + + private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) + { + var density = GetPixelDensity(); + if (density.DpiX != 1 || density.DpiY != 1) + e.Surface.Canvas.Scale(density.DpiX, density.DpiY); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, SKColor.Empty)); + } + + public void InvalidateRenderer() => + Invalidate(); + + private PixelDensity GetPixelDensity() + { +#if HAS_UNO_WINUI + var d = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f; + return new(d, d); +#else + var scaleFactor = (float)XamlRoot.RasterizationScale; + return new(scaleFactor, scaleFactor); +#endif + } + + private readonly struct PixelDensity(float dpiX, float dpiY) + { + public float DpiX { get; } = dpiX; + public float DpiY { get; } = dpiY; + } +} diff --git a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs new file mode 100644 index 000000000..970cc2529 --- /dev/null +++ b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs @@ -0,0 +1,80 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using SkiaSharp; +using SkiaSharp.Views.Windows; + +namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; + +internal partial class GPURenderMode : SKSwapChainPanel, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + } + + private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) + { + var density = GetPixelDensity(); + if (density.DpiX != 1 || density.DpiY != 1) + e.Surface.Canvas.Scale(density.DpiX, density.DpiY); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, SKColor.Empty)); + } + + public void InvalidateRenderer() => + Invalidate(); + + private PixelDensity GetPixelDensity() + { +#if HAS_UNO_WINUI + var d = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f; + return new(d, d); +#else + var scaleFactor = (float)XamlRoot.RasterizationScale; + return new(scaleFactor, scaleFactor); +#endif + } + + private readonly struct PixelDensity(float dpiX, float dpiY) + { + public float DpiX { get; } = dpiX; + public float DpiY { get; } = dpiY; + } +} diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index 26f8018dc..957a87a09 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -15,6 +15,8 @@ + + From dc7d487685d8276c8cb690d6cbdccd88598567ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:34:55 -0600 Subject: [PATCH 22/94] maui add render modes --- .../MotionCanvas.cs | 216 +++++++++++++++--- .../Rendering/CPURenderMode.cs | 72 ++++++ .../Rendering/GPURenderMode.cs | 72 ++++++ 3 files changed, 322 insertions(+), 38 deletions(-) create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs index 6d7443230..ccddb8bbc 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs @@ -21,12 +21,12 @@ // SOFTWARE. using System; -using System.Threading.Tasks; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using Microsoft.Maui.Controls; using Microsoft.Maui.Devices; using Microsoft.Maui.Layouts; +using SkiaSharp; using SkiaSharp.Views.Maui; using SkiaSharp.Views.Maui.Controls; @@ -37,9 +37,7 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// public class MotionCanvas : AbsoluteLayout { - private bool _isDrawingLoopRunning = false; - private bool _isLoaded = true; - private double _density = 1; + private readonly Renderer _renderer; private SKCanvasView? _canvasView; private SKGLView? _glView; @@ -50,11 +48,11 @@ public MotionCanvas() { InitializeView(); + CanvasCore = new(); + _renderer = new(CanvasCore, InvalidateChart); + Loaded += OnLoaded; Unloaded += OnUnloaded; - - _density = DeviceDisplay.MainDisplayInfo.Density; - DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; } /// @@ -65,30 +63,32 @@ public MotionCanvas() /// public CoreMotionCanvas CanvasCore { get; } = new(); - /// - /// Invalidates this instance. - /// - /// - public void Invalidate() => - RunDrawingLoop(); + private void InvalidateChart() + { + _canvasView?.InvalidateSurface(); + _glView?.InvalidateSurface(); + } private void OnCanvasViewPaintSurface(object? sender, SKPaintSurfaceEventArgs args) { - args.Surface.Canvas.Scale((float)_density, (float)_density); + if (_renderer.Density != 1) + args.Surface.Canvas.Scale(_renderer.Density, _renderer.Density); + CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); + new SkiaSharpDrawingContext( + CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); } private void OnGlViewPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) { - args.Surface.Canvas.Scale((float)_density, (float)_density); + if (_renderer.Density != 1) + args.Surface.Canvas.Scale(_renderer.Density, _renderer.Density); + CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, new SkiaSharp.SKImageInfo((int)Width, (int)Height), args.Surface, args.Surface.Canvas)); + new SkiaSharpDrawingContext( + CanvasCore, new SKImageInfo((int)Width, (int)Height), args.Surface, args.Surface.Canvas)); } - private void OnCanvasCoreInvalidated(CoreMotionCanvas sender) => - Invalidate(); - private void InitializeView() { if (LiveCharts.UseGPU) @@ -113,36 +113,176 @@ private void InitializeView() } } - private async void RunDrawingLoop() + private void OnLoaded(object? sender, EventArgs e) => + _renderer.Start(); + + private void OnUnloaded(object? sender, EventArgs e) + { + _renderer.Stop(); + CanvasCore.Dispose(); + } +} + +/// +/// Defines the renderer class for Maui. +/// +public partial class Renderer +{ + private readonly CoreMotionCanvas _canvas; + private readonly Action _invalidator; + + /// + /// Gets the screen density of the device. + /// + public float Density { get; private set; } = 1; + + /// + /// Initializes a new instance of the class. + /// + /// The livecharts canvas. + /// The action to invalidate the canvas. + public Renderer(CoreMotionCanvas canvas, Action invalidator) { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; + Density = (float)DeviceDisplay.MainDisplayInfo.Density; + DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); + _canvas = canvas; + _invalidator = invalidator; + } - while (!CanvasCore.IsValid && _isLoaded) + /// + /// Starts the rendering loop for the canvas. + /// + public void Start() + { + if (!LiveCharts.UseVSync) { - _canvasView?.InvalidateSurface(); - _glView?.InvalidateSurface(); - await Task.Delay(ts); + _canvas.Invalidated += StartLiveChartsDrawingLoop; + return; } - _isDrawingLoopRunning = false; +#if WINDOWS + StartWindowsRenderer(); +#elif ANDROID + StartAndroidRenderer(); +#else + // if no platform-specific renderer is available, + // then use the livecharts drawing loop, + // slower but works on all platforms + + _canvas.Invalidated += StartLiveChartsDrawingLoop; +#endif } - private void OnLoaded(object? sender, EventArgs e) + /// + /// Ends the rendering loop for the canvas. + /// + public void Stop() { - _isLoaded = true; - CanvasCore.Invalidated += OnCanvasCoreInvalidated; + if (!LiveCharts.UseVSync) + { + _canvas.Invalidated -= StartLiveChartsDrawingLoop; + return; + } + +#if WINDOWS + StopWindowsRenderer(); +#elif ANDROID + StopAndroidRenderer(); +#else + _canvas.Invalidated -= StartLiveChartsDrawingLoop; +#endif } - private void OnUnloaded(object? sender, EventArgs e) + private void StartLiveChartsDrawingLoop(CoreMotionCanvas canvas) => + _canvas.RunDrawingLoop(_invalidator); + + private void MainDisplayInfoChanged(object? sender, EventArgs e) => + Density = (float)DeviceDisplay.MainDisplayInfo.Density; +} + +#if WINDOWS + +public partial class Renderer +{ + private void StartWindowsRenderer() { - _isLoaded = false; - CanvasCore.Invalidated -= OnCanvasCoreInvalidated; - CanvasCore.Dispose(); + Microsoft.UI.Xaml.Media.CompositionTarget.Rendering += OnRendering; + _canvas.Invalidated += OnLiveChartsCanvasInvalidated; } - private void MainDisplayInfoChanged(object? sender, EventArgs e) => - _density = DeviceDisplay.MainDisplayInfo.Density; + private void StopWindowsRenderer() + { + Microsoft.UI.Xaml.Media.CompositionTarget.Rendering -= OnRendering; + _canvas.Invalidated -= OnLiveChartsCanvasInvalidated; + } + + private void OnRendering(object? sender, object e) + { + // this is called on every vsync tick + if (_canvas.IsValid) return; + _invalidator(); + } + + private void OnLiveChartsCanvasInvalidated(CoreMotionCanvas obj) => + // this is the first call to invalidate the canvas + // when livecharts detect a change in the data/properties + _invalidator(); +} + +#endif + +#if ANDROID + +public class VSyncTicker(Action onFrameTick) + : Java.Lang.Object, Android.Views.Choreographer.IFrameCallback +{ + private readonly Android.Views.Choreographer _chor = Android.Views.Choreographer.Instance!; + + public void Start() => + _chor.PostFrameCallback(this); + + // frameTimeNanos: + // the absolute timestamp (in nanoseconds) that the system’s choreographer assigns to this frame. + public void DoFrame(long frameTimeNanos) + { + onFrameTick(); + _chor.PostFrameCallback(this); + } + + public void Stop() => + _chor.RemoveFrameCallback(this); } + +public partial class Renderer +{ + private VSyncTicker _vsyncTicker = null!; + + private void StartAndroidRenderer() + { + _vsyncTicker = new VSyncTicker(OnRendering); + _vsyncTicker.Start(); + _canvas.Invalidated += OnLiveChartsCanvasInvalidated; + } + + private void StopAndroidRenderer() + { + _vsyncTicker.Stop(); + _vsyncTicker = null!; + _canvas.Invalidated -= OnLiveChartsCanvasInvalidated; + } + + private void OnRendering() + { + // this is called on every vsync tick + if (_canvas.IsValid) return; + _invalidator(); + } + + private void OnLiveChartsCanvasInvalidated(CoreMotionCanvas obj) => + // this is the first call to invalidate the canvas + // when livecharts detect a change in the data/properties + _invalidator(); +} + +#endif diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs new file mode 100644 index 000000000..acbc8fa6b --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs @@ -0,0 +1,72 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.Maui.Devices; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace LiveChartsCore.SkiaSharpView.Maui.Rendering; + +internal class CPURenderMode : SKCanvasView, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + private float _pixelDensity = 1; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; + DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + DeviceDisplay.MainDisplayInfoChanged -= MainDisplayInfoChanged; + } + + public void InvalidateRenderer() => + InvalidateSurface(); + + private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) + { + if (_pixelDensity != 1) + args.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface)); + } + + private void MainDisplayInfoChanged(object? sender, EventArgs e) => + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs new file mode 100644 index 000000000..73cfce411 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs @@ -0,0 +1,72 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.Maui.Devices; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace LiveChartsCore.SkiaSharpView.Maui.Rendering; + +internal class GPURenderMode : SKGLView, IRenderMode +{ + private CoreMotionCanvas _canvas = null!; + private float _pixelDensity = 1; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + _canvas = canvas; + PaintSurface += OnPaintSurface; + + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; + DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; + +#if DEBUG + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); +#endif + } + + public void DisposeRenderMode() + { + _canvas = null!; + PaintSurface -= OnPaintSurface; + DeviceDisplay.MainDisplayInfoChanged -= MainDisplayInfoChanged; + } + + public void InvalidateRenderer() => + InvalidateSurface(); + + private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) + { + if (_pixelDensity != 1) + e.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); + + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface)); + } + + private void MainDisplayInfoChanged(object? sender, EventArgs e) => + _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; +} From 34c437154c5765e70d02e6c701f7c90345f3b8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:36:29 -0600 Subject: [PATCH 23/94] simplify skiasharpdrawingcontext --- .../Drawing/SkiaSharpDrawingContext.cs | 14 +++++--------- .../SKCharts/InMemorySkiaSharpChart.cs | 17 +++++++---------- .../SKCharts/SKGeoMap.cs | 7 +++---- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs index 0400e7957..193e211c2 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs @@ -36,13 +36,11 @@ namespace LiveChartsCore.SkiaSharpView.Drawing; /// The motion canvas. /// The information. /// The surface. -/// The canvas. /// Indicates whether the canvas is cleared on frame draw. public class SkiaSharpDrawingContext( CoreMotionCanvas motionCanvas, SKImageInfo info, - SKSurface? surface, - SKCanvas canvas, + SKSurface surface, bool clearOnBeginDraw = true) : DrawingContext { @@ -52,17 +50,15 @@ public class SkiaSharpDrawingContext( /// The motion canvas. /// The information. /// The surface. - /// The canvas. /// The background. /// Indicates whether the canvas is cleared on frame draw. public SkiaSharpDrawingContext( CoreMotionCanvas motionCanvas, SKImageInfo info, - SKSurface? surface, - SKCanvas canvas, + SKSurface surface, SKColor background, bool clearOnBeginDraw = true) - : this(motionCanvas, info, surface, canvas, clearOnBeginDraw) + : this(motionCanvas, info, surface, clearOnBeginDraw) { Background = background; } @@ -89,7 +85,7 @@ public SkiaSharpDrawingContext( /// /// The surface. /// - public SKSurface? Surface { get; set; } = surface; + public SKSurface Surface { get; set; } = surface; /// /// Gets or sets the canvas. @@ -97,7 +93,7 @@ public SkiaSharpDrawingContext( /// /// The canvas. /// - public SKCanvas Canvas { get; set; } = canvas; + public SKCanvas Canvas => Surface.Canvas; /// /// Gets or sets the paint. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs index 99d7f80cd..661062e58 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs @@ -94,7 +94,7 @@ public virtual SKImage GetImage() using var surface = SKSurface.Create(new SKImageInfo(Width, Height)); using var canvas = surface.Canvas; - DrawOnCanvas(canvas, surface); + DrawOnCanvas(surface); return surface.Snapshot(); } @@ -129,19 +129,18 @@ public virtual void SaveImage(string path, SKEncodedImageFormat format = SKEncod /// /// Draws the image to the specified canvas. /// - /// The canvas + /// The surface. /// Indicates whether the canvas should be cleared when the draw starts, default is false. - public virtual void SaveImage(SKCanvas canvas, bool clearCanvasOnBeginDraw = false) => - DrawOnCanvas(canvas, null, clearCanvasOnBeginDraw); + public virtual void SaveImage(SKSurface surface, bool clearCanvasOnBeginDraw = false) => + DrawOnCanvas(surface, clearCanvasOnBeginDraw); /// /// Draws the chart to the specified canvas. /// - /// The canvas. /// The surface. /// [probably an obsolete param] Indicates whether the canvas should be cleared when the draw starts, default is false. /// - public virtual void DrawOnCanvas(SKCanvas canvas, SKSurface? surface = null, bool clearCanvasOnBeginDraw = false) + public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw = false) { if (CoreChart is null || CoreChart is not Chart skiaChart) throw new Exception("Something is missing :("); @@ -152,8 +151,7 @@ public virtual void DrawOnCanvas(SKCanvas canvas, SKSurface? surface = null, boo new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface!, - canvas, + surface, Background, clearCanvasOnBeginDraw)); @@ -172,8 +170,7 @@ public virtual void DrawOnCanvas(SKCanvas canvas, SKSurface? surface = null, boo new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface!, - canvas, + surface, Background, clearCanvasOnBeginDraw)); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs index 2fe9f2de9..cfd243648 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs @@ -120,8 +120,8 @@ public object? ViewCommand } } - /// - public override void DrawOnCanvas(SKCanvas canvas, SKSurface? surface, bool clearCanvasOnBeginDraw = false) + /// + public override void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw = false) { Canvas.DisableAnimations = true; @@ -131,8 +131,7 @@ public override void DrawOnCanvas(SKCanvas canvas, SKSurface? surface, bool clea new SkiaSharpDrawingContext( Canvas, new SKImageInfo(Width, Height), - surface!, - canvas, + surface, Background, clearCanvasOnBeginDraw)); From f1c2e8acc0a2ad9ce39d93b0928816507ec51f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 08:37:41 -0600 Subject: [PATCH 24/94] add default render settings --- .../LiveChartsSkiaSharp.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index c45c6c702..5d1a20f5b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -50,9 +50,21 @@ public static class LiveChartsSkiaSharp /// The settings. public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) { - if (!LiveCharts.HasBackend) _ = settings.AddSkiaSharp(); - if (!LiveCharts.HasDefaultTheme) _ = settings.AddDefaultTheme(); - if (!LiveCharts.HasDefaultMappers) _ = settings.AddDefaultMappers(); + if (!LiveCharts.s_hasBackend) + _ = settings.AddSkiaSharp(); + + if (!LiveCharts.s_hasDefaultTheme) + _ = settings.AddDefaultTheme(); + + if (!LiveCharts.s_hasDefaultMappers) + _ = settings.AddDefaultMappers(); + + if (!LiveCharts.s_hasDefaultHardwareAcceleration) + _ = settings.RenderingSettings( + useHardwareAcceleration: true, + tryUseVSync: true, + targetFps: 60, // 60 as a fallback when VSync is not available + showFps: false); return settings; } @@ -64,7 +76,7 @@ public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) /// public static LiveChartsSettings AddSkiaSharp(this LiveChartsSettings settings) { - LiveCharts.HasBackend = true; + LiveCharts.s_hasBackend = true; PropertyDefinition.Parsers[typeof(Paint)] = HexToPaintTypeConverter.Parse; PropertyDefinition.Parsers[typeof(LvcColor)] = HexToLvcColorTypeConverter.Parse; From c043a065f34d390bcd7f5a3628b6c136a43cf59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 12:04:51 -0600 Subject: [PATCH 25/94] update maui motion canvas --- .../MotionCanvas.cs | 249 +----------------- 1 file changed, 12 insertions(+), 237 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs index ccddb8bbc..c5228f48c 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs @@ -22,13 +22,10 @@ using System; using LiveChartsCore.Motion; -using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.Behaviours; +using LiveChartsCore.SkiaSharpView.Maui.Rendering; using Microsoft.Maui.Controls; -using Microsoft.Maui.Devices; using Microsoft.Maui.Layouts; -using SkiaSharp; -using SkiaSharp.Views.Maui; -using SkiaSharp.Views.Maui.Controls; namespace LiveChartsCore.SkiaSharpView.Maui; @@ -37,252 +34,30 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// public class MotionCanvas : AbsoluteLayout { - private readonly Renderer _renderer; - private SKCanvasView? _canvasView; - private SKGLView? _glView; + private readonly CanvasRenderSettings _settings; /// /// Initializes a new instance of the class. /// public MotionCanvas() { - InitializeView(); + _settings = new(); - CanvasCore = new(); - _renderer = new(CanvasCore, InvalidateChart); + var view = (View)_settings.RenderMode; + AbsoluteLayout.SetLayoutBounds(view, new(0, 0, 1, 1)); + AbsoluteLayout.SetLayoutFlags(view, AbsoluteLayoutFlags.SizeProportional | AbsoluteLayoutFlags.PositionProportional); + Children.Add(view); Loaded += OnLoaded; Unloaded += OnUnloaded; } - /// - /// Gets the canvas core. - /// - /// - /// The canvas core. - /// + /// public CoreMotionCanvas CanvasCore { get; } = new(); - private void InvalidateChart() - { - _canvasView?.InvalidateSurface(); - _glView?.InvalidateSurface(); - } - - private void OnCanvasViewPaintSurface(object? sender, SKPaintSurfaceEventArgs args) - { - if (_renderer.Density != 1) - args.Surface.Canvas.Scale(_renderer.Density, _renderer.Density); - - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext( - CanvasCore, args.Info, args.Surface, args.Surface.Canvas)); - } - - private void OnGlViewPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) - { - if (_renderer.Density != 1) - args.Surface.Canvas.Scale(_renderer.Density, _renderer.Density); - - CanvasCore.DrawFrame( - new SkiaSharpDrawingContext( - CanvasCore, new SKImageInfo((int)Width, (int)Height), args.Surface, args.Surface.Canvas)); - } - - private void InitializeView() - { - if (LiveCharts.UseGPU) - { - _glView = new SKGLView(); - _glView.PaintSurface += OnGlViewPaintSurface; - - AbsoluteLayout.SetLayoutBounds(_glView, new(0, 0, 1, 1)); - AbsoluteLayout.SetLayoutFlags(_glView, AbsoluteLayoutFlags.SizeProportional | AbsoluteLayoutFlags.PositionProportional); - - Children.Add(_glView); - } - else - { - _canvasView = new SKCanvasView(); - _canvasView.PaintSurface += OnCanvasViewPaintSurface; - - AbsoluteLayout.SetLayoutBounds(_canvasView, new(0, 0, 1, 1)); - AbsoluteLayout.SetLayoutFlags(_canvasView, AbsoluteLayoutFlags.SizeProportional | AbsoluteLayoutFlags.PositionProportional); - - Children.Add(_canvasView); - } - } - private void OnLoaded(object? sender, EventArgs e) => - _renderer.Start(); - - private void OnUnloaded(object? sender, EventArgs e) - { - _renderer.Stop(); - CanvasCore.Dispose(); - } -} - -/// -/// Defines the renderer class for Maui. -/// -public partial class Renderer -{ - private readonly CoreMotionCanvas _canvas; - private readonly Action _invalidator; - - /// - /// Gets the screen density of the device. - /// - public float Density { get; private set; } = 1; - - /// - /// Initializes a new instance of the class. - /// - /// The livecharts canvas. - /// The action to invalidate the canvas. - public Renderer(CoreMotionCanvas canvas, Action invalidator) - { - Density = (float)DeviceDisplay.MainDisplayInfo.Density; - DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; - - _canvas = canvas; - _invalidator = invalidator; - } - - /// - /// Starts the rendering loop for the canvas. - /// - public void Start() - { - if (!LiveCharts.UseVSync) - { - _canvas.Invalidated += StartLiveChartsDrawingLoop; - return; - } - -#if WINDOWS - StartWindowsRenderer(); -#elif ANDROID - StartAndroidRenderer(); -#else - // if no platform-specific renderer is available, - // then use the livecharts drawing loop, - // slower but works on all platforms - - _canvas.Invalidated += StartLiveChartsDrawingLoop; -#endif - } + _settings.Initialize(CanvasCore); - /// - /// Ends the rendering loop for the canvas. - /// - public void Stop() - { - if (!LiveCharts.UseVSync) - { - _canvas.Invalidated -= StartLiveChartsDrawingLoop; - return; - } - -#if WINDOWS - StopWindowsRenderer(); -#elif ANDROID - StopAndroidRenderer(); -#else - _canvas.Invalidated -= StartLiveChartsDrawingLoop; -#endif - } - - private void StartLiveChartsDrawingLoop(CoreMotionCanvas canvas) => - _canvas.RunDrawingLoop(_invalidator); - - private void MainDisplayInfoChanged(object? sender, EventArgs e) => - Density = (float)DeviceDisplay.MainDisplayInfo.Density; -} - -#if WINDOWS - -public partial class Renderer -{ - private void StartWindowsRenderer() - { - Microsoft.UI.Xaml.Media.CompositionTarget.Rendering += OnRendering; - _canvas.Invalidated += OnLiveChartsCanvasInvalidated; - } - - private void StopWindowsRenderer() - { - Microsoft.UI.Xaml.Media.CompositionTarget.Rendering -= OnRendering; - _canvas.Invalidated -= OnLiveChartsCanvasInvalidated; - } - - private void OnRendering(object? sender, object e) - { - // this is called on every vsync tick - if (_canvas.IsValid) return; - _invalidator(); - } - - private void OnLiveChartsCanvasInvalidated(CoreMotionCanvas obj) => - // this is the first call to invalidate the canvas - // when livecharts detect a change in the data/properties - _invalidator(); -} - -#endif - -#if ANDROID - -public class VSyncTicker(Action onFrameTick) - : Java.Lang.Object, Android.Views.Choreographer.IFrameCallback -{ - private readonly Android.Views.Choreographer _chor = Android.Views.Choreographer.Instance!; - - public void Start() => - _chor.PostFrameCallback(this); - - // frameTimeNanos: - // the absolute timestamp (in nanoseconds) that the system’s choreographer assigns to this frame. - public void DoFrame(long frameTimeNanos) - { - onFrameTick(); - _chor.PostFrameCallback(this); - } - - public void Stop() => - _chor.RemoveFrameCallback(this); -} - -public partial class Renderer -{ - private VSyncTicker _vsyncTicker = null!; - - private void StartAndroidRenderer() - { - _vsyncTicker = new VSyncTicker(OnRendering); - _vsyncTicker.Start(); - _canvas.Invalidated += OnLiveChartsCanvasInvalidated; - } - - private void StopAndroidRenderer() - { - _vsyncTicker.Stop(); - _vsyncTicker = null!; - _canvas.Invalidated -= OnLiveChartsCanvasInvalidated; - } - - private void OnRendering() - { - // this is called on every vsync tick - if (_canvas.IsValid) return; - _invalidator(); - } - - private void OnLiveChartsCanvasInvalidated(CoreMotionCanvas obj) => - // this is the first call to invalidate the canvas - // when livecharts detect a change in the data/properties - _invalidator(); + private void OnUnloaded(object? sender, EventArgs e) => + _settings.Dispose(CanvasCore); } - -#endif From 473abb99593a784b4f3b1334613b34077201ebc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 12:05:09 -0600 Subject: [PATCH 26/94] update maui sample to net9 --- samples/MauiSample/MauiSample.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/MauiSample/MauiSample.csproj b/samples/MauiSample/MauiSample.csproj index 6da85a0d9..14586be7d 100644 --- a/samples/MauiSample/MauiSample.csproj +++ b/samples/MauiSample/MauiSample.csproj @@ -3,8 +3,8 @@ enable $(GlobalLangVersion) - net8.0-android;net8.0-ios;net8.0-maccatalyst - $(TargetFrameworks);net8.0-windows10.0.19041.0 + net9.0-android;net9.0-ios;net9.0-maccatalyst + $(TargetFrameworks);net9.0-windows10.0.19041.0 From 3d8d89fd4595fd9db4583af0f5fd292f27ef1b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 12:35:27 -0600 Subject: [PATCH 27/94] use latest skiasharp version in winui --- .../LiveChartsCore.SkiaSharpView.WinUI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj index a14bc94db..8c358518f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj @@ -59,8 +59,8 @@ - - + + From eb407e58adf9ad223329e518459bdd5c0bac6304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 17:19:03 -0600 Subject: [PATCH 28/94] update uno sample --- .../Properties/launchSettings.json | 9 ++++++++ .../Styles/ColorPaletteOverride.xaml | 22 +++++++++---------- .../UnoPlatformSample.csproj | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/samples/UnoPlatformSample/UnoPlatformSample/Properties/launchSettings.json b/samples/UnoPlatformSample/UnoPlatformSample/Properties/launchSettings.json index e1d2b819a..d92434e82 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample/Properties/launchSettings.json +++ b/samples/UnoPlatformSample/UnoPlatformSample/Properties/launchSettings.json @@ -27,6 +27,15 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, + // Note: In order to select this profile, you'll need to comment the `Packaged` profile below until this is fixed: https://aka.platform.uno/wasdk-maui-debug-profile-issue + "UnoApp1 (WinAppSDK Unpackaged)": { + "commandName": "Project", + "compatibleTargetFramework": "windows" + }, + "UnoApp1 (WinAppSDK Packaged)": { + "commandName": "MsixPackage", + "compatibleTargetFramework": "windows" + }, "UnoPlatformSample (Desktop)": { "commandName": "Project", "compatibleTargetFramework": "desktop" diff --git a/samples/UnoPlatformSample/UnoPlatformSample/Styles/ColorPaletteOverride.xaml b/samples/UnoPlatformSample/UnoPlatformSample/Styles/ColorPaletteOverride.xaml index b47f33098..6993f7850 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample/Styles/ColorPaletteOverride.xaml +++ b/samples/UnoPlatformSample/UnoPlatformSample/Styles/ColorPaletteOverride.xaml @@ -1,8 +1,9 @@ - + #5946D2 + #5946D2 #FFFFFF #E5DEFF #170065 @@ -15,8 +16,8 @@ #CFE4FF #001D36 #B3261E - #F9DEDC #FFFFFF + #F9DEDC #410E0B #FCFBFF #1C1B1F @@ -25,14 +26,14 @@ #F2EFF5 #8B8494 #79747E - #F4EFF4 - #313033 - #C8BFFF - #5946D2 #C9C5D0 + #E6E1E5 + #1C1B1F + #2A009F #C7BFFF + #C7BFFF #2A009F #4129BA #E4DFFF @@ -45,8 +46,8 @@ #00497D #D1E4FF #FFB4AB - #93000A #690005 + #93000A #FFDAD6 #1C1B1F #E5E1E6 @@ -55,11 +56,10 @@ #47464F #C9C5D0 #928F99 - #1C1B1F + #57545D #E6E1E5 + #1C1B1F #2A009F - #C7BFFF - #57545D - + \ No newline at end of file diff --git a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj index 4f1809299..2f2122449 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj +++ b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj @@ -1,6 +1,6 @@  - net9.0-android;net9.0-ios;net9.0-browserwasm;net9.0-desktop + net9.0-android;net9.0-ios;net9.0-windows10.0.26100;net9.0-browserwasm;net9.0-desktop Exe true From 2173683bb51e48cdc273c4e006221788da1ebd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 17:20:01 -0600 Subject: [PATCH 29/94] smaples updates --- samples/WPFSample/Axes/Style/View.xaml | 3 ++- samples/WinUISample/WinUISample/MainWindow.xaml | 2 +- samples/WinUISample/WinUISample/Samples/Axes/Paging/View.xaml | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/WPFSample/Axes/Style/View.xaml b/samples/WPFSample/Axes/Style/View.xaml index 2d4fb504b..5bacc9f17 100644 --- a/samples/WPFSample/Axes/Style/View.xaml +++ b/samples/WPFSample/Axes/Style/View.xaml @@ -5,7 +5,7 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF" xmlns:vms="clr-namespace:ViewModelsSamples.Axes.Style;assembly=ViewModelsSamples" - Background="#303030"> + > @@ -19,6 +19,7 @@ - + From fe3d82d22e361cba3ab9a3e883dcd30aa9b01bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 17:20:39 -0600 Subject: [PATCH 30/94] update uno to latest skiasharp --- .../LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index dfd76f94a..f63e4adaa 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -66,10 +66,10 @@ - + - + From a48e6034c9c0300a8d3455291b0fe6e28e17311c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 17:21:05 -0600 Subject: [PATCH 31/94] internals to maui --- src/LiveChartsCore.Behaviours/AssemblyInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LiveChartsCore.Behaviours/AssemblyInfo.cs b/src/LiveChartsCore.Behaviours/AssemblyInfo.cs index e5069d257..bad75d9bf 100644 --- a/src/LiveChartsCore.Behaviours/AssemblyInfo.cs +++ b/src/LiveChartsCore.Behaviours/AssemblyInfo.cs @@ -24,3 +24,4 @@ [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WinUI")] [assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.Uno.WinUI")] +[assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.Maui")] From 5cfedd8bcaf88839e5b1c0da2e8ce0b27e422ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 17:21:19 -0600 Subject: [PATCH 32/94] experimental bg --- .../Rendering/GPURenderMode.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs index f961e1fd8..d403f24b2 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs @@ -63,12 +63,8 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) if (density.dpix != 1 || density.dpiy != 1) args.Surface.Canvas.Scale(density.dpix, density.dpiy); - var c = ((Control)Parent).Background is not SolidColorBrush bg - ? Colors.White - : bg.Color; - FrameRequest?.Invoke( - new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, new SKColor(c.R, c.G, c.B))); + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, SKColor.Empty)); } private ResolutionHelper GetPixelDensity() @@ -81,4 +77,19 @@ private ResolutionHelper GetPixelDensity() var matrix = compositionTarget.TransformToDevice; return new((float)matrix.M11, (float)matrix.M22); } + + private SKColor GetBackground(DependencyObject? element) + { + if (element is not FrameworkElement fe) + return SKColors.Transparent; + + if (fe is Control control && control.Background is SolidColorBrush bg) + return new SKColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A); + + var parent = fe.Parent ?? fe.TemplatedParent; + + return parent is null + ? SKColors.Transparent + : GetBackground(parent); + } } From 0fcfcd2431480c3e2b16ecd7c8682a295ee8ba8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 21 Jul 2025 17:21:27 -0600 Subject: [PATCH 33/94] update note --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index f0cf542a0..98e1f3ed1 100644 --- a/readme.txt +++ b/readme.txt @@ -1 +1 @@ -For Uno, open ./samples/UnoPlatform_v5/UnoPlatform.slnx +For Uno, open ./samples/UnoPlatform/UnoPlatform.slnx From 53492ebd78e75bda421c6844e4fbb80dbc45482d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 12:30:02 -0600 Subject: [PATCH 34/94] smarter tooltip changes detection --- src/LiveChartsCore/Chart.cs | 12 +++++++++--- src/LiveChartsCore/Kernel/ChartPoint.cs | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/LiveChartsCore/Chart.cs b/src/LiveChartsCore/Chart.cs index e37253918..ce89e5e94 100644 --- a/src/LiveChartsCore/Chart.cs +++ b/src/LiveChartsCore/Chart.cs @@ -542,7 +542,7 @@ protected void UpdateBounds() /// Initializes the visuals collector. /// protected void InitializeVisualsCollector() => - _toDeleteElements = new HashSet(_everMeasuredElements); + _toDeleteElements = [.. _everMeasuredElements]; /// /// Adds a visual element to the chart. @@ -702,12 +702,18 @@ protected bool DrawToolTip() foreach (var point in hovered) { - if (_activePoints.Contains(point)) continue; + if (_activePoints.Contains(point) && + point.HoverKey.Item1 == point.Coordinate.PrimaryValue && + point.HoverKey.Item2 == point.Coordinate.SecondaryValue) + { + continue; + } point.Context.Series.OnPointerEnter(point); _ = _activePoints.Add(point); _ = added.Add(point); + point.HoverKey = (point.Coordinate.PrimaryValue, point.Coordinate.SecondaryValue); } var removed = CleanHoveredPoints(hovered); @@ -810,7 +816,7 @@ private List CleanHoveredPoints(HashSet hovered) #if NET5_0_OR_GREATER #else - active = active.ToArray(); + active = [.. active]; #endif foreach (var point in active) diff --git a/src/LiveChartsCore/Kernel/ChartPoint.cs b/src/LiveChartsCore/Kernel/ChartPoint.cs index 364304fdd..414477b05 100644 --- a/src/LiveChartsCore/Kernel/ChartPoint.cs +++ b/src/LiveChartsCore/Kernel/ChartPoint.cs @@ -54,6 +54,8 @@ private ChartPoint() Context = new ChartPointContext(); } + internal (double, double) HoverKey { get; set; } + /// /// Gets a new instance of an empty chart point. /// From 8fd0180c5b015cc290fe1f541e3a7b1a9870073f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 15:25:14 -0600 Subject: [PATCH 35/94] VirtualBackroundColor --- src/LiveChartsCore/Chart.cs | 3 +-- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 1 + src/LiveChartsCore/Themes/Theme.cs | 19 ++++++++++++++----- .../ThemesExtensions.cs | 2 ++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/LiveChartsCore/Chart.cs b/src/LiveChartsCore/Chart.cs index ce89e5e94..a44960d14 100644 --- a/src/LiveChartsCore/Chart.cs +++ b/src/LiveChartsCore/Chart.cs @@ -55,7 +55,6 @@ public abstract class Chart private readonly ActionThrottler _updateThrottler; private readonly ActionThrottler _tooltipThrottler; private readonly ActionThrottler _panningThrottler; - private LvcPoint _pointerPanningStartPosition = new(-10, -10); private LvcPoint _pointerPanningPosition = new(-10, -10); private LvcPoint _pointerPreviousPanningPosition = new(-10, -10); private bool _isPanning = false; @@ -347,7 +346,6 @@ protected internal virtual void InvokePointerDown(LvcPoint point, bool isSeconda { _isPanning = true; _pointerPreviousPanningPosition = point; - _pointerPanningStartPosition = point; lock (Canvas.Sync) { @@ -613,6 +611,7 @@ public Theme GetTheme() { var theme = View.ChartTheme ?? LiveCharts.DefaultSettings.GetTheme(); theme.Setup(View.IsDarkMode); + Canvas._virtualBackgroundColor = theme.VirtualBackroundColor; return theme; } diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 439f13ed3..21243e527 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -46,6 +46,7 @@ public class CoreMotionCanvas : IDisposable private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; + internal LvcColor _virtualBackgroundColor; static CoreMotionCanvas() { diff --git a/src/LiveChartsCore/Themes/Theme.cs b/src/LiveChartsCore/Themes/Theme.cs index af39b58fc..4bfee3ec9 100644 --- a/src/LiveChartsCore/Themes/Theme.cs +++ b/src/LiveChartsCore/Themes/Theme.cs @@ -38,7 +38,6 @@ public class Theme private readonly object _darkId = new(); private bool _initialized = false; private bool _lastKnownDarkMode = false; - private bool _isUIDark; internal LvcThemeKind _themeRequest = LvcThemeKind.Unknown; /// @@ -50,10 +49,13 @@ public class Theme /// Gets a value indicating whether the theme is dark. /// When the is Unknown, the theme is determined by the system settings. /// - public bool IsDark => - RequestedTheme == LvcThemeKind.Unknown - ? _isUIDark + public bool IsDark + { + get => RequestedTheme == LvcThemeKind.Unknown + ? field : RequestedTheme == LvcThemeKind.Dark; + private set; + } /// /// Gets or sets the theme request. @@ -65,6 +67,13 @@ public class Theme /// public LvcColor[] Colors { get; set; } = []; + /// + /// Gets or sets the virtual background color, + /// it means the color to use to clear the canvas before drawing, + /// if the control has a background color set, this property will be ignored. + /// + public LvcColor VirtualBackroundColor { get; set; } = new(255, 255, 255); + /// /// Gets or sets the default easing function. /// @@ -330,7 +339,7 @@ public class Theme internal void Setup(bool isUIDark) { - _isUIDark = isUIDark; + IsDark = isUIDark; if (!_initialized || _lastKnownDarkMode != IsDark) { _lastKnownDarkMode = IsDark; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs index 2f6e491f0..1a86e9b6c 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs @@ -68,6 +68,7 @@ public static LiveChartsSettings AddDefaultTheme( if (theme.IsDark) { theme.Colors = ColorPalletes.MaterialDesign200; + theme.VirtualBackroundColor = new(0, 0, 0); theme.TooltipBackgroundPaint = new SolidColorPaint(new(45, 45, 45, 230)) { @@ -79,6 +80,7 @@ public static LiveChartsSettings AddDefaultTheme( else { theme.Colors = ColorPalletes.MaterialDesign500; + theme.VirtualBackroundColor = new(255, 255, 255); theme.TooltipBackgroundPaint = new SolidColorPaint(new(235, 235, 235, 230)) { From 7aac95898613370a80c31dac466f401b95e1196a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 15:25:31 -0600 Subject: [PATCH 36/94] use VirtualBackroundColor in wpf --- .../Rendering/CPURenderMode.cs | 17 +++++++++++++- .../Rendering/GPURenderMode.cs | 22 ++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs index 6931919b0..0ffa9fff4 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs @@ -21,6 +21,9 @@ // SOFTWARE. using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using SkiaSharp.Views.Desktop; @@ -59,7 +62,8 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) if (density.dpix != 1 || density.dpiy != 1) args.Surface.Canvas.Scale(density.dpix, density.dpiy); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface)); + FrameRequest?.Invoke( + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, GetBackground().AsSKColor())); } private ResolutionHelper GetPixelDensity() @@ -72,4 +76,15 @@ private ResolutionHelper GetPixelDensity() var matrix = compositionTarget.TransformToDevice; return new((float)matrix.M11, (float)matrix.M22); } + + private LvcColor GetBackground() + { + var parentBg = Parent is Control control && control.Background is SolidColorBrush bg + ? new LvcColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A) + : LvcColor.Empty; + + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; + } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs index d403f24b2..2332572a5 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs @@ -23,9 +23,9 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; -using SkiaSharp; using SkiaSharp.Views.Desktop; using SkiaSharp.Views.WPF; @@ -64,7 +64,7 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) args.Surface.Canvas.Scale(density.dpix, density.dpiy); FrameRequest?.Invoke( - new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, SKColor.Empty)); + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, GetBackground().AsSKColor())); } private ResolutionHelper GetPixelDensity() @@ -78,18 +78,14 @@ private ResolutionHelper GetPixelDensity() return new((float)matrix.M11, (float)matrix.M22); } - private SKColor GetBackground(DependencyObject? element) + private LvcColor GetBackground() { - if (element is not FrameworkElement fe) - return SKColors.Transparent; + var parentBg = Parent is Control control && control.Background is SolidColorBrush bg + ? new LvcColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A) + : LvcColor.Empty; - if (fe is Control control && control.Background is SolidColorBrush bg) - return new SKColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A); - - var parent = fe.Parent ?? fe.TemplatedParent; - - return parent is null - ? SKColors.Transparent - : GetBackground(parent); + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; } } From 5b2d4f084eb281cd58011129da793e2ef8c90745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 15:25:44 -0600 Subject: [PATCH 37/94] simplify wpf sample --- samples/WPFSample/MainWindow.xaml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/samples/WPFSample/MainWindow.xaml b/samples/WPFSample/MainWindow.xaml index 09e0f1db4..02b2594c3 100644 --- a/samples/WPFSample/MainWindow.xaml +++ b/samples/WPFSample/MainWindow.xaml @@ -46,16 +46,7 @@ - - - - - - + From aeb5fa6d3f863beac96f7fbe0158f53355090e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 15:39:28 -0600 Subject: [PATCH 38/94] disable GPU on WPF by default --- src/LiveChartsCore/LiveCharts.cs | 1 + src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs | 1 + src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs | 1 + .../LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs | 6 ++++-- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/LiveChartsCore/LiveCharts.cs b/src/LiveChartsCore/LiveCharts.cs index de09c28f0..c3645731f 100644 --- a/src/LiveChartsCore/LiveCharts.cs +++ b/src/LiveChartsCore/LiveCharts.cs @@ -35,6 +35,7 @@ public static class LiveCharts internal static bool s_hasBackend = false; internal static bool s_hasDefaultTheme = false; internal static bool s_hasDefaultMappers = false; + internal static bool s_forceDefaultHardwareAcceleration = false; internal static bool s_hasDefaultHardwareAcceleration = false; /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs index c0f24bcac..16db2b03f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs @@ -48,6 +48,7 @@ public abstract partial class ChartControl : UserControl, IChartView /// Default colors are not valid protected ChartControl() { + LiveCharts.s_forceDefaultHardwareAcceleration = true; LiveCharts.Configure(config => config.UseDefaults()); Content = new MotionCanvas(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs index 0c7c69599..c4c44ed9e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs @@ -53,6 +53,7 @@ public class GeoMap : UserControl, IGeoMapView /// public GeoMap() { + LiveCharts.s_forceDefaultHardwareAcceleration = true; LiveCharts.Configure(config => config.UseDefaults()); MouseDown += OnMouseDown; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index 5d1a20f5b..cab092d43 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -61,7 +61,7 @@ public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) if (!LiveCharts.s_hasDefaultHardwareAcceleration) _ = settings.RenderingSettings( - useHardwareAcceleration: true, + useHardwareAcceleration: !LiveCharts.s_forceDefaultHardwareAcceleration, tryUseVSync: true, targetFps: 60, // 60 as a fallback when VSync is not available showFps: false); @@ -107,7 +107,9 @@ public static LiveChartsSettings HasGlobalSKTypeface(this LiveChartsSettings set /// The alpha overrides. /// public static SKColor AsSKColor(this LvcColor color, byte? alphaOverrides = null) => - new(color.R, color.G, color.B, alphaOverrides ?? color.A); + color == LvcColor.Empty + ? SKColor.Empty + : new(color.R, color.G, color.B, alphaOverrides ?? color.A); /// /// Creates a new color based on the From af24c7cf57c53486995224962a5f2711ed92796b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 15:48:08 -0600 Subject: [PATCH 39/94] use virtualbgc in winui --- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 17 +++++++++++++++-- .../_Shared.WinUI/Rendering/GPURenderMode.cs | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs index c8f83c075..19f05e6db 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -20,9 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; -using SkiaSharp; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; using SkiaSharp.Views.Windows; namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; @@ -55,7 +57,7 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) if (density.DpiX != 1 || density.DpiY != 1) e.Surface.Canvas.Scale(density.DpiX, density.DpiY); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, SKColor.Empty)); + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, GetBackground().AsSKColor())); } public void InvalidateRenderer() => @@ -77,4 +79,15 @@ private readonly struct PixelDensity(float dpiX, float dpiY) public float DpiX { get; } = dpiX; public float DpiY { get; } = dpiY; } + + private LvcColor GetBackground() + { + var parentBg = Parent is Control control && control.Background is SolidColorBrush bg + ? new LvcColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A) + : LvcColor.Empty; + + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; + } } diff --git a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs index 970cc2529..1657096e5 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs @@ -20,9 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; -using SkiaSharp; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; using SkiaSharp.Views.Windows; namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; @@ -55,7 +57,7 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) if (density.DpiX != 1 || density.DpiY != 1) e.Surface.Canvas.Scale(density.DpiX, density.DpiY); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, SKColor.Empty)); + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, GetBackground().AsSKColor())); } public void InvalidateRenderer() => @@ -77,4 +79,15 @@ private readonly struct PixelDensity(float dpiX, float dpiY) public float DpiX { get; } = dpiX; public float DpiY { get; } = dpiY; } + + private LvcColor GetBackground() + { + var parentBg = Parent is Control control && control.Background is SolidColorBrush bg + ? new LvcColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A) + : LvcColor.Empty; + + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; + } } From 62906fb3e0535344f570ccb678bed32f7cfd4267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 22 Jul 2025 16:23:57 -0600 Subject: [PATCH 40/94] maui use vbgc --- .../Rendering/CPURenderMode.cs | 16 +++++++++++++++- .../Rendering/GPURenderMode.cs | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs index acbc8fa6b..814641776 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs @@ -21,8 +21,10 @@ // SOFTWARE. using System; +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.Maui.Controls; using Microsoft.Maui.Devices; using SkiaSharp.Views.Maui; using SkiaSharp.Views.Maui.Controls; @@ -64,9 +66,21 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) if (_pixelDensity != 1) args.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface)); + FrameRequest?.Invoke( + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, GetBackground().AsSKColor())); } private void MainDisplayInfoChanged(object? sender, EventArgs e) => _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; + + private LvcColor GetBackground() + { + var parentBg = Parent is VisualElement control && control.Background is SolidColorBrush bg && bg.Color is not null + ? new LvcColor((byte)(bg.Color.Red * 255), (byte)(bg.Color.Green * 255), (byte)(bg.Color.Blue * 255), (byte)(bg.Color.Alpha * 255)) + : LvcColor.Empty; + + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; + } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs index 73cfce411..ea0bf410f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs @@ -21,8 +21,10 @@ // SOFTWARE. using System; +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.Maui.Controls; using Microsoft.Maui.Devices; using SkiaSharp.Views.Maui; using SkiaSharp.Views.Maui.Controls; @@ -64,9 +66,21 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) if (_pixelDensity != 1) e.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface)); + FrameRequest?.Invoke( + new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, GetBackground().AsSKColor())); } private void MainDisplayInfoChanged(object? sender, EventArgs e) => _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; + + private LvcColor GetBackground() + { + var parentBg = Parent is VisualElement control && control.Background is SolidColorBrush bg && bg.Color is not null + ? new LvcColor((byte)(bg.Color.Red * 255), (byte)(bg.Color.Green * 255), (byte)(bg.Color.Blue * 255), (byte)(bg.Color.Alpha * 255)) + : LvcColor.Empty; + + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; + } } From 1427468af212306b91e5332ddb9b11896a69f5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 13:04:36 -0600 Subject: [PATCH 41/94] code generation NuGet package settings --- .../LiveChartsGenerators.csproj | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/generators/LiveChartsGenerators/LiveChartsGenerators.csproj b/generators/LiveChartsGenerators/LiveChartsGenerators.csproj index f451b14d3..46b63df3e 100644 --- a/generators/LiveChartsGenerators/LiveChartsGenerators.csproj +++ b/generators/LiveChartsGenerators/LiveChartsGenerators.csproj @@ -5,11 +5,29 @@ $(GlobalLangVersion) enable enable - false + true true Generated true + + Analyzer + true + true + 1.0.0 + LiveCharts code generation. + MIT + $(LiveChartsAuthors) + true + snupkg + portable + true + https://github.com/beto-rodriguez/LiveCharts2 + + true + true + true + true From 8a0cd4f5bf6339c5c569e049be3fba72fa4bc5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 13:06:25 -0600 Subject: [PATCH 42/94] referece NuGet package source generation in samples because this is easier to compile on macos at least for maui --- samples/ViewModelsSamples/ViewModelsSamples.csproj | 5 ++++- .../LiveChartsCore.SkiaSharpView.Avalonia.csproj | 8 +++++++- .../LiveChartsCore.SkiaSharpView.Maui.csproj | 8 +++++++- .../LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj | 11 +++++++++-- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/samples/ViewModelsSamples/ViewModelsSamples.csproj b/samples/ViewModelsSamples/ViewModelsSamples.csproj index d4fd81c4b..13c7cc752 100644 --- a/samples/ViewModelsSamples/ViewModelsSamples.csproj +++ b/samples/ViewModelsSamples/ViewModelsSamples.csproj @@ -17,7 +17,10 @@ - + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj index a16a1da26..8fdc53eef 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj @@ -52,7 +52,13 @@ - + + + + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj index b388e535e..68b9d6d1f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj @@ -61,9 +61,15 @@ - + + + all + analyzers + + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index f63e4adaa..881790167 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -63,14 +63,21 @@ - - + + + + + all + analyzers + + + From ca371b98d95335e7e124c93157b39690b462ab7f Mon Sep 17 00:00:00 2001 From: Alberto Rodriguez Date: Wed, 23 Jul 2025 13:20:43 -0600 Subject: [PATCH 43/94] use net 8 to compile maui view --- src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/global.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/global.json diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/global.json b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/global.json new file mode 100644 index 000000000..144dcb048 --- /dev/null +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.412", + "rollForward": "latestFeature" + } +} \ No newline at end of file From 1677597fa0d0002776145cff6eca2efb560da49e Mon Sep 17 00:00:00 2001 From: Alberto Rodriguez Date: Wed, 23 Jul 2025 13:54:30 -0600 Subject: [PATCH 44/94] update ios/maccatalyst min versions --- samples/MauiSample/MauiSample.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/MauiSample/MauiSample.csproj b/samples/MauiSample/MauiSample.csproj index 14586be7d..472ebb048 100644 --- a/samples/MauiSample/MauiSample.csproj +++ b/samples/MauiSample/MauiSample.csproj @@ -32,8 +32,8 @@ 1.0 1 - 11.0 - 13.1 + 12.2 + 15.0 21.0 10.0.17763.0 10.0.17763.0 From 7b25873fe9376bdf38df3736f99636dbb0c33d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 18:03:15 -0600 Subject: [PATCH 45/94] update avalonia motion canvas --- src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs index bd3651649..510fe60ca 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs @@ -108,12 +108,12 @@ public void Render(ImmediateDrawingContext context) throw new Exception("SkiaSharp is not supported."); using var lease = leaseFeature.Lease(); + if (lease.SkSurface is null) return; motionCanvas.DrawFrame( new SkiaSharpDrawingContext(motionCanvas, new SKImageInfo((int)Bounds.Width, (int)Bounds.Height), lease.SkSurface, - lease.SkCanvas, false)); } From 083cd005631ba89c48e4c361877302d9c5423420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 18:20:48 -0600 Subject: [PATCH 46/94] use vbgc in avalonia --- .../MotionCanvas.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs index 510fe60ca..a36bb5284 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs @@ -71,7 +71,7 @@ public override void Render(DrawingContext context) if (_isDeatached) return; context.Custom(new ChartFrameOperation( - CanvasCore, new Rect(0, 0, Bounds.Width, Bounds.Height))); + CanvasCore, new Rect(0, 0, Bounds.Width, Bounds.Height), GetBackground().AsSKColor())); if (CanvasCore.IsValid) return; _ = Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); @@ -97,7 +97,8 @@ private void OnDetached(object? sender, VisualTreeAttachmentEventArgs e) // https://github.com/AvaloniaUI/Avalonia/blob/release/11.0.0/samples/RenderDemo/Pages/CustomSkiaPage.cs private class ChartFrameOperation( CoreMotionCanvas motionCanvas, - Rect bounds) + Rect bounds, + SKColor background) : ICustomDrawOperation { public Rect Bounds { get; } = bounds; @@ -114,6 +115,7 @@ public void Render(ImmediateDrawingContext context) new SkiaSharpDrawingContext(motionCanvas, new SKImageInfo((int)Bounds.Width, (int)Bounds.Height), lease.SkSurface, + background, false)); } @@ -123,4 +125,15 @@ public void Dispose() { } public bool Equals(ICustomDrawOperation? other) => false; } + + private LiveChartsCore.Drawing.LvcColor GetBackground() + { + var parentBg = Parent is UserControl control && control.Background is SolidColorBrush bg + ? new LiveChartsCore.Drawing.LvcColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A) + : LiveChartsCore.Drawing.LvcColor.Empty; + + return parentBg != LiveChartsCore.Drawing.LvcColor.Empty + ? parentBg + : CanvasCore._virtualBackgroundColor; + } } From c714e2f582b10c652dc2f63c0700950918578715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 18:41:11 -0600 Subject: [PATCH 47/94] update winforms motioncanvas --- .../ChartControl.cs | 1 + .../GeoMap.cs | 3 +-- ...veChartsCore.SkiaSharpView.WinForms.csproj | 19 ++--------------- .../MotionCanvas.Designer.cs | 11 ---------- .../MotionCanvas.cs | 21 ++++++++----------- 5 files changed, 13 insertions(+), 42 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs index 71cbdd4ae..de954253d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs @@ -57,6 +57,7 @@ protected ChartControl() Name = "CartesianChart"; ResumeLayout(true); + LiveCharts.s_isHardwareAccelerationByDefault = false; LiveCharts.Configure(config => config.UseDefaults()); InitializeChartControl(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs index 332f1b2bf..481b815e3 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs @@ -22,12 +22,10 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel; using System.Windows.Forms; using LiveChartsCore.Drawing; using LiveChartsCore.Geo; -using LiveChartsCore.Kernel; using LiveChartsCore.Kernel.Observers; using LiveChartsCore.Measure; using LiveChartsCore.Motion; @@ -58,6 +56,7 @@ public partial class GeoMap : UserControl, IGeoMapView public GeoMap() { InitializeComponent(); + LiveCharts.s_isHardwareAccelerationByDefault = false; LiveCharts.Configure(config => config.UseDefaults()); _activeMap = Maps.GetWorldMap(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj index 12b2f72ac..3ca18f035 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj @@ -45,23 +45,8 @@ - - - - - - - + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs index 8b5a8ddbf..5e723b5dd 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs @@ -56,19 +56,12 @@ private void InitializeComponent() { if (LiveCharts.UseGPU) { -#if NET6_0_OR_GREATER - // workaround #250115 this._skglControl = new SKGLControl(); this._skglControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; this._skglControl.Size = new System.Drawing.Size(1000, 1000); this._skglControl.TabIndex = 1; this._skglControl.PaintSurface += new System.EventHandler(this.SkglControl_PaintSurface); this.Controls.Add(this._skglControl); -#else - throw new PlatformNotSupportedException( - "GPU rendering is only supported in .NET 6.0 or greater, " + - "because https://github.com/mono/SkiaSharp/issues/3111 needs to be fixed."); -#endif } else { @@ -89,10 +82,6 @@ private void InitializeComponent() #endregion private SKControl _skControl; - -#if NET6_0_OR_GREATER - // workaround #250115 private SKGLControl _skglControl; -#endif } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs index 0b0f33e93..f723aa983 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs @@ -24,6 +24,7 @@ using System.ComponentModel; using System.Threading.Tasks; using System.Windows.Forms; +using LiveChartsCore.Drawing; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using SkiaSharp.Views.Desktop; @@ -73,17 +74,11 @@ protected override void OnHandleDestroyed(EventArgs e) private void SkControl_PaintSurface(object sender, SKPaintSurfaceEventArgs e) => CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, e.Surface.Canvas)); + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, GetBackground().AsSKColor())); -#if NET6_0_OR_GREATER - // workaround #250115 private void SkglControl_PaintSurface(object sender, SKPaintGLSurfaceEventArgs e) => CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, e.Surface.Canvas) - { - Background = new SkiaSharp.SKColor(Parent!.BackColor.R, Parent.BackColor.G, Parent.BackColor.B) - }); -#endif + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, GetBackground().AsSKColor())); private void CanvasCore_Invalidated(CoreMotionCanvas sender) => RunDrawingLoop(); @@ -93,19 +88,21 @@ private async void RunDrawingLoop() if (_isDrawingLoopRunning) return; _isDrawingLoopRunning = true; - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); + var ts = TimeSpan.FromSeconds(1 / LiveCharts.TargetFps); while (!CanvasCore.IsValid) { _skControl?.Invalidate(); -#if NET6_0_OR_GREATER - // workaround #250115 _skglControl?.Invalidate(); -#endif await Task.Delay(ts); } _isDrawingLoopRunning = false; } + + private LvcColor GetBackground() => + true + ? new LvcColor(Parent!.BackColor.R, Parent.BackColor.G, Parent.BackColor.B) + : CanvasCore._virtualBackgroundColor; // are themes relevant in Win Forms? } From afd7d23235ba2b5c2cac4c731c3fd28f2cde6450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 18:41:59 -0600 Subject: [PATCH 48/94] update eto motion canvas --- .../LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs index f428c98ed..0a121e2a6 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs @@ -71,7 +71,7 @@ protected override void OnUnLoad(EventArgs e) private void SkControl_PaintSurface(object sender, SKPaintEventArgs e) => CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, e.Surface.Canvas)); + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface)); private void CanvasCore_Invalidated(CoreMotionCanvas sender) => RunDrawingLoop(); @@ -81,7 +81,7 @@ private async void RunDrawingLoop() if (_isDrawingLoopRunning) return; _isDrawingLoopRunning = true; - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); + var ts = TimeSpan.FromSeconds(1 / LiveCharts.TargetFps); while (!CanvasCore.IsValid) { From 25e4598e82b39eeefb32c1415a91dd076c1ea5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 23 Jul 2025 18:43:45 -0600 Subject: [PATCH 49/94] rename --- src/LiveChartsCore/LiveCharts.cs | 2 +- src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs | 2 +- src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs | 2 +- src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LiveChartsCore/LiveCharts.cs b/src/LiveChartsCore/LiveCharts.cs index c3645731f..a71bbba3b 100644 --- a/src/LiveChartsCore/LiveCharts.cs +++ b/src/LiveChartsCore/LiveCharts.cs @@ -35,7 +35,7 @@ public static class LiveCharts internal static bool s_hasBackend = false; internal static bool s_hasDefaultTheme = false; internal static bool s_hasDefaultMappers = false; - internal static bool s_forceDefaultHardwareAcceleration = false; + internal static bool s_isHardwareAccelerationByDefault = true; internal static bool s_hasDefaultHardwareAcceleration = false; /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs index 16db2b03f..446078ccf 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs @@ -48,7 +48,7 @@ public abstract partial class ChartControl : UserControl, IChartView /// Default colors are not valid protected ChartControl() { - LiveCharts.s_forceDefaultHardwareAcceleration = true; + LiveCharts.s_isHardwareAccelerationByDefault = false; LiveCharts.Configure(config => config.UseDefaults()); Content = new MotionCanvas(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs index c4c44ed9e..d3749769d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs @@ -53,7 +53,7 @@ public class GeoMap : UserControl, IGeoMapView /// public GeoMap() { - LiveCharts.s_forceDefaultHardwareAcceleration = true; + LiveCharts.s_isHardwareAccelerationByDefault = false; LiveCharts.Configure(config => config.UseDefaults()); MouseDown += OnMouseDown; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index cab092d43..f41c1f526 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -61,7 +61,7 @@ public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) if (!LiveCharts.s_hasDefaultHardwareAcceleration) _ = settings.RenderingSettings( - useHardwareAcceleration: !LiveCharts.s_forceDefaultHardwareAcceleration, + useHardwareAcceleration: LiveCharts.s_isHardwareAccelerationByDefault, tryUseVSync: true, targetFps: 60, // 60 as a fallback when VSync is not available showFps: false); From 302f0c439cbdba29e388db7c45def0cd02b3a313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 13:37:25 -0600 Subject: [PATCH 50/94] Mov behaviors assembly to a shared project this makes it easier to consume the assembly by multiple views/targets --- src/LiveChartsCore.Behaviours/AssemblyInfo.cs | 27 ------- .../LiveChartsCore.Behaviours.csproj | 72 ------------------ src/LiveChartsCore.Behaviours/images/icon.png | Bin 7880 -> 0 bytes .../ChartBehaviour.Android.cs | 0 .../ChartBehaviour.MacCatalyst.cs | 0 .../ChartBehaviour.Windows.cs | 0 .../ChartBehaviour._shared.cs | 0 .../Events/EventArgs.cs | 0 .../Events/Handler.cs | 0 .../Events/PinchEventArgs.cs | 0 .../Events/PinchHandler.cs | 0 .../Events/PressedEventArgs.cs | 0 .../Events/PressedHandler.cs | 0 .../Events/ScreenEventArgs.cs | 0 .../Events/ScreenHandler.cs | 0 .../Events/ScrollEventArgs.cs | 0 .../Events/ScrollHandler.cs | 0 .../NativeTicker.Android.cs | 5 ++ .../NativeTicker.Fallback.cs} | 24 +++--- .../NativeTicker.Mac.cs | 5 ++ .../NativeTicker.Windows.cs | 5 ++ src/_Shared.Native/_Shared.Native.projitems | 31 ++++++++ src/_Shared.Native/_Shared.Native.shproj | 13 ++++ 23 files changed, 72 insertions(+), 110 deletions(-) delete mode 100644 src/LiveChartsCore.Behaviours/AssemblyInfo.cs delete mode 100644 src/LiveChartsCore.Behaviours/LiveChartsCore.Behaviours.csproj delete mode 100644 src/LiveChartsCore.Behaviours/images/icon.png rename src/{LiveChartsCore.Behaviours => _Shared.Native}/ChartBehaviour.Android.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/ChartBehaviour.MacCatalyst.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/ChartBehaviour.Windows.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/ChartBehaviour._shared.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/EventArgs.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/Handler.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/PinchEventArgs.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/PinchHandler.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/PressedEventArgs.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/PressedHandler.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/ScreenEventArgs.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/ScreenHandler.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/ScrollEventArgs.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/Events/ScrollHandler.cs (100%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/NativeTicker.Android.cs (95%) rename src/{LiveChartsCore.Behaviours/NativeTicker._shared.cs => _Shared.Native/NativeTicker.Fallback.cs} (69%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/NativeTicker.Mac.cs (94%) rename src/{LiveChartsCore.Behaviours => _Shared.Native}/NativeTicker.Windows.cs (93%) create mode 100644 src/_Shared.Native/_Shared.Native.projitems create mode 100644 src/_Shared.Native/_Shared.Native.shproj diff --git a/src/LiveChartsCore.Behaviours/AssemblyInfo.cs b/src/LiveChartsCore.Behaviours/AssemblyInfo.cs deleted file mode 100644 index bad75d9bf..000000000 --- a/src/LiveChartsCore.Behaviours/AssemblyInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -// The MIT License(MIT) -// -// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.WinUI")] -[assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.Uno.WinUI")] -[assembly: InternalsVisibleTo("LiveChartsCore.SkiaSharpView.Maui")] diff --git a/src/LiveChartsCore.Behaviours/LiveChartsCore.Behaviours.csproj b/src/LiveChartsCore.Behaviours/LiveChartsCore.Behaviours.csproj deleted file mode 100644 index 465e9e434..000000000 --- a/src/LiveChartsCore.Behaviours/LiveChartsCore.Behaviours.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - enable - $(GlobalLangVersion) - - - netstandard2.0;netstandard2.1; - net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst; - - - $(TargetFrameworks); - net6.0-windows10.0.19041.0; - net8.0-windows10.0.19041.0; - - - true - - 11.0 - 13.1 - 21.0 - 10.0.17763.0 - 10.0.17763.0 - 6.5 - - win-x86;win-x64;win-arm64; - - LiveChartsCore.Behaviours - LiveChartsCore.Behaviours - $(LiveChartsVersion) - icon.png - Adds user interaction for touch screens, touch pads and mouse. - MIT - $(LiveChartsAuthors) - true - snupkg - portable - true - https://github.com/beto-rodriguez/LiveCharts2 - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - - true - true - true - true - - - - - $(NoWarn);NETSDK1206 - - - - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - true - - - - - - - - - - - - - - - - - diff --git a/src/LiveChartsCore.Behaviours/images/icon.png b/src/LiveChartsCore.Behaviours/images/icon.png deleted file mode 100644 index d0c1c566005cbef8d7c17888043cabb90abe923c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7880 zcma)BWm6jrvrW)oMT)yS6e&=k5Uj`pMO)mRqHS>v!QG*_ySqau?oz?sJwR{*xAz~s zANK6GJu|yAD`z9rRTc2CDX{?n0G^VfoaR3d`F{gp{KI}pv+;k9!u5l$tCq91tB2V) zD}c16v$++$lB1c8m8O-MrPtR{tM>o^?H46E=}(?ZCq`JFB)jd;)}qRZNm*6Q1k6=f zkBm{!0>-F3vR%8yiaCQ$-FajC@>cEQ$dEv*wHpQGC zY=lIuAVA6EoqT+o@inGR-aNl`Mb(0a@JAA_DTILVVfWnV<-BErNEpHO>8T)WA z=OxepvtdybtHN*)wJ*ZM zyfnC(|HBE3o7Xvc_)}uCc2yIo`f(QpI*Mbp-Tft<<3KSR9tED9=B2pjeiPolG`gD8 zL(z?~vNJWZq}fmCWJ%w@Yj|8xQpSA=-+uoriE7jK3u*Ah-KoN@A~5(pT~Mxo+3d^IBqiTT zxo5!1`OC)RfZPOVEyy^73OUlk3Snu2!3hnnQDiM^YH~A@Viq?|`%@uy0P1|Ew3Si8C&CpF6-R@-m;gHMu&>Izt0vf+D zc$6f$g`9u(K8)+q^Ach9osT7SrMwyjw^;I`1k{%gs1Se08CKionn(IkX<;!FTcanX zR%ix{jdarG{t&mEF#qLIOCdd!nqib?s7ml=k1GOEkhX707UwxP7NHR<^mo4SK=6~> z=fejcnxhs$)D5>awZxvTO`W|o-z!YzchZWjMIkpTE*v{(7v}yI@{fZ|q|5ivCaAGR zCg*Lfq>djqU}#lG3&V5=8C+6xcfL>7@k@|xVQ#3HL2l4)6idOPa?<&j`MPx5VVHpHbw;+nwKyqYN|e5 zeipam^wnLrhxO--eT;{-hzuzU)@!5tEAZ4PZP{E;w7;HpcfU9Lf${J2nIj!#j=RlR zP80^<`8=iMg1+CX&*~RUPm~T7@U|@D%X&i(ohfKR`YO~&!|!DK^CK>ij2^zL!bPOxlc&HVUeCDL9mt!) zD2O?b`@;<3kc4BnoXmkxu%vQN0Y{_BS-`l5{Pr`3HqkXP{^z_|m#YxYF5vFAPoL8F zp&ytz>1jfy3yd5^D?ZFsUH>KUjU<5n5Lzkz(NKx(orZOmpREwkeB6;1v5`GszM3DC zOfH`Kc&h#G7bW}-eU;c-7N#H)(Z?pD;zYadNlq0=6%tf_aO*Do>BK2`A)W7A7Tkpa zT5F?ra^04e%YT%^X1$L~eI5R7n&I1eg_bD|2SmY{^5wT842^0dn9up3O>0=VYHgVi zCmdr9AH&fkxjqx&kLQcqQAGXwCyBDo+UubBKlVRG#_uCp1GeuK4UT^n(p9fW_x7f9 zlB9`9+k%UH{}IL}mXzm!PddS!ER8QSA^Cs5V=rhlxyayV!BF3XAAA-~z6D|A==_8_ zOo5*KFwG{`pa<$*V!l&xxZyQg?1DnP45$D#R_RX- z4m?gq3K!im2q&UP^_y(vKqt-vsoK_({*5q=#KJ( zo+GxD&9`OIcN>sotQ1W#(J8r=8D=aPlRSJRpL~qX^ZNk5iXhM`@XG*-&ksw!pGRup zNp0Z{d*GAUu$*Bw!EI#lD09dq)~X=v9PfU!?j(#AK>|d4CSsv|iUf-=B5A^KMbIHMrMM&(5gBv{6`X zz(jRnJb-p5aN9DJo*SZcEz_KV&nQPm%0rM=Y!r2Gm|16zxkH8?Rd6LI9tU}Nn5D659w)X9a*PZ)`hE!`ghzeJ<(Do+ohWD#+d}X z?_{gEOr>i!y93P!pK?F~D7LT4PO^4t~aK<}U#A|Gp^oZ+0siR8&s!%hNY9z~Z0Z^uDCEdI^4KV?KNQ zvRa|u8(jv5ZawF?u;vRdLu2Uo1t!oGroNk(VS-cgk<>q~4phj6PID}MHKLehB*zGd z;UT)Ddr`CSx&b?GYwgxi~r#pqkq>RT3>mq^0j!A9)mS4;E3O9T}B z_r^aS54a0#L%tx10kt(|zyCCyTT{OG_vQgTDe#@HTQmn2rpZs?T$^dwr6w3-@nn9? z0axek^F;fJO4uuLo<=dO?c8RN zTOYq#NYS&Pi#{}7+yTWxbSDt&Z`*v?2)MElG#hB@Tz32HaC7q&CH;*^QVw7o#rh{8 zrz#50UJY)mjGaXH45EgtSoR&**YKs?%f7u&O14(>8z{N0+(-(qZAt)oOu_jb=2rQR zOMm2FDo%WcC>Fx%PN!?&zRg3^MJmadtxuM$xSh|urw`1<#Z=vx$1UM*x^LQ(F(~Z{ zf7MjrmP{%0$JpUSbx=cW4gwNKd(@gPWDj4GlXKAERuzgB>Md4cmRtA?!~~nU0iJ1d zR6gH%CKef$;J!?7lIh-ywL?C{k~m;je1It-EinA7t9T8gfK9kw?9QcCbc@W(f3;xT{BX2C0}PH3HOO!tQyg|>4vBR% z0J)S`*^4P{iFW*qptV5j8p`>P>URZU-A3hTP8>?EGA#G9+m6q33kB3fZhs7o*h@M+G))2RDUZnN2uq<;@q}6U3WG z)ZzI#$(rnbc$mE8lZVKn3YOW0pwC#jrV-%KyQh3eSBifP)^Wc8WnubGg#wWD@dy7_ zv7yM}v2SnDbT9qWIrjeqF}zm@9YZxcNf#N~ zQy@e>iQ42sZ!m>EHDWvxC-=4mU79s%bfl^PZAzg&CKHW_bmN0goScNPenc4w1kJ{( zl?GwAN2fs~NsXef^6D{7C0#+a>H6A@X22hr;qAv;1kIiFeBKG})7hndh)j+*M{B$I zFHvXhvZ%GO0jEO=i0-RrtxMn6R$@BdsAM|f-K%l?{_E!lE2IpYsWerF2TI2cOBm>DRIcM1i0{Zas9$e*mgY4^|SL6Q(CZFwI&6r!v z;bUP6U}GxJ9}*dn!09cB9kS^%Pvf3RF#=kyLnQ@l@p(;*W@#}nxTKe29q~$MtknmI zP-pn!^YMX8#^7?V5EgA)gHtt3%T7nnbYVZ+Zn`F~j>PgS#rPHZXerCs#&g<~MAliQmZRU|id}T^8Ot=z8}<6@Sj_UU zPD6^4i^Rf+JU3L+?p$qPTbJ@s6*#4%l?O>+zAwJvl;6=EiBpgy@;w}=a(9M%Ab0_G z2mq&N$N9I}ifHkf^bE%0(WAx}FKD-Od>p4dZmmRFv7;E=>oT!&Mu%Y-Fa<;tnv#N8 zzH(zyNzdjn)ZJh2y7;b}>93Vz093ZputHdnrax(LVA<;;cI$Lv4WxTcdJm+;9{ zRDDo5rL;N2;PEs?Asvo-B|ug9>jwcG%GVetDG-NOra^2m%*FczZ5O3c`G+H<@BH(q zD|*TxV>Ewu)N1mBMcTMwzn}{_^wR_=ME%^FWn3& zi$yoLP6C7jB&idwraag-$*3;A1qdOY`$NqG3)Je&4I=s*+L`P zI#H(_a{4mr@8gjPo6GbD`rH3W9f(cuS51S=2iaN=r!2H}vi1;+x+@mi0VZSkIph#G zYs-`YE*&ye$yXcw8#rSinrGVPbBF@{G92)UEIEi#jxBT`7zIZhvVmnzZ4-wV0Fu-O9S#<3f3t$5^>)OqF#U-x91X=S;YonDO+w!{E z76{O1`M;B;bzKtV9wA!X>h33-XFMO!01=-~OyjA9dF`cYGy8INK=Xb9uZqY_a^5nz zT+AMB!9Gw-LyJbGS1DX}66Okzd3pbFu8Sc& z-P@7&Nre6G&+kVnsdU@|Z?HI1xL;LF!N1_iULo$$FfKkExjXjH28tVV>gbSLqAgpQ zQ9V=0%;J#z_4C~V5dou*iH}q6{Z?*ar!M>PykK=3cO|i^DY;Y~!jcL7Nr?QZ`GCn7 z^ff~>fXM>jNQ|KOTmSE+I-z0E?y_|O#QC?TZl~EYm7!BCyj8vB!(g0iB{u=d4=>2x zRP$qsP4A4L)P%(dqhlZvI&6?=3ZMsDh5`jN7(EBR&`K2m$gBW_gB`@g7#S?zH|X>m zo9uoW*mHyn5&M#O;A5oH0(b zF@R|-kgsK;Te&L%7_2{iCbHbu&mUn((9xzMsc4>Z9ZFxz9#vH>JErV58%b{lU$^D_ z1p~`k^bp=XI9mTM-*Rk{u`LNLtm`WgA$Z;OV*L|)c>ctV>lPN(a!7Yfe5`H2IF-3B z$X;by$%1qM&E$NT_kNITzh9ViFoQOm6=AyxptJ zSyut#3DA#!&0so`|F44Z?d(8(2K55yRkgudIpIdYv?~`bYote4Cd1^QFCvAwjdZ4_1?GYn=kIEKtsid9O5FNCCGUDNCrN(QR#aw zg#FBc3Xh?!1F^;c1jm%v4LV3U!JbUKT$}bMp*8c{pKG%DAOyt$Q{GQH!f~Gv4#m}m z-H!d%!e|E`AciQpBQAH$hv}yq5h6c&GzB&mc^gaxdZ9V&VXNGGqrPY-#p>*Ojg4qA z^zM$?ed0iBnGQe?s{PB~yNklmmTkT~GegeP zP-nDkr7gG7X+r?CEJN@0Y<|2$TIltk;P_Stmng)uhVI*zm9vNri}!Mb(7foQ zpe$cD<4a!n*8o3ArV;5Y{SZ=n8Oe`v$98OuRW=vcQ18$0F1Chj9rU!kpVBM`TIPG6 zw*H!!Zw|k35kz?zTxt#IP2|kIev%>!oTdzXcbRxLjDEwlc| z-aCfE8V#5R8uq%J^?N_e3rcnJLDCDGZ(Peo)rva}e#ymqw9_xM$TG}l;lf{R0shB2 zpdLc4hHfavH%G1<{bIgA2@bi^3HJ{Ka3@DFAXelY8Q77LBEPmyff~Ompl%(&=zN!g zbxuxN%b&v1x`8Q8KjmtG8sKw?)6Df;nmosY;}7S;SP#cqIydgIK{bcw#Hi0q6TRL* z21bRjelpD)n4a|RHT@z8;ttd9Zm4^PP5qK*(Bz|yiEv7rQ?KuAzWu!zgdW$bwnYNW zBy(8`PN3dlAHyJuP#~uJIp6wy^4vZR+({Iza5aT_M?)d~miuJUg;_$RSA+8aJf$Lw z+h|ok0#Ty`e|g$h;n@cXi}*KPd*F8u2?xMTTV41}Q5>h~1I`mm5IL+ZF|*#XhAMFY zDrwev4AF2r7mjs8s*TZNWPGE|ng4Bjq4Sdy%k>j$$%Od&2Ad^Xp9EDLV(Lo7nwZ!) z@Vu2t7B+e*MSO*0rrg#$?$1gtY6Cq-^xpVl!wnaqgJUs0xmAW+b!MY8kX^ieLK z7ZFVNhob;eD?vRv5#=x zLzWbS*s8K0D`Zf0Yc@{#ymnDT+X|M!-*J4fIh#kvR<_vOUVreQ*DdG#CBlx0&m8qz z(rirJG5(ox)2;|UcPT%Yao39&H~CxdvJ`7HHWB2p)D28S$nIXEHfHLKupkrMDCo}v zS^ulv`R(f0ZSNTKc<5R#2(`%~aId0Y+$TC$nC$$$V{>uk0-$u#wy*XY(}QOTQR%Z@ zlsstc8K&0zoS`Tl^zQlm_SGIPrBTvS^|0FsBmT{Wi#ANb%Kd8p4R6QQO1T9dK{kRi zP4qP$@F)euQyLd`8EQ29BQYtU8@VP~3VqRC)bik&Pw`~11$&$yb9)QNR|?mNN=4JZ z`GV#?{ECv(9o+1EAC*aQg9{BA9`+bP`zI;aNMUWJlo0)V2km=7jMDMiya#&{LY*zq zcE0~%vnP0M7#p4U=;p2#`?~iUXa7CcEIM!B)MbgBtW@WUR4U{0_ps=6E2}(3)L!%3 z!d%{-@1j~;%@o&_n_EQgPJvaF(q|j|rCQ%`SoI`13Z5N%1MdE&9#wc7MZKR(psnTu zc*VVM{U=4oZ`+}#eQZ6sk2?3&vZ*E$zmrcvNvKeJ)-xLheep;GyKbC<9pjSj21&$m z>e^rD_5?KT4`enN+8y@uKza?+4#Ri};o<_&AkiQV)p&be>ZTmg>xZEa>-T~O^TMok zw?TRswrSMbHAX$f>mOUy_CD~>1J_#!NB&8yJ7}K8(;YrGEB$YMmnJ@`DlJ0)or8UD z5;a+3wT*KGR%LPY@H8WH{&js-K1+wvO@P|f^GS%pUVdV2&aBNyG;cMzRA+YV)Mq*z z@2AB~WH;s-`80^~)v4Fcy4sWJJqzX4WLOYhCH@W`l@3kq%8-)DpW({0r^{6<689`6 z#lIoBYZrDgBI32&g=KfFe?jn&zo(B+eGP2q-eF4W)YIn+ppma9+kYNAV#rNS|7kYu zGpjPBrX5=F1j@nyX&c8s?` z(t48B1X@_lKG_mNiJfJr)Vf|@xDPiALem#`ERIKGQs+tYW5vg;AFpBIGfq8K^vP~; zpqDz?J~t=5q~t4q`b+`o5giaD!xuxHQA diff --git a/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs b/src/_Shared.Native/NativeTicker.Fallback.cs similarity index 69% rename from src/LiveChartsCore.Behaviours/NativeTicker._shared.cs rename to src/_Shared.Native/NativeTicker.Fallback.cs index c07a24c8e..b5961b7cb 100644 --- a/src/LiveChartsCore.Behaviours/NativeTicker._shared.cs +++ b/src/_Shared.Native/NativeTicker.Fallback.cs @@ -20,17 +20,19 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if !WINDOWS && !ANDROID && !IOS && !MACCATALYST -using LiveChartsCore.Motion; +//#if !WINDOWS && !ANDROID && !IOS && !MACCATALYST +//using LiveChartsCore.Motion; -namespace LiveChartsCore.Behaviours; +//namespace LiveChartsCore.Behaviours; -internal partial class NativeFrameTicker : IFrameTicker -{ - void IFrameTicker.InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => - throw new System.NotImplementedException(); +//internal partial class NativeFrameTicker : IFrameTicker +//{ +// private readonly AsyncLoopTicker _asyncLoopTicker = new(); - void IFrameTicker.DisposeTicker() => - throw new System.NotImplementedException(); -} -#endif +// void IFrameTicker.InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => +// _asyncLoopTicker.InitializeTicker(canvas, renderMode); + +// void IFrameTicker.DisposeTicker() => +// _asyncLoopTicker.DisposeTicker(); +//} +//#endif diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs b/src/_Shared.Native/NativeTicker.Mac.cs similarity index 94% rename from src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs rename to src/_Shared.Native/NativeTicker.Mac.cs index 4169c6bac..60614ec95 100644 --- a/src/LiveChartsCore.Behaviours/NativeTicker.Mac.cs +++ b/src/_Shared.Native/NativeTicker.Mac.cs @@ -42,6 +42,11 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _displayLink.AddToRunLoop(NSRunLoop.Main, NSRunLoopMode.Common); _canvas.Invalidated += OnCoreInvalidated; + +#if DEBUG + System.Diagnostics.Trace.WriteLine( + "[LiveCharts Info] FrameSync: CADisplayLink (iOS/Catalyst)"); +#endif } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs b/src/_Shared.Native/NativeTicker.Windows.cs similarity index 93% rename from src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs rename to src/_Shared.Native/NativeTicker.Windows.cs index b155876e2..51027ada8 100644 --- a/src/LiveChartsCore.Behaviours/NativeTicker.Windows.cs +++ b/src/_Shared.Native/NativeTicker.Windows.cs @@ -39,6 +39,11 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _canvas.Invalidated += OnCoreInvalidated; CompositionTarget.Rendering += OnCompositonTargetRendering; + +#if DEBUG + System.Diagnostics.Trace.WriteLine( + "[LiveCharts Info] FrameSync: CompositionTarget.Rendering (Windows)"); +#endif } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems new file mode 100644 index 000000000..3de18abc7 --- /dev/null +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -0,0 +1,31 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + ebf70b6d-1416-4cc6-a540-f4ebf2371819 + + + _Shared.Native + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/_Shared.Native/_Shared.Native.shproj b/src/_Shared.Native/_Shared.Native.shproj new file mode 100644 index 000000000..3de57bf5b --- /dev/null +++ b/src/_Shared.Native/_Shared.Native.shproj @@ -0,0 +1,13 @@ + + + + ebf70b6d-1416-4cc6-a540-f4ebf2371819 + 14.0 + + + + + + + + From f03e72eed0ae12f19ac164d2b57e4b1eb872ce0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 13:44:29 -0600 Subject: [PATCH 51/94] comsume shared.native instead of behaviours --- LiveCharts.slnx | 11 ++++++----- global.json | 13 ++++++++----- .../LiveChartsCore.SkiaSharpView.Maui.csproj | 2 +- ...eChartsCore.SkiaSharpView.Uno.WinUI.csproj | 19 ++++--------------- .../LiveChartsCore.SkiaSharpView.WinUI.csproj | 2 +- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/LiveCharts.slnx b/LiveCharts.slnx index 683baef83..0e667ebd2 100644 --- a/LiveCharts.slnx +++ b/LiveCharts.slnx @@ -79,7 +79,11 @@ - + + + + + @@ -118,10 +122,6 @@ - - - - @@ -165,4 +165,5 @@ + diff --git a/global.json b/global.json index 15b954efd..42c52c8dc 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,9 @@ { - "sdk": { - "version": "9.0.101", - "rollForward": "latestFeature" - } -} \ No newline at end of file + "sdk": { + "version": "9.0.101", + "rollForward": "latestFeature" + }, + "msbuild-sdks": { + "Uno.Sdk": "6.1.23" + } +} diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj index 68b9d6d1f..0d2d34aef 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj @@ -43,6 +43,7 @@ true + @@ -59,7 +60,6 @@ - diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index 881790167..e063e7b36 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -1,28 +1,19 @@ - + enable $(GlobalLangVersion) - net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst; + net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst;net9.0-desktop; + $(TargetFrameworks); - net6.0-windows10.0.19041.0; net8.0-windows10.0.19041.0 true - 11.0 - 13.1 - 21.0 - 10.0.17763.0 - 10.0.17763.0 - 6.5 - - win-x86;win-x64;win-arm64 - true @@ -48,6 +39,7 @@ true + @@ -59,10 +51,7 @@ - - - diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj index 8c358518f..490399d55 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj @@ -33,6 +33,7 @@ true + @@ -65,7 +66,6 @@ - From a13aef59e3279cfe95f6f740a3fba70403ecd974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 13:52:56 -0600 Subject: [PATCH 52/94] Add Uno desktop target, remove fallback --- src/_Shared.Native/NativeTicker.Fallback.cs | 38 --------------------- src/_Shared.Native/NativeTicker.Windows.cs | 2 +- src/_Shared.Native/_Shared.Native.projitems | 1 - 3 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 src/_Shared.Native/NativeTicker.Fallback.cs diff --git a/src/_Shared.Native/NativeTicker.Fallback.cs b/src/_Shared.Native/NativeTicker.Fallback.cs deleted file mode 100644 index b5961b7cb..000000000 --- a/src/_Shared.Native/NativeTicker.Fallback.cs +++ /dev/null @@ -1,38 +0,0 @@ -// The MIT License(MIT) -// -// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -//#if !WINDOWS && !ANDROID && !IOS && !MACCATALYST -//using LiveChartsCore.Motion; - -//namespace LiveChartsCore.Behaviours; - -//internal partial class NativeFrameTicker : IFrameTicker -//{ -// private readonly AsyncLoopTicker _asyncLoopTicker = new(); - -// void IFrameTicker.InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => -// _asyncLoopTicker.InitializeTicker(canvas, renderMode); - -// void IFrameTicker.DisposeTicker() => -// _asyncLoopTicker.DisposeTicker(); -//} -//#endif diff --git a/src/_Shared.Native/NativeTicker.Windows.cs b/src/_Shared.Native/NativeTicker.Windows.cs index 51027ada8..993dd5ab4 100644 --- a/src/_Shared.Native/NativeTicker.Windows.cs +++ b/src/_Shared.Native/NativeTicker.Windows.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if WINDOWS +#if WINDOWS || DESKTOP using LiveChartsCore.Motion; using Microsoft.UI.Xaml.Media; diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 3de18abc7..2ef3297e2 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -24,7 +24,6 @@ - From 15702404666c48a389b43190133d2115eeb775f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 14:28:10 -0600 Subject: [PATCH 53/94] add noUI ticker, add log uno desktop --- Directory.Build.props | 12 +++++ src/LiveChartsCore/Motion/AsyncLoopTicker.cs | 5 +++ .../Motion/CanvasRenderSettings.cs | 27 +++++------ src/_Shared.Native/NativeTicker.NoUI.cs | 45 +++++++++++++++++++ src/_Shared.Native/_Shared.Native.projitems | 1 + src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 7 +++ .../_Shared.WinUI/Rendering/CPURenderMode.cs | 4 ++ 7 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 src/_Shared.Native/NativeTicker.NoUI.cs diff --git a/Directory.Build.props b/Directory.Build.props index bf4b8a0d4..d45d5df99 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,4 +11,16 @@ 3.119.0 + + + $(DefineConstants);HAS_UI + diff --git a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs index 723d50447..547c3639f 100644 --- a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs +++ b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs @@ -36,6 +36,11 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _renderMode = renderMode; _canvas.Invalidated += OnCoreInvalidated; + +#if DEBUG + System.Diagnostics.Trace.WriteLine( + "[LiveCharts Info] FrameSync: LiveCharts internal loop (no platform ticker)"); +#endif } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/LiveChartsCore/Motion/CanvasRenderSettings.cs b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs index 214c4e641..640e0b79b 100644 --- a/src/LiveChartsCore/Motion/CanvasRenderSettings.cs +++ b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs @@ -22,27 +22,22 @@ namespace LiveChartsCore.Motion; -internal class CanvasRenderSettings - where TCPURenderMode : IRenderMode, new() - where TGPURenderMode : IRenderMode, new() - where TVSyncTicker : IFrameTicker, new() +internal class CanvasRenderSettings( + IRenderMode? renderMode = null) + where TCPURenderMode : IRenderMode, new() + where TGPURenderMode : IRenderMode, new() + where TVSyncTicker : IFrameTicker, new() { private static bool? s_canUseGPU; - public CanvasRenderSettings() - { - RenderMode = LiveCharts.UseGPU && CanUseGPU() + public IRenderMode RenderMode { get; } = renderMode ?? + (LiveCharts.UseGPU && CanUseGPU() ? new TGPURenderMode() - : new TCPURenderMode(); - - Ticker = LiveCharts.TryUseVSync - ? new TVSyncTicker() - : new AsyncLoopTicker(); - } - - public IRenderMode RenderMode { get; } + : new TCPURenderMode()); - public IFrameTicker Ticker { get; } + public IFrameTicker Ticker { get; } = LiveCharts.TryUseVSync + ? new TVSyncTicker() + : new AsyncLoopTicker(); public void Initialize(CoreMotionCanvas canvas) { diff --git a/src/_Shared.Native/NativeTicker.NoUI.cs b/src/_Shared.Native/NativeTicker.NoUI.cs new file mode 100644 index 000000000..486daf189 --- /dev/null +++ b/src/_Shared.Native/NativeTicker.NoUI.cs @@ -0,0 +1,45 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if !HAS_UI + +// This code is reached maybe only on test environments. +// HAS_UI is true when the target framework contains any of the following: +// -windows, -android, -ios, -maccatalyst, -tizen, -desktop, -browserwasm + +using LiveChartsCore.Motion; + +namespace LiveChartsCore.Behaviours; + +internal partial class NativeFrameTicker : IFrameTicker +{ + // use the livecharts async loop ticker when there is no UI available. + private readonly IFrameTicker _ticker = new AsyncLoopTicker(); + + public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => + _ticker.InitializeTicker(canvas, renderMode); + + public void DisposeTicker() => + _ticker.DisposeTicker(); +} + +#endif diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 2ef3297e2..358fbdc2d 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -23,6 +23,7 @@ + diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index 7f3f5a2f7..15d1101ad 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -42,7 +42,14 @@ public partial class MotionCanvas : Canvas /// public MotionCanvas() { +#if DESKTOP + // The CPURenderMode class inherits from SKXamlCanvas which is the control Uno uses to + // render SkiaSharp on the netx-destop target. + // as of today, SwapChainPanel is not available on the netx-desktop target. + _settings = new(new CPURenderMode()); +#else _settings = new(); +#endif Children.Add((UIElement)_settings.RenderMode); diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs index 19f05e6db..fd0dd5841 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -41,7 +41,11 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) PaintSurface += OnPaintSurface; #if DEBUG +#if DESKTOP + System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using Uno's Skia renderer."); +#else System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); +#endif #endif } From 6e2a236388010c30d70f49a5ab977e68299eda04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 14:28:21 -0600 Subject: [PATCH 54/94] update uno sdk --- samples/UnoPlatformSample/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/UnoPlatformSample/global.json b/samples/UnoPlatformSample/global.json index 378615b13..509ebabc0 100644 --- a/samples/UnoPlatformSample/global.json +++ b/samples/UnoPlatformSample/global.json @@ -1,7 +1,7 @@ { // To update the version of Uno please update the version of the Uno.Sdk here. See https://aka.platform.uno/upgrade-uno-packages for more information. "msbuild-sdks": { - "Uno.Sdk": "6.0.146" + "Uno.Sdk": "6.1.23" }, "sdk":{ "allowPrerelease": false From b6833665329d8e2972aa991c4cdf337a6e30c80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 14:28:36 -0600 Subject: [PATCH 55/94] remove xaml from uno --- .../{GeoMap.xaml.cs => GeoMap.cs} | 5 +++-- .../GeoMap.xaml | 12 ------------ 2 files changed, 3 insertions(+), 14 deletions(-) rename src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/{GeoMap.xaml.cs => GeoMap.cs} (98%) delete mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs similarity index 98% rename from src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml.cs rename to src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs index eced1816d..e4a9a3a4d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs @@ -49,7 +49,8 @@ public sealed partial class GeoMap : UserControl, IGeoMapView /// public GeoMap() { - InitializeComponent(); + Content = new MotionCanvas(); + LiveCharts.Configure(config => config.UseDefaults()); _core = new GeoMapChart(this); @@ -160,7 +161,7 @@ public object? ViewCommand } /// - public CoreMotionCanvas Canvas => canvas.CanvasCore; + public CoreMotionCanvas Canvas => ((MotionCanvas)Content).CanvasCore; /// public DrawnMap ActiveMap diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml deleted file mode 100644 index b3c4bfeb8..000000000 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - From e8448265880cf5fb30452eab5cb6cecf0a13a071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Thu, 24 Jul 2025 15:52:21 -0600 Subject: [PATCH 56/94] log external render --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 25 +++++++++++++++---- ...icker.Windows.cs => NativeTicker.WinUI.cs} | 6 +++++ src/_Shared.Native/_Shared.Native.projitems | 2 +- .../MotionCanvas.cs | 1 + 4 files changed, 28 insertions(+), 6 deletions(-) rename src/_Shared.Native/{NativeTicker.Windows.cs => NativeTicker.WinUI.cs} (92%) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 21243e527..8e8ec0d8d 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -47,6 +47,7 @@ public class CoreMotionCanvas : IDisposable private static readonly TimeSpan s_baseFrameDelay = TimeSpan.FromMilliseconds(1000d / LiveCharts.TargetFps); private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; internal LvcColor _virtualBackgroundColor; + internal static string? s_externalRenderer; static CoreMotionCanvas() { @@ -187,11 +188,25 @@ public void DrawFrame(TDrawingContext context) MeasureFPS(drawStartTime); if (_totalSeconds > 0) - context.LogOnCanvas( - $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + - $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + - $"GPU [ {LiveCharts.UseGPU} ] " + - $"VSync [ {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]"); + { + if (s_externalRenderer is null) + { + // LiveCharts is controlling the GPU and VSync + context.LogOnCanvas( + $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + + $"GPU [ {LiveCharts.UseGPU} ] " + + $"VSync [ {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]"); + } + else + { + // Avalonia or Uno-desktop is controlling the GPU and VSync + context.LogOnCanvas( + $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + + $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + + $"GPU and VSync controlled by {s_externalRenderer}"); + } + } } IsValid = isValid; diff --git a/src/_Shared.Native/NativeTicker.Windows.cs b/src/_Shared.Native/NativeTicker.WinUI.cs similarity index 92% rename from src/_Shared.Native/NativeTicker.Windows.cs rename to src/_Shared.Native/NativeTicker.WinUI.cs index 993dd5ab4..6a620a22f 100644 --- a/src/_Shared.Native/NativeTicker.Windows.cs +++ b/src/_Shared.Native/NativeTicker.WinUI.cs @@ -41,8 +41,14 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) CompositionTarget.Rendering += OnCompositonTargetRendering; #if DEBUG +#if DESKTOP + CoreMotionCanvas.s_externalRenderer = "Uno Desktop"; + System.Diagnostics.Trace.WriteLine( + "[LiveCharts Info] FrameSync: CompositionTarget.Rendering"); +#else System.Diagnostics.Trace.WriteLine( "[LiveCharts Info] FrameSync: CompositionTarget.Rendering (Windows)"); +#endif #endif } diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 358fbdc2d..06b5c6c35 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs index a36bb5284..3ecee0bfe 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs @@ -48,6 +48,7 @@ public class MotionCanvas : UserControl /// public MotionCanvas() { + CoreMotionCanvas.s_externalRenderer = "Avalonia"; AttachedToVisualTree += OnAttached; DetachedFromVisualTree += OnDetached; } From 93e020cd9508465cc3e76831e15255d2311ef165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 12:12:54 -0600 Subject: [PATCH 57/94] improve canvas log --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 56 +++++++++++-------- .../Drawing/SkiaSharpDrawingContext.cs | 15 +++-- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 8e8ec0d8d..b33a83246 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using LiveChartsCore.Drawing; using LiveChartsCore.Painting; @@ -38,6 +39,7 @@ public class CoreMotionCanvas : IDisposable internal HashSet _paintTasks = []; private int _frames = 0; private Stopwatch? _fspSw; + private int _jitteredDrawCount; private double _totalDrawTime = 0; private double _lastDrawTime = 0; private double _totalFrames = 0; @@ -189,23 +191,20 @@ public void DrawFrame(TDrawingContext context) if (_totalSeconds > 0) { - if (s_externalRenderer is null) - { - // LiveCharts is controlling the GPU and VSync - context.LogOnCanvas( - $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + - $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + - $"GPU [ {LiveCharts.UseGPU} ] " + - $"VSync [ {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]"); - } - else - { - // Avalonia or Uno-desktop is controlling the GPU and VSync - context.LogOnCanvas( - $"FPS [ {_totalFrames / _totalSeconds:N2} ] " + - $"render time [ last {_lastDrawTime:N2}ms / average {_totalDrawTime / _totalFrames:N2}ms ] " + - $"GPU and VSync controlled by {s_externalRenderer}"); - } + var sb = new StringBuilder(); + + sb.Append($"FPS [ {_totalFrames / _totalSeconds:N2} ]"); + sb.Append($"`render time last/avrg [ {_lastDrawTime:N2} / {_totalDrawTime / _totalFrames:N2} ] ms"); + + sb.Append(s_externalRenderer is null + ? $"`GPU / VSync [ {LiveCharts.UseGPU} / {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]" + : $"`GPU / VSync by [ {s_externalRenderer} ]"); + + if (_jitteredDrawCount > 0) + sb.Append( + $"`jittered draws [ {_jitteredDrawCount} ]"); + + context.LogOnCanvas(sb.ToString()); } } @@ -230,14 +229,25 @@ public void DrawFrame(TDrawingContext context) } } - var timeInDrawOperation = s_clock.ElapsedTicks - drawStartTime; - var delay = s_baseFrameDelay.Ticks - timeInDrawOperation; + if (!LiveCharts.TryUseVSync) + { + var timeInDrawOperation = s_clock.ElapsedTicks - drawStartTime; + var delay = s_baseFrameDelay.Ticks - timeInDrawOperation; - var frameDelay = delay <= s_jitterThreshold - ? s_baseFrameDelay - : new TimeSpan(delay); + TimeSpan frameDelay; - _nextFrameDelay = frameDelay; + if (delay <= s_jitterThreshold) + { + _jitteredDrawCount++; + frameDelay = s_baseFrameDelay; + } + else + { + frameDelay = new TimeSpan(delay); + } + + _nextFrameDelay = frameDelay; + } } /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs index 193e211c2..099c233bf 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs @@ -129,10 +129,17 @@ public override void LogOnCanvas(string log) FakeBoldText = true }; - Canvas.DrawText( - log, - new SKPoint(50, 10 + p.TextSize), - p); + var lines = log.Split('`'); + + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i]; + if (string.IsNullOrWhiteSpace(line)) continue; + Canvas.DrawText( + line, + new SKPoint(50, 10 + p.TextSize * i), + p); + } } /// From 77db78e94caa9220d5fc2741e14fbc31c920429c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 12:13:32 -0600 Subject: [PATCH 58/94] blazor use requestanimationframe --- .../GeoMap.razor.cs | 1 + .../JsFlexibleContainer.razor.cs | 3 + .../JsInterop/DomJsInterop.cs | 36 ++++-- .../MotionCanvas.razor | 35 ++---- .../MotionCanvas.razor.cs | 108 +++++++++++------- .../wwwroot/domInterop.js | 23 ++++ .../wwwroot/domInterop.js.map | 2 +- .../wwwroot/domInterop.ts | 27 +++++ 8 files changed, 157 insertions(+), 78 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs index 89a5bc2df..21df855cf 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs @@ -26,6 +26,7 @@ using LiveChartsCore.Kernel.Observers; using LiveChartsCore.Motion; using LiveChartsCore.Painting; +using LiveChartsCore.SkiaSharpView.Blazor.JsInterop; using LiveChartsCore.SkiaSharpView.Painting; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsFlexibleContainer.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsFlexibleContainer.razor.cs index 9ec75bf00..4ca327dd9 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsFlexibleContainer.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsFlexibleContainer.razor.cs @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using LiveChartsCore.SkiaSharpView.Blazor.JsInterop; using LiveChartsCore.SkiaSharpView.Blazor.JsInterop.Models; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -82,6 +83,8 @@ public partial class JsFlexibleContainer : IDisposable /// protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) return; + _dom ??= new DomJsInterop(JS); var wrapperBounds = await _dom.GetBoundingClientRect(Container); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsInterop/DomJsInterop.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsInterop/DomJsInterop.cs index 154a8f358..11c86e0cf 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsInterop/DomJsInterop.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/JsInterop/DomJsInterop.cs @@ -24,7 +24,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; -namespace LiveChartsCore.SkiaSharpView.Blazor; +namespace LiveChartsCore.SkiaSharpView.Blazor.JsInterop; /// /// An object that handles the comminication with the DOM. @@ -35,18 +35,40 @@ namespace LiveChartsCore.SkiaSharpView.Blazor; /// public class DomJsInterop(IJSRuntime jsRuntime) : IAsyncDisposable { - private readonly Lazy> _moduleTask = new Lazy>(() => + private static readonly Dictionary>> s_resizeEvent = []; + private readonly Lazy> _moduleTask = new(() => jsRuntime.InvokeAsync( "import", "./_content/LiveChartsCore.SkiaSharpView.Blazor/domInterop.js") .AsTask()); - private static readonly Dictionary>> s_resizeEvent = []; + + /// + /// Starts the frame ticker, this will call the OnFrameTick method in the DotNetObjectReference. + /// + /// The dotnet ref to the motion canvas. + public async ValueTask StartFrameTicker(DotNetObjectReference motionCanvasRef) + { + var module = await _moduleTask.Value; + + await module.InvokeVoidAsync("DOMInterop.startFrameTicker", motionCanvasRef); + } + + /// + /// Stops the frame ticker, this will stop calling the OnFrameTick method in the DotNetObjectReference. + /// + /// + public async ValueTask StopFrameTicker(DotNetObjectReference motionCanvasRef) + { + var module = await _moduleTask.Value; + + await module.InvokeVoidAsync("DOMInterop.stopFrameTicker", motionCanvasRef); + } /// /// Gets the bounding client rectangle of the given element. /// /// The HTMl element reference. - /// + /// The dom rectangle. public async ValueTask GetBoundingClientRect(ElementReference elementReference) { var module = await _moduleTask.Value; @@ -61,7 +83,6 @@ public async ValueTask GetBoundingClientRect(ElementReference elementRe /// The x coordinate (left property in css). /// The y coordinate (top property in css). /// Indicates whether the function should add the given element postion to each coordinate. - /// public async ValueTask SetPosition( ElementReference elementReference, double x, double y, ElementReference? relativeTo = null) { @@ -76,7 +97,6 @@ public async ValueTask SetPosition( /// The HTML element. /// The elemnt id. /// The handler. - /// public async ValueTask OnResize(ElementReference element, string elementId, Action handler) { if (!s_resizeEvent.TryGetValue(elementId, out var actions)) @@ -94,10 +114,8 @@ public async ValueTask OnResize(ElementReference element, string elementId, Acti /// Removes the handler from the specified element id. /// /// The element id. - public void RemoveOnResizeListener(string elementId) - { + public void RemoveOnResizeListener(string elementId) => _ = s_resizeEvent.Remove(elementId); - } /// /// Removes the given resize handler. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor index b143ab78e..dc600a6b0 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor @@ -31,28 +31,13 @@ @implements IDisposable -@if(LiveCharts.UseGPU) -{ - - -} -else -{ - - -} + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs index 0b7e59667..d013c68e0 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs @@ -21,26 +21,25 @@ // SOFTWARE. using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Blazor.JsInterop; using LiveChartsCore.SkiaSharpView.Drawing; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; using SkiaSharp.Views.Blazor; namespace LiveChartsCore.SkiaSharpView.Blazor; /// -public partial class MotionCanvas : IDisposable +public partial class MotionCanvas : IDisposable, IRenderMode { private SKGLView? _glView; - private SKCanvasView? _canvas; - private bool _disposing = false; - private bool _isDrawingLoopRunning = false; + private DotNetObjectReference? _dotNetRef; + private DomJsInterop? _dom; + private IFrameTicker _ticker = null!; - /// - /// Called when the control is initialized. - /// - protected override void OnInitialized() => - CanvasCore.Invalidated += CanvasCore_Invalidated; + [Inject] + private IJSRuntime JS { get; set; } = null!; /// /// Gets the (core). @@ -77,6 +76,12 @@ protected override void OnInitialized() => [Parameter] public EventCallback OnPointerOutCallback { get; set; } + event CoreMotionCanvas.FrameRequestHandler IRenderMode.FrameRequest + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + /// /// Called when the pointer goes down. /// @@ -112,44 +117,61 @@ protected virtual void OnPointerOut(PointerEventArgs e) => _ = OnPointerOutCallback.InvokeAsync(e); private void OnPaintGlSurface(SKPaintGLSurfaceEventArgs e) => - CanvasCore.DrawFrame(new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, e.Surface.Canvas)); + CanvasCore.DrawFrame(new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface)); - private void OnPaintSurface(SKPaintSurfaceEventArgs e) => - CanvasCore.DrawFrame(new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, e.Surface.Canvas)); + /// + protected override void OnAfterRender(bool firstRender) + { + if (!firstRender) return; + + _dom ??= new DomJsInterop(JS); + _dotNetRef = DotNetObjectReference.Create(this); + + _ticker = LiveCharts.TryUseVSync + ? new RequestAnimationFrameTicker(_dom, _dotNetRef) + : new AsyncLoopTicker(); + + _ticker.InitializeTicker(CanvasCore, this); + } - private void CanvasCore_Invalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); + void IDisposable.Dispose() + { + _ticker?.DisposeTicker(); + _ticker = null!; + _glView?.Dispose(); + _ = (_dom?.StopFrameTicker(_dotNetRef!)); + _dotNetRef?.Dispose(); + _dotNetRef = null; + _dom = null; + } - private async void RunDrawingLoop() + /// + /// Called when the frame ticker ticks. + /// + [JSInvokable] + public void OnFrameTick() { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - var ts = TimeSpan.FromSeconds(1 / LiveCharts.MaxFps); - - if (LiveCharts.UseGPU) - { - while (!CanvasCore.IsValid && !_disposing) - { -#pragma warning disable CA1416 // Validate platform compatibility - _glView?.Invalidate(); -#pragma warning restore CA1416 // Validate platform compatibility - await Task.Delay(ts); - } - } - else - { - while (!CanvasCore.IsValid && !_disposing) - { -#pragma warning disable CA1416 // Validate platform compatibility - _canvas?.Invalidate(); -#pragma warning restore CA1416 // Validate platform compatibility - await Task.Delay(ts); - } - } - - _isDrawingLoopRunning = false; + if (CanvasCore.IsValid) return; + _glView.Invalidate(); } - void IDisposable.Dispose() => _disposing = true; + void IRenderMode.InitializeRenderMode(CoreMotionCanvas canvas) => + throw new NotImplementedException(); + + void IRenderMode.InvalidateRenderer() => + _glView?.Invalidate(); + + void IRenderMode.DisposeRenderMode() => + throw new NotImplementedException(); + + internal class RequestAnimationFrameTicker( + DomJsInterop jsInterop, DotNetObjectReference dotnetRef) + : IFrameTicker + { + public async void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => + await jsInterop.StartFrameTicker(dotnetRef); + + public async void DisposeTicker() => + await jsInterop.StopFrameTicker(dotnetRef); + } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js index 60ac8a0eb..4d6033310 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js @@ -23,5 +23,28 @@ export var DOMInterop; element.style.left = (x + rx) + 'px'; } DOMInterop.setPosition = setPosition; + const activeTickers = new Map(); + function createTicker(dotNetRef) { + function tick() { + if (!activeTickers.has(dotNetRef)) + return; // If unsubscribed, stop loop + dotNetRef + .invokeMethodAsync("OnFrameTick") + .catch(() => activeTickers.delete(dotNetRef)); // Auto-clean if component is disposed + requestAnimationFrame(tick); + } + requestAnimationFrame(tick); + activeTickers.set(dotNetRef, tick); + } + function startFrameTicker(dotNetRef) { + if (activeTickers.has(dotNetRef)) + return; + createTicker(dotNetRef); + } + DOMInterop.startFrameTicker = startFrameTicker; + function stopFrameTicker(dotNetRef) { + activeTickers.delete(dotNetRef); + } + DOMInterop.stopFrameTicker = stopFrameTicker; })(DOMInterop || (DOMInterop = {})); //# sourceMappingURL=domInterop.js.map \ No newline at end of file diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js.map b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js.map index 5c31b238c..be915734c 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js.map +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.js.map @@ -1 +1 @@ -{"version":3,"file":"domInterop.js","sourceRoot":"","sources":["domInterop.ts"],"names":[],"mappings":"AAEA,MAAM,KAAW,UAAU,CAuC1B;AAvCD,WAAiB,UAAU;IACvB,SAAgB,qBAAqB,CACjC,OAAoB;QAEpB,OAAO,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAC3C,CAAC;IAJe,gCAAqB,wBAIpC,CAAA;IAED,SAAgB,sBAAsB,CAClC,OAAoB,EACpB,SAAiB;QAEjB,IAAI,QAAQ,GAAG,IAAI,cAAc,CAAC;YAC9B,MAAM,CAAC,iBAAiB,CACpB,qCAAqC,EACrC,cAAc,EACd,SAAS,EACT,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAZe,iCAAsB,yBAYrC,CAAA;IAED,SAAgB,WAAW,CACvB,OAAoB,EACpB,CAAS,EACT,CAAS,EACT,UAAmC;QAEnC,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,IAAI,EAAE,GAAG,CAAC,CAAC;QAEX,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,MAAM,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAC;YAChD,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YACjB,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;QACpB,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACzC,CAAC;IAjBe,sBAAW,cAiB1B,CAAA;AACL,CAAC,EAvCgB,UAAU,KAAV,UAAU,QAuC1B"} \ No newline at end of file +{"version":3,"file":"domInterop.js","sourceRoot":"","sources":["domInterop.ts"],"names":[],"mappings":"AAEA,MAAM,KAAW,UAAU,CAkE1B;AAlED,WAAiB,UAAU;IACvB,SAAgB,qBAAqB,CACjC,OAAoB;QAEpB,OAAO,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAC3C,CAAC;IAJe,gCAAqB,wBAIpC,CAAA;IAED,SAAgB,sBAAsB,CAClC,OAAoB,EACpB,SAAiB;QAEjB,IAAI,QAAQ,GAAG,IAAI,cAAc,CAAC;YAC9B,MAAM,CAAC,iBAAiB,CACpB,qCAAqC,EACrC,cAAc,EACd,SAAS,EACT,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAZe,iCAAsB,yBAYrC,CAAA;IAED,SAAgB,WAAW,CACvB,OAAoB,EACpB,CAAS,EACT,CAAS,EACT,UAAmC;QAEnC,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,IAAI,EAAE,GAAG,CAAC,CAAC;QAEX,IAAI,UAAU,EAAE,CAAC;YACb,IAAI,MAAM,GAAG,UAAU,CAAC,qBAAqB,EAAE,CAAC;YAChD,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YACjB,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;QACpB,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACzC,CAAC;IAjBe,sBAAW,cAiB1B,CAAA;IAED,MAAM,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC;IAEhC,SAAS,YAAY,CAAC,SAAc;QAChC,SAAS,IAAI;YACT,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC;gBAC7B,OAAO,CAAC,6BAA6B;YAEzC,SAAS;iBACJ,iBAAiB,CAAC,aAAa,CAAC;iBAChC,KAAK,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,sCAAsC;YAEzF,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;QAED,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC5B,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,SAAgB,gBAAgB,CAAC,SAAc;QAC3C,IAAI,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO;QACzC,YAAY,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;IAHe,2BAAgB,mBAG/B,CAAA;IAED,SAAgB,eAAe,CAAC,SAAc;QAC1C,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IAFe,0BAAe,kBAE9B,CAAA;AACL,CAAC,EAlEgB,UAAU,KAAV,UAAU,QAkE1B"} \ No newline at end of file diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.ts b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.ts index c2f9bb841..e54af6ad6 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.ts +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/wwwroot/domInterop.ts @@ -39,4 +39,31 @@ export namespace DOMInterop { element.style.top = (y + ry) + 'px'; element.style.left = (x + rx) + 'px'; } + + const activeTickers = new Map(); + + function createTicker(dotNetRef: any) { + function tick() { + if (!activeTickers.has(dotNetRef)) + return; + + dotNetRef + .invokeMethodAsync("OnFrameTick") + .catch(() => activeTickers.delete(dotNetRef)); // Auto-clean if component is disposed + + requestAnimationFrame(tick); + } + + requestAnimationFrame(tick); + activeTickers.set(dotNetRef, tick); + } + + export function startFrameTicker(dotNetRef: any): void { + if (activeTickers.has(dotNetRef)) return; + createTicker(dotNetRef); + } + + export function stopFrameTicker(dotNetRef: any): void { + activeTickers.delete(dotNetRef); + } } From aeb87cf2c7ac4c6e91762dd873d14c090578418f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 12:32:54 -0600 Subject: [PATCH 59/94] fix tests --- .../CoreObjectsTests/ChangingPaintTasks.cs | 3 +-- .../LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs | 3 ++- .../OtherTests/VisualElementsTests.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs b/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs index ad647e761..9c3fac1e6 100644 --- a/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs +++ b/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs @@ -386,8 +386,7 @@ public static int DrawChart(InMemorySkiaSharpChart chart, bool animated = false) new SkiaSharpDrawingContext( canvas, new SKImageInfo(100, 100), - SKSurface.CreateNull(100, 100), - new SKCanvas(new SKBitmap()))); + SKSurface.Create(new SKImageInfo(100, 100)))); } return frames; diff --git a/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs b/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs index e717effe1..f1dd23894 100644 --- a/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs +++ b/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs @@ -115,7 +115,8 @@ public void MaxWidth() "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Magnis dis parturient montes nascetur ridiculus mus mauris vitae. Dolor sed viverra ipsum nunc aliquet bibendum. At lectus urna duis convallis convallis. Rutrum quisque non tellus orci ac auctor augue mauris. Id aliquet lectus proin nibh nisl condimentum id. Viverra aliquet eget sit amet tellus cras adipiscing. Volutpat ac tincidunt vitae semper quis. Convallis a cras semper auctor neque. Imperdiet nulla malesuada pellentesque elit.\r\n\r\nViverra nam libero justo laoreet sit amet cursus sit. Sem integer vitae justo eget magna fermentum iaculis. Nulla facilisi etiam dignissim diam quis enim lobortis. At ultrices mi tempus imperdiet nulla. Tellus rutrum tellus pellentesque eu tincidunt tortor. Lacus luctus accumsan tortor posuere ac ut. Viverra nam libero justo laoreet sit amet. In dictum non consectetur a. Odio ut sem nulla pharetra diam sit amet nisl. Porttitor massa id neque aliquam vestibulum morbi blandit. Cras sed felis eget velit aliquet sagittis id consectetur. Mi bibendum neque egestas congue. Massa massa ultricies mi quis hendrerit dolor magna eget est. Leo urna molestie at elementum eu facilisis sed odio. Justo eget magna fermentum iaculis eu non diam. Vitae aliquet nec ullamcorper sit amet risus nullam. Ut tellus elementum sagittis vitae et leo duis ut. Eget nunc scelerisque viverra mauris. Egestas purus viverra accumsan in nisl nisi.\r\n\r\nNisl condimentum id venenatis a condimentum vitae sapien. Ut pharetra sit amet aliquam id diam maecenas ultricies. Hac habitasse platea dictumst quisque sagittis purus. Maecenas pharetra convallis posuere morbi leo urna molestie. Suspendisse ultrices gravida dictum fusce. In est ante in nibh mauris cursus mattis molestie a. Volutpat lacus laoreet non curabitur gravida arcu ac tortor dignissim. Eget lorem dolor sed viverra ipsum nunc aliquet bibendum enim. Sed odio morbi quis commodo odio aenean sed adipiscing. Aliquam eleifend mi in nulla posuere sollicitudin aliquam. Eget lorem dolor sed viverra ipsum nunc aliquet bibendum. Tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla facilisi. Dolor sit amet consectetur adipiscing elit ut.\r\n\r\nEst ultricies integer quis auctor elit sed vulputate mi sit. Enim ut tellus elementum sagittis. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Integer malesuada nunc vel risus commodo viverra maecenas. Praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam phasellus vestibulum. Netus et malesuada fames ac turpis egestas sed tempus urna. Pellentesque elit eget gravida cum sociis natoque penatibus et magnis. Donec ac odio tempor orci dapibus. Netus et malesuada fames ac turpis. Ultrices in iaculis nunc sed augue lacus viverra. Vulputate mi sit amet mauris. Scelerisque felis imperdiet proin fermentum leo vel orci porta non. Malesuada bibendum arcu vitae elementum curabitur vitae nunc sed.\r\n\r\nQuam viverra orci sagittis eu volutpat odio. Quis ipsum suspendisse ultrices gravida. Ac placerat vestibulum lectus mauris ultrices eros in cursus turpis. Aliquam vestibulum morbi blandit cursus. Mauris cursus mattis molestie a iaculis at erat pellentesque adipiscing. Enim nunc faucibus a pellentesque sit amet porttitor. Urna et pharetra pharetra massa massa. Nisi vitae suscipit tellus mauris a diam maecenas sed. Sit amet purus gravida quis blandit turpis cursus in. Felis eget nunc lobortis mattis aliquam faucibus purus in massa. Consequat id porta nibh venenatis. Tincidunt arcu non sodales neque sodales ut etiam. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl. Tortor at risus viverra adipiscing. Dis parturient montes nascetur ridiculus mus."; var canvas = new CoreMotionCanvas(); - var drawingContext = new SkiaSharpDrawingContext(canvas, SKImageInfo.Empty, SKSurface.CreateNull(100, 100), null!); + var drawingContext = new SkiaSharpDrawingContext( + canvas, SKImageInfo.Empty, SKSurface.Create(new SKImageInfo(100, 100))); var paint = new SolidColorPaint { Color = SKColors.Red }; paint.InitializeTask(drawingContext); diff --git a/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs b/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs index b715661f0..5914f2288 100644 --- a/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs +++ b/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs @@ -58,8 +58,7 @@ void Draw() new SkiaSharpDrawingContext( chart.CoreCanvas, new SKImageInfo(chart.Width, chart.Height), - null!, - canvas, + surface, SKColors.White, true)); } From ea0229d2d1603b8abbab859c89c30e7bc9c0c722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 14:04:17 -0600 Subject: [PATCH 60/94] update uno project --- src/_Shared.Native/NativeTicker.WinUI.cs | 5 ++++- ...veChartsCore.SkiaSharpView.Uno.WinUI.csproj | 18 +++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/_Shared.Native/NativeTicker.WinUI.cs b/src/_Shared.Native/NativeTicker.WinUI.cs index 6a620a22f..4ba259bed 100644 --- a/src/_Shared.Native/NativeTicker.WinUI.cs +++ b/src/_Shared.Native/NativeTicker.WinUI.cs @@ -20,7 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if WINDOWS || DESKTOP +#if WINDOWS || DESKTOP || BROWSERWASM + +// on desktop and browserwasm the uno implementation of composition target rendering is used. +// for the rest of the uno targets, the native ticker is used. using LiveChartsCore.Motion; using Microsoft.UI.Xaml.Media; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index e063e7b36..c6bd55006 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -3,8 +3,14 @@ enable $(GlobalLangVersion) + true + Library + $(LatestSkiaSharpVersion) + SkiaRenderer + - net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst;net9.0-desktop; + net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst; + net9.0-desktop;net9.0-browserwasm; @@ -50,13 +56,11 @@ - - - - - - + From 651e7ac258062316b7e544633ce47e8f551028b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 14:43:17 -0600 Subject: [PATCH 61/94] rename chart behaviour to pointercontroller --- ...tBehaviour.Android.cs => PointerController.Android.cs} | 2 +- ...ur.MacCatalyst.cs => PointerController.MacCatalyst.cs} | 6 +++--- ...tBehaviour.Windows.cs => PointerController.Windows.cs} | 2 +- ...tBehaviour._shared.cs => PointerController._shared.cs} | 2 +- src/_Shared.Native/_Shared.Native.projitems | 8 ++++---- .../LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs | 4 ++-- .../{ChartBehaviour.cs => PointerController.cs} | 4 ++-- .../{ChartBehaviour.cs => PointerController.cs} | 2 +- src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) rename src/_Shared.Native/{ChartBehaviour.Android.cs => PointerController.Android.cs} (99%) rename src/_Shared.Native/{ChartBehaviour.MacCatalyst.cs => PointerController.MacCatalyst.cs} (97%) rename src/_Shared.Native/{ChartBehaviour.Windows.cs => PointerController.Windows.cs} (98%) rename src/_Shared.Native/{ChartBehaviour._shared.cs => PointerController._shared.cs} (98%) rename src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/{ChartBehaviour.cs => PointerController.cs} (97%) rename src/skiasharp/_Shared.WinUI/{ChartBehaviour.cs => PointerController.cs} (99%) diff --git a/src/_Shared.Native/ChartBehaviour.Android.cs b/src/_Shared.Native/PointerController.Android.cs similarity index 99% rename from src/_Shared.Native/ChartBehaviour.Android.cs rename to src/_Shared.Native/PointerController.Android.cs index 4584a0736..2e7cc99d3 100644 --- a/src/_Shared.Native/ChartBehaviour.Android.cs +++ b/src/_Shared.Native/PointerController.Android.cs @@ -31,7 +31,7 @@ namespace LiveChartsCore.Behaviours; /// /// A class that adds platform-specific events to the chart. /// -public abstract partial class ChartBehaviour +public abstract partial class PointerController { private bool _isPinching; private bool _isDown; diff --git a/src/_Shared.Native/ChartBehaviour.MacCatalyst.cs b/src/_Shared.Native/PointerController.MacCatalyst.cs similarity index 97% rename from src/_Shared.Native/ChartBehaviour.MacCatalyst.cs rename to src/_Shared.Native/PointerController.MacCatalyst.cs index 018c2883f..ed3cd121e 100644 --- a/src/_Shared.Native/ChartBehaviour.MacCatalyst.cs +++ b/src/_Shared.Native/PointerController.MacCatalyst.cs @@ -32,7 +32,7 @@ namespace LiveChartsCore.Behaviours; /// /// A class that adds platform-specific events to the chart. /// -public partial class ChartBehaviour +public partial class PointerController { private DateTime _previousPress = DateTime.MinValue; @@ -61,9 +61,9 @@ public partial class ChartBehaviour protected UIPanGestureRecognizer MacCatalystPanGestureRecognizer { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ChartBehaviour() + public PointerController() { #if MACCATALYST MacCatalystHoverGestureRecognizer = new UIHoverGestureRecognizer(OnHover); diff --git a/src/_Shared.Native/ChartBehaviour.Windows.cs b/src/_Shared.Native/PointerController.Windows.cs similarity index 98% rename from src/_Shared.Native/ChartBehaviour.Windows.cs rename to src/_Shared.Native/PointerController.Windows.cs index a41443e1d..c3693f45b 100644 --- a/src/_Shared.Native/ChartBehaviour.Windows.cs +++ b/src/_Shared.Native/PointerController.Windows.cs @@ -30,7 +30,7 @@ namespace LiveChartsCore.Behaviours; /// /// A class that adds platform-specific events to the chart. /// -public partial class ChartBehaviour +public partial class PointerController { /// /// Called on windows pointer pressed events. diff --git a/src/_Shared.Native/ChartBehaviour._shared.cs b/src/_Shared.Native/PointerController._shared.cs similarity index 98% rename from src/_Shared.Native/ChartBehaviour._shared.cs rename to src/_Shared.Native/PointerController._shared.cs index 6cd2e5de3..44df2a7d2 100644 --- a/src/_Shared.Native/ChartBehaviour._shared.cs +++ b/src/_Shared.Native/PointerController._shared.cs @@ -28,7 +28,7 @@ namespace LiveChartsCore.Behaviours; /// /// A class that adds platform-specific events to the chart. /// -public abstract partial class ChartBehaviour +public abstract partial class PointerController { /// /// Gets or sets the screen size, only used internally by the Android handler, to implement a diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 06b5c6c35..982c8b29c 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -9,10 +9,10 @@ _Shared.Native - - - - + + + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs index ca8293e85..9fc4df10b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs @@ -40,7 +40,7 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// public class ChartViewHandler : ContentViewHandler { - private readonly ChartBehaviour _chartBehaviour; + private readonly PointerController _chartBehaviour; private ChartView? ChartView => VirtualView as ChartView; @@ -49,7 +49,7 @@ public class ChartViewHandler : ContentViewHandler /// public ChartViewHandler() { - _chartBehaviour = new ChartBehaviour(); + _chartBehaviour = new PointerController(); _chartBehaviour.Pressed += OnPressed; _chartBehaviour.Moved += OnMoved; _chartBehaviour.Released += OnReleased; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartBehaviour.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs similarity index 97% rename from src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartBehaviour.cs rename to src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs index 3f344b97b..3ddcf883c 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartBehaviour.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs @@ -38,12 +38,12 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// /// The chart behaviour for MAUI. /// -public partial class ChartBehaviour : Behaviours.ChartBehaviour +public partial class PointerController : Behaviours.PointerController { private static double s_density; private static LvcSize s_screenSize; - static ChartBehaviour() + static PointerController() { var deviceDisplay = DeviceDisplay.Current; deviceDisplay.MainDisplayInfoChanged += (_, args) => diff --git a/src/skiasharp/_Shared.WinUI/ChartBehaviour.cs b/src/skiasharp/_Shared.WinUI/PointerController.cs similarity index 99% rename from src/skiasharp/_Shared.WinUI/ChartBehaviour.cs rename to src/skiasharp/_Shared.WinUI/PointerController.cs index 6fae11814..3a2cd90d8 100644 --- a/src/skiasharp/_Shared.WinUI/ChartBehaviour.cs +++ b/src/skiasharp/_Shared.WinUI/PointerController.cs @@ -28,7 +28,7 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// /// The chart behaviour for WinUI and Uno Platform. /// -public partial class ChartBehaviour : Behaviours.ChartBehaviour +public partial class ChartBehaviour : Behaviours.PointerController { private static double s_density; private static LvcSize s_screenSize = new(); diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index 957a87a09..1370ab7c1 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -10,7 +10,7 @@ - + From 88337df4432e80467edd5b0dba13e35751aa9e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 14:46:34 -0600 Subject: [PATCH 62/94] rename behaviours ns to native --- src/_Shared.Native/Events/EventArgs.cs | 2 +- src/_Shared.Native/Events/Handler.cs | 2 +- src/_Shared.Native/Events/PinchEventArgs.cs | 2 +- src/_Shared.Native/Events/PinchHandler.cs | 2 +- src/_Shared.Native/Events/PressedEventArgs.cs | 2 +- src/_Shared.Native/Events/PressedHandler.cs | 2 +- src/_Shared.Native/Events/ScreenEventArgs.cs | 2 +- src/_Shared.Native/Events/ScreenHandler.cs | 2 +- src/_Shared.Native/Events/ScrollEventArgs.cs | 2 +- src/_Shared.Native/Events/ScrollHandler.cs | 2 +- src/_Shared.Native/NativeTicker.Android.cs | 2 +- src/_Shared.Native/NativeTicker.Mac.cs | 2 +- src/_Shared.Native/NativeTicker.NoUI.cs | 2 +- src/_Shared.Native/NativeTicker.WinUI.cs | 2 +- src/_Shared.Native/PointerController.Android.cs | 2 +- src/_Shared.Native/PointerController.MacCatalyst.cs | 2 +- src/_Shared.Native/PointerController.Windows.cs | 2 +- src/_Shared.Native/PointerController._shared.cs | 4 ++-- .../CartesianChart.cs | 4 ++-- .../ChartControl.cs | 8 ++++---- .../LiveChartsCore.SkiaSharpView.Maui/ChartView.cs | 12 ++++++------ .../ChartViewHandler.cs | 2 +- .../MotionCanvas.cs | 2 +- .../PointerController.cs | 2 +- src/skiasharp/_Shared.WinUI/CartesianChart.cs | 2 +- src/skiasharp/_Shared.WinUI/ChartControl.cs | 12 ++++++------ src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 2 +- src/skiasharp/_Shared.WinUI/PointerController.cs | 2 +- 28 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/_Shared.Native/Events/EventArgs.cs b/src/_Shared.Native/Events/EventArgs.cs index d8d1a4ec1..850be091a 100644 --- a/src/_Shared.Native/Events/EventArgs.cs +++ b/src/_Shared.Native/Events/EventArgs.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the screen event args. diff --git a/src/_Shared.Native/Events/Handler.cs b/src/_Shared.Native/Events/Handler.cs index f78c6ab4d..2463e21ba 100644 --- a/src/_Shared.Native/Events/Handler.cs +++ b/src/_Shared.Native/Events/Handler.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the pinch event handler. diff --git a/src/_Shared.Native/Events/PinchEventArgs.cs b/src/_Shared.Native/Events/PinchEventArgs.cs index c5cd40264..a87488497 100644 --- a/src/_Shared.Native/Events/PinchEventArgs.cs +++ b/src/_Shared.Native/Events/PinchEventArgs.cs @@ -22,7 +22,7 @@ using LiveChartsCore.Drawing; -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the pinch event args. diff --git a/src/_Shared.Native/Events/PinchHandler.cs b/src/_Shared.Native/Events/PinchHandler.cs index 473abc2a4..7b533dff0 100644 --- a/src/_Shared.Native/Events/PinchHandler.cs +++ b/src/_Shared.Native/Events/PinchHandler.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the pinch event handler. diff --git a/src/_Shared.Native/Events/PressedEventArgs.cs b/src/_Shared.Native/Events/PressedEventArgs.cs index 9a64ebeef..d2f741b6c 100644 --- a/src/_Shared.Native/Events/PressedEventArgs.cs +++ b/src/_Shared.Native/Events/PressedEventArgs.cs @@ -22,7 +22,7 @@ using LiveChartsCore.Drawing; -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the pointer event args. diff --git a/src/_Shared.Native/Events/PressedHandler.cs b/src/_Shared.Native/Events/PressedHandler.cs index 3a5e7cf03..0e53e956f 100644 --- a/src/_Shared.Native/Events/PressedHandler.cs +++ b/src/_Shared.Native/Events/PressedHandler.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the sreen event handler. diff --git a/src/_Shared.Native/Events/ScreenEventArgs.cs b/src/_Shared.Native/Events/ScreenEventArgs.cs index 704ef05f8..c20059dc4 100644 --- a/src/_Shared.Native/Events/ScreenEventArgs.cs +++ b/src/_Shared.Native/Events/ScreenEventArgs.cs @@ -22,7 +22,7 @@ using LiveChartsCore.Drawing; -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the screen event args. diff --git a/src/_Shared.Native/Events/ScreenHandler.cs b/src/_Shared.Native/Events/ScreenHandler.cs index db8d49624..f81c22083 100644 --- a/src/_Shared.Native/Events/ScreenHandler.cs +++ b/src/_Shared.Native/Events/ScreenHandler.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the sreen event handler. diff --git a/src/_Shared.Native/Events/ScrollEventArgs.cs b/src/_Shared.Native/Events/ScrollEventArgs.cs index e95257cef..8431e1ef5 100644 --- a/src/_Shared.Native/Events/ScrollEventArgs.cs +++ b/src/_Shared.Native/Events/ScrollEventArgs.cs @@ -22,7 +22,7 @@ using LiveChartsCore.Drawing; -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the scroll event args. diff --git a/src/_Shared.Native/Events/ScrollHandler.cs b/src/_Shared.Native/Events/ScrollHandler.cs index 224a936cc..855afe5e8 100644 --- a/src/_Shared.Native/Events/ScrollHandler.cs +++ b/src/_Shared.Native/Events/ScrollHandler.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace LiveChartsCore.Behaviours.Events; +namespace LiveChartsCore.Native.Events; /// /// Defines the scrroll event handler. diff --git a/src/_Shared.Native/NativeTicker.Android.cs b/src/_Shared.Native/NativeTicker.Android.cs index 2606b89b1..2647f49f2 100644 --- a/src/_Shared.Native/NativeTicker.Android.cs +++ b/src/_Shared.Native/NativeTicker.Android.cs @@ -25,7 +25,7 @@ using System; using LiveChartsCore.Motion; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; internal partial class NativeFrameTicker : IFrameTicker { diff --git a/src/_Shared.Native/NativeTicker.Mac.cs b/src/_Shared.Native/NativeTicker.Mac.cs index 60614ec95..efe58386e 100644 --- a/src/_Shared.Native/NativeTicker.Mac.cs +++ b/src/_Shared.Native/NativeTicker.Mac.cs @@ -26,7 +26,7 @@ using Foundation; using LiveChartsCore.Motion; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; internal partial class NativeFrameTicker : IFrameTicker { diff --git a/src/_Shared.Native/NativeTicker.NoUI.cs b/src/_Shared.Native/NativeTicker.NoUI.cs index 486daf189..f8ba577c3 100644 --- a/src/_Shared.Native/NativeTicker.NoUI.cs +++ b/src/_Shared.Native/NativeTicker.NoUI.cs @@ -28,7 +28,7 @@ using LiveChartsCore.Motion; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; internal partial class NativeFrameTicker : IFrameTicker { diff --git a/src/_Shared.Native/NativeTicker.WinUI.cs b/src/_Shared.Native/NativeTicker.WinUI.cs index 4ba259bed..18bac619f 100644 --- a/src/_Shared.Native/NativeTicker.WinUI.cs +++ b/src/_Shared.Native/NativeTicker.WinUI.cs @@ -28,7 +28,7 @@ using LiveChartsCore.Motion; using Microsoft.UI.Xaml.Media; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; internal partial class NativeFrameTicker : IFrameTicker { diff --git a/src/_Shared.Native/PointerController.Android.cs b/src/_Shared.Native/PointerController.Android.cs index 2e7cc99d3..e2c5896ff 100644 --- a/src/_Shared.Native/PointerController.Android.cs +++ b/src/_Shared.Native/PointerController.Android.cs @@ -26,7 +26,7 @@ using Android.Views; using LiveChartsCore.Drawing; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; /// /// A class that adds platform-specific events to the chart. diff --git a/src/_Shared.Native/PointerController.MacCatalyst.cs b/src/_Shared.Native/PointerController.MacCatalyst.cs index ed3cd121e..9c27e84ef 100644 --- a/src/_Shared.Native/PointerController.MacCatalyst.cs +++ b/src/_Shared.Native/PointerController.MacCatalyst.cs @@ -27,7 +27,7 @@ using LiveChartsCore.Drawing; using UIKit; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; /// /// A class that adds platform-specific events to the chart. diff --git a/src/_Shared.Native/PointerController.Windows.cs b/src/_Shared.Native/PointerController.Windows.cs index c3693f45b..0c71ee9ea 100644 --- a/src/_Shared.Native/PointerController.Windows.cs +++ b/src/_Shared.Native/PointerController.Windows.cs @@ -25,7 +25,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; /// /// A class that adds platform-specific events to the chart. diff --git a/src/_Shared.Native/PointerController._shared.cs b/src/_Shared.Native/PointerController._shared.cs index 44df2a7d2..9f9e86bef 100644 --- a/src/_Shared.Native/PointerController._shared.cs +++ b/src/_Shared.Native/PointerController._shared.cs @@ -20,10 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using LiveChartsCore.Behaviours.Events; +using LiveChartsCore.Native.Events; using LiveChartsCore.Drawing; -namespace LiveChartsCore.Behaviours; +namespace LiveChartsCore.Native; /// /// A class that adds platform-specific events to the chart. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.cs index 82773a5c7..dab0a1de9 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/CartesianChart.cs @@ -38,13 +38,13 @@ namespace LiveChartsCore.SkiaSharpView.Maui; [XamlCompilation(XamlCompilationOptions.Compile)] public partial class CartesianChart : ChartControl, ICartesianChartView { - internal override void OnScrolled(object? sender, Behaviours.Events.ScrollEventArgs args) + internal override void OnScrolled(object? sender, Native.Events.ScrollEventArgs args) { var c = (CartesianChartEngine)CoreChart; c.Zoom(args.Location, args.ScrollDelta > 0 ? ZoomDirection.ZoomIn : ZoomDirection.ZoomOut); } - internal override void OnPinched(object? sender, Behaviours.Events.PinchEventArgs args) + internal override void OnPinched(object? sender, Native.Events.PinchEventArgs args) { var c = (CartesianChartEngine)CoreChart; var p = args.PinchStart; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs index 49dd207d5..b25f2cf7f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs @@ -118,7 +118,7 @@ private void RemoveUIElement(object item) _ = CanvasView.Children.Remove(view); } - internal override void OnPressed(object? sender, Behaviours.Events.PressedEventArgs args) + internal override void OnPressed(object? sender, Native.Events.PressedEventArgs args) { // not implemented yet? // https://github.com/dotnet/maui/issues/16202 @@ -131,7 +131,7 @@ internal override void OnPressed(object? sender, Behaviours.Events.PressedEventA CoreChart.InvokePointerDown(args.Location, args.IsSecondaryPress); } - internal override void OnMoved(object? sender, Behaviours.Events.ScreenEventArgs args) + internal override void OnMoved(object? sender, Native.Events.ScreenEventArgs args) { var location = args.Location; @@ -142,7 +142,7 @@ internal override void OnMoved(object? sender, Behaviours.Events.ScreenEventArgs CoreChart.InvokePointerMove(location); } - internal override void OnReleased(object? sender, Behaviours.Events.PressedEventArgs args) + internal override void OnReleased(object? sender, Native.Events.PressedEventArgs args) { var cArgs = new PointerCommandArgs(this, new(args.Location.X, args.Location.Y), args); if (ReleasedCommand?.CanExecute(cArgs) == true) @@ -151,7 +151,7 @@ internal override void OnReleased(object? sender, Behaviours.Events.PressedEvent CoreChart.InvokePointerUp(args.Location, args.IsSecondaryPress); } - internal override void OnExited(object? sender, Behaviours.Events.EventArgs args) => + internal override void OnExited(object? sender, Native.Events.EventArgs args) => CoreChart.InvokePointerLeft(); void IChartView.InvokeOnUIThread(Action action) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartView.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartView.cs index 5219e4077..657bbd65e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartView.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartView.cs @@ -41,10 +41,10 @@ static ChartView() } } - internal virtual void OnPressed(object? sender, Behaviours.Events.PressedEventArgs args) { } - internal virtual void OnMoved(object? sender, Behaviours.Events.ScreenEventArgs args) { } - internal virtual void OnReleased(object? sender, Behaviours.Events.PressedEventArgs args) { } - internal virtual void OnScrolled(object? sender, Behaviours.Events.ScrollEventArgs args) { } - internal virtual void OnPinched(object? sender, Behaviours.Events.PinchEventArgs args) { } - internal virtual void OnExited(object? sender, Behaviours.Events.EventArgs args) { } + internal virtual void OnPressed(object? sender, Native.Events.PressedEventArgs args) { } + internal virtual void OnMoved(object? sender, Native.Events.ScreenEventArgs args) { } + internal virtual void OnReleased(object? sender, Native.Events.PressedEventArgs args) { } + internal virtual void OnScrolled(object? sender, Native.Events.ScrollEventArgs args) { } + internal virtual void OnPinched(object? sender, Native.Events.PinchEventArgs args) { } + internal virtual void OnExited(object? sender, Native.Events.EventArgs args) { } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs index 9fc4df10b..ed697ff3a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs @@ -30,7 +30,7 @@ using PlatformView = System.Object; #endif -using LiveChartsCore.Behaviours.Events; +using LiveChartsCore.Native.Events; using Microsoft.Maui.Handlers; namespace LiveChartsCore.SkiaSharpView.Maui; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs index c5228f48c..0824c9fbf 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs @@ -22,7 +22,7 @@ using System; using LiveChartsCore.Motion; -using LiveChartsCore.Behaviours; +using LiveChartsCore.Native; using LiveChartsCore.SkiaSharpView.Maui.Rendering; using Microsoft.Maui.Controls; using Microsoft.Maui.Layouts; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs index 3ddcf883c..51fe6d18b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs @@ -38,7 +38,7 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// /// The chart behaviour for MAUI. /// -public partial class PointerController : Behaviours.PointerController +public partial class PointerController : Native.PointerController { private static double s_density; private static LvcSize s_screenSize; diff --git a/src/skiasharp/_Shared.WinUI/CartesianChart.cs b/src/skiasharp/_Shared.WinUI/CartesianChart.cs index 1c6bd2420..0a41e4f47 100644 --- a/src/skiasharp/_Shared.WinUI/CartesianChart.cs +++ b/src/skiasharp/_Shared.WinUI/CartesianChart.cs @@ -27,7 +27,7 @@ // // ============================================================================== -using LiveChartsCore.Behaviours.Events; +using LiveChartsCore.Native.Events; using LiveChartsCore.Drawing; using LiveChartsCore.Kernel.Sketches; using LiveChartsCore.Measure; diff --git a/src/skiasharp/_Shared.WinUI/ChartControl.cs b/src/skiasharp/_Shared.WinUI/ChartControl.cs index 1e29543c8..6560484cd 100644 --- a/src/skiasharp/_Shared.WinUI/ChartControl.cs +++ b/src/skiasharp/_Shared.WinUI/ChartControl.cs @@ -119,7 +119,7 @@ private void RemoveUIElement(object item) _ = CanvasView.Children.Remove(uiElement); } - private void OnPressed(object? sender, Behaviours.Events.PressedEventArgs args) + private void OnPressed(object? sender, Native.Events.PressedEventArgs args) { // is this working on all platforms? //if (args.KeyModifiers > 0) return; @@ -131,7 +131,7 @@ private void OnPressed(object? sender, Behaviours.Events.PressedEventArgs args) CoreChart?.InvokePointerDown(args.Location, args.IsSecondaryPress); } - private void OnMoved(object? sender, Behaviours.Events.ScreenEventArgs args) + private void OnMoved(object? sender, Native.Events.ScreenEventArgs args) { var location = args.Location; @@ -142,7 +142,7 @@ private void OnMoved(object? sender, Behaviours.Events.ScreenEventArgs args) CoreChart?.InvokePointerMove(location); } - private void OnReleased(object? sender, Behaviours.Events.PressedEventArgs args) + private void OnReleased(object? sender, Native.Events.PressedEventArgs args) { var cArgs = new PointerCommandArgs(this, new(args.Location.X, args.Location.Y), args); if (PointerReleasedCommand?.CanExecute(cArgs) == true) @@ -151,7 +151,7 @@ private void OnReleased(object? sender, Behaviours.Events.PressedEventArgs args) CoreChart?.InvokePointerUp(args.Location, args.IsSecondaryPress); } - private void OnExited(object? sender, Behaviours.Events.EventArgs args) => + private void OnExited(object? sender, Native.Events.EventArgs args) => CoreChart?.InvokePointerLeft(); /// @@ -162,7 +162,7 @@ private void OnExited(object? sender, Behaviours.Events.EventArgs args) => /// The source of the scroll event. This can be if the event is not associated with a /// specific sender. /// The event data containing details about the scroll action, such as scroll direction and position. - protected virtual void OnScrolled(object? sender, Behaviours.Events.ScrollEventArgs args) { } + protected virtual void OnScrolled(object? sender, Native.Events.ScrollEventArgs args) { } /// /// Handles the pinch gesture event, allowing zooming functionality in the chart. @@ -172,7 +172,7 @@ protected virtual void OnScrolled(object? sender, Behaviours.Events.ScrollEventA /// behavior of pinch gestures. /// The source of the event. This can be . /// The event data containing information about the pinch gesture, including its location and scroll delta. - protected virtual void OnPinched(object? sender, Behaviours.Events.PinchEventArgs args) { } + protected virtual void OnPinched(object? sender, Native.Events.PinchEventArgs args) { } private ISeries InflateSeriesTemplate(object item) { diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index 15d1101ad..df794b665 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using LiveChartsCore.Behaviours; +using LiveChartsCore.Native; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.WinUI.Rendering; using Microsoft.UI.Xaml; diff --git a/src/skiasharp/_Shared.WinUI/PointerController.cs b/src/skiasharp/_Shared.WinUI/PointerController.cs index 3a2cd90d8..d1ce39217 100644 --- a/src/skiasharp/_Shared.WinUI/PointerController.cs +++ b/src/skiasharp/_Shared.WinUI/PointerController.cs @@ -28,7 +28,7 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// /// The chart behaviour for WinUI and Uno Platform. /// -public partial class ChartBehaviour : Behaviours.PointerController +public partial class ChartBehaviour : Native.PointerController { private static double s_density; private static LvcSize s_screenSize = new(); From b71fc43253687f7c9e563936cfed85234b0e1960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 15:27:23 -0600 Subject: [PATCH 63/94] simplify pointercontroller by platform --- .../PointerController.Android.cs | 44 +++++--- ...acCatalyst.cs => PointerController.Mac.cs} | 74 +++++++------ ....Windows.cs => PointerController.WinUI.cs} | 64 +++++------ .../PointerController._shared.cs | 103 ------------------ src/_Shared.Native/PointerController.cs | 44 ++++++++ src/_Shared.Native/_Shared.Native.projitems | 6 +- 6 files changed, 144 insertions(+), 191 deletions(-) rename src/_Shared.Native/{PointerController.MacCatalyst.cs => PointerController.Mac.cs} (74%) rename src/_Shared.Native/{PointerController.Windows.cs => PointerController.WinUI.cs} (60%) delete mode 100644 src/_Shared.Native/PointerController._shared.cs create mode 100644 src/_Shared.Native/PointerController.cs diff --git a/src/_Shared.Native/PointerController.Android.cs b/src/_Shared.Native/PointerController.Android.cs index e2c5896ff..c86c25e63 100644 --- a/src/_Shared.Native/PointerController.Android.cs +++ b/src/_Shared.Native/PointerController.Android.cs @@ -28,11 +28,9 @@ namespace LiveChartsCore.Native; -/// -/// A class that adds platform-specific events to the chart. -/// -public abstract partial class PointerController +internal abstract partial class PointerController { + private LvcSize _screenSize = new(320, 480); // only used to implement a workaround for https://github.com/dotnet/maui/issues/18547. private bool _isPinching; private bool _isDown; private LvcPoint _lastTouch; @@ -41,12 +39,29 @@ public abstract partial class PointerController private CustomScaleListener _customScaleListener = null!; private DateTime _previousPress = DateTime.MinValue; - /// - /// Called on android hover events. - /// - /// the sender. - /// the event args. - protected void OnAndroidHover(object? sender, View.HoverEventArgs e) + private void InitializePlatform(object view) + { + var androidView = (View)view; + + androidView.Touch += OnAndroidTouched; + androidView.Hover += OnAndroidHover; + } + + private void DisposePlatform(object view) + { + var androidView = (View)view; + + androidView.Touch -= OnAndroidTouched; + androidView.Hover -= OnAndroidHover; + + _scaleDetector?.Dispose(); + _scaleDetector = null; + + _customScaleListener.Dispose(); + _customScaleListener = null!; + } + + private void OnAndroidHover(object? sender, View.HoverEventArgs e) { if (e.Event is null) return; @@ -54,12 +69,7 @@ protected void OnAndroidHover(object? sender, View.HoverEventArgs e) Moved?.Invoke(sender, new(p, e.Event)); } - /// - /// Called on android touch events. - /// - /// the sender. - /// the event args. - protected void OnAndroidTouched(object? sender, View.TouchEventArgs e) + private void OnAndroidTouched(object? sender, View.TouchEventArgs e) { var viewGroup = (ViewGroup?)sender; if (e.Event is null || viewGroup is null) return; @@ -131,7 +141,7 @@ protected void OnAndroidTouched(object? sender, View.TouchEventArgs e) _lastTouch = p; var yTolerance = 0.20 * viewGroup.Height / Density; - var screenTolerance = 0.25 * ScreenSize.Height / Density; + var screenTolerance = 0.25 * _screenSize.Height / Density; if (screenTolerance < yTolerance) yTolerance = screenTolerance; var yMovement = Math.Abs(p.Y - _touchStart.Y); diff --git a/src/_Shared.Native/PointerController.MacCatalyst.cs b/src/_Shared.Native/PointerController.Mac.cs similarity index 74% rename from src/_Shared.Native/PointerController.MacCatalyst.cs rename to src/_Shared.Native/PointerController.Mac.cs index 9c27e84ef..f897c9c51 100644 --- a/src/_Shared.Native/PointerController.MacCatalyst.cs +++ b/src/_Shared.Native/PointerController.Mac.cs @@ -29,55 +29,69 @@ namespace LiveChartsCore.Native; -/// -/// A class that adds platform-specific events to the chart. -/// -public partial class PointerController +internal partial class PointerController { private DateTime _previousPress = DateTime.MinValue; - + private float _previousScale = 1; + private UILongPressGestureRecognizer _longPressGestureRecognizer; + private UIPinchGestureRecognizer _pinchGestureRecognizer; + private UIPanGestureRecognizer _panGestureRecognizer; #if MACCATALYST + private UIHoverGestureRecognizer _hoverGestureRecognizer; +#endif + + private void InitializePlatform(object view) + { + var macView = (UIView)view; - /// - /// Gets the hover gesture recognizer. - /// - protected UIHoverGestureRecognizer MacCatalystHoverGestureRecognizer { get; } + macView.UserInteractionEnabled = true; + macView.AddGestureRecognizer(_longPressGestureRecognizer); + macView.AddGestureRecognizer(_pinchGestureRecognizer); + macView.AddGestureRecognizer(_panGestureRecognizer); +#if MACCATALYST + macView.AddGestureRecognizer(_hoverGestureRecognizer); #endif + } + + private void DisposePlatform(object view) + { + var macView = (UIView)view; - /// - /// Gets the long press gesture recognizer. - /// - protected UILongPressGestureRecognizer MacCatalystLongPressGestureRecognizer { get; } + macView.RemoveGestureRecognizer(_longPressGestureRecognizer); + macView.RemoveGestureRecognizer(_pinchGestureRecognizer); + macView.RemoveGestureRecognizer(_panGestureRecognizer); - /// - /// Gets the pinch gesture recognizer. - /// - protected UIPinchGestureRecognizer MacCatalystPinchGestureRecognizer { get; } + _longPressGestureRecognizer.Dispose(); + _pinchGestureRecognizer.Dispose(); + _panGestureRecognizer.Dispose(); - /// - /// Gets the pan gesture recognizer. - /// - protected UIPanGestureRecognizer MacCatalystPanGestureRecognizer { get; } + _longPressGestureRecognizer = null!; + _pinchGestureRecognizer = null!; + _panGestureRecognizer = null!; + +#if MACCATALYST + macView.RemoveGestureRecognizer(_hoverGestureRecognizer); + _hoverGestureRecognizer.Dispose(); + _hoverGestureRecognizer = null!; +#endif + } - /// - /// Initializes a new instance of the class. - /// public PointerController() { #if MACCATALYST - MacCatalystHoverGestureRecognizer = new UIHoverGestureRecognizer(OnHover); + _hoverGestureRecognizer = new UIHoverGestureRecognizer(OnHover); #endif - MacCatalystLongPressGestureRecognizer = new UILongPressGestureRecognizer(OnLongPress) + _longPressGestureRecognizer = new UILongPressGestureRecognizer(OnLongPress) { MinimumPressDuration = 0, ShouldRecognizeSimultaneously = (g1, g2) => true }; - MacCatalystPinchGestureRecognizer = new UIPinchGestureRecognizer(OnPinch) + _pinchGestureRecognizer = new UIPinchGestureRecognizer(OnPinch) { ShouldRecognizeSimultaneously = (g1, g2) => true }; - MacCatalystPanGestureRecognizer = new UIPanGestureRecognizer(OnPan) + _panGestureRecognizer = new UIPanGestureRecognizer(OnPan) { #if MACCATALYST AllowedScrollTypesMask = UIScrollTypeMask.Discrete | UIScrollTypeMask.Continuous, @@ -88,7 +102,6 @@ public PointerController() } #if MACCATALYST - private void OnHover(UIHoverGestureRecognizer e) { var view = e.View; @@ -109,7 +122,6 @@ private void OnHover(UIHoverGestureRecognizer e) break; } } - #endif private void OnLongPress(UILongPressGestureRecognizer e) @@ -140,8 +152,6 @@ private void OnLongPress(UILongPressGestureRecognizer e) } } - private float _previousScale = 1; - private void OnPinch(UIPinchGestureRecognizer e) { var view = e.View; diff --git a/src/_Shared.Native/PointerController.Windows.cs b/src/_Shared.Native/PointerController.WinUI.cs similarity index 60% rename from src/_Shared.Native/PointerController.Windows.cs rename to src/_Shared.Native/PointerController.WinUI.cs index 0c71ee9ea..bcd6b4d02 100644 --- a/src/_Shared.Native/PointerController.Windows.cs +++ b/src/_Shared.Native/PointerController.WinUI.cs @@ -27,17 +27,31 @@ namespace LiveChartsCore.Native; -/// -/// A class that adds platform-specific events to the chart. -/// -public partial class PointerController +internal partial class PointerController { - /// - /// Called on windows pointer pressed events. - /// - /// The sender. - /// The event args. - protected void OnWindowsPointerPressed(object sender, PointerRoutedEventArgs e) + private void InitializePlatform(object view) + { + var winUIView = (UIElement)view; + + winUIView.PointerPressed += OnWindowsPointerPressed; + winUIView.PointerMoved += OnWindowsPointerMoved; + winUIView.PointerReleased += OnWindowsPointerReleased; + winUIView.PointerWheelChanged += OnWindowsPointerWheelChanged; + winUIView.PointerExited += OnWindowsPointerExited; + } + + private void DisposePlatform(object view) + { + var winUIView = (UIElement)view; + + winUIView.PointerPressed -= OnWindowsPointerPressed; + winUIView.PointerMoved -= OnWindowsPointerMoved; + winUIView.PointerReleased -= OnWindowsPointerReleased; + winUIView.PointerWheelChanged -= OnWindowsPointerWheelChanged; + winUIView.PointerExited -= OnWindowsPointerExited; + } + + private void OnWindowsPointerPressed(object sender, PointerRoutedEventArgs e) { var p = e.GetCurrentPoint(sender as UIElement); if (p is null) return; @@ -47,12 +61,7 @@ protected void OnWindowsPointerPressed(object sender, PointerRoutedEventArgs e) new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); } - /// - /// Called on windows pointer moved events. - /// - /// The sender. - /// The events. - protected void OnWindowsPointerMoved(object sender, PointerRoutedEventArgs e) + private void OnWindowsPointerMoved(object sender, PointerRoutedEventArgs e) { var p = e.GetCurrentPoint(sender as UIElement); if (p is null) return; @@ -62,12 +71,7 @@ protected void OnWindowsPointerMoved(object sender, PointerRoutedEventArgs e) new(new(p.Position.X, p.Position.Y), e)); } - /// - /// Called on windows pointer released events. - /// - /// The sender. - /// The event args. - protected void OnWindowsPointerReleased(object sender, PointerRoutedEventArgs e) + private void OnWindowsPointerReleased(object sender, PointerRoutedEventArgs e) { var p = e.GetCurrentPoint(sender as UIElement); if (p is null) return; @@ -77,26 +81,14 @@ protected void OnWindowsPointerReleased(object sender, PointerRoutedEventArgs e) new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); } - /// - /// Called on windows pointer wheel changed events. - /// - /// The sender. - /// The event args. - protected void OnWindowsPointerWheelChanged(object sender, PointerRoutedEventArgs e) + private void OnWindowsPointerWheelChanged(object sender, PointerRoutedEventArgs e) { var p = e.GetCurrentPoint(sender as UIElement); Scrolled?.Invoke(sender, new(new(p.Position.X, p.Position.Y), p.Properties.MouseWheelDelta, e)); } - /// - /// Called on windows pointer entered events. - /// - /// The sender. - /// The event args. - protected void OnWindowsPointerExited(object sender, PointerRoutedEventArgs e) - { + private void OnWindowsPointerExited(object sender, PointerRoutedEventArgs e) => Exited?.Invoke(sender, new(e)); - } } #endif diff --git a/src/_Shared.Native/PointerController._shared.cs b/src/_Shared.Native/PointerController._shared.cs deleted file mode 100644 index 9f9e86bef..000000000 --- a/src/_Shared.Native/PointerController._shared.cs +++ /dev/null @@ -1,103 +0,0 @@ -// The MIT License(MIT) -// -// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using LiveChartsCore.Native.Events; -using LiveChartsCore.Drawing; - -namespace LiveChartsCore.Native; - -/// -/// A class that adds platform-specific events to the chart. -/// -public abstract partial class PointerController -{ - /// - /// Gets or sets the screen size, only used internally by the Android handler, to implement a - /// workaround for https://github.com/dotnet/maui/issues/18547. - /// - public virtual LvcSize ScreenSize { get; } = new(320, 480); - - /// - /// Gets or sets the screen density. - /// - public virtual double Density { get; } = 1.0; - - /// - /// Called when the pointer/tap is pressed. - /// - public event PressedHandler? Pressed; - - /// - /// Called when the pointer/tap is released. - /// - public event PressedHandler? Released; - - /// - /// Called when the pointer/tap moves. - /// - public event ScreenHandler? Moved; - - /// - /// Called when the pointer exits the control. - /// - public event Handler? Exited; - - /// - /// Called when the control is pinched. - /// - public event PinchHandler? Pinched; - - /// - /// Called when the control is scrolled. - /// - public event ScrollHandler? Scrolled; - - internal void InvokePressed(object sender, PressedEventArgs e) - { - Pressed?.Invoke(sender, e); - } - - internal void InvokeReleased(object sender, PressedEventArgs e) - { - Released?.Invoke(sender, e); - } - - internal void InvokeMoved(object sender, ScreenEventArgs e) - { - Moved?.Invoke(sender, e); - } - - internal void InvokeExited(object sender, EventArgs e) - { - Exited?.Invoke(sender, e); - } - - internal void InvokePinched(object sender, PinchEventArgs e) - { - Pinched?.Invoke(sender, e); - } - - internal void InvokeScrolled(object sender, ScrollEventArgs e) - { - Scrolled?.Invoke(sender, e); - } -} diff --git a/src/_Shared.Native/PointerController.cs b/src/_Shared.Native/PointerController.cs new file mode 100644 index 000000000..9af9c2487 --- /dev/null +++ b/src/_Shared.Native/PointerController.cs @@ -0,0 +1,44 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using LiveChartsCore.Drawing; +using LiveChartsCore.Native.Events; + +namespace LiveChartsCore.Native; + +internal abstract partial class PointerController +{ + public static LvcSize ScreenSize { get; private set; } = new(320, 480); + public static double Density { get; private set; } = 1.0; + + public event PressedHandler? Pressed; + + public event PressedHandler? Released; + + public event ScreenHandler? Moved; + + public event Handler? Exited; + + public event PinchHandler? Pinched; + + public event ScrollHandler? Scrolled; +} diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 982c8b29c..044f54f1c 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -10,9 +10,9 @@ - - - + + + From b3702b8370b08dfe100833dff9f7b77c0aceb532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 16:14:32 -0600 Subject: [PATCH 64/94] add platforms folders in native handlers --- .../Android/NativeTicker.cs} | 0 .../Android/PointerController.cs} | 6 +-- .../Platforms/INativePointerController.cs | 30 +++++++++++++ .../Mac/NativeTicker.cs} | 0 .../Mac/PointerController.cs} | 6 +-- .../NoUI/NativeTicker.cs} | 0 .../Platforms/NoUI/PointerController.cs | 44 +++++++++++++++++++ .../WinUI/NativeTicker.cs} | 0 .../WinUI/PointerController.cs} | 6 +-- src/_Shared.Native/PointerController.cs | 3 +- src/_Shared.Native/_Shared.Native.projitems | 21 ++++++--- 11 files changed, 99 insertions(+), 17 deletions(-) rename src/_Shared.Native/{NativeTicker.Android.cs => Platforms/Android/NativeTicker.cs} (100%) rename src/_Shared.Native/{PointerController.Android.cs => Platforms/Android/PointerController.cs} (97%) create mode 100644 src/_Shared.Native/Platforms/INativePointerController.cs rename src/_Shared.Native/{NativeTicker.Mac.cs => Platforms/Mac/NativeTicker.cs} (100%) rename src/_Shared.Native/{PointerController.Mac.cs => Platforms/Mac/PointerController.cs} (97%) rename src/_Shared.Native/{NativeTicker.NoUI.cs => Platforms/NoUI/NativeTicker.cs} (100%) create mode 100644 src/_Shared.Native/Platforms/NoUI/PointerController.cs rename src/_Shared.Native/{NativeTicker.WinUI.cs => Platforms/WinUI/NativeTicker.cs} (100%) rename src/_Shared.Native/{PointerController.WinUI.cs => Platforms/WinUI/PointerController.cs} (95%) diff --git a/src/_Shared.Native/NativeTicker.Android.cs b/src/_Shared.Native/Platforms/Android/NativeTicker.cs similarity index 100% rename from src/_Shared.Native/NativeTicker.Android.cs rename to src/_Shared.Native/Platforms/Android/NativeTicker.cs diff --git a/src/_Shared.Native/PointerController.Android.cs b/src/_Shared.Native/Platforms/Android/PointerController.cs similarity index 97% rename from src/_Shared.Native/PointerController.Android.cs rename to src/_Shared.Native/Platforms/Android/PointerController.cs index c86c25e63..a8ed3bbec 100644 --- a/src/_Shared.Native/PointerController.Android.cs +++ b/src/_Shared.Native/Platforms/Android/PointerController.cs @@ -28,7 +28,7 @@ namespace LiveChartsCore.Native; -internal abstract partial class PointerController +internal partial class PointerController : INativePointerController { private LvcSize _screenSize = new(320, 480); // only used to implement a workaround for https://github.com/dotnet/maui/issues/18547. private bool _isPinching; @@ -39,7 +39,7 @@ internal abstract partial class PointerController private CustomScaleListener _customScaleListener = null!; private DateTime _previousPress = DateTime.MinValue; - private void InitializePlatform(object view) + public void InitializeController(object view) { var androidView = (View)view; @@ -47,7 +47,7 @@ private void InitializePlatform(object view) androidView.Hover += OnAndroidHover; } - private void DisposePlatform(object view) + public void DisposeController(object view) { var androidView = (View)view; diff --git a/src/_Shared.Native/Platforms/INativePointerController.cs b/src/_Shared.Native/Platforms/INativePointerController.cs new file mode 100644 index 000000000..507a67326 --- /dev/null +++ b/src/_Shared.Native/Platforms/INativePointerController.cs @@ -0,0 +1,30 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Native; + +internal interface INativePointerController +{ + void InitializeController(object view); + + void DisposeController(object view); +} diff --git a/src/_Shared.Native/NativeTicker.Mac.cs b/src/_Shared.Native/Platforms/Mac/NativeTicker.cs similarity index 100% rename from src/_Shared.Native/NativeTicker.Mac.cs rename to src/_Shared.Native/Platforms/Mac/NativeTicker.cs diff --git a/src/_Shared.Native/PointerController.Mac.cs b/src/_Shared.Native/Platforms/Mac/PointerController.cs similarity index 97% rename from src/_Shared.Native/PointerController.Mac.cs rename to src/_Shared.Native/Platforms/Mac/PointerController.cs index f897c9c51..61bf0e46c 100644 --- a/src/_Shared.Native/PointerController.Mac.cs +++ b/src/_Shared.Native/Platforms/Mac/PointerController.cs @@ -29,7 +29,7 @@ namespace LiveChartsCore.Native; -internal partial class PointerController +internal partial class PointerController : INativePointerController { private DateTime _previousPress = DateTime.MinValue; private float _previousScale = 1; @@ -40,7 +40,7 @@ internal partial class PointerController private UIHoverGestureRecognizer _hoverGestureRecognizer; #endif - private void InitializePlatform(object view) + public void InitializeController(object view) { var macView = (UIView)view; @@ -54,7 +54,7 @@ private void InitializePlatform(object view) #endif } - private void DisposePlatform(object view) + public void DisposeController(object view) { var macView = (UIView)view; diff --git a/src/_Shared.Native/NativeTicker.NoUI.cs b/src/_Shared.Native/Platforms/NoUI/NativeTicker.cs similarity index 100% rename from src/_Shared.Native/NativeTicker.NoUI.cs rename to src/_Shared.Native/Platforms/NoUI/NativeTicker.cs diff --git a/src/_Shared.Native/Platforms/NoUI/PointerController.cs b/src/_Shared.Native/Platforms/NoUI/PointerController.cs new file mode 100644 index 000000000..00003a026 --- /dev/null +++ b/src/_Shared.Native/Platforms/NoUI/PointerController.cs @@ -0,0 +1,44 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if !HAS_UI + +// This code is reached maybe only on test environments. +// HAS_UI is true when the target framework contains any of the following: +// -windows, -android, -ios, -maccatalyst, -tizen, -desktop, -browserwasm + +namespace LiveChartsCore.Native; + +internal partial class PointerController : INativePointerController +{ + public void InitializeController(object view) + { + // ignored. + } + + public void DisposeController(object view) + { + // ignored. + } +} + +#endif diff --git a/src/_Shared.Native/NativeTicker.WinUI.cs b/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs similarity index 100% rename from src/_Shared.Native/NativeTicker.WinUI.cs rename to src/_Shared.Native/Platforms/WinUI/NativeTicker.cs diff --git a/src/_Shared.Native/PointerController.WinUI.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs similarity index 95% rename from src/_Shared.Native/PointerController.WinUI.cs rename to src/_Shared.Native/Platforms/WinUI/PointerController.cs index bcd6b4d02..93fe3c930 100644 --- a/src/_Shared.Native/PointerController.WinUI.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -27,9 +27,9 @@ namespace LiveChartsCore.Native; -internal partial class PointerController +internal partial class PointerController : INativePointerController { - private void InitializePlatform(object view) + public void InitializeController(object view) { var winUIView = (UIElement)view; @@ -40,7 +40,7 @@ private void InitializePlatform(object view) winUIView.PointerExited += OnWindowsPointerExited; } - private void DisposePlatform(object view) + public void DisposeController(object view) { var winUIView = (UIElement)view; diff --git a/src/_Shared.Native/PointerController.cs b/src/_Shared.Native/PointerController.cs index 9af9c2487..aa8663dfb 100644 --- a/src/_Shared.Native/PointerController.cs +++ b/src/_Shared.Native/PointerController.cs @@ -25,9 +25,10 @@ namespace LiveChartsCore.Native; -internal abstract partial class PointerController +internal partial class PointerController { public static LvcSize ScreenSize { get; private set; } = new(320, 480); + public static double Density { get; private set; } = 1.0; public event PressedHandler? Pressed; diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 044f54f1c..452af9915 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -9,9 +9,11 @@ _Shared.Native - - - + + + + + @@ -23,9 +25,14 @@ - - - - + + + + + + + + + \ No newline at end of file From 10517f76e701376a9d86fb28cb0519d00eb89160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 16:15:13 -0600 Subject: [PATCH 65/94] update maui to pointercontroller class --- .../ChartViewHandler.cs | 22 ++-- .../PointerController.cs | 104 ++---------------- 2 files changed, 21 insertions(+), 105 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs index ed697ff3a..dbbfabc69 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartViewHandler.cs @@ -30,6 +30,7 @@ using PlatformView = System.Object; #endif +using LiveChartsCore.Native; using LiveChartsCore.Native.Events; using Microsoft.Maui.Handlers; @@ -40,7 +41,7 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// public class ChartViewHandler : ContentViewHandler { - private readonly PointerController _chartBehaviour; + private readonly PointerController _pointerController; private ChartView? ChartView => VirtualView as ChartView; @@ -49,26 +50,27 @@ public class ChartViewHandler : ContentViewHandler /// public ChartViewHandler() { - _chartBehaviour = new PointerController(); - _chartBehaviour.Pressed += OnPressed; - _chartBehaviour.Moved += OnMoved; - _chartBehaviour.Released += OnReleased; - _chartBehaviour.Scrolled += OnScrolled; - _chartBehaviour.Pinched += OnPinched; - _chartBehaviour.Exited += OnExited; + _pointerController = new PointerController(); + + _pointerController.Pressed += OnPressed; + _pointerController.Moved += OnMoved; + _pointerController.Released += OnReleased; + _pointerController.Scrolled += OnScrolled; + _pointerController.Pinched += OnPinched; + _pointerController.Exited += OnExited; } /// protected override void ConnectHandler(PlatformView platformView) { base.ConnectHandler(platformView); - _chartBehaviour.On(platformView); + _pointerController.InitializeController(platformView); } /// protected override void DisconnectHandler(PlatformView platformView) { - _chartBehaviour.Off(platformView); + _pointerController.DisposeController(platformView); base.DisconnectHandler(platformView); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs index 51fe6d18b..c6ef26c7d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs @@ -20,111 +20,25 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if IOS || MACCATALYST -using PlatformView = Microsoft.Maui.Platform.ContentView; -#elif ANDROID -using PlatformView = Microsoft.Maui.Platform.ContentViewGroup; -#elif WINDOWS -using PlatformView = Microsoft.Maui.Platform.ContentPanel; -#else -using PlatformView = System.Object; -#endif - using LiveChartsCore.Drawing; using Microsoft.Maui.Devices; -namespace LiveChartsCore.SkiaSharpView.Maui; +namespace LiveChartsCore.Native; -/// -/// The chart behaviour for MAUI. -/// -public partial class PointerController : Native.PointerController +internal partial class PointerController { - private static double s_density; - private static LvcSize s_screenSize; - static PointerController() { - var deviceDisplay = DeviceDisplay.Current; - deviceDisplay.MainDisplayInfoChanged += (_, args) => - { - var displayInfo = args.DisplayInfo; - UpdateScreenInfo(displayInfo); - }; - - UpdateScreenInfo(deviceDisplay.MainDisplayInfo); - return; - - static void UpdateScreenInfo(DisplayInfo displayInfo) - { - s_density = displayInfo.Density; - s_screenSize = new LvcSize((float)displayInfo.Width, (float)displayInfo.Height); - } + DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; + UpdateScreenInfo(DeviceDisplay.Current.MainDisplayInfo); } - /// - public override LvcSize ScreenSize => s_screenSize; - - /// - public override double Density => s_density; - - /// - /// Attaches the native events on the specified platform view. - /// - /// The platform view. - public void On(PlatformView platformView) - { -#if ANDROID - platformView.Touch += OnAndroidTouched; - platformView.Hover += OnAndroidHover; -#endif - -#if MACCATALYST || IOS - platformView.UserInteractionEnabled = true; -#if MACCATALYST - platformView.AddGestureRecognizer(MacCatalystHoverGestureRecognizer); -#endif - platformView.AddGestureRecognizer(MacCatalystLongPressGestureRecognizer); - platformView.AddGestureRecognizer(MacCatalystPinchGestureRecognizer); - platformView.AddGestureRecognizer(MacCatalystPanGestureRecognizer); -#endif - -#if WINDOWS - platformView.PointerPressed += OnWindowsPointerPressed; - platformView.PointerMoved += OnWindowsPointerMoved; - platformView.PointerReleased += OnWindowsPointerReleased; - platformView.PointerWheelChanged += OnWindowsPointerWheelChanged; - platformView.PointerExited += OnWindowsPointerExited; -#endif - } + private static void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) => + UpdateScreenInfo(e.DisplayInfo); - /// - /// Detaches the native events on the specified platform view. - /// - /// The platform view. - public void Off(PlatformView platformView) + private static void UpdateScreenInfo(DisplayInfo displayInfo) { -#if ANDROID - platformView.Touch -= OnAndroidTouched; - platformView.Hover -= OnAndroidHover; -#endif - -#if MACCATALYST || IOS - platformView.UserInteractionEnabled = false; -#if MACCATALYST - platformView.RemoveGestureRecognizer(MacCatalystHoverGestureRecognizer); -#endif - platformView.RemoveGestureRecognizer(MacCatalystLongPressGestureRecognizer); - platformView.RemoveGestureRecognizer(MacCatalystPinchGestureRecognizer); - platformView.RemoveGestureRecognizer(MacCatalystPanGestureRecognizer); -#endif - -#if WINDOWS - platformView.PointerPressed -= OnWindowsPointerPressed; - platformView.PointerMoved -= OnWindowsPointerMoved; - platformView.PointerReleased -= OnWindowsPointerReleased; - platformView.PointerWheelChanged -= OnWindowsPointerWheelChanged; - platformView.PointerExited -= OnWindowsPointerExited; -#endif + Density = displayInfo.Density; + ScreenSize = new LvcSize((float)displayInfo.Width, (float)displayInfo.Height); } } From 23110e4028223ebc529a8ad778fb1c937f31f544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 16:15:49 -0600 Subject: [PATCH 66/94] asyncticker fix --- src/LiveChartsCore/Motion/AsyncLoopTicker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs index 547c3639f..e68dbbeb5 100644 --- a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs +++ b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs @@ -51,7 +51,7 @@ private async Task RunDrawingLoop() if (_isDrawingLoopRunning) return; _isDrawingLoopRunning = true; - while (!_canvas.IsValid) + while (_canvas is not null && !_canvas.IsValid) { _renderMode.InvalidateRenderer(); await Task.Delay(_canvas._nextFrameDelay); From 8266be43817d1d2cd8124382237a385c8c45989d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 16:38:21 -0600 Subject: [PATCH 67/94] use android native api to gert screen density this is easier than consuming maui and uno apis --- .../Platforms/Android/PointerController.cs | 9 ++++ src/_Shared.Native/PointerController.cs | 5 --- .../PointerController.cs | 44 ------------------- 3 files changed, 9 insertions(+), 49 deletions(-) delete mode 100644 src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs diff --git a/src/_Shared.Native/Platforms/Android/PointerController.cs b/src/_Shared.Native/Platforms/Android/PointerController.cs index a8ed3bbec..f7598ae1a 100644 --- a/src/_Shared.Native/Platforms/Android/PointerController.cs +++ b/src/_Shared.Native/Platforms/Android/PointerController.cs @@ -39,6 +39,15 @@ internal partial class PointerController : INativePointerController private CustomScaleListener _customScaleListener = null!; private DateTime _previousPress = DateTime.MinValue; + private float Density + { + get + { + var metrics = Android.App.Application.Context.Resources?.DisplayMetrics; + return metrics?.Density ?? 1f; + } + } + public void InitializeController(object view) { var androidView = (View)view; diff --git a/src/_Shared.Native/PointerController.cs b/src/_Shared.Native/PointerController.cs index aa8663dfb..a24a240bf 100644 --- a/src/_Shared.Native/PointerController.cs +++ b/src/_Shared.Native/PointerController.cs @@ -20,17 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using LiveChartsCore.Drawing; using LiveChartsCore.Native.Events; namespace LiveChartsCore.Native; internal partial class PointerController { - public static LvcSize ScreenSize { get; private set; } = new(320, 480); - - public static double Density { get; private set; } = 1.0; - public event PressedHandler? Pressed; public event PressedHandler? Released; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs deleted file mode 100644 index c6ef26c7d..000000000 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/PointerController.cs +++ /dev/null @@ -1,44 +0,0 @@ -// The MIT License(MIT) -// -// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using LiveChartsCore.Drawing; -using Microsoft.Maui.Devices; - -namespace LiveChartsCore.Native; - -internal partial class PointerController -{ - static PointerController() - { - DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; - UpdateScreenInfo(DeviceDisplay.Current.MainDisplayInfo); - } - - private static void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) => - UpdateScreenInfo(e.DisplayInfo); - - private static void UpdateScreenInfo(DisplayInfo displayInfo) - { - Density = displayInfo.Density; - ScreenSize = new LvcSize((float)displayInfo.Width, (float)displayInfo.Height); - } -} From 37e9cc7ebbc4cdee0dfd0a688a900e4945ebfbf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 17:23:56 -0600 Subject: [PATCH 68/94] remove workaround --- src/LiveChartsCore/LiveChartsCore.csproj | 12 ------------ .../LiveChartsCore.SkiaSharpView.csproj | 12 ------------ 2 files changed, 24 deletions(-) diff --git a/src/LiveChartsCore/LiveChartsCore.csproj b/src/LiveChartsCore/LiveChartsCore.csproj index 8284c8bf9..a8a17adc0 100644 --- a/src/LiveChartsCore/LiveChartsCore.csproj +++ b/src/LiveChartsCore/LiveChartsCore.csproj @@ -11,18 +11,6 @@ $(TargetFrameworks); net462; - - net6.0-windows; - net8.0-windows; net6.0-windows10.0.19041.0; net8.0-windows10.0.19041.0; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj index 80d265f1e..776957fdf 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj @@ -11,18 +11,6 @@ $(TargetFrameworks); net462; - - net6.0-windows; - net8.0-windows; net8.0-windows10.0.19041.0; net8.0-windows10.0.20348.0; From f6c69540b65ec6c03a19dcd9c15567386480b7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 17:37:22 -0600 Subject: [PATCH 69/94] capture pointer on winui --- .../Platforms/WinUI/PointerController.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/_Shared.Native/Platforms/WinUI/PointerController.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs index 93fe3c930..f506e7cf3 100644 --- a/src/_Shared.Native/Platforms/WinUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -53,9 +53,13 @@ public void DisposeController(object view) private void OnWindowsPointerPressed(object sender, PointerRoutedEventArgs e) { - var p = e.GetCurrentPoint(sender as UIElement); + var element = (UIElement)sender; + + var p = e.GetCurrentPoint(element); if (p is null) return; + _ = element.CapturePointer(e.Pointer); + Pressed?.Invoke( sender, new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); @@ -73,9 +77,13 @@ private void OnWindowsPointerMoved(object sender, PointerRoutedEventArgs e) private void OnWindowsPointerReleased(object sender, PointerRoutedEventArgs e) { - var p = e.GetCurrentPoint(sender as UIElement); + var element = (UIElement)sender; + + var p = e.GetCurrentPoint(element); if (p is null) return; + element.ReleasePointerCapture(element.PointerCaptures[0]); + Released?.Invoke( sender, new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); From 59725f236842bc519466a432429d81eb41d5220d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 17:58:30 -0600 Subject: [PATCH 70/94] implement pointercontroller onm uno/winui --- .../Platforms/WinUI/PointerController.cs | 4 +- src/skiasharp/_Shared.WinUI/CartesianChart.cs | 6 +- src/skiasharp/_Shared.WinUI/ChartControl.cs | 43 ++-- .../_Shared.WinUI/PointerController.cs | 210 ------------------ .../_Shared.WinUI/_Shared.WinUI.projitems | 1 - 5 files changed, 18 insertions(+), 246 deletions(-) delete mode 100644 src/skiasharp/_Shared.WinUI/PointerController.cs diff --git a/src/_Shared.Native/Platforms/WinUI/PointerController.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs index f506e7cf3..276e67548 100644 --- a/src/_Shared.Native/Platforms/WinUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -20,7 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if WINDOWS +#if WINDOWS || DESKTOP || BROWSERWASM + +// on desktop and browserwasm the uno implementation of pointer events is used. using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; diff --git a/src/skiasharp/_Shared.WinUI/CartesianChart.cs b/src/skiasharp/_Shared.WinUI/CartesianChart.cs index 0a41e4f47..c28370be8 100644 --- a/src/skiasharp/_Shared.WinUI/CartesianChart.cs +++ b/src/skiasharp/_Shared.WinUI/CartesianChart.cs @@ -37,15 +37,13 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// public sealed partial class CartesianChart : ChartControl, ICartesianChartView { - /// - protected override void OnScrolled(object? sender, ScrollEventArgs args) + internal override void OnScrolled(object? sender, ScrollEventArgs args) { var c = (CartesianChartEngine)CoreChart; c.Zoom(args.Location, args.ScrollDelta > 0 ? ZoomDirection.ZoomIn : ZoomDirection.ZoomOut); } - /// - protected override void OnPinched(object? sender, PinchEventArgs args) + internal override void OnPinched(object? sender, PinchEventArgs args) { var c = (CartesianChartEngine)CoreChart; var p = args.PinchStart; diff --git a/src/skiasharp/_Shared.WinUI/ChartControl.cs b/src/skiasharp/_Shared.WinUI/ChartControl.cs index 6560484cd..34a54b4c9 100644 --- a/src/skiasharp/_Shared.WinUI/ChartControl.cs +++ b/src/skiasharp/_Shared.WinUI/ChartControl.cs @@ -32,6 +32,7 @@ using LiveChartsCore.Drawing; using LiveChartsCore.Kernel.Events; using LiveChartsCore.Kernel.Sketches; +using LiveChartsCore.Native; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; @@ -42,7 +43,7 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; public abstract partial class ChartControl : UserControl, IChartView { private readonly ThemeListener _themeListener; - private readonly ChartBehaviour _chartBehaviour; + private readonly PointerController _pointerController; private static readonly bool s_isWebAssembly = RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); /// @@ -65,14 +66,14 @@ public ChartControl() _themeListener = new(CoreChart.ApplyTheme, DispatcherQueue); - _chartBehaviour = new ChartBehaviour(); + _pointerController = new PointerController(); - _chartBehaviour.Pressed += OnPressed; - _chartBehaviour.Moved += OnMoved; - _chartBehaviour.Released += OnReleased; - _chartBehaviour.Scrolled += OnScrolled; - _chartBehaviour.Pinched += OnPinched; - _chartBehaviour.Exited += OnExited; + _pointerController.Pressed += OnPressed; + _pointerController.Moved += OnMoved; + _pointerController.Released += OnReleased; + _pointerController.Scrolled += OnScrolled; + _pointerController.Pinched += OnPinched; + _pointerController.Exited += OnExited; } /// @@ -94,7 +95,7 @@ LvcColor IChartView.BackColor private void OnLoaded(object sender, RoutedEventArgs e) { _themeListener.Listen(); - _chartBehaviour.On(this); + _pointerController.InitializeController(this); StartObserving(); CoreChart.Load(); } @@ -102,7 +103,7 @@ private void OnLoaded(object sender, RoutedEventArgs e) private void OnUnloaded(object sender, RoutedEventArgs e) { _themeListener.Dispose(); - _chartBehaviour.Off(this); + _pointerController.DisposeController(this); StopObserving(); CoreChart.Unload(); } @@ -154,25 +155,9 @@ private void OnReleased(object? sender, Native.Events.PressedEventArgs args) private void OnExited(object? sender, Native.Events.EventArgs args) => CoreChart?.InvokePointerLeft(); - /// - /// Invoked when a scroll event occurs, allowing derived classes to handle or respond to the event. - /// - /// This method is designed to be overridden in derived classes to provide custom handling for - /// scroll events. If not overridden, the base implementation does nothing. - /// The source of the scroll event. This can be if the event is not associated with a - /// specific sender. - /// The event data containing details about the scroll action, such as scroll direction and position. - protected virtual void OnScrolled(object? sender, Native.Events.ScrollEventArgs args) { } + internal virtual void OnScrolled(object? sender, Native.Events.ScrollEventArgs args) { } - /// - /// Handles the pinch gesture event, allowing zooming functionality in the chart. - /// - /// This method is invoked when a pinch gesture is detected. It enables zooming in or out of the chart - /// based on the scroll delta provided in the event arguments. Override this method in a derived class to customize the - /// behavior of pinch gestures. - /// The source of the event. This can be . - /// The event data containing information about the pinch gesture, including its location and scroll delta. - protected virtual void OnPinched(object? sender, Native.Events.PinchEventArgs args) { } + internal virtual void OnPinched(object? sender, Native.Events.PinchEventArgs args) { } private ISeries InflateSeriesTemplate(object item) { @@ -193,8 +178,6 @@ void IChartView.InvokeOnUIThread(Action action) { if (s_isWebAssembly) { - // IF UNO WASM, just run the action directly. - // is this required on wasm isnt this already implemented in net 9? action(); return; } diff --git a/src/skiasharp/_Shared.WinUI/PointerController.cs b/src/skiasharp/_Shared.WinUI/PointerController.cs deleted file mode 100644 index d1ce39217..000000000 --- a/src/skiasharp/_Shared.WinUI/PointerController.cs +++ /dev/null @@ -1,210 +0,0 @@ -// The MIT License(MIT) -// -// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using LiveChartsCore.Drawing; -using Microsoft.UI.Xaml; - -namespace LiveChartsCore.SkiaSharpView.WinUI; - -/// -/// The chart behaviour for WinUI and Uno Platform. -/// -public partial class ChartBehaviour : Native.PointerController -{ - private static double s_density; - private static LvcSize s_screenSize = new(); - - /// - public override LvcSize ScreenSize => s_screenSize; - - /// - public override double Density => s_density; - - /// - /// Attaches the native events on the specified element. - /// - /// The element. - public void On(FrameworkElement element) - { - // TODO: Detect the DPI and screen size changes. - -#if HAS_UNO || HAS_UNO_WINUI - var currentView = Windows.Graphics.Display.DisplayInformation.GetForCurrentView(); - - s_density = currentView.LogicalDpi / 96.0f; - s_screenSize = new( - currentView.ScreenWidthInRawPixels, - currentView.ScreenHeightInRawPixels); -#else - void getDensity(object s, RoutedEventArgs e) - { - s_density = element.XamlRoot.RasterizationScale; - element.Loaded -= getDensity; - } - - if (element.IsLoaded) getDensity(null!, null!); - else element.Loaded += getDensity; -#endif - -#if ANDROID - - element.Touch += OnAndroidTouched; - element.Hover += OnAndroidHover; - -#elif MACCATALYST || IOS - - element.UserInteractionEnabled = true; -#if MACCATALYST - element.AddGestureRecognizer(MacCatalystHoverGestureRecognizer); -#endif - element.AddGestureRecognizer(MacCatalystLongPressGestureRecognizer); - element.AddGestureRecognizer(MacCatalystPinchGestureRecognizer); - element.AddGestureRecognizer(MacCatalystPanGestureRecognizer); - -#elif WINDOWS - - element.PointerPressed += OnWindowsPointerPressed; - element.PointerMoved += OnWindowsPointerMoved; - element.PointerReleased += OnWindowsPointerReleased; - element.PointerWheelChanged += OnWindowsPointerWheelChanged; - element.PointerExited += OnWindowsPointerExited; - -#elif HAS_UNO || HAS_UNO_WINUI - - element.PointerPressed += OnUnoPointerPressed; - element.PointerMoved += OnUnoPointerMoved; - element.PointerReleased += OnUnoPointerReleased; - element.PointerWheelChanged += OnUnoPointerWheelChanged; - element.PointerExited += OnUnoPointerExited; - -#endif - } - - /// - /// Detaches the native events from the specified element. - /// - /// The element. - public void Off(FrameworkElement element) - { -#if ANDROID - - element.Touch -= OnAndroidTouched; - element.Hover -= OnAndroidHover; - -#elif MACCATALYST || IOS - - element.UserInteractionEnabled = false; -#if MACCATALYST - element.RemoveGestureRecognizer(MacCatalystHoverGestureRecognizer); -#endif - element.RemoveGestureRecognizer(MacCatalystLongPressGestureRecognizer); - element.RemoveGestureRecognizer(MacCatalystPinchGestureRecognizer); - element.RemoveGestureRecognizer(MacCatalystPanGestureRecognizer); - -#elif WINDOWS - - element.PointerPressed -= OnWindowsPointerPressed; - element.PointerMoved -= OnWindowsPointerMoved; - element.PointerReleased -= OnWindowsPointerReleased; - element.PointerWheelChanged -= OnWindowsPointerWheelChanged; - element.PointerExited -= OnWindowsPointerExited; - -#elif HAS_UNO || HAS_UNO_WINUI - - element.PointerPressed -= OnUnoPointerPressed; - element.PointerMoved -= OnUnoPointerMoved; - element.PointerReleased -= OnUnoPointerReleased; - element.PointerWheelChanged -= OnUnoPointerWheelChanged; - element.PointerExited -= OnUnoPointerExited; - -#endif - } - -#if (HAS_UNO || HAS_UNO_WINUI) && !ANDROID && !IOS && !MACCATALYST && !WINDOWS - // is this just wasm? - - /// - /// On uno pointer pressed. - /// - /// - /// - protected void OnUnoPointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) - { - var p = e.GetCurrentPoint(sender as UIElement); - if (p is null) return; - - InvokePressed( - sender, - new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); - } - - /// - /// On uno pointer moved. - /// - /// - /// - protected void OnUnoPointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) - { - var p = e.GetCurrentPoint(sender as UIElement); - if (p is null) return; - - InvokeMoved( - sender, - new(new(p.Position.X, p.Position.Y), e)); - } - - /// - /// On uno pointer released. - /// - /// - /// - protected void OnUnoPointerReleased(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) - { - var p = e.GetCurrentPoint(sender as UIElement); - if (p is null) return; - - InvokeReleased( - sender, - new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); - } - - /// - /// On uno pointer wheel changed. - /// - /// - /// - protected void OnUnoPointerWheelChanged(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) - { - var p = e.GetCurrentPoint(sender as UIElement); - - InvokeScrolled(sender, new(new(p.Position.X, p.Position.Y), p.Properties.MouseWheelDelta, e)); - } - - /// - /// On uno pointer exited. - /// - /// - /// - protected void OnUnoPointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) => - InvokeExited(sender, new(e)); -#endif -} diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index 1370ab7c1..f710cb2cf 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -10,7 +10,6 @@ - From 69225ea814600647cd2ffb197471a2c597f0702b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 18:14:25 -0600 Subject: [PATCH 71/94] update slnx files --- LiveCharts.Maui.slnx | 2 +- LiveCharts.Uno.slnx | 7 +++++++ LiveCharts.WinUI.slnx | 1 + samples/UnoPlatformSample/UnoPlatformSample.slnx | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/LiveCharts.Maui.slnx b/LiveCharts.Maui.slnx index 73ff34fe6..5a61a8b4b 100644 --- a/LiveCharts.Maui.slnx +++ b/LiveCharts.Maui.slnx @@ -6,10 +6,10 @@ - + diff --git a/LiveCharts.Uno.slnx b/LiveCharts.Uno.slnx index 28a5489bc..417ccd912 100644 --- a/LiveCharts.Uno.slnx +++ b/LiveCharts.Uno.slnx @@ -1,4 +1,10 @@ + + + + + + @@ -6,4 +12,5 @@ + diff --git a/LiveCharts.WinUI.slnx b/LiveCharts.WinUI.slnx index a9f1579c5..39b9a1bc9 100644 --- a/LiveCharts.WinUI.slnx +++ b/LiveCharts.WinUI.slnx @@ -13,4 +13,5 @@ + diff --git a/samples/UnoPlatformSample/UnoPlatformSample.slnx b/samples/UnoPlatformSample/UnoPlatformSample.slnx index 95aa0fb35..997f2a697 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample.slnx +++ b/samples/UnoPlatformSample/UnoPlatformSample.slnx @@ -1,13 +1,13 @@ - + From 0fa9de10bc5014cb8d309fb5dcfd0b6354038a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 19:48:08 -0600 Subject: [PATCH 72/94] allow each view to define its own default render settings --- .../Kernel/LiveChartsSettings.cs | 43 +++----------- .../Kernel/RenderingSettings.cs | 59 +++++++++++++++++++ src/LiveChartsCore/LiveCharts.cs | 58 +++--------------- .../Motion/CanvasRenderSettings.cs | 6 +- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 8 +-- .../LiveChartsSkiaSharp.cs | 20 +++---- .../ThemesExtensions.cs | 2 - 7 files changed, 91 insertions(+), 105 deletions(-) create mode 100644 src/LiveChartsCore/Kernel/RenderingSettings.cs diff --git a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs index b61ee6c87..b53360f02 100644 --- a/src/LiveChartsCore/Kernel/LiveChartsSettings.cs +++ b/src/LiveChartsCore/Kernel/LiveChartsSettings.cs @@ -39,6 +39,10 @@ public class LiveChartsSettings private readonly Dictionary _mappers = []; private object _theme = new(); + internal bool HasBackedDefined => _currentProvider is not null; + internal bool HasThemeDefined => _theme is not null and Theme; + internal bool HasMappersDefined => _mappers.Count > 0; + /// /// Gets or sets the default easing function. /// @@ -429,8 +433,6 @@ public Theme GetTheme() /// The current settings. public LiveChartsSettings AddDefaultMappers() { - LiveCharts.s_hasDefaultMappers = true; - return HasMap((model, index) => new(index, model)) .HasMap((model, index) => new(index, model)) @@ -447,41 +449,14 @@ public LiveChartsSettings AddDefaultMappers() } /// - /// Indicates whether hardware acceleration is used to render the charts, this will only work if - /// the current platform and device supports it. See also. . - /// - /// - /// Indicates whether hardware acceleration is used, this will only work - /// if the platform and device support it, default is true. This is ignored in Avalonia, in avalonia the - /// frame rate and rendering cadence is determined by the Avalonia rendering loop. - /// - /// - /// Indicates whether the rendering cadence should be aligned with the display refresh rate, - /// gpu acceleration is required for this to work. This is ignored in Avalonia, in avalonia the frame rate - /// and rendering cadence is determined by the Avalonia rendering loop. - /// - /// - /// The target frames per second for the rendering engine, this property is ignored when - /// is true and GPU acceleration is enabled, - /// This is ignored in Avalonia, in avalonia the frame rate and rendering cadence is determined by the - /// Avalonia rendering loop. - /// - /// - /// When true, The chart will also draw the frames per second in the top left corner of the chart. - /// + /// Defines the rendering settings for LiveCharts. + /// + /// The rendering settings. /// The current settings. public LiveChartsSettings RenderingSettings( - bool useHardwareAcceleration, - bool tryUseVSync, - double targetFps = 60, - bool showFps = false) + RenderingSettings settings) { - LiveCharts.s_hasDefaultHardwareAcceleration = true; - - LiveCharts.UseGPU = useHardwareAcceleration; - LiveCharts.TryUseVSync = tryUseVSync; - LiveCharts.TargetFps = targetFps; - LiveCharts.ShowFPS = showFps; + LiveCharts.RenderingSettings = settings; return this; } diff --git a/src/LiveChartsCore/Kernel/RenderingSettings.cs b/src/LiveChartsCore/Kernel/RenderingSettings.cs new file mode 100644 index 000000000..d054b61db --- /dev/null +++ b/src/LiveChartsCore/Kernel/RenderingSettings.cs @@ -0,0 +1,59 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace LiveChartsCore.Kernel; + +/// +/// LiveCharts rendering settings, this class is used to configure the rendering engine. +/// +public class RenderingSettings +{ + internal static RenderingSettings Default { get; set; } = new(); + + /// + /// Indicates whether hardware acceleration is used, this will only work + /// if the platform and device support it. + /// This is ignored in Avalonia and Uno-Desktop because in those platforms + /// the frame rate and rendering cadence is determined by them. + /// + public bool UseGPU { get; set; } = true; + + /// Indicates whether the rendering cadence should be aligned with the display refresh rate, + /// GPU acceleration is required for this to work. + /// This is ignored in Avalonia and Uno-Desktop because in those platforms + /// the frame rate and rendering cadence is determined by them. + public bool TryUseVSync { get; set; } = true; + + /// + /// Defines the desired frames per second when using the LiveCharts render loop, this has no effect + /// when is true and is true. + /// Default is 60. + /// + public double LiveChartsRenderLoopFPS { get; set; } = 60; + + /// + /// Gets or sets a value indicating whether the chart should show the frames per second + /// and more information about the rendering in the top left corner of the chart. Default is false. + /// + public bool ShowFPS { get; set; } = false; + +} diff --git a/src/LiveChartsCore/LiveCharts.cs b/src/LiveChartsCore/LiveCharts.cs index a71bbba3b..4e325268a 100644 --- a/src/LiveChartsCore/LiveCharts.cs +++ b/src/LiveChartsCore/LiveCharts.cs @@ -30,14 +30,6 @@ namespace LiveChartsCore; /// public static class LiveCharts { - private static bool s_useGPU = true; - private static bool s_gpuSetByUser = false; - internal static bool s_hasBackend = false; - internal static bool s_hasDefaultTheme = false; - internal static bool s_hasDefaultMappers = false; - internal static bool s_isHardwareAccelerationByDefault = true; - internal static bool s_hasDefaultHardwareAcceleration = false; - /// /// A constant that indicates that the tool tip should not add the current label. /// @@ -54,48 +46,20 @@ public static class LiveCharts /// public static bool EnableLogging { get; set; } = false; - /// - /// Gets or sets a value indicating whether LiveCharts should show the frames per second. - /// - public static bool ShowFPS { get; set; } = false; - /// /// Gets or sets the maximum fps requested. /// - [Obsolete($"Renamed to {nameof(TargetFps)}")] - public static double MaxFps { get => TargetFps; set => TargetFps = value; } - - /// - /// Gets or sets the target frames per second for the rendering engine, - /// this property is ignored when is true and - /// GPU acceleration is enabled, default is 60 fps. - /// - public static double TargetFps { get; set; } = 60; - - /// - /// Attempts to align rendering cadence with display refresh rate (VSync) when supported. - /// Requires GPU acceleration. May be ignored in software-mode or virtual environments. - /// In WPF and WinUI the rendering cadence is regulated by the CompositionTarget.Rendering event, - /// which dispatches frame updates synchronized with the display refresh cycle. - /// In Avalonia, this value is ignored as the chart is rendered based on Avalonia's rendering loop. - /// - public static bool TryUseVSync { get; set; } = true; + [Obsolete($"Renamed to {nameof(RenderingSettings)}.{nameof(RenderingSettings.LiveChartsRenderLoopFPS)}")] + public static double MaxFps + { + get => RenderingSettings.LiveChartsRenderLoopFPS; + set => RenderingSettings.LiveChartsRenderLoopFPS = value; + } /// - /// Gets or sets a value indicating whether LiveCharts should use the GPU for rendering. - /// When set to true, the library will attempt to use GPU acceleration. - /// This has no effect on Avalonia since Avalonia determines the rendering backend. - /// When GPU rendering is not available, it will fallback to software rendering. + /// Defines the rendering settins for LiveCharts. /// - public static bool UseGPU - { - get => s_useGPU; - set - { - s_useGPU = value; - s_gpuSetByUser = true; - } - } + public static RenderingSettings RenderingSettings { get; internal set; } = null!; /// /// Gets the current settings. @@ -161,10 +125,4 @@ public static TimeSpan AsTimeSpan(this double ticks) if (ticks < 0) ticks = 0; return TimeSpan.FromTicks((long)ticks); } - - internal static void SetUseGPUIfNotSetByUser(bool value) - { - if (s_gpuSetByUser) return; - s_useGPU = value; - } } diff --git a/src/LiveChartsCore/Motion/CanvasRenderSettings.cs b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs index 640e0b79b..eebbcb6d0 100644 --- a/src/LiveChartsCore/Motion/CanvasRenderSettings.cs +++ b/src/LiveChartsCore/Motion/CanvasRenderSettings.cs @@ -31,11 +31,11 @@ internal class CanvasRenderSettings(TDrawingContext context) $"thread: {Environment.CurrentManagedThreadId}"); #endif - var showFps = LiveCharts.ShowFPS; + var showFps = LiveCharts.RenderingSettings.ShowFPS; var drawStartTime = s_clock.ElapsedTicks; lock (Sync) @@ -197,7 +197,7 @@ public void DrawFrame(TDrawingContext context) sb.Append($"`render time last/avrg [ {_lastDrawTime:N2} / {_totalDrawTime / _totalFrames:N2} ] ms"); sb.Append(s_externalRenderer is null - ? $"`GPU / VSync [ {LiveCharts.UseGPU} / {LiveCharts.UseGPU && LiveCharts.TryUseVSync} ]" + ? $"`GPU / VSync [ {LiveCharts.RenderingSettings.UseGPU} / {LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync} ]" : $"`GPU / VSync by [ {s_externalRenderer} ]"); if (_jitteredDrawCount > 0) @@ -229,7 +229,7 @@ public void DrawFrame(TDrawingContext context) } } - if (!LiveCharts.TryUseVSync) + if (!LiveCharts.RenderingSettings.TryUseVSync) { var timeInDrawOperation = s_clock.ElapsedTicks - drawStartTime; var delay = s_baseFrameDelay.Ticks - timeInDrawOperation; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index f41c1f526..f4c007b19 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -47,24 +47,22 @@ public static class LiveChartsSkiaSharp /// Configures LiveCharts using the default settings for SkiaSharp. /// /// The settings. + /// The optional rendering settings. /// The settings. - public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) + public static LiveChartsSettings UseDefaults( + this LiveChartsSettings settings, RenderingSettings? renderingSettings = null) { - if (!LiveCharts.s_hasBackend) + if (!LiveCharts.DefaultSettings.HasBackedDefined) _ = settings.AddSkiaSharp(); - if (!LiveCharts.s_hasDefaultTheme) + if (!LiveCharts.DefaultSettings.HasThemeDefined) _ = settings.AddDefaultTheme(); - if (!LiveCharts.s_hasDefaultMappers) + if (!LiveCharts.DefaultSettings.HasMappersDefined) _ = settings.AddDefaultMappers(); - if (!LiveCharts.s_hasDefaultHardwareAcceleration) - _ = settings.RenderingSettings( - useHardwareAcceleration: LiveCharts.s_isHardwareAccelerationByDefault, - tryUseVSync: true, - targetFps: 60, // 60 as a fallback when VSync is not available - showFps: false); + if (LiveCharts.RenderingSettings is null) + _ = settings.RenderingSettings(renderingSettings ?? RenderingSettings.Default); return settings; } @@ -76,8 +74,6 @@ public static LiveChartsSettings UseDefaults(this LiveChartsSettings settings) /// public static LiveChartsSettings AddSkiaSharp(this LiveChartsSettings settings) { - LiveCharts.s_hasBackend = true; - PropertyDefinition.Parsers[typeof(Paint)] = HexToPaintTypeConverter.Parse; PropertyDefinition.Parsers[typeof(LvcColor)] = HexToLvcColorTypeConverter.Parse; PropertyDefinition.Parsers[typeof(Margin)] = MarginTypeConverter.ParseMargin; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs index 1a86e9b6c..8f6bf7122 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/ThemesExtensions.cs @@ -53,8 +53,6 @@ public static LiveChartsSettings AddDefaultTheme( Action? themeSettings = null, LvcThemeKind requestedTheme = LvcThemeKind.Unknown) { - LiveCharts.s_hasDefaultTheme = true; - return settings .HasTheme(theme => { From 9ceacf1a06e9e5b437e1892bc729ee2f48707416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 21:19:16 -0600 Subject: [PATCH 73/94] add recommended settings per platform [WIP] --- .../Kernel/RenderingSettings.cs | 2 +- .../ChartControl.cs | 2 - .../GeoMap.axaml.cs | 1 - .../MotionCanvas.cs | 25 ++++++ .../ChartControl.cs | 3 - .../LiveChartsCore.SkiaSharp.WPF/GeoMap.cs | 4 - .../MotionCanvas.cs | 27 ++++++ .../ChartControl.cs | 4 +- .../GeoMap.cs | 33 +++---- .../MotionCanvas.Designer.cs | 2 +- .../MotionCanvas.cs | 85 ++++++++++++------- .../ChartControl.razor.cs | 9 -- .../GeoMap.razor.cs | 34 +++----- .../MotionCanvas.razor.cs | 27 +++++- .../ChartControl.cs | 3 +- .../GeoMap.cs | 32 +++---- .../MotionCanvas.cs | 66 +++++++++----- .../ChartControl.cs | 2 - .../GeoMap.xaml.cs | 1 - .../MotionCanvas.cs | 27 ++++++ .../GeoMap.cs | 1 - .../GeoMap.xaml.cs | 1 - src/skiasharp/_Shared.WinUI/ChartControl.cs | 2 - .../_Shared.WinUI/MotionCanvas.settings.cs | 60 +++++++++++++ .../_Shared.WinUI/_Shared.WinUI.projitems | 1 + 25 files changed, 305 insertions(+), 149 deletions(-) create mode 100644 src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs diff --git a/src/LiveChartsCore/Kernel/RenderingSettings.cs b/src/LiveChartsCore/Kernel/RenderingSettings.cs index d054b61db..a45b85b01 100644 --- a/src/LiveChartsCore/Kernel/RenderingSettings.cs +++ b/src/LiveChartsCore/Kernel/RenderingSettings.cs @@ -46,7 +46,7 @@ public class RenderingSettings /// /// Defines the desired frames per second when using the LiveCharts render loop, this has no effect /// when is true and is true. - /// Default is 60. + /// Default is 60. This is ignored in Avalonia, the frame rate is determined by Avalonia itself. /// public double LiveChartsRenderLoopFPS { get; set; } = 60; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/ChartControl.cs index 703f9c105..09342bb1a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/ChartControl.cs @@ -53,8 +53,6 @@ public abstract partial class ChartControl : UserControl, IChartView, ICustomHit /// protected ChartControl() { - LiveCharts.Configure(config => config.UseDefaults()); - Content = new MotionCanvas(); AttachedToVisualTree += OnAttachedToVisualTree; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/GeoMap.axaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/GeoMap.axaml.cs index 9e3600dea..a7403cb7b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/GeoMap.axaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/GeoMap.axaml.cs @@ -52,7 +52,6 @@ public partial class GeoMap : UserControl, IGeoMapView public GeoMap() { InitializeComponent(); - LiveCharts.Configure(config => config.UseDefaults()); _core = new GeoMapChart(this); _seriesObserver = new CollectionDeepObserver(() => _core?.Update()); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs index 3ecee0bfe..99b1992be 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs @@ -30,6 +30,7 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Skia; using Avalonia.Threading; +using LiveChartsCore.Kernel; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using SkiaSharp; @@ -43,6 +44,30 @@ public class MotionCanvas : UserControl { private bool _isDeatached = false; + /// + /// Gets the recommended rendering settings for Avalonia. + /// + public static RenderingSettings RecommendedAvaloniaRenderingSettings { get; } + = new() + { + // Ignored, handled by Avalonia + UseGPU = true, + + // Ignored, handled by Avalonia + TryUseVSync = true, + + // Ignored, handled by Avalonia + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; + + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedAvaloniaRenderingSettings)); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs index 446078ccf..e9c55ccb6 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/ChartControl.cs @@ -48,9 +48,6 @@ public abstract partial class ChartControl : UserControl, IChartView /// Default colors are not valid protected ChartControl() { - LiveCharts.s_isHardwareAccelerationByDefault = false; - LiveCharts.Configure(config => config.UseDefaults()); - Content = new MotionCanvas(); SizeChanged += (s, e) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs index d3749769d..caf517539 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/GeoMap.cs @@ -22,7 +22,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Windows; @@ -53,9 +52,6 @@ public class GeoMap : UserControl, IGeoMapView /// public GeoMap() { - LiveCharts.s_isHardwareAccelerationByDefault = false; - LiveCharts.Configure(config => config.UseDefaults()); - MouseDown += OnMouseDown; MouseMove += OnMouseMove; MouseUp += OnMouseUp; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs index 156d38e91..35a4d8c1f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/MotionCanvas.cs @@ -22,6 +22,7 @@ using System.Windows; using System.Windows.Controls; +using LiveChartsCore.Kernel; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.WPF.Rendering; namespace LiveChartsCore.SkiaSharpView.WPF; @@ -32,8 +33,34 @@ namespace LiveChartsCore.SkiaSharpView.WPF; /// public class MotionCanvas : UserControl { + /// + /// Gets the recommended rendering settings for Uno and WinUI. + /// + public static RenderingSettings RecommendedWPFRenderingSettings { get; } + = new() + { + // GPU disabled in WPF by default for 2 reasons: + // 1. https://github.com/mono/SkiaSharp/issues/3309 + // 2. OpenTK pointer events are sluggish. + UseGPU = false, + + // TryUseVSync makes no sense when GPU is false + TryUseVSync = false, + + // Because GPU is false, this is the target FPS: + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; + private readonly CanvasRenderSettings _settings; + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedWPFRenderingSettings)); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs index de954253d..ae56ee190 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/ChartControl.cs @@ -32,6 +32,7 @@ using System.Drawing; using System.Windows.Forms; using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; using LiveChartsCore.Kernel.Sketches; namespace LiveChartsCore.SkiaSharpView.WinForms; @@ -57,9 +58,6 @@ protected ChartControl() Name = "CartesianChart"; ResumeLayout(true); - LiveCharts.s_isHardwareAccelerationByDefault = false; - LiveCharts.Configure(config => config.UseDefaults()); - InitializeChartControl(); InitializeObservedProperties(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs index 481b815e3..c8ff86f99 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/GeoMap.cs @@ -43,12 +43,7 @@ public partial class GeoMap : UserControl, IGeoMapView { private readonly GeoMapChart _core; private readonly CollectionDeepObserver _seriesObserver; - private IEnumerable _series = []; private DrawnMap _activeMap; - private MapProjection _mapProjection = MapProjection.Default; - private Paint? _stroke = new SolidColorPaint(new SKColor(255, 255, 255, 255)) { PaintStyle = PaintStyle.Stroke }; - private Paint? _fill = new SolidColorPaint(new SKColor(240, 240, 240, 255)) { PaintStyle = PaintStyle.Fill }; - private object? _viewCommand = null; /// /// Initializes a new instance of the class. @@ -56,8 +51,6 @@ public partial class GeoMap : UserControl, IGeoMapView public GeoMap() { InitializeComponent(); - LiveCharts.s_isHardwareAccelerationByDefault = false; - LiveCharts.Configure(config => config.UseDefaults()); _activeMap = Maps.GetWorldMap(); _core = new GeoMapChart(this); @@ -93,13 +86,13 @@ public GeoMap() [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public object? ViewCommand { - get => _viewCommand; + get; set { - _viewCommand = value; + field = value; if (value is not null) _core.ViewTo(value); } - } + } = null; /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public DrawnMap ActiveMap { get => _activeMap; set { _activeMap = value; OnPropertyChanged(); } } @@ -112,47 +105,47 @@ public object? ViewCommand /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public MapProjection MapProjection { get => _mapProjection; set { _mapProjection = value; OnPropertyChanged(); } } + public MapProjection MapProjection { get; set { field = value; OnPropertyChanged(); } } = MapProjection.Default; /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Paint? Stroke { - get => _stroke; + get; set { if (value is not null) value.PaintStyle = PaintStyle.Stroke; - _stroke = value; + field = value; OnPropertyChanged(); } - } + } = new SolidColorPaint(new SKColor(255, 255, 255, 255)) { PaintStyle = PaintStyle.Stroke }; /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Paint? Fill { - get => _fill; + get; set { if (value is not null) value.PaintStyle = PaintStyle.Fill; - _fill = value; + field = value; OnPropertyChanged(); } - } + } = new SolidColorPaint(new SKColor(240, 240, 240, 255)) { PaintStyle = PaintStyle.Fill }; /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IEnumerable Series { - get => _series; + get; set { _seriesObserver?.Dispose(); _seriesObserver?.Initialize(value); - _series = value; + field = value; OnPropertyChanged(); } - } + } = []; void IGeoMapView.InvokeOnUIThread(Action action) { diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs index 5e723b5dd..53549f9c5 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.Designer.cs @@ -54,7 +54,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - if (LiveCharts.UseGPU) + if (LiveCharts.RenderingSettings.UseGPU) { this._skglControl = new SKGLControl(); this._skglControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs index f723aa983..b0220a862 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs @@ -22,9 +22,9 @@ using System; using System.ComponentModel; -using System.Threading.Tasks; using System.Windows.Forms; using LiveChartsCore.Drawing; +using LiveChartsCore.Kernel; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Drawing; using SkiaSharp.Views.Desktop; @@ -35,9 +35,36 @@ namespace LiveChartsCore.SkiaSharpView.WinForms; /// The motion canvas control for windows forms, . /// /// -public partial class MotionCanvas : UserControl +public partial class MotionCanvas : UserControl, IRenderMode { - private bool _isDrawingLoopRunning = false; + private IFrameTicker _ticker = null!; + + /// + /// Gets the recommended rendering settings for WinForms. + /// + public static RenderingSettings RecommendedWinFormsRenderingSettings { get; } = + new() + { + // GPU disabled in WinForms by default for 2 reasons: + // 1. https://github.com/mono/SkiaSharp/issues/3309 + // 2. OpenTK pointer events are sluggish (at least in WPF). + UseGPU = false, + + // TryUseVSync makes no sense when GPU is false. + // Also, WinForms does not support VSync (at least not implemented by livecharts). + TryUseVSync = false, + + // Because GPU is false, this is the target FPS: + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; + + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedWinFormsRenderingSettings)); + } /// /// Initializes a new instance of the class. @@ -47,12 +74,13 @@ public MotionCanvas() InitializeComponent(); } - /// - /// Gets the canvas core. - /// - /// - /// The canvas core. - /// + event CoreMotionCanvas.FrameRequestHandler IRenderMode.FrameRequest + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public CoreMotionCanvas CanvasCore { get; } = new(); @@ -60,15 +88,15 @@ public MotionCanvas() protected override void CreateHandle() { base.CreateHandle(); - CanvasCore.Invalidated += CanvasCore_Invalidated; + _ticker = new AsyncLoopTicker(); + _ticker.InitializeTicker(CanvasCore, this); } /// protected override void OnHandleDestroyed(EventArgs e) { base.OnHandleDestroyed(e); - - CanvasCore.Invalidated -= CanvasCore_Invalidated; + _ticker.DisposeTicker(); CanvasCore.Dispose(); } @@ -80,29 +108,20 @@ private void SkglControl_PaintSurface(object sender, SKPaintGLSurfaceEventArgs e CanvasCore.DrawFrame( new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, GetBackground().AsSKColor())); - private void CanvasCore_Invalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); - - private async void RunDrawingLoop() - { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - var ts = TimeSpan.FromSeconds(1 / LiveCharts.TargetFps); - - while (!CanvasCore.IsValid) - { - _skControl?.Invalidate(); - _skglControl?.Invalidate(); - - await Task.Delay(ts); - } - - _isDrawingLoopRunning = false; - } - private LvcColor GetBackground() => true ? new LvcColor(Parent!.BackColor.R, Parent.BackColor.G, Parent.BackColor.B) : CanvasCore._virtualBackgroundColor; // are themes relevant in Win Forms? + + void IRenderMode.InitializeRenderMode(CoreMotionCanvas canvas) => + throw new NotImplementedException(); + + void IRenderMode.InvalidateRenderer() + { + _skControl?.Invalidate(); + _skglControl?.Invalidate(); + } + + void IRenderMode.DisposeRenderMode() => + throw new NotImplementedException(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/ChartControl.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/ChartControl.razor.cs index c830c0eb7..6aa5ef151 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/ChartControl.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/ChartControl.razor.cs @@ -47,15 +47,6 @@ public abstract partial class ChartControl : IBlazorChart, IDisposable, IChartVi /// protected ChartControl() { - // on blazor by default we use the GPU - // just because it looks MUCH better - // the user can disable this feature by calling - // LiveCharts.UseGPU = false; - // or by setting the UseGPU property to false in the chart - LiveCharts.SetUseGPUIfNotSetByUser(true); - - LiveCharts.Configure(config => config.UseDefaults()); - _observer = new(ConfigureObserver, () => CoreChart?.Update()); InitializeObservedProperties(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs index 21df855cf..6337614e7 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/GeoMap.razor.cs @@ -20,8 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System.Collections.Specialized; -using System.ComponentModel; using LiveChartsCore.Geo; using LiveChartsCore.Kernel.Observers; using LiveChartsCore.Motion; @@ -48,12 +46,7 @@ public partial class GeoMap : IGeoMapView, IDisposable private double _canvasHeight; private CollectionDeepObserver? _seriesObserver; private GeoMapChart? _core; - private IEnumerable _series = []; private DrawnMap? _activeMap; - private MapProjection _mapProjection = MapProjection.Default; - private Paint? _stroke = new SolidColorPaint(new SKColor(255, 255, 255, 255)) { PaintStyle = PaintStyle.Stroke }; - private Paint? _fill = new SolidColorPaint(new SKColor(240, 240, 240, 255)) { PaintStyle = PaintStyle.Fill }; - private object? _viewCommand = null; /// /// Called when the control initializes. @@ -62,7 +55,6 @@ protected override void OnInitialized() { base.OnInitialized(); - LiveCharts.Configure(config => config.UseDefaults()); _activeMap = Maps.GetWorldMap(); } @@ -109,13 +101,13 @@ protected override async Task OnAfterRenderAsync(bool firstRender) [Parameter] public object? ViewCommand { - get => _viewCommand; + get; set { - _viewCommand = value; + field = value; if (value is not null) _core?.ViewTo(value); } - } + } = null; /// [Parameter] public DrawnMap ActiveMap @@ -136,47 +128,47 @@ public DrawnMap ActiveMap /// [Parameter] - public MapProjection MapProjection { get => _mapProjection; set { _mapProjection = value; OnPropertyChanged(); } } + public MapProjection MapProjection { get; set { field = value; OnPropertyChanged(); } } = MapProjection.Default; /// [Parameter] public Paint? Stroke { - get => _stroke; + get; set { if (value is not null) value.PaintStyle = PaintStyle.Stroke; - _stroke = value; + field = value; OnPropertyChanged(); } - } + } = new SolidColorPaint(new SKColor(255, 255, 255, 255)) { PaintStyle = PaintStyle.Stroke }; /// [Parameter] public Paint? Fill { - get => _fill; + get; set { if (value is not null) value.PaintStyle = PaintStyle.Fill; - _fill = value; + field = value; OnPropertyChanged(); } - } + } = new SolidColorPaint(new SKColor(240, 240, 240, 255)) { PaintStyle = PaintStyle.Fill }; /// [Parameter] public IEnumerable Series { - get => _series; + get; set { _seriesObserver?.Dispose(); _seriesObserver?.Initialize(value); - _series = value; + field = value; OnPropertyChanged(); } - } + } = []; void IGeoMapView.InvokeOnUIThread(Action action) => _ = InvokeAsync(action); //.Wait(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs index d013c68e0..4e336ddc1 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using LiveChartsCore.Kernel; using LiveChartsCore.Motion; using LiveChartsCore.SkiaSharpView.Blazor.JsInterop; using LiveChartsCore.SkiaSharpView.Drawing; @@ -38,6 +39,30 @@ public partial class MotionCanvas : IDisposable, IRenderMode private DomJsInterop? _dom; private IFrameTicker _ticker = null!; + /// + /// Gets the recommended rendering settings for Blazor. + /// + public static RenderingSettings RecommendedBlazorRenderingSettings { get; } + = new() + { + // Actually only GL view is supported in Blazor. + UseGPU = true, + + // uses requestAnimationFrame under the hood. + TryUseVSync = true, + + // A fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; + + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedBlazorRenderingSettings)); + } + [Inject] private IJSRuntime JS { get; set; } = null!; @@ -127,7 +152,7 @@ protected override void OnAfterRender(bool firstRender) _dom ??= new DomJsInterop(JS); _dotNetRef = DotNetObjectReference.Create(this); - _ticker = LiveCharts.TryUseVSync + _ticker = LiveCharts.RenderingSettings.TryUseVSync ? new RequestAnimationFrameTicker(_dom, _dotNetRef) : new AsyncLoopTicker(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/ChartControl.cs index 665ddb5f0..574baec21 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/ChartControl.cs @@ -48,8 +48,6 @@ protected ChartControl() Content = motionCanvas; BackgroundColor = Colors.White; - LiveCharts.Configure(config => config.UseDefaults()); - InitializeChartControl(); InitializeObservedProperties(); @@ -62,6 +60,7 @@ protected ChartControl() Content.MouseLeave += OnMouseLeave; } + /// public MotionCanvas CanvasView => (MotionCanvas)Content; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/GeoMap.cs index b5ea1ce16..c30b80447 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/GeoMap.cs @@ -42,19 +42,13 @@ public class GeoMap : Panel, IGeoMapView private readonly MotionCanvas _motionCanvas = new(); private readonly GeoMapChart _core; private CollectionDeepObserver _seriesObserver; - private IEnumerable _series = []; private DrawnMap _activeMap; - private MapProjection _mapProjection = MapProjection.Default; - private Paint? _stroke = new SolidColorPaint(new SKColor(255, 255, 255, 255)) { PaintStyle = PaintStyle.Stroke }; - private Paint? _fill = new SolidColorPaint(new SKColor(240, 240, 240, 255)) { PaintStyle = PaintStyle.Fill }; - private object? _viewCommand = null; /// /// Initializes a new instance of the class. /// public GeoMap() { - LiveCharts.Configure(config => config.UseDefaults()); _activeMap = Maps.GetWorldMap(); _core = new GeoMapChart(this); @@ -90,13 +84,13 @@ public GeoMap() /// public object? ViewCommand { - get => _viewCommand; + get; set { - _viewCommand = value; + field = value; if (value is not null) _core.ViewTo(value); } - } + } = null; /// public DrawnMap ActiveMap { get => _activeMap; set { _activeMap = value; OnPropertyChanged(); } } @@ -107,43 +101,43 @@ public object? ViewCommand float IGeoMapView.Height => ClientSize.Height; /// - public MapProjection MapProjection { get => _mapProjection; set { _mapProjection = value; OnPropertyChanged(); } } + public MapProjection MapProjection { get; set { field = value; OnPropertyChanged(); } } = MapProjection.Default; /// public Paint? Stroke { - get => _stroke; + get; set { if (value is not null) value.PaintStyle = PaintStyle.Stroke; - _stroke = value; + field = value; OnPropertyChanged(); } - } + } = new SolidColorPaint(new SKColor(255, 255, 255, 255)) { PaintStyle = PaintStyle.Stroke }; /// public Paint? Fill { - get => _fill; + get; set { if (value is not null) value.PaintStyle = PaintStyle.Fill; - _fill = value; + field = value; OnPropertyChanged(); } - } + } = new SolidColorPaint(new SKColor(240, 240, 240, 255)) { PaintStyle = PaintStyle.Fill }; /// public IEnumerable Series { - get => _series; + get; set { _seriesObserver.Dispose(); - _series = value; + field = value; OnPropertyChanged(); } - } + } = []; void IGeoMapView.InvokeOnUIThread(Action action) => Application.Instance.InvokeAsync(action).Wait(); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs index 0a121e2a6..90050ced9 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs @@ -21,20 +21,46 @@ // SOFTWARE. using System; -using System.Threading.Tasks; using Eto.Forms; using LiveChartsCore.SkiaSharpView.Drawing; using Eto.SkiaDraw; using LiveChartsCore.Motion; +using LiveChartsCore.Kernel; namespace LiveChartsCore.SkiaSharpView.Eto; /// /// The motion canvas control for windows forms, . /// -public class MotionCanvas : SkiaDrawable +public class MotionCanvas : SkiaDrawable, IRenderMode { - private bool _isDrawingLoopRunning = false; + private IFrameTicker _ticker = null!; + + /// + /// Gets the recommended rendering settings for ETO. + /// + public static RenderingSettings RecommendedETORenderingSettings { get; } = + new() + { + // Not sure if this is supported in Eto, maybe it is already using GPU? + UseGPU = false, + + // this is disconnected from the OS refresh rate + // if interested in VSync, please open an issue in the LiveCharts repository + // with info about the how we can implement it in Eto. + TryUseVSync = true, + + // Because GPU is false, this is the target FPS: + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; + + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedETORenderingSettings)); + } /// /// Initializes a new instance of the class. @@ -44,6 +70,12 @@ public MotionCanvas() Paint += new EventHandler(SkControl_PaintSurface); } + event CoreMotionCanvas.FrameRequestHandler IRenderMode.FrameRequest + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + /// /// Gets the canvas core. /// @@ -56,16 +88,15 @@ public MotionCanvas() protected override void OnLoadComplete(EventArgs e) { base.OnLoadComplete(e); - - CanvasCore.Invalidated += CanvasCore_Invalidated; + _ticker = new AsyncLoopTicker(); + _ticker.InitializeTicker(CanvasCore, this); } /// protected override void OnUnLoad(EventArgs e) { base.OnUnLoad(e); - - CanvasCore.Invalidated -= CanvasCore_Invalidated; + _ticker.DisposeTicker(); CanvasCore.Dispose(); } @@ -73,22 +104,13 @@ private void SkControl_PaintSurface(object sender, SKPaintEventArgs e) => CanvasCore.DrawFrame( new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface)); - private void CanvasCore_Invalidated(CoreMotionCanvas sender) => - RunDrawingLoop(); - private async void RunDrawingLoop() - { - if (_isDrawingLoopRunning) return; - _isDrawingLoopRunning = true; - - var ts = TimeSpan.FromSeconds(1 / LiveCharts.TargetFps); + void IRenderMode.InitializeRenderMode(CoreMotionCanvas canvas) => + throw new NotImplementedException(); - while (!CanvasCore.IsValid) - { - Invalidate(); - await Task.Delay(ts); - } + void IRenderMode.InvalidateRenderer() => + Invalidate(); - _isDrawingLoopRunning = false; - } + void IRenderMode.DisposeRenderMode() => + throw new NotImplementedException(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs index b25f2cf7f..598423dbe 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/ChartControl.cs @@ -45,8 +45,6 @@ public abstract partial class ChartControl : ChartView, IChartView /// protected ChartControl() { - LiveCharts.Configure(config => config.UseDefaults()); - Content = new MotionCanvas(); SizeChanged += (s, e) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/GeoMap.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/GeoMap.xaml.cs index 2dedf9f7d..3fd97807e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/GeoMap.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/GeoMap.xaml.cs @@ -52,7 +52,6 @@ public partial class GeoMap : ContentView, IGeoMapView public GeoMap() { InitializeComponent(); - LiveCharts.Configure(config => config.UseDefaults()); _core = new GeoMapChart(this); SizeChanged += GeoMap_SizeChanged; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs index 0824c9fbf..92152bc14 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/MotionCanvas.cs @@ -21,6 +21,7 @@ // SOFTWARE. using System; +using LiveChartsCore.Kernel; using LiveChartsCore.Motion; using LiveChartsCore.Native; using LiveChartsCore.SkiaSharpView.Maui.Rendering; @@ -34,8 +35,34 @@ namespace LiveChartsCore.SkiaSharpView.Maui; /// public class MotionCanvas : AbsoluteLayout { + /// + /// Gets the recommended rendering settings for MAUI. + /// + public static RenderingSettings RecommendedMAUIRenderingSettings { get; } + = new() + { + // GPU via SKGLView + UseGPU = true, + + // Windows: CompositionTarget.Rendering + // Android: Coreograoher + // IOS/Catalyst: CADisplayLink + TryUseVSync = true, + + // fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; + private readonly CanvasRenderSettings _settings; + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedMAUIRenderingSettings)); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs index e4a9a3a4d..6196fda21 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/GeoMap.cs @@ -51,7 +51,6 @@ public GeoMap() { Content = new MotionCanvas(); - LiveCharts.Configure(config => config.UseDefaults()); _core = new GeoMapChart(this); PointerPressed += OnPointerPressed; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/GeoMap.xaml.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/GeoMap.xaml.cs index eced1816d..0ba8a18ac 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/GeoMap.xaml.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/GeoMap.xaml.cs @@ -50,7 +50,6 @@ public sealed partial class GeoMap : UserControl, IGeoMapView public GeoMap() { InitializeComponent(); - LiveCharts.Configure(config => config.UseDefaults()); _core = new GeoMapChart(this); PointerPressed += OnPointerPressed; diff --git a/src/skiasharp/_Shared.WinUI/ChartControl.cs b/src/skiasharp/_Shared.WinUI/ChartControl.cs index 34a54b4c9..cadd54ddf 100644 --- a/src/skiasharp/_Shared.WinUI/ChartControl.cs +++ b/src/skiasharp/_Shared.WinUI/ChartControl.cs @@ -51,8 +51,6 @@ public abstract partial class ChartControl : UserControl, IChartView /// public ChartControl() { - LiveCharts.Configure(config => config.UseDefaults()); - Content = new MotionCanvas(); SizeChanged += (s, e) => diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs new file mode 100644 index 000000000..3e0fba439 --- /dev/null +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs @@ -0,0 +1,60 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using Microsoft.UI.Xaml.Controls; +using LiveChartsCore.Kernel; + +#pragma warning disable IDE0028 // Simplify collection initialization + +namespace LiveChartsCore.SkiaSharpView.WinUI; + +/// +/// The motion canvas control for WinUI and Uno Platform. +/// +public partial class MotionCanvas : Canvas +{ + /// + /// Gets the recommended rendering settings for Uno and WinUI. + /// + public static RenderingSettings RecommendedUnoRenderingSettings { get; } + = new() + { + // GPU disabled in WPF by default for 2 reasons: + // 1. https://github.com/mono/SkiaSharp/issues/3309 + // 2. OpenTK pointer events are sluggish. + UseGPU = true, + + // TryUseVSync makes no sense when GPU is false + TryUseVSync = true, + + // Because GPU is false, this is the target FPS: + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = true + }; + + static MotionCanvas() + { + LiveCharts.Configure(config => config.UseDefaults(RecommendedUnoRenderingSettings)); + } +} diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index f710cb2cf..270f801df 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -11,6 +11,7 @@ + From da838a2a5577bb83601ff3f444dddf8b7d97674f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Fri, 25 Jul 2025 22:17:17 -0600 Subject: [PATCH 74/94] improve fps log --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 7e699adc9..8868e074d 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -193,16 +193,20 @@ public void DrawFrame(TDrawingContext context) { var sb = new StringBuilder(); - sb.Append($"FPS [ {_totalFrames / _totalSeconds:N2} ]"); - sb.Append($"`render time last/avrg [ {_lastDrawTime:N2} / {_totalDrawTime / _totalFrames:N2} ] ms"); +#if DEBUG + sb.Append($"[~~ {_totalFrames / _totalSeconds:N2} ~~] FPS (DEBUG DECRESED PERFORMANCE)"); +#else + sb.Append($"[ {_totalFrames / _totalSeconds:N2} ] FPS"); +#endif + sb.Append($"`[ {_lastDrawTime:N2}ms / {_totalDrawTime / _totalFrames:N2}ms ] render time last / avrg"); sb.Append(s_externalRenderer is null - ? $"`GPU / VSync [ {LiveCharts.RenderingSettings.UseGPU} / {LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync} ]" - : $"`GPU / VSync by [ {s_externalRenderer} ]"); + ? $"`[ {LiveCharts.RenderingSettings.UseGPU} / {LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync} ] GPU / VSync" + : $"`[ {s_externalRenderer} ] handling GPU / VSync by"); if (_jitteredDrawCount > 0) sb.Append( - $"`jittered draws [ {_jitteredDrawCount} ]"); + $"`[ {_jitteredDrawCount} ] jittered draws"); context.LogOnCanvas(sb.ToString()); } From 6aaaee577b4a5af24e8dbe38f4948299adb6d41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 26 Jul 2025 09:08:54 -0600 Subject: [PATCH 75/94] remove skiarenderer LiveCharts should be able to work on both native and skia drawn. removing this should remove the dependency on the skia renderer. --- .../LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index c6bd55006..ca9e244a3 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -6,7 +6,6 @@ true Library $(LatestSkiaSharpVersion) - SkiaRenderer net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst; From b11102ff4a09623a2f5e47bb1bcfeeb47de5e6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 26 Jul 2025 09:09:03 -0600 Subject: [PATCH 76/94] typo --- .../LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index ca9e244a3..d76ebaa25 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -56,10 +56,7 @@ - + From 52238ce287249662ced40338390357e87b24f2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 26 Jul 2025 23:08:42 -0600 Subject: [PATCH 77/94] update skiasharp context --- .../Drawing/SkiaSharpDrawingContext.cs | 20 ++++++------------- .../SKCharts/InMemorySkiaSharpChart.cs | 4 ++-- .../SKCharts/SKGeoMap.cs | 2 +- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs index 099c233bf..e4a9aec07 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs @@ -35,12 +35,12 @@ namespace LiveChartsCore.SkiaSharpView.Drawing; /// /// The motion canvas. /// The information. -/// The surface. +/// The canvas. /// Indicates whether the canvas is cleared on frame draw. public class SkiaSharpDrawingContext( CoreMotionCanvas motionCanvas, SKImageInfo info, - SKSurface surface, + SKCanvas canvas, bool clearOnBeginDraw = true) : DrawingContext { @@ -49,16 +49,16 @@ public class SkiaSharpDrawingContext( /// /// The motion canvas. /// The information. - /// The surface. + /// The canvas. /// The background. /// Indicates whether the canvas is cleared on frame draw. public SkiaSharpDrawingContext( CoreMotionCanvas motionCanvas, SKImageInfo info, - SKSurface surface, + SKCanvas canvas, SKColor background, bool clearOnBeginDraw = true) - : this(motionCanvas, info, surface, clearOnBeginDraw) + : this(motionCanvas, info, canvas, clearOnBeginDraw) { Background = background; } @@ -79,21 +79,13 @@ public SkiaSharpDrawingContext( /// public SKImageInfo Info { get; set; } = info; - /// - /// Gets or sets the surface. - /// - /// - /// The surface. - /// - public SKSurface Surface { get; set; } = surface; - /// /// Gets or sets the canvas. /// /// /// The canvas. /// - public SKCanvas Canvas => Surface.Canvas; + public SKCanvas Canvas { get; } = canvas; /// /// Gets or sets the paint. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs index 661062e58..5f6d2bcd7 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs @@ -151,7 +151,7 @@ public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface, + surface.Canvas, Background, clearCanvasOnBeginDraw)); @@ -170,7 +170,7 @@ public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface, + surface.Canvas, Background, clearCanvasOnBeginDraw)); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs index cfd243648..3e488b4df 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs @@ -131,7 +131,7 @@ public override void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw new SkiaSharpDrawingContext( Canvas, new SKImageInfo(Width, Height), - surface, + surface.Canvas, Background, clearCanvasOnBeginDraw)); From 0afbbe65b90db59a639ba979ad01fe1fb5497bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 26 Jul 2025 23:09:46 -0600 Subject: [PATCH 78/94] add uno skia renderer --- .../UnoPlatformSample.csproj | 24 ++-- ...eChartsCore.SkiaSharpView.Uno.WinUI.csproj | 35 ++---- src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 8 +- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 7 +- .../_Shared.WinUI/Rendering/GPURenderMode.cs | 2 +- .../_Shared.WinUI/Rendering/SkiaRenderMode.cs | 110 ++++++++++++++++++ .../_Shared.WinUI/_Shared.WinUI.projitems | 1 + 7 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs diff --git a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj index 2f2122449..7db69548f 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj +++ b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj @@ -21,23 +21,17 @@ UnoFeatures let's you quickly add and manage implicit package references based on the features you want to use. https://aka.platform.uno/singleproject-features --> - - Material; - Dsp; - Hosting; - Toolkit; - Logging; - MVUX; - Configuration; - HttpKiota; - Serialization; - Localization; - Navigation; - ThemeService; - SkiaRenderer; - + + + $(UnoFeatures) + + LiveChartsSamples\%(RecursiveDir)%(Filename)%(Extension) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index d76ebaa25..0fa365fa4 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -56,8 +56,16 @@ - - + + + + @@ -66,28 +74,7 @@ analyzers - - - - - - - - - - - - - - %(Filename) - - - - - - - - + diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index df794b665..7712e0126 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -42,11 +42,9 @@ public partial class MotionCanvas : Canvas /// public MotionCanvas() { -#if DESKTOP - // The CPURenderMode class inherits from SKXamlCanvas which is the control Uno uses to - // render SkiaSharp on the netx-destop target. - // as of today, SwapChainPanel is not available on the netx-desktop target. - _settings = new(new CPURenderMode()); +#if __UNO_SKIA__ || DESKTOP || BROWSERWASM + // then force the skiarendermode. + _settings = new(new SkiaRenderMode()); #else _settings = new(); #endif diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs index fd0dd5841..90349f290 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -41,11 +41,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) PaintSurface += OnPaintSurface; #if DEBUG -#if DESKTOP - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using Uno's Skia renderer."); -#else System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); -#endif #endif } @@ -61,7 +57,8 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e) if (density.DpiX != 1 || density.DpiY != 1) e.Surface.Canvas.Scale(density.DpiX, density.DpiY); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, GetBackground().AsSKColor())); + FrameRequest?.Invoke(new SkiaSharpDrawingContext( + _canvas, e.Info, e.Surface.Canvas, GetBackground().AsSKColor())); } public void InvalidateRenderer() => diff --git a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs index 1657096e5..a9ab81da5 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs @@ -57,7 +57,7 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) if (density.DpiX != 1 || density.DpiY != 1) e.Surface.Canvas.Scale(density.DpiX, density.DpiY); - FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, GetBackground().AsSKColor())); + FrameRequest?.Invoke(new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface.Canvas, GetBackground().AsSKColor())); } public void InvalidateRenderer() => diff --git a/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs new file mode 100644 index 000000000..df3344ebd --- /dev/null +++ b/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs @@ -0,0 +1,110 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if __UNO_SKIA__ || DESKTOP || BROWSERWASM + +using System.Diagnostics; +using LiveChartsCore.Drawing; +using LiveChartsCore.Motion; +using LiveChartsCore.SkiaSharpView.Drawing; +using Microsoft.UI; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using SkiaSharp; +using Uno.WinUI.Graphics2DSK; +using Windows.Foundation; + +namespace LiveChartsCore.SkiaSharpView.WinUI.Rendering; + +internal partial class SkiaRenderMode : Grid, IRenderMode +{ + private readonly SkiaRenderMode2 _renderMode = new(); + + public SkiaRenderMode() + { + IsHitTestVisible = true; + Background = new SolidColorBrush(Colors.Transparent); + Children.Add(_renderMode); + } + + public event CoreMotionCanvas.FrameRequestHandler FrameRequest + { + add => _renderMode.FrameRequest += value; + remove => _renderMode.FrameRequest -= value; + } + + public void InitializeRenderMode(CoreMotionCanvas canvas) => + _renderMode.InitializeRenderMode(canvas); + + public void DisposeRenderMode() => + _renderMode.Dispose(); + + public void InvalidateRenderer() => + _renderMode.InvalidateRenderer(); + + // nested because it seems that SKCanvasElement does not support pointer events + internal partial class SkiaRenderMode2 : SKCanvasElement, IRenderMode + { + private CoreMotionCanvas _canvas = null!; + + public event CoreMotionCanvas.FrameRequestHandler? FrameRequest; + + public void InitializeRenderMode(CoreMotionCanvas canvas) + { + Background = new SolidColorBrush(Colors.Transparent); + IsHitTestVisible = true; + + _canvas = canvas; +#if DEBUG + Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(SkiaRenderMode)}."); +#endif + } + + private void SkiaRenderMode_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) => + Debug.WriteLine("skia sees pointer moved"); + + public void DisposeRenderMode() => + _canvas = null!; + + public void InvalidateRenderer() => + Invalidate(); + + protected override void RenderOverride(SKCanvas canvas, Size area) + { + FrameRequest?.Invoke(new SkiaSharpDrawingContext( + _canvas, new SKImageInfo((int)area.Width, (int)area.Height), canvas, GetBackground().AsSKColor())); + } + + private LvcColor GetBackground() + { + var parentBg = Parent is Control control && control.Background is SolidColorBrush bg + ? new LvcColor(bg.Color.R, bg.Color.G, bg.Color.B, bg.Color.A) + : LvcColor.Empty; + + return parentBg != LvcColor.Empty + ? parentBg + : _canvas._virtualBackgroundColor; + } + } +} + +#endif diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index 270f801df..718881568 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -15,6 +15,7 @@ + From 0bb5507dbcd2cde06b2045acf0ce5e147f45f355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 26 Jul 2025 23:22:29 -0600 Subject: [PATCH 79/94] update native platforms --- .../Platforms/Android/NativeTicker.cs | 4 +++- .../Platforms/Android/PointerController.cs | 4 +++- src/_Shared.Native/Platforms/Mac/NativeTicker.cs | 4 +++- .../Platforms/Mac/PointerController.cs | 4 +++- src/_Shared.Native/Platforms/NoUI/NativeTicker.cs | 2 +- .../Platforms/NoUI/PointerController.cs | 2 +- src/_Shared.Native/Platforms/WinUI/NativeTicker.cs | 14 +++++--------- .../Platforms/WinUI/PointerController.cs | 7 ++++--- 8 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/_Shared.Native/Platforms/Android/NativeTicker.cs b/src/_Shared.Native/Platforms/Android/NativeTicker.cs index 2647f49f2..059b241d4 100644 --- a/src/_Shared.Native/Platforms/Android/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/Android/NativeTicker.cs @@ -20,7 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if ANDROID +#if ANDROID && !__UNO_SKIA__ + +// reachable on maui android or uno android (without skia renderer) using System; using LiveChartsCore.Motion; diff --git a/src/_Shared.Native/Platforms/Android/PointerController.cs b/src/_Shared.Native/Platforms/Android/PointerController.cs index f7598ae1a..482fb07e4 100644 --- a/src/_Shared.Native/Platforms/Android/PointerController.cs +++ b/src/_Shared.Native/Platforms/Android/PointerController.cs @@ -20,7 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if ANDROID +#if ANDROID && !__UNO_SKIA__ + +// reachable on maui android or uno android (without skia renderer) using System; using Android.Views; diff --git a/src/_Shared.Native/Platforms/Mac/NativeTicker.cs b/src/_Shared.Native/Platforms/Mac/NativeTicker.cs index efe58386e..938c5ae67 100644 --- a/src/_Shared.Native/Platforms/Mac/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/Mac/NativeTicker.cs @@ -20,7 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if IOS || MACCATALYST +#if (IOS || MACCATALYST) && !__UNO_SKIA__ + +// reachable on maui ios/catalys or uno ios/catalyst (without skia renderer) using CoreAnimation; using Foundation; diff --git a/src/_Shared.Native/Platforms/Mac/PointerController.cs b/src/_Shared.Native/Platforms/Mac/PointerController.cs index 61bf0e46c..e18623d57 100644 --- a/src/_Shared.Native/Platforms/Mac/PointerController.cs +++ b/src/_Shared.Native/Platforms/Mac/PointerController.cs @@ -20,7 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if MACCATALYST || IOS +#if (MACCATALYST || IOS) && !__UNO_SKIA__ + +// reachable on maui ios/catalys or uno ios/catalyst (without skia renderer) using System; using CoreGraphics; diff --git a/src/_Shared.Native/Platforms/NoUI/NativeTicker.cs b/src/_Shared.Native/Platforms/NoUI/NativeTicker.cs index f8ba577c3..bb961fce1 100644 --- a/src/_Shared.Native/Platforms/NoUI/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/NoUI/NativeTicker.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if !HAS_UI +#if !HAS_UI_LVC // This code is reached maybe only on test environments. // HAS_UI is true when the target framework contains any of the following: diff --git a/src/_Shared.Native/Platforms/NoUI/PointerController.cs b/src/_Shared.Native/Platforms/NoUI/PointerController.cs index 00003a026..6ef222cca 100644 --- a/src/_Shared.Native/Platforms/NoUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/NoUI/PointerController.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if !HAS_UI +#if !HAS_UI_LVC // This code is reached maybe only on test environments. // HAS_UI is true when the target framework contains any of the following: diff --git a/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs b/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs index 18bac619f..266b86047 100644 --- a/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs @@ -20,10 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if WINDOWS || DESKTOP || BROWSERWASM +#if WINDOWS || __UNO_SKIA__ || DESKTOP || BROWSERWASM -// on desktop and browserwasm the uno implementation of composition target rendering is used. -// for the rest of the uno targets, the native ticker is used. +// reachable on winui, maui winui, uno winui and uno with skia renderer using LiveChartsCore.Motion; using Microsoft.UI.Xaml.Media; @@ -44,14 +43,11 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) CompositionTarget.Rendering += OnCompositonTargetRendering; #if DEBUG -#if DESKTOP - CoreMotionCanvas.s_externalRenderer = "Uno Desktop"; +#if __UNO_SKIA__ || DESKTOP || BROWSERWASM + CoreMotionCanvas.s_externalRenderer = "Uno SkiaRenderer via CompositionTarget.Rendering"; +#endif System.Diagnostics.Trace.WriteLine( "[LiveCharts Info] FrameSync: CompositionTarget.Rendering"); -#else - System.Diagnostics.Trace.WriteLine( - "[LiveCharts Info] FrameSync: CompositionTarget.Rendering (Windows)"); -#endif #endif } diff --git a/src/_Shared.Native/Platforms/WinUI/PointerController.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs index 276e67548..3fb4478a5 100644 --- a/src/_Shared.Native/Platforms/WinUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -20,13 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if WINDOWS || DESKTOP || BROWSERWASM +#if WINDOWS || __UNO_SKIA__ || DESKTOP || BROWSERWASM -// on desktop and browserwasm the uno implementation of pointer events is used. +// reachable on winui, maui winui, uno winui and uno with skia renderer using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; - namespace LiveChartsCore.Native; internal partial class PointerController : INativePointerController @@ -99,6 +98,8 @@ private void OnWindowsPointerWheelChanged(object sender, PointerRoutedEventArgs private void OnWindowsPointerExited(object sender, PointerRoutedEventArgs e) => Exited?.Invoke(sender, new(e)); + + } #endif From de504b5ff4089cec0c6c176d5a8571caf2106c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sat, 26 Jul 2025 23:22:45 -0600 Subject: [PATCH 80/94] update directory props --- Directory.Build.props | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index d45d5df99..e180baa92 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,17 +10,35 @@ 2.88.9 3.119.0 + + Material; + Dsp; + Hosting; + Toolkit; + Logging; + MVUX; + Configuration; + HttpKiota; + Serialization; + Localization; + Navigation; + ThemeService; + + + - - $(DefineConstants);HAS_UI + $(DefineConstants);HAS_UI_LVC + From 2cb30bbce3ccca0f31c8458232c47e38b991896e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sun, 27 Jul 2025 00:28:15 -0600 Subject: [PATCH 81/94] more hacks --- src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 3 +- .../_Shared.WinUI/MotionCanvas.settings.cs | 79 +++++++++++++++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index 7712e0126..cd8f873dc 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -42,7 +42,8 @@ public partial class MotionCanvas : Canvas /// public MotionCanvas() { -#if __UNO_SKIA__ || DESKTOP || BROWSERWASM +#if (__UNO_SKIA__ || DESKTOP) && !BROWSERWASM + // no wasm, see note #250727 // then force the skiarendermode. _settings = new(new SkiaRenderMode()); #else diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs index 3e0fba439..8b3f699df 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs @@ -36,22 +36,89 @@ public partial class MotionCanvas : Canvas /// Gets the recommended rendering settings for Uno and WinUI. /// public static RenderingSettings RecommendedUnoRenderingSettings { get; } +#if (__UNO_SKIA__ || DESKTOP) && !BROWSERWASM + // --------------------------------- + // if skia renderer + // --------------------------------- = new() { - // GPU disabled in WPF by default for 2 reasons: - // 1. https://github.com/mono/SkiaSharp/issues/3309 - // 2. OpenTK pointer events are sluggish. + // ignored, defined by uno UseGPU = true, - // TryUseVSync makes no sense when GPU is false + // ignored, defined by uno TryUseVSync = true, - // Because GPU is false, this is the target FPS: + // fallback value when VSync is not used. LiveChartsRenderLoopFPS = 60, // make this true to see the FPS in the top left corner of the chart - ShowFPS = true + ShowFPS = false }; +#elif BROWSERWASM + // note #250727 + // --------------------------------- + // for a reason the browser wasm using the skia renderer + // just throws, at least on uno sdk 6.1.23 and skiasharp view 3.119.0 + // it seems that _visual field is missing in wasm builds, which is used by the + // Uno.WinUI.Graphics2DSK.SKCanvasElement. + // so for wasm, lets render the charts our way. + // Unhandled dispatcher exception: Error: Field not found: Microsoft.UI.Composition.ContainerVisual Microsoft.UI.Xaml.UIElement._visual Due to: Could not find field in class ( at LiveChartsCore.SkiaSharpView.WinUI.Rendering.SkiaRenderMode.SkiaRenderMode2.InvalidateRenderer() in /_/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs:line 89 + // at LiveChartsCore.SkiaSharpView.WinUI.Rendering.SkiaRenderMode.InvalidateRenderer() in /_/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs:line 62 + // at LiveChartsCore.Native.NativeFrameTicker.OnCompositonTargetRendering(Object sender, Object e) in /_/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs:line 60 + // at Uno.UI.Dispatching.NativeDispatcher.DispatchItems() in C:\a\1\s\src\Uno.UI.Dispatching\Native\NativeDispatcher.cs:line 106 + // at Uno.UI.Dispatching.NativeDispatcher.DispatcherCallback() in C:\a\1\s\src\Uno.UI.Dispatching\Native\NativeDispatcher.wasm.cs:line 25 + // at Uno.UI.Dispatching.NativeDispatcher.__Wrapper_DispatcherCallback_1192908908(JSMarshalerArgument* __arguments_buffer) in C:\a\1\s\src\Uno.UI.Dispatching\obj\Uno.UI.Dispatching.Wasm\Release\net9.0\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JSExportGenerator\JSExports.g.cs:line 35 + // Error: Field not found: Microsoft.UI.Composition.ContainerVisual Microsoft.UI.Xaml.UIElement._visual Due to: Could not find field in class + // --------------------------------- + = new() + { + // try use gl + UseGPU = true, + // connect to the browser's requestAnimationFrame via uno? + TryUseVSync = true, + // fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 20, + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; +#elif WINDOWS + // --------------------------------- + // if winui + // --------------------------------- + = new() + { + // at least on uno sdk 6.1.23 and skiasharp view 3.119.0 + // SwapChainPanel does not work. + UseGPU = false, + + // via CompositionTarget.Rendering + TryUseVSync = true, + + // fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; +#elif ANDROID && !__UNO_SKIA__ + // --------------------------------- + // if android without skia renderer + // --------------------------------- + = new() + { + // ignored, defined by uno + UseGPU = true, + + // ignored, defined by uno + TryUseVSync = true, + + // fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; +#endif static MotionCanvas() { From 1a491238c30f68471f4df8eecf061c7d0e717ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sun, 27 Jul 2025 00:28:35 -0600 Subject: [PATCH 82/94] add props --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index e180baa92..2d0e259d5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,7 +23,7 @@ Localization; Navigation; ThemeService; - + SkiaRenderer; From fa050a3deca4e7996bb4c715c88c234bde813ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Sun, 27 Jul 2025 08:56:46 -0600 Subject: [PATCH 83/94] update skiasharpdrawingcontext --- .../MotionCanvas.cs | 2 +- .../Rendering/CPURenderMode.cs | 2 +- .../Rendering/GPURenderMode.cs | 2 +- .../MotionCanvas.cs | 4 ++-- .../SKCharts/InMemorySkiaSharpChart.cs | 13 ++++++------- .../SKCharts/SKCartesianChart.cs | 2 -- .../LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs | 2 -- .../LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs | 2 -- .../SKCharts/SKPolarChart.cs | 2 -- .../MotionCanvas.razor.cs | 3 ++- .../MotionCanvas.cs | 2 +- .../Rendering/CPURenderMode.cs | 2 +- .../Rendering/GPURenderMode.cs | 2 +- .../CoreObjectsTests/ChangingPaintTasks.cs | 2 +- .../OtherTests/LabelsMeasureTest.cs | 2 +- .../OtherTests/VisualElementsTests.cs | 2 +- .../SeriesTests/_MemoryTests.cs | 2 +- 17 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs index 99b1992be..395171992 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs @@ -140,7 +140,7 @@ public void Render(ImmediateDrawingContext context) motionCanvas.DrawFrame( new SkiaSharpDrawingContext(motionCanvas, new SKImageInfo((int)Bounds.Width, (int)Bounds.Height), - lease.SkSurface, + lease.SkCanvas, background, false)); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs index 0ffa9fff4..67cf3715c 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs @@ -63,7 +63,7 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) args.Surface.Canvas.Scale(density.dpix, density.dpiy); FrameRequest?.Invoke( - new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, GetBackground().AsSKColor())); + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface.Canvas, GetBackground().AsSKColor())); } private ResolutionHelper GetPixelDensity() diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs index 2332572a5..eb0ecae9e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs @@ -64,7 +64,7 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs args) args.Surface.Canvas.Scale(density.dpix, density.dpiy); FrameRequest?.Invoke( - new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, GetBackground().AsSKColor())); + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface.Canvas, GetBackground().AsSKColor())); } private ResolutionHelper GetPixelDensity() diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs index b0220a862..d56092c1f 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/MotionCanvas.cs @@ -102,11 +102,11 @@ protected override void OnHandleDestroyed(EventArgs e) private void SkControl_PaintSurface(object sender, SKPaintSurfaceEventArgs e) => CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, GetBackground().AsSKColor())); + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface.Canvas, GetBackground().AsSKColor())); private void SkglControl_PaintSurface(object sender, SKPaintGLSurfaceEventArgs e) => CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface, GetBackground().AsSKColor())); + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface.Canvas, GetBackground().AsSKColor())); private LvcColor GetBackground() => true diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs index 5f6d2bcd7..2760ac94a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs @@ -32,17 +32,16 @@ namespace LiveChartsCore.SkiaSharpView.SKCharts; /// /// A chart that is able to generate images or draw to a given canvas. /// -public abstract class InMemorySkiaSharpChart +/// +/// Initializes a new instance of the class. +/// +public abstract class InMemorySkiaSharpChart(IChartView? chartView = null) { - private readonly IChartView? _chartView; + private readonly IChartView? _chartView = chartView; - /// - /// Initializes a new instance of the class. - /// - public InMemorySkiaSharpChart(IChartView? chartView = null) + static InMemorySkiaSharpChart() { LiveCharts.Configure(config => config.UseDefaults()); - _chartView = chartView; } /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs index a88b2c243..020b6d689 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKCartesianChart.cs @@ -49,8 +49,6 @@ public class SKCartesianChart : InMemorySkiaSharpChart, ICartesianChartView public SKCartesianChart(ICartesianChartView? chartView = null) : base(chartView) { - LiveCharts.Configure(config => config.UseDefaults()); - Core = new CartesianChartEngine(this, config => config.UseDefaults(), CoreCanvas); Core.Measuring += OnCoreMeasuring; Core.UpdateStarted += OnCoreUpdateStarted; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs index 3e488b4df..898037ad1 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs @@ -44,8 +44,6 @@ public class SKGeoMap : InMemorySkiaSharpChart, IGeoMapView /// public SKGeoMap() { - LiveCharts.Configure(config => config.UseDefaults()); - _core = new GeoMapChart(this); ActiveMap = Maps.GetWorldMap(); } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs index 118bdb165..9319785f9 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPieChart.cs @@ -49,8 +49,6 @@ public class SKPieChart : InMemorySkiaSharpChart, IPieChartView public SKPieChart(IPieChartView? chartView = null) : base(chartView) { - LiveCharts.Configure(config => config.UseDefaults()); - Core = new PieChartEngine(this, config => config.UseDefaults(), CoreCanvas); Core.Measuring += OnCoreMeasuring; Core.UpdateStarted += OnCoreUpdateStarted; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs index 35b370f0d..f9abf4383 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKPolarChart.cs @@ -49,8 +49,6 @@ public class SKPolarChart : InMemorySkiaSharpChart, IPolarChartView public SKPolarChart(IChartView? chartView = null) : base(chartView) { - LiveCharts.Configure(config => config.UseDefaults()); - Core = new PolarChartEngine(this, config => config.UseDefaults(), CoreCanvas); Core.Measuring += OnCoreMeasuring; Core.UpdateStarted += OnCoreUpdateStarted; diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs index 4e336ddc1..03155dd5e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs @@ -142,7 +142,8 @@ protected virtual void OnPointerOut(PointerEventArgs e) => _ = OnPointerOutCallback.InvokeAsync(e); private void OnPaintGlSurface(SKPaintGLSurfaceEventArgs e) => - CanvasCore.DrawFrame(new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface)); + CanvasCore.DrawFrame( + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface.Canvas)); /// protected override void OnAfterRender(bool firstRender) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs index 90050ced9..2016461d8 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/MotionCanvas.cs @@ -102,7 +102,7 @@ protected override void OnUnLoad(EventArgs e) private void SkControl_PaintSurface(object sender, SKPaintEventArgs e) => CanvasCore.DrawFrame( - new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface)); + new SkiaSharpDrawingContext(CanvasCore, e.Info, e.Surface.Canvas)); void IRenderMode.InitializeRenderMode(CoreMotionCanvas canvas) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs index 814641776..cb563df5a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs @@ -67,7 +67,7 @@ private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs args) args.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); FrameRequest?.Invoke( - new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface, GetBackground().AsSKColor())); + new SkiaSharpDrawingContext(_canvas, args.Info, args.Surface.Canvas, GetBackground().AsSKColor())); } private void MainDisplayInfoChanged(object? sender, EventArgs e) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs index ea0bf410f..2264a061e 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs @@ -67,7 +67,7 @@ private void OnPaintSurface(object? sender, SKPaintGLSurfaceEventArgs e) e.Surface.Canvas.Scale(_pixelDensity, _pixelDensity); FrameRequest?.Invoke( - new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface, GetBackground().AsSKColor())); + new SkiaSharpDrawingContext(_canvas, e.Info, e.Surface.Canvas, GetBackground().AsSKColor())); } private void MainDisplayInfoChanged(object? sender, EventArgs e) => diff --git a/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs b/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs index 9c3fac1e6..f330be636 100644 --- a/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs +++ b/tests/LiveChartsCore.UnitTesting/CoreObjectsTests/ChangingPaintTasks.cs @@ -386,7 +386,7 @@ public static int DrawChart(InMemorySkiaSharpChart chart, bool animated = false) new SkiaSharpDrawingContext( canvas, new SKImageInfo(100, 100), - SKSurface.Create(new SKImageInfo(100, 100)))); + SKSurface.Create(new SKImageInfo(100, 100)).Canvas)); } return frames; diff --git a/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs b/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs index f1dd23894..41055714e 100644 --- a/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs +++ b/tests/LiveChartsCore.UnitTesting/OtherTests/LabelsMeasureTest.cs @@ -116,7 +116,7 @@ public void MaxWidth() var canvas = new CoreMotionCanvas(); var drawingContext = new SkiaSharpDrawingContext( - canvas, SKImageInfo.Empty, SKSurface.Create(new SKImageInfo(100, 100))); + canvas, SKImageInfo.Empty, SKSurface.Create(new SKImageInfo(100, 100)).Canvas); var paint = new SolidColorPaint { Color = SKColors.Red }; paint.InitializeTask(drawingContext); diff --git a/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs b/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs index 5914f2288..20b15c3e7 100644 --- a/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs +++ b/tests/LiveChartsCore.UnitTesting/OtherTests/VisualElementsTests.cs @@ -58,7 +58,7 @@ void Draw() new SkiaSharpDrawingContext( chart.CoreCanvas, new SKImageInfo(chart.Width, chart.Height), - surface, + canvas, SKColors.White, true)); } diff --git a/tests/LiveChartsCore.UnitTesting/SeriesTests/_MemoryTests.cs b/tests/LiveChartsCore.UnitTesting/SeriesTests/_MemoryTests.cs index 83c090b9c..e80222462 100644 --- a/tests/LiveChartsCore.UnitTesting/SeriesTests/_MemoryTests.cs +++ b/tests/LiveChartsCore.UnitTesting/SeriesTests/_MemoryTests.cs @@ -9,7 +9,7 @@ namespace LiveChartsCore.UnitTesting.SeriesTests; -[TestClass] +//[TestClass] public class _MemoryTests { private static readonly int s_repeatCount; From 0bce0a242406d2699384a90d00524122177ed705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 28 Jul 2025 12:55:25 -0600 Subject: [PATCH 84/94] improve logs --- src/LiveChartsCore/Motion/AsyncLoopTicker.cs | 1 + src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 17 ++++-- .../Platforms/Android/NativeTicker.cs | 5 +- .../Platforms/Mac/NativeTicker.cs | 5 +- .../Platforms/WinUI/NativeTicker.cs | 8 +-- .../SKCharts/InMemorySkiaSharpChart.cs | 16 +++--- .../SKCharts/SKGeoMap.cs | 6 +-- src/skiasharp/_Shared.WinUI/MotionCanvas.cs | 3 +- .../_Shared.WinUI/MotionCanvas.settings.cs | 53 ++++++++----------- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 4 +- .../_Shared.WinUI/Rendering/GPURenderMode.cs | 4 +- .../_Shared.WinUI/Rendering/SkiaRenderMode.cs | 6 +-- 12 files changed, 56 insertions(+), 72 deletions(-) diff --git a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs index e68dbbeb5..582c4093e 100644 --- a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs +++ b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs @@ -36,6 +36,7 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _renderMode = renderMode; _canvas.Invalidated += OnCoreInvalidated; + CoreMotionCanvas.s_tickerName = nameof(AsyncLoopTicker); #if DEBUG System.Diagnostics.Trace.WriteLine( diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 8868e074d..383694dc5 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -50,6 +50,8 @@ public class CoreMotionCanvas : IDisposable private static readonly long s_jitterThreshold = s_baseFrameDelay.Ticks / 2; internal LvcColor _virtualBackgroundColor; internal static string? s_externalRenderer; + internal static string? s_rendererName; + internal static string? s_tickerName; static CoreMotionCanvas() { @@ -194,15 +196,22 @@ public void DrawFrame(TDrawingContext context) var sb = new StringBuilder(); #if DEBUG - sb.Append($"[~~ {_totalFrames / _totalSeconds:N2} ~~] FPS (DEBUG DECRESED PERFORMANCE)"); + sb.Append($"[~~ {_totalFrames / _totalSeconds:N2} ~~] FPS (DEBUG)"); #else sb.Append($"[ {_totalFrames / _totalSeconds:N2} ] FPS"); #endif sb.Append($"`[ {_lastDrawTime:N2}ms / {_totalDrawTime / _totalFrames:N2}ms ] render time last / avrg"); - sb.Append(s_externalRenderer is null - ? $"`[ {LiveCharts.RenderingSettings.UseGPU} / {LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync} ] GPU / VSync" - : $"`[ {s_externalRenderer} ] handling GPU / VSync by"); + if (s_externalRenderer is null) + { + sb.Append($"`[ {(LiveCharts.RenderingSettings.UseGPU ? "GPU" : "CPU")} ] via {s_rendererName}"); + var isVSynced = LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync; + sb.Append($"`[ {(isVSynced ? "VSync" : "VSync disabled")} ] handled by {s_tickerName}"); + } + else + { + sb.Append($"`{s_externalRenderer} handling GPU / VSync"); + } if (_jitteredDrawCount > 0) sb.Append( diff --git a/src/_Shared.Native/Platforms/Android/NativeTicker.cs b/src/_Shared.Native/Platforms/Android/NativeTicker.cs index 059b241d4..c6a80bbe9 100644 --- a/src/_Shared.Native/Platforms/Android/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/Android/NativeTicker.cs @@ -44,10 +44,7 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _canvas.Invalidated += OnCoreInvalidated; -#if DEBUG - System.Diagnostics.Trace.WriteLine( - "[LiveCharts Info] FrameSync: Choreographer (Android)"); -#endif + CoreMotionCanvas.s_tickerName = "Choreographer Android"; } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/_Shared.Native/Platforms/Mac/NativeTicker.cs b/src/_Shared.Native/Platforms/Mac/NativeTicker.cs index 938c5ae67..425eafde8 100644 --- a/src/_Shared.Native/Platforms/Mac/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/Mac/NativeTicker.cs @@ -45,10 +45,7 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _canvas.Invalidated += OnCoreInvalidated; -#if DEBUG - System.Diagnostics.Trace.WriteLine( - "[LiveCharts Info] FrameSync: CADisplayLink (iOS/Catalyst)"); -#endif + CoreMotionCanvas.s_tickerName = "CADisplayLink iOS/Catalyst"; } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs b/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs index 266b86047..62b11f2b6 100644 --- a/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs +++ b/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs @@ -42,13 +42,7 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _canvas.Invalidated += OnCoreInvalidated; CompositionTarget.Rendering += OnCompositonTargetRendering; -#if DEBUG -#if __UNO_SKIA__ || DESKTOP || BROWSERWASM - CoreMotionCanvas.s_externalRenderer = "Uno SkiaRenderer via CompositionTarget.Rendering"; -#endif - System.Diagnostics.Trace.WriteLine( - "[LiveCharts Info] FrameSync: CompositionTarget.Rendering"); -#endif + CoreMotionCanvas.s_tickerName = "CompositionTarget.Rendering WinUI"; } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs index 2760ac94a..41388b19d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/InMemorySkiaSharpChart.cs @@ -93,7 +93,7 @@ public virtual SKImage GetImage() using var surface = SKSurface.Create(new SKImageInfo(Width, Height)); using var canvas = surface.Canvas; - DrawOnCanvas(surface); + DrawOnCanvas(canvas); return surface.Snapshot(); } @@ -128,18 +128,18 @@ public virtual void SaveImage(string path, SKEncodedImageFormat format = SKEncod /// /// Draws the image to the specified canvas. /// - /// The surface. + /// The canvas. /// Indicates whether the canvas should be cleared when the draw starts, default is false. - public virtual void SaveImage(SKSurface surface, bool clearCanvasOnBeginDraw = false) => - DrawOnCanvas(surface, clearCanvasOnBeginDraw); + public virtual void SaveImage(SKCanvas canvas, bool clearCanvasOnBeginDraw = false) => + DrawOnCanvas(canvas, clearCanvasOnBeginDraw); /// /// Draws the chart to the specified canvas. /// - /// The surface. + /// The canvas. /// [probably an obsolete param] Indicates whether the canvas should be cleared when the draw starts, default is false. /// - public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw = false) + public virtual void DrawOnCanvas(SKCanvas canvas, bool clearCanvasOnBeginDraw = false) { if (CoreChart is null || CoreChart is not Chart skiaChart) throw new Exception("Something is missing :("); @@ -150,7 +150,7 @@ public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface.Canvas, + canvas, Background, clearCanvasOnBeginDraw)); @@ -169,7 +169,7 @@ public virtual void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw new SkiaSharpDrawingContext( CoreCanvas, new SKImageInfo(Width, Height), - surface.Canvas, + canvas, Background, clearCanvasOnBeginDraw)); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs index 898037ad1..4e75b8839 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/SKCharts/SKGeoMap.cs @@ -118,8 +118,8 @@ public object? ViewCommand } } - /// - public override void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw = false) + /// + public override void DrawOnCanvas(SKCanvas canvas, bool clearCanvasOnBeginDraw = false) { Canvas.DisableAnimations = true; @@ -129,7 +129,7 @@ public override void DrawOnCanvas(SKSurface surface, bool clearCanvasOnBeginDraw new SkiaSharpDrawingContext( Canvas, new SKImageInfo(Width, Height), - surface.Canvas, + canvas, Background, clearCanvasOnBeginDraw)); diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs index cd8f873dc..8e8dc9f69 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.cs @@ -42,8 +42,7 @@ public partial class MotionCanvas : Canvas /// public MotionCanvas() { -#if (__UNO_SKIA__ || DESKTOP) && !BROWSERWASM - // no wasm, see note #250727 +#if __UNO_SKIA__ || DESKTOP // then force the skiarendermode. _settings = new(new SkiaRenderMode()); #else diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs index 8b3f699df..2f1afe647 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs @@ -36,7 +36,7 @@ public partial class MotionCanvas : Canvas /// Gets the recommended rendering settings for Uno and WinUI. /// public static RenderingSettings RecommendedUnoRenderingSettings { get; } -#if (__UNO_SKIA__ || DESKTOP) && !BROWSERWASM +#if __UNO_SKIA__ || DESKTOP // --------------------------------- // if skia renderer // --------------------------------- @@ -52,34 +52,7 @@ public partial class MotionCanvas : Canvas LiveChartsRenderLoopFPS = 60, // make this true to see the FPS in the top left corner of the chart - ShowFPS = false - }; -#elif BROWSERWASM - // note #250727 - // --------------------------------- - // for a reason the browser wasm using the skia renderer - // just throws, at least on uno sdk 6.1.23 and skiasharp view 3.119.0 - // it seems that _visual field is missing in wasm builds, which is used by the - // Uno.WinUI.Graphics2DSK.SKCanvasElement. - // so for wasm, lets render the charts our way. - // Unhandled dispatcher exception: Error: Field not found: Microsoft.UI.Composition.ContainerVisual Microsoft.UI.Xaml.UIElement._visual Due to: Could not find field in class ( at LiveChartsCore.SkiaSharpView.WinUI.Rendering.SkiaRenderMode.SkiaRenderMode2.InvalidateRenderer() in /_/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs:line 89 - // at LiveChartsCore.SkiaSharpView.WinUI.Rendering.SkiaRenderMode.InvalidateRenderer() in /_/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs:line 62 - // at LiveChartsCore.Native.NativeFrameTicker.OnCompositonTargetRendering(Object sender, Object e) in /_/src/_Shared.Native/Platforms/WinUI/NativeTicker.cs:line 60 - // at Uno.UI.Dispatching.NativeDispatcher.DispatchItems() in C:\a\1\s\src\Uno.UI.Dispatching\Native\NativeDispatcher.cs:line 106 - // at Uno.UI.Dispatching.NativeDispatcher.DispatcherCallback() in C:\a\1\s\src\Uno.UI.Dispatching\Native\NativeDispatcher.wasm.cs:line 25 - // at Uno.UI.Dispatching.NativeDispatcher.__Wrapper_DispatcherCallback_1192908908(JSMarshalerArgument* __arguments_buffer) in C:\a\1\s\src\Uno.UI.Dispatching\obj\Uno.UI.Dispatching.Wasm\Release\net9.0\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JSExportGenerator\JSExports.g.cs:line 35 - // Error: Field not found: Microsoft.UI.Composition.ContainerVisual Microsoft.UI.Xaml.UIElement._visual Due to: Could not find field in class - // --------------------------------- - = new() - { - // try use gl - UseGPU = true, - // connect to the browser's requestAnimationFrame via uno? - TryUseVSync = true, - // fallback value when VSync is not used. - LiveChartsRenderLoopFPS = 20, - // make this true to see the FPS in the top left corner of the chart - ShowFPS = false + ShowFPS = true }; #elif WINDOWS // --------------------------------- @@ -116,11 +89,31 @@ public partial class MotionCanvas : Canvas LiveChartsRenderLoopFPS = 60, // make this true to see the FPS in the top left corner of the chart - ShowFPS = false + ShowFPS = true + }; +#else + // --------------------------------- + // fallback settings + // --------------------------------- + = new() + { + // ignored, defined by uno + UseGPU = false, + + // ignored, defined by uno + TryUseVSync = false, + + // fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = true }; #endif +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. static MotionCanvas() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. { LiveCharts.Configure(config => config.UseDefaults(RecommendedUnoRenderingSettings)); } diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs index 90349f290..d67c9b404 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -40,9 +40,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; -#if DEBUG - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); -#endif + CoreMotionCanvas.s_rendererName = nameof(CPURenderMode); } public void DisposeRenderMode() diff --git a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs index a9ab81da5..a46cd52f5 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs @@ -40,9 +40,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; -#if DEBUG - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); -#endif + CoreMotionCanvas.s_rendererName = nameof(CPURenderMode); } public void DisposeRenderMode() diff --git a/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs index df3344ebd..42b2a5154 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#if __UNO_SKIA__ || DESKTOP || BROWSERWASM +#if __UNO_SKIA__ || DESKTOP using System.Diagnostics; using LiveChartsCore.Drawing; @@ -74,9 +74,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) IsHitTestVisible = true; _canvas = canvas; -#if DEBUG - Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(SkiaRenderMode)}."); -#endif + CoreMotionCanvas.s_externalRenderer = nameof(SkiaRenderMode); } private void SkiaRenderMode_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) => From ea1649138570de238b05882262a5505814948d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 28 Jul 2025 12:55:46 -0600 Subject: [PATCH 85/94] add uno net 9 targets --- .../LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index 0fa365fa4..c782b09ed 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -9,12 +9,17 @@ net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst; - net9.0-desktop;net9.0-browserwasm; + net9.0;net9.0-android;net9.0-ios;net9.0-browserwasm;net9.0-desktop + + + + $(TargetFrameworks); - net8.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0; + net9.0-windows10.0.26100.0; true From 079b29530d10663fe839893f83bae86af348fd5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 28 Jul 2025 16:13:18 -0600 Subject: [PATCH 86/94] uno settings --- Directory.Build.props | 18 +---- LiveCharts.slnx | 5 ++ build/RenderSettings.Build.props | 43 +++++++++++ build/RenderSettings.Uno.Build.props | 11 +++ .../UnoPlatformSample.csproj | 73 +++++++++++-------- .../Samples/Pies/Icons/CustomPieSeries.cs | 4 +- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 46 ++++++------ .../Drawing/SkiaSharpDrawingContext.cs | 15 +++- .../LiveChartsCore.SkiaSharpView.csproj | 2 + .../LiveChartsSkiaSharp.cs | 43 ++++++++++- ...eChartsCore.SkiaSharpView.Uno.WinUI.csproj | 6 +- .../_Shared.WinUI/MotionCanvas.settings.cs | 34 ++++++--- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 2 +- .../_Shared.WinUI/Rendering/GPURenderMode.cs | 2 +- .../_Shared.WinUI/Rendering/SkiaRenderMode.cs | 2 +- 15 files changed, 210 insertions(+), 96 deletions(-) create mode 100644 build/RenderSettings.Build.props create mode 100644 build/RenderSettings.Uno.Build.props diff --git a/Directory.Build.props b/Directory.Build.props index 2d0e259d5..4966bde02 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,22 +10,6 @@ 2.88.9 3.119.0 - - Material; - Dsp; - Hosting; - Toolkit; - Logging; - MVUX; - Configuration; - HttpKiota; - Serialization; - Localization; - Navigation; - ThemeService; - SkiaRenderer; - - $(DefineConstants);HAS_UI_LVC - + diff --git a/LiveCharts.slnx b/LiveCharts.slnx index 0e667ebd2..57283f55b 100644 --- a/LiveCharts.slnx +++ b/LiveCharts.slnx @@ -83,6 +83,7 @@ + @@ -116,6 +117,10 @@ + + + + diff --git a/build/RenderSettings.Build.props b/build/RenderSettings.Build.props new file mode 100644 index 000000000..01b9099a5 --- /dev/null +++ b/build/RenderSettings.Build.props @@ -0,0 +1,43 @@ + + + + $(DefineConstants);__GPU_ENABLED__ + + + + $(DefineConstants);__VSYNC_ENABLED__ + + + + $(DefineConstants);__FPS_10__ + + + + $(DefineConstants);__FPS_20__ + + + + $(DefineConstants);__FPS_30__ + + + + $(DefineConstants);__FPS_45__ + + + + $(DefineConstants);__FPS_60__ + + + + $(DefineConstants);__FPS_75__ + + + + $(DefineConstants);__FPS_90__ + + + + $(DefineConstants);__FPS_120__ + + + diff --git a/build/RenderSettings.Uno.Build.props b/build/RenderSettings.Uno.Build.props new file mode 100644 index 000000000..55f906b47 --- /dev/null +++ b/build/RenderSettings.Uno.Build.props @@ -0,0 +1,11 @@ + + + + true + + + + $(UnoFeatures);SkiaRenderer + + + diff --git a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj index 7db69548f..713bbd26d 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj +++ b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj @@ -1,37 +1,46 @@  - - net9.0-android;net9.0-ios;net9.0-windows10.0.26100;net9.0-browserwasm;net9.0-desktop - - Exe - true - - - UnoPlatformSample - - com.companyname.UnoPlatformSample - - 1.0 - 1 - - btord - - UnoPlatformSample powered by Uno Platform. - - - - - - $(UnoFeatures) + net9.0-android;net9.0-ios;net9.0-windows10.0.26100;net9.0-browserwasm;net9.0-desktop + + Exe + true + + + UnoPlatformSample + + com.companyname.UnoPlatformSample + + 1.0 + 1 + + btord + + UnoPlatformSample powered by Uno Platform. + + + + + Material; + Dsp; + Hosting; + Toolkit; + Logging; + MVUX; + Configuration; + HttpKiota; + Serialization; + Localization; + Navigation; + ThemeService; + + + + LiveChartsSamples\%(RecursiveDir)%(Filename)%(Extension) @@ -43,8 +52,8 @@ - - + + diff --git a/samples/WinUISample/WinUISample/Samples/Pies/Icons/CustomPieSeries.cs b/samples/WinUISample/WinUISample/Samples/Pies/Icons/CustomPieSeries.cs index 819bcea22..27acfd627 100644 --- a/samples/WinUISample/WinUISample/Samples/Pies/Icons/CustomPieSeries.cs +++ b/samples/WinUISample/WinUISample/Samples/Pies/Icons/CustomPieSeries.cs @@ -1,12 +1,10 @@ using Microsoft.UI.Xaml; -using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Drawing.Geometries; using LiveChartsCore.SkiaSharpView.WinUI; -using ViewModelsSamples.Pies.Icons; namespace WinUISample.Pies.Icons; -public class CustomPieSeries : XamlPieSeries +public partial class CustomPieSeries : XamlPieSeries { public CustomPieSeries() { diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 383694dc5..85a495b3c 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -42,7 +42,9 @@ public class CoreMotionCanvas : IDisposable private int _jitteredDrawCount; private double _totalDrawTime = 0; private double _lastDrawTime = 0; - private double _totalFrames = 0; + private double _lastKnownFPS = 0; + private double _averageRenderTime = 0; + private double _totalFrames = double.Epsilon; private double _totalSeconds = 0; internal TimeSpan _nextFrameDelay = s_baseFrameDelay; private static readonly double s_ticksPerMillisecond = Stopwatch.Frequency / 1000d; @@ -191,34 +193,31 @@ public void DrawFrame(TDrawingContext context) { MeasureFPS(drawStartTime); - if (_totalSeconds > 0) - { - var sb = new StringBuilder(); + var sb = new StringBuilder(); #if DEBUG - sb.Append($"[~~ {_totalFrames / _totalSeconds:N2} ~~] FPS (DEBUG)"); + sb.Append($"[~~ {_lastKnownFPS:N2} ~~] FPS (DEBUG)"); #else - sb.Append($"[ {_totalFrames / _totalSeconds:N2} ] FPS"); + sb.Append($"[ {_lastKnownFPS:N2} ] FPS"); #endif - sb.Append($"`[ {_lastDrawTime:N2}ms / {_totalDrawTime / _totalFrames:N2}ms ] render time last / avrg"); + sb.Append($"`[ {_lastDrawTime:N2}ms / {_averageRenderTime:N2}ms ] render time last / avrg"); - if (s_externalRenderer is null) - { - sb.Append($"`[ {(LiveCharts.RenderingSettings.UseGPU ? "GPU" : "CPU")} ] via {s_rendererName}"); - var isVSynced = LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync; - sb.Append($"`[ {(isVSynced ? "VSync" : "VSync disabled")} ] handled by {s_tickerName}"); - } - else - { - sb.Append($"`{s_externalRenderer} handling GPU / VSync"); - } + if (s_externalRenderer is null) + { + sb.Append($"`[ {(LiveCharts.RenderingSettings.UseGPU ? "GPU" : "CPU")} ] via {s_rendererName}"); + var isVSynced = LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync; + sb.Append($"`[ {(isVSynced ? "VSync" : "VSync disabled")} ] handled by {s_tickerName}"); + } + else + { + sb.Append($"`{s_externalRenderer} handling GPU / VSync"); + } - if (_jitteredDrawCount > 0) - sb.Append( - $"`[ {_jitteredDrawCount} ] jittered draws"); + if (_jitteredDrawCount > 0) + sb.Append( + $"`[ {_jitteredDrawCount} ] jittered draws"); - context.LogOnCanvas(sb.ToString()); - } + context.LogOnCanvas(sb.ToString()); } IsValid = isValid; @@ -236,7 +235,6 @@ public void DrawFrame(TDrawingContext context) _frames = 0; _fspSw = null; _totalDrawTime = 0; - _lastDrawTime = 0; _totalFrames = 0; _totalSeconds = 0; } @@ -383,6 +381,8 @@ private void MeasureFPS(long drawStartTime) _totalFrames += logEach; _totalSeconds += elapsedSeconds; + _lastKnownFPS = _totalFrames / _totalSeconds; + _averageRenderTime = _totalDrawTime / _totalFrames; // it not exactly the last frame time, is the 20th frame time // so we can actually read the time in the log. diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs index e4a9aec07..98fa99045 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/SkiaSharpDrawingContext.cs @@ -115,21 +115,28 @@ public override void LogOnCanvas(string log) { using var p = new SKPaint { - Color = SKColors.Blue, + Color = SKColors.White, TextSize = 14, - IsAntialias = true, - FakeBoldText = true + IsAntialias = true + }; + + using var backgroundPaint = new SKPaint + { + Color = SKColors.Black.WithAlpha(180), + Style = SKPaintStyle.Fill }; var lines = log.Split('`'); + Canvas.DrawRect(new(10, 0, 400, (p.TextSize + 4f) * lines.Length), backgroundPaint); + for (var i = 0; i < lines.Length; i++) { var line = lines[i]; if (string.IsNullOrWhiteSpace(line)) continue; Canvas.DrawText( line, - new SKPoint(50, 10 + p.TextSize * i), + new SKPoint(10, 10 + 2 + (p.TextSize + 4f) * i), p); } } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj index 776957fdf..0ab834c12 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj @@ -42,6 +42,8 @@ true + + false diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index f4c007b19..f3a95adb5 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -62,7 +62,48 @@ public static LiveChartsSettings UseDefaults( _ = settings.AddDefaultMappers(); if (LiveCharts.RenderingSettings is null) - _ = settings.RenderingSettings(renderingSettings ?? RenderingSettings.Default); + { + var targetRenderSettings = renderingSettings ?? RenderingSettings.Default; + + // the next conditions are used to test the rendering settings across + // multiple os/frameworks via cli flags. + +#if __GPU_ENABLED__ + targetRenderSettings.UseGPU = true; +#endif +#if __VSYNC_ENABLED__ + targetRenderSettings.TryUseVSync = true; +#endif +#if __FPS_10__ + targetRenderSettings.LiveChartsRenderLoopFPS = 10; +#endif +#if __FPS_20__ + targetRenderSettings.LiveChartsRenderLoopFPS = 20; +#endif +#if __FPS_30__ + targetRenderSettings.LiveChartsRenderLoopFPS = 30; +#endif +#if __FPS_45__ + targetRenderSettings.LiveChartsRenderLoopFPS = 45; +#endif +#if __FPS_60__ + targetRenderSettings.LiveChartsRenderLoopFPS = 60; +#endif +#if __FPS_75__ + targetRenderSettings.LiveChartsRenderLoopFPS = 75; +#endif +#if __FPS_90__ + targetRenderSettings.LiveChartsRenderLoopFPS = 90; +#endif +#if __FPS_120__ + targetRenderSettings.LiveChartsRenderLoopFPS = 120; +#endif +#if __GPU_ENABLED__ || __VSYNC_ENABLED__ || __FPS_10__ || __FPS_20__ || __FPS_30__ || __FPS_45__ || __FPS_60__ || __FPS_75__ || __FPS_90__ || __FPS_120__ + targetRenderSettings.ShowFPS = true; +#endif + + _ = settings.RenderingSettings(targetRenderSettings); + } return settings; } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj index c782b09ed..fbadfe780 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj @@ -12,10 +12,6 @@ net9.0;net9.0-android;net9.0-ios;net9.0-browserwasm;net9.0-desktop - - - - $(TargetFrameworks); net8.0-windows10.0.19041.0; @@ -49,6 +45,8 @@ true + + diff --git a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs index 2f1afe647..aa5d3ef6d 100644 --- a/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs +++ b/src/skiasharp/_Shared.WinUI/MotionCanvas.settings.cs @@ -42,17 +42,15 @@ public partial class MotionCanvas : Canvas // --------------------------------- = new() { - // ignored, defined by uno + // both ignored, defined by uno skia renderer UseGPU = true, - - // ignored, defined by uno TryUseVSync = true, // fallback value when VSync is not used. LiveChartsRenderLoopFPS = 60, // make this true to see the FPS in the top left corner of the chart - ShowFPS = true + ShowFPS = false }; #elif WINDOWS // --------------------------------- @@ -79,21 +77,39 @@ public partial class MotionCanvas : Canvas // --------------------------------- = new() { - // ignored, defined by uno + // use hardware acceleration UseGPU = true, - // ignored, defined by uno + // via Choreographer + TryUseVSync = true, + + // fallback value when VSync is not used. + LiveChartsRenderLoopFPS = 60, + + // make this true to see the FPS in the top left corner of the chart + ShowFPS = false + }; +#elif (IOS || MACCATALYST) && !__UNO_SKIA__ + // --------------------------------- + // if ios/catalyst without skia renderer + // --------------------------------- + = new() + { + // use hardware acceleration + UseGPU = true, + + // via CADisplayLink TryUseVSync = true, // fallback value when VSync is not used. LiveChartsRenderLoopFPS = 60, // make this true to see the FPS in the top left corner of the chart - ShowFPS = true + ShowFPS = false }; #else // --------------------------------- - // fallback settings + // fallback settings, probably only reached on wasm without skia renderer // --------------------------------- = new() { @@ -107,7 +123,7 @@ public partial class MotionCanvas : Canvas LiveChartsRenderLoopFPS = 60, // make this true to see the FPS in the top left corner of the chart - ShowFPS = true + ShowFPS = false }; #endif diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs index d67c9b404..d59be5359 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -40,7 +40,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; - CoreMotionCanvas.s_rendererName = nameof(CPURenderMode); + CoreMotionCanvas.s_rendererName = $"{nameof(CPURenderMode)} and {nameof(SKXamlCanvas)}"; ; } public void DisposeRenderMode() diff --git a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs index a46cd52f5..edb7a73c7 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/GPURenderMode.cs @@ -40,7 +40,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; - CoreMotionCanvas.s_rendererName = nameof(CPURenderMode); + CoreMotionCanvas.s_rendererName = $"{nameof(GPURenderMode)} and {nameof(SKSwapChainPanel)}"; } public void DisposeRenderMode() diff --git a/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs index 42b2a5154..b9ddc60db 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs @@ -74,7 +74,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) IsHitTestVisible = true; _canvas = canvas; - CoreMotionCanvas.s_externalRenderer = nameof(SkiaRenderMode); + CoreMotionCanvas.s_externalRenderer = $"{nameof(SkiaRenderMode)} via {nameof(SKCanvasElement)}"; } private void SkiaRenderMode_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) => From 7b3a13127c6e2a2f2a8cb958691d26f2deb31cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 28 Jul 2025 23:28:37 -0600 Subject: [PATCH 87/94] consume source generators from nuget --- src/LiveChartsCore/LiveChartsCore.csproj | 7 +++++-- .../LiveChartsCore.SkiaSharpView.WPF.csproj | 8 +++++++- .../LiveChartsCore.SkiaSharpView.WinForms.csproj | 8 +++++++- .../LiveChartsCore.SkiaSharpView.csproj | 7 +++++++ .../LiveChartsCore.SkiaSharpView.Blazor.csproj | 8 +++++++- .../LiveChartsCore.SkiaSharpView.Eto.csproj | 8 +++++++- .../LiveChartsCore.SkiaSharpView.WinUI.csproj | 8 +++++++- 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/LiveChartsCore/LiveChartsCore.csproj b/src/LiveChartsCore/LiveChartsCore.csproj index a8a17adc0..405c68681 100644 --- a/src/LiveChartsCore/LiveChartsCore.csproj +++ b/src/LiveChartsCore/LiveChartsCore.csproj @@ -70,9 +70,12 @@ - + - + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj index 8dd70d50f..a7e8d833b 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/LiveChartsCore.SkiaSharpView.WPF.csproj @@ -49,7 +49,13 @@ - + + + + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj index 3ca18f035..8a6d1e8e0 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj @@ -52,7 +52,13 @@ - + + + + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj index 0ab834c12..b4103aef6 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj @@ -64,6 +64,13 @@ + + + all + analyzers + + + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj index ea5e25df3..1e072ee84 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/LiveChartsCore.SkiaSharpView.Blazor.csproj @@ -54,7 +54,13 @@ - + + + + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj index 4c146011d..b884204cd 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj @@ -47,7 +47,13 @@ - + + + + + all + analyzers + diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj index 490399d55..7d0166007 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj @@ -67,7 +67,13 @@ - + + + + + all + analyzers + From 405c6924a3ffdb78ce50e1c25cfb74778b085f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Mon, 28 Jul 2025 23:30:32 -0600 Subject: [PATCH 88/94] enables msbuild to control render mode this makes it easier to test the library on different modes --- Directory.Build.props | 14 +++++++- build/RenderSettings.Build.props | 16 +++++++-- build/RenderSettings.Uno.Build.props | 9 ++--- build/pack.ps1 | 2 +- .../UnoPlatformSample.csproj | 8 +++++ src/LiveChartsCore/Motion/AsyncLoopTicker.cs | 5 --- src/LiveChartsCore/Motion/CoreMotionCanvas.cs | 5 +-- .../MotionCanvas.cs | 2 +- .../Rendering/CPURenderMode.cs | 4 +-- .../Rendering/CompositionTargetTicker.cs | 2 ++ .../Rendering/GPURenderMode.cs | 4 +-- .../LiveChartsCore.SkiaSharpView.csproj | 5 ++- .../LiveChartsSkiaSharp.cs | 34 +++++++++++-------- .../MotionCanvas.razor.cs | 7 ++-- .../Rendering/CPURenderMode.cs | 4 +-- .../Rendering/GPURenderMode.cs | 4 +-- .../_Shared.WinUI/Rendering/CPURenderMode.cs | 2 +- 17 files changed, 78 insertions(+), 49 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4966bde02..534c8ac6a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,11 +7,23 @@ preview 8.0.82 - 2.88.9 + 2.88.9 3.119.0 + + + true + true + 30 + true + true + + - $(DefineConstants);__GPU_ENABLED__ + $(DefineConstants);__GPU_TRUE__ + + + $(DefineConstants);__GPU_FALSE__ - $(DefineConstants);__VSYNC_ENABLED__ + $(DefineConstants);__VSYNC_TRUE__ + + + $(DefineConstants);__VSYNC_FALSE__ + + + + $(DefineConstants);__DIAGNOSE__ diff --git a/build/RenderSettings.Uno.Build.props b/build/RenderSettings.Uno.Build.props index 55f906b47..9eda236ac 100644 --- a/build/RenderSettings.Uno.Build.props +++ b/build/RenderSettings.Uno.Build.props @@ -1,11 +1,12 @@ - - true + + $(UnoFeatures);SkiaRenderer + $(DefineConstants);__USES_UNO_RENDERER__ - - $(UnoFeatures);SkiaRenderer + + $(DefineConstants);__NO_USES_UNO_RENDERER__ diff --git a/build/pack.ps1 b/build/pack.ps1 index 039d3146e..1aa5315fa 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -47,7 +47,7 @@ foreach ($p in $projects) { Remove-Item $($folder + "/bin") -Force -Recurse } - $expression = "dotnet pack $($p.src) -o $nupkgOutputPath -c $configuration -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg" + $expression = "dotnet pack $($p.src) -o $nupkgOutputPath -c $configuration -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -p:IsPacking=true" Write-Progress -Activity "$name" -Status "Packing..." $result = Invoke-Expression $expression diff --git a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj index 713bbd26d..1e787aca6 100644 --- a/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj +++ b/samples/UnoPlatformSample/UnoPlatformSample/UnoPlatformSample.csproj @@ -37,8 +37,16 @@ ThemeService; + + + + + diff --git a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs index 582c4093e..d824aac02 100644 --- a/src/LiveChartsCore/Motion/AsyncLoopTicker.cs +++ b/src/LiveChartsCore/Motion/AsyncLoopTicker.cs @@ -37,11 +37,6 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _canvas.Invalidated += OnCoreInvalidated; CoreMotionCanvas.s_tickerName = nameof(AsyncLoopTicker); - -#if DEBUG - System.Diagnostics.Trace.WriteLine( - "[LiveCharts Info] FrameSync: LiveCharts internal loop (no platform ticker)"); -#endif } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs index 85a495b3c..c2c09ff55 100644 --- a/src/LiveChartsCore/Motion/CoreMotionCanvas.cs +++ b/src/LiveChartsCore/Motion/CoreMotionCanvas.cs @@ -204,13 +204,14 @@ public void DrawFrame(TDrawingContext context) if (s_externalRenderer is null) { - sb.Append($"`[ {(LiveCharts.RenderingSettings.UseGPU ? "GPU" : "CPU")} ] via {s_rendererName}"); + sb.Append($"`{s_rendererName}"); var isVSynced = LiveCharts.RenderingSettings.UseGPU && LiveCharts.RenderingSettings.TryUseVSync; sb.Append($"`[ {(isVSynced ? "VSync" : "VSync disabled")} ] handled by {s_tickerName}"); } else { - sb.Append($"`{s_externalRenderer} handling GPU / VSync"); + sb.Append($"`{s_externalRenderer}"); + sb.Append($"`{s_tickerName}"); } if (_jitteredDrawCount > 0) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs index 395171992..cae879ca5 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/MotionCanvas.cs @@ -73,7 +73,7 @@ static MotionCanvas() /// public MotionCanvas() { - CoreMotionCanvas.s_externalRenderer = "Avalonia"; + CoreMotionCanvas.s_externalRenderer = "Avalonia renderer"; AttachedToVisualTree += OnAttached; DetachedFromVisualTree += OnDetached; } diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs index 67cf3715c..9e0e274e8 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CPURenderMode.cs @@ -42,9 +42,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; -#if DEBUG - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); -#endif + CoreMotionCanvas.s_rendererName = $"{nameof(CPURenderMode)} and {nameof(SKElement)}"; } public void DisposeRenderMode() diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs index e6ace58ea..33ea59b05 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/CompositionTargetTicker.cs @@ -38,6 +38,8 @@ public void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) _canvas.Invalidated += OnCoreInvalidated; CompositionTarget.Rendering += OnCompositonTargetRendering; + + CoreMotionCanvas.s_tickerName = $"{nameof(CompositionTarget)}"; } private void OnCoreInvalidated(CoreMotionCanvas obj) => diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs index eb0ecae9e..cdff7820d 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.WPF/Rendering/GPURenderMode.cs @@ -42,9 +42,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; -#if DEBUG - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); -#endif + CoreMotionCanvas.s_rendererName = $"{nameof(GPURenderMode)} and {nameof(SKGLElement)}"; } public void DisposeRenderMode() diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj index b4103aef6..869c67f78 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj @@ -55,12 +55,11 @@ - - + + - diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs index f3a95adb5..a152dcce4 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsSkiaSharp.cs @@ -68,38 +68,44 @@ public static LiveChartsSettings UseDefaults( // the next conditions are used to test the rendering settings across // multiple os/frameworks via cli flags. -#if __GPU_ENABLED__ - targetRenderSettings.UseGPU = true; +#if __GPU_TRUE__ + targetRenderSettings.UseGPU = true; #endif -#if __VSYNC_ENABLED__ - targetRenderSettings.TryUseVSync = true; +#if __GPU_FALSE__ + targetRenderSettings.UseGPU = false; +#endif +#if __VSYNC_TRUE__ + targetRenderSettings.TryUseVSync = true; +#endif +#if __VSYNC_FALSE__ + targetRenderSettings.TryUseVSync = false; #endif #if __FPS_10__ - targetRenderSettings.LiveChartsRenderLoopFPS = 10; + targetRenderSettings.LiveChartsRenderLoopFPS = 10; #endif #if __FPS_20__ - targetRenderSettings.LiveChartsRenderLoopFPS = 20; + targetRenderSettings.LiveChartsRenderLoopFPS = 20; #endif #if __FPS_30__ - targetRenderSettings.LiveChartsRenderLoopFPS = 30; + targetRenderSettings.LiveChartsRenderLoopFPS = 30; #endif #if __FPS_45__ - targetRenderSettings.LiveChartsRenderLoopFPS = 45; + targetRenderSettings.LiveChartsRenderLoopFPS = 45; #endif #if __FPS_60__ - targetRenderSettings.LiveChartsRenderLoopFPS = 60; + targetRenderSettings.LiveChartsRenderLoopFPS = 60; #endif #if __FPS_75__ - targetRenderSettings.LiveChartsRenderLoopFPS = 75; + targetRenderSettings.LiveChartsRenderLoopFPS = 75; #endif #if __FPS_90__ - targetRenderSettings.LiveChartsRenderLoopFPS = 90; + targetRenderSettings.LiveChartsRenderLoopFPS = 90; #endif #if __FPS_120__ - targetRenderSettings.LiveChartsRenderLoopFPS = 120; + targetRenderSettings.LiveChartsRenderLoopFPS = 120; #endif -#if __GPU_ENABLED__ || __VSYNC_ENABLED__ || __FPS_10__ || __FPS_20__ || __FPS_30__ || __FPS_45__ || __FPS_60__ || __FPS_75__ || __FPS_90__ || __FPS_120__ - targetRenderSettings.ShowFPS = true; +#if __DIAGNOSE__ + targetRenderSettings.ShowFPS = true; #endif _ = settings.RenderingSettings(targetRenderSettings); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs index 03155dd5e..383a4a3dd 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Blazor/MotionCanvas.razor.cs @@ -194,8 +194,11 @@ internal class RequestAnimationFrameTicker( DomJsInterop jsInterop, DotNetObjectReference dotnetRef) : IFrameTicker { - public async void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) => - await jsInterop.StartFrameTicker(dotnetRef); + public async void InitializeTicker(CoreMotionCanvas canvas, IRenderMode renderMode) + { + await jsInterop.StartFrameTicker(dotnetRef); + CoreMotionCanvas.s_tickerName = $"{nameof(RequestAnimationFrameTicker)}"; + } public async void DisposeTicker() => await jsInterop.StopFrameTicker(dotnetRef); diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs index cb563df5a..7fe1d7f85 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/CPURenderMode.cs @@ -46,9 +46,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; -#if DEBUG - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(CPURenderMode)}."); -#endif + CoreMotionCanvas.s_rendererName = $"{nameof(CPURenderMode)} and {nameof(SKCanvasView)}"; } public void DisposeRenderMode() diff --git a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs index 2264a061e..3f19ea991 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/Rendering/GPURenderMode.cs @@ -46,9 +46,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _pixelDensity = (float)DeviceDisplay.MainDisplayInfo.Density; DeviceDisplay.MainDisplayInfoChanged += MainDisplayInfoChanged; -#if DEBUG - System.Diagnostics.Trace.WriteLine($"[LiveCharts Info] LiveCharts is using {nameof(GPURenderMode)}."); -#endif + CoreMotionCanvas.s_rendererName = $"{nameof(GPURenderMode)} and {nameof(SKGLView)}"; } public void DisposeRenderMode() diff --git a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs index d59be5359..c37ecfea2 100644 --- a/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs +++ b/src/skiasharp/_Shared.WinUI/Rendering/CPURenderMode.cs @@ -40,7 +40,7 @@ public void InitializeRenderMode(CoreMotionCanvas canvas) _canvas = canvas; PaintSurface += OnPaintSurface; - CoreMotionCanvas.s_rendererName = $"{nameof(CPURenderMode)} and {nameof(SKXamlCanvas)}"; ; + CoreMotionCanvas.s_rendererName = $"{nameof(CPURenderMode)} and {nameof(SKXamlCanvas)}"; } public void DisposeRenderMode() From 8b70c8fae167fcc59223a980e73c3820a4f9f16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 29 Jul 2025 18:06:24 -0600 Subject: [PATCH 89/94] uno skia renderer zoom by gesture --- src/LiveChartsCore/Chart.cs | 2 +- .../Platforms/WinUI/PointerController.cs | 33 +++++++++++++++++++ src/skiasharp/_Shared.WinUI/CartesianChart.cs | 10 ++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/LiveChartsCore/Chart.cs b/src/LiveChartsCore/Chart.cs index a44960d14..40ded6c1f 100644 --- a/src/LiveChartsCore/Chart.cs +++ b/src/LiveChartsCore/Chart.cs @@ -57,7 +57,7 @@ public abstract class Chart private readonly ActionThrottler _panningThrottler; private LvcPoint _pointerPanningPosition = new(-10, -10); private LvcPoint _pointerPreviousPanningPosition = new(-10, -10); - private bool _isPanning = false; + internal bool _isPanning = false; private readonly HashSet _activePoints = []; private LvcSize _previousSize = new(); private int _nextSeriesId = 0; diff --git a/src/_Shared.Native/Platforms/WinUI/PointerController.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs index 3fb4478a5..aae2e9546 100644 --- a/src/_Shared.Native/Platforms/WinUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -24,12 +24,16 @@ // reachable on winui, maui winui, uno winui and uno with skia renderer +using System; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; namespace LiveChartsCore.Native; internal partial class PointerController : INativePointerController { + private bool _isPinching; + private DateTime _pressedTime; + public void InitializeController(object view) { var winUIView = (UIElement)view; @@ -39,6 +43,11 @@ public void InitializeController(object view) winUIView.PointerReleased += OnWindowsPointerReleased; winUIView.PointerWheelChanged += OnWindowsPointerWheelChanged; winUIView.PointerExited += OnWindowsPointerExited; + + winUIView.ManipulationMode = ManipulationModes.Scale; + winUIView.ManipulationStarted += OnPinchSarted; + winUIView.ManipulationDelta += OnPinching; + winUIView.ManipulationCompleted += OnPinchCompleted; } public void DisposeController(object view) @@ -50,6 +59,10 @@ public void DisposeController(object view) winUIView.PointerReleased -= OnWindowsPointerReleased; winUIView.PointerWheelChanged -= OnWindowsPointerWheelChanged; winUIView.PointerExited -= OnWindowsPointerExited; + + winUIView.ManipulationStarted -= OnPinchSarted; + winUIView.ManipulationDelta -= OnPinching; + winUIView.ManipulationCompleted -= OnPinchCompleted; } private void OnWindowsPointerPressed(object sender, PointerRoutedEventArgs e) @@ -59,15 +72,22 @@ private void OnWindowsPointerPressed(object sender, PointerRoutedEventArgs e) var p = e.GetCurrentPoint(element); if (p is null) return; +#if DESKTOP _ = element.CapturePointer(e.Pointer); +#endif Pressed?.Invoke( sender, new(new(p.Position.X, p.Position.Y), p.Properties.IsRightButtonPressed, e)); + + _pressedTime = DateTime.Now; } private void OnWindowsPointerMoved(object sender, PointerRoutedEventArgs e) { + // wait 100ms to ensure it is not a pinch gesture. + if (_isPinching || ((DateTime.Now - _pressedTime).TotalMilliseconds < 100)) return; + var p = e.GetCurrentPoint(sender as UIElement); if (p is null) return; @@ -83,7 +103,9 @@ private void OnWindowsPointerReleased(object sender, PointerRoutedEventArgs e) var p = e.GetCurrentPoint(element); if (p is null) return; +#if DESKTOP element.ReleasePointerCapture(element.PointerCaptures[0]); +#endif Released?.Invoke( sender, @@ -99,7 +121,18 @@ private void OnWindowsPointerWheelChanged(object sender, PointerRoutedEventArgs private void OnWindowsPointerExited(object sender, PointerRoutedEventArgs e) => Exited?.Invoke(sender, new(e)); + private void OnPinchSarted(object sender, ManipulationStartedRoutedEventArgs e) => + _isPinching = true; + + private void OnPinching(object sender, ManipulationDeltaRoutedEventArgs e) + { + var element = (UIElement)sender; + + Pinched?.Invoke(sender, new(e.Delta.Scale, new(e.Position.X, e.Position.Y), e)); + } + private void OnPinchCompleted(object sender, ManipulationCompletedRoutedEventArgs e) => + _isPinching = false; } #endif diff --git a/src/skiasharp/_Shared.WinUI/CartesianChart.cs b/src/skiasharp/_Shared.WinUI/CartesianChart.cs index c28370be8..e05410cc3 100644 --- a/src/skiasharp/_Shared.WinUI/CartesianChart.cs +++ b/src/skiasharp/_Shared.WinUI/CartesianChart.cs @@ -47,8 +47,14 @@ internal override void OnPinched(object? sender, PinchEventArgs args) { var c = (CartesianChartEngine)CoreChart; var p = args.PinchStart; - var s = c.ControlSize; - var pivot = new LvcPoint((float)(p.X * s.Width), (float)(p.Y * s.Height)); + var pivot = new LvcPoint(p.X, p.Y); c.Zoom(pivot, ZoomDirection.DefinedByScaleFactor, args.Scale, true); + + // hack: + // when the pinch started, the isPanning property is set to true, + // when the pinch is completed, the pointerUp will be called, + // and within that method panning will occur, lets prevent that + // by setting isPanning to false here. + c._isPanning = false; } } From a1f6bacf02bda06339503fa953139a791e76781e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 29 Jul 2025 18:51:51 -0600 Subject: [PATCH 90/94] uno secondryClick on touble tap --- src/LiveChartsCore/CartesianChartEngine.cs | 6 ++++++ src/skiasharp/_Shared.WinUI/CartesianChart.cs | 2 +- src/skiasharp/_Shared.WinUI/ChartControl.cs | 9 +++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/LiveChartsCore/CartesianChartEngine.cs b/src/LiveChartsCore/CartesianChartEngine.cs index 7cfbb62e3..c2a67d9c0 100644 --- a/src/LiveChartsCore/CartesianChartEngine.cs +++ b/src/LiveChartsCore/CartesianChartEngine.cs @@ -1137,6 +1137,12 @@ protected internal override void InvokePointerLeft() } } + internal void ClearPointerDown() + { + _isPanning = false; + _sectionZoomingStart = null; + } + internal void SubscribeSharedEvents(HashSet instance) { // An experimental feature, it allows a chart to propagate some events to other charts, diff --git a/src/skiasharp/_Shared.WinUI/CartesianChart.cs b/src/skiasharp/_Shared.WinUI/CartesianChart.cs index e05410cc3..439582e69 100644 --- a/src/skiasharp/_Shared.WinUI/CartesianChart.cs +++ b/src/skiasharp/_Shared.WinUI/CartesianChart.cs @@ -55,6 +55,6 @@ internal override void OnPinched(object? sender, PinchEventArgs args) // when the pinch is completed, the pointerUp will be called, // and within that method panning will occur, lets prevent that // by setting isPanning to false here. - c._isPanning = false; + c.ClearPointerDown(); } } diff --git a/src/skiasharp/_Shared.WinUI/ChartControl.cs b/src/skiasharp/_Shared.WinUI/ChartControl.cs index cadd54ddf..89ad243ba 100644 --- a/src/skiasharp/_Shared.WinUI/ChartControl.cs +++ b/src/skiasharp/_Shared.WinUI/ChartControl.cs @@ -42,6 +42,7 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// public abstract partial class ChartControl : UserControl, IChartView { + private DateTime _lastPresed; private readonly ThemeListener _themeListener; private readonly PointerController _pointerController; private static readonly bool s_isWebAssembly = RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); @@ -120,14 +121,14 @@ private void RemoveUIElement(object item) private void OnPressed(object? sender, Native.Events.PressedEventArgs args) { - // is this working on all platforms? - //if (args.KeyModifiers > 0) return; - var cArgs = new PointerCommandArgs(this, new(args.Location.X, args.Location.Y), args); if (PointerPressedCommand?.CanExecute(cArgs) == true) PointerPressedCommand.Execute(cArgs); - CoreChart?.InvokePointerDown(args.Location, args.IsSecondaryPress); + var isSecondary = (DateTime.Now - _lastPresed).TotalMilliseconds < 500; + + CoreChart?.InvokePointerDown(args.Location, args.IsSecondaryPress || isSecondary); + _lastPresed = DateTime.Now; } private void OnMoved(object? sender, Native.Events.ScreenEventArgs args) From dcd428a697f228c509613364407cbb1d240a53d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 29 Jul 2025 18:52:12 -0600 Subject: [PATCH 91/94] improve touch zooming in avalonia --- .../LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.cs b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.cs index f7c8db800..65c3d196a 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/CartesianChart.cs @@ -76,5 +76,12 @@ private void OnPinched(object? sender, PinchEventArgs e) _previousPinchScale = scale; c.Zoom(pivot, ZoomDirection.DefinedByScaleFactor, 1 - delta, true); + + // hack: + // when the pinch started, the isPanning property is set to true, + // when the pinch is completed, the pointerUp will be called, + // and within that method panning will occur, lets prevent that + // by setting isPanning to false here. + c.ClearPointerDown(); } } From 7e003b602b92f5ea68e4b2c1ec04291c09800092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 29 Jul 2025 19:42:18 -0600 Subject: [PATCH 92/94] move skiaRenderMode from Shared.WinUI to Uno only --- .../Rendering/SkiaRenderMode.cs | 0 src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems | 1 - 2 files changed, 1 deletion(-) rename src/skiasharp/{_Shared.WinUI => LiveChartsCore.SkiaSharpView.Uno.WinUI}/Rendering/SkiaRenderMode.cs (100%) diff --git a/src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs b/src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/Rendering/SkiaRenderMode.cs similarity index 100% rename from src/skiasharp/_Shared.WinUI/Rendering/SkiaRenderMode.cs rename to src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/Rendering/SkiaRenderMode.cs diff --git a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems index 718881568..270f801df 100644 --- a/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems +++ b/src/skiasharp/_Shared.WinUI/_Shared.WinUI.projitems @@ -15,7 +15,6 @@ - From 848807a2a0249bfcd142910e0b0d202427bc62ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 30 Jul 2025 08:49:03 -0600 Subject: [PATCH 93/94] validate isPinching only on touch devices --- .../Platforms/WinUI/PointerController.cs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/_Shared.Native/Platforms/WinUI/PointerController.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs index aae2e9546..0deb6930a 100644 --- a/src/_Shared.Native/Platforms/WinUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -33,6 +33,7 @@ internal partial class PointerController : INativePointerController { private bool _isPinching; private DateTime _pressedTime; + private static bool? s_isTouchDevice; public void InitializeController(object view) { @@ -121,8 +122,12 @@ private void OnWindowsPointerWheelChanged(object sender, PointerRoutedEventArgs private void OnWindowsPointerExited(object sender, PointerRoutedEventArgs e) => Exited?.Invoke(sender, new(e)); - private void OnPinchSarted(object sender, ManipulationStartedRoutedEventArgs e) => + private void OnPinchSarted(object sender, ManipulationStartedRoutedEventArgs e) + { + if (!IsTouchDevice()) return; + _isPinching = true; + } private void OnPinching(object sender, ManipulationDeltaRoutedEventArgs e) { @@ -133,6 +138,50 @@ private void OnPinching(object sender, ManipulationDeltaRoutedEventArgs e) private void OnPinchCompleted(object sender, ManipulationCompletedRoutedEventArgs e) => _isPinching = false; + + private static bool IsTouchDevice() + { + if (s_isTouchDevice.HasValue) + return s_isTouchDevice.Value; + + var isWindowsTouchEnabled = false; + +#if WINDOWS + isWindowsTouchEnabled = WindowsTouchSupportHelper.IsWindowsTouchEnabled(); +#endif + + var result = + OperatingSystem.IsAndroid() || + (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || + //(OperatingSystem.IsBrowser() && IsTouchSupportedInJs()) || is this needed? + (OperatingSystem.IsWindows() && isWindowsTouchEnabled); + + s_isTouchDevice = result; + + return result; + } + +#if WINDOWS + public static class WindowsTouchSupportHelper + { + private const int SM_MAXIMUMTOUCHES = 95; + + [System.Runtime.InteropServices.DllImport("user32.dll")] + private static extern int GetSystemMetrics(int nIndex); + + public static bool IsWindowsTouchEnabled() + { + try + { + return GetSystemMetrics(SM_MAXIMUMTOUCHES) > 0; + } + catch + { + return false; + } + } + } +#endif } #endif From 1a1fc3e458efd5b3f41193771ec7f046f72c8304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Wed, 30 Jul 2025 10:05:35 -0600 Subject: [PATCH 94/94] ensure device has touch input to use secondary pointer down on double tap --- src/_Shared.Native/Platforms/NativeHelpers.cs | 74 +++++++++++++++++++ .../Platforms/WinUI/PointerController.cs | 46 +----------- src/_Shared.Native/_Shared.Native.projitems | 1 + src/skiasharp/_Shared.WinUI/ChartControl.cs | 8 +- 4 files changed, 81 insertions(+), 48 deletions(-) create mode 100644 src/_Shared.Native/Platforms/NativeHelpers.cs diff --git a/src/_Shared.Native/Platforms/NativeHelpers.cs b/src/_Shared.Native/Platforms/NativeHelpers.cs new file mode 100644 index 000000000..223d49850 --- /dev/null +++ b/src/_Shared.Native/Platforms/NativeHelpers.cs @@ -0,0 +1,74 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; + +namespace LiveChartsCore.Native; + +internal partial class NativeHelpers +{ + private static bool? s_isTouchDevice; + + public static bool IsTouchDevice() + { + if (s_isTouchDevice.HasValue) + return s_isTouchDevice.Value; + + var isWindowsTouchEnabled = false; + +#if WINDOWS + isWindowsTouchEnabled = WindowsTouchSupportHelper.IsWindowsTouchEnabled(); +#endif + + var result = + OperatingSystem.IsAndroid() || + (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || + //(OperatingSystem.IsBrowser() && IsTouchSupportedInJs()) || is this needed? + (OperatingSystem.IsWindows() && isWindowsTouchEnabled); + + s_isTouchDevice = result; + + return result; + } + +#if WINDOWS + public static class WindowsTouchSupportHelper + { + private const int SM_MAXIMUMTOUCHES = 95; + + [System.Runtime.InteropServices.DllImport("user32.dll")] + private static extern int GetSystemMetrics(int nIndex); + + public static bool IsWindowsTouchEnabled() + { + try + { + return GetSystemMetrics(SM_MAXIMUMTOUCHES) > 0; + } + catch + { + return false; + } + } + } +#endif +} diff --git a/src/_Shared.Native/Platforms/WinUI/PointerController.cs b/src/_Shared.Native/Platforms/WinUI/PointerController.cs index 0deb6930a..19ad69155 100644 --- a/src/_Shared.Native/Platforms/WinUI/PointerController.cs +++ b/src/_Shared.Native/Platforms/WinUI/PointerController.cs @@ -33,7 +33,6 @@ internal partial class PointerController : INativePointerController { private bool _isPinching; private DateTime _pressedTime; - private static bool? s_isTouchDevice; public void InitializeController(object view) { @@ -124,7 +123,7 @@ private void OnWindowsPointerExited(object sender, PointerRoutedEventArgs e) => private void OnPinchSarted(object sender, ManipulationStartedRoutedEventArgs e) { - if (!IsTouchDevice()) return; + if (!NativeHelpers.IsTouchDevice()) return; _isPinching = true; } @@ -139,49 +138,6 @@ private void OnPinching(object sender, ManipulationDeltaRoutedEventArgs e) private void OnPinchCompleted(object sender, ManipulationCompletedRoutedEventArgs e) => _isPinching = false; - private static bool IsTouchDevice() - { - if (s_isTouchDevice.HasValue) - return s_isTouchDevice.Value; - - var isWindowsTouchEnabled = false; - -#if WINDOWS - isWindowsTouchEnabled = WindowsTouchSupportHelper.IsWindowsTouchEnabled(); -#endif - - var result = - OperatingSystem.IsAndroid() || - (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || - //(OperatingSystem.IsBrowser() && IsTouchSupportedInJs()) || is this needed? - (OperatingSystem.IsWindows() && isWindowsTouchEnabled); - - s_isTouchDevice = result; - - return result; - } - -#if WINDOWS - public static class WindowsTouchSupportHelper - { - private const int SM_MAXIMUMTOUCHES = 95; - - [System.Runtime.InteropServices.DllImport("user32.dll")] - private static extern int GetSystemMetrics(int nIndex); - - public static bool IsWindowsTouchEnabled() - { - try - { - return GetSystemMetrics(SM_MAXIMUMTOUCHES) > 0; - } - catch - { - return false; - } - } - } -#endif } #endif diff --git a/src/_Shared.Native/_Shared.Native.projitems b/src/_Shared.Native/_Shared.Native.projitems index 452af9915..3f13d2188 100644 --- a/src/_Shared.Native/_Shared.Native.projitems +++ b/src/_Shared.Native/_Shared.Native.projitems @@ -12,6 +12,7 @@ + diff --git a/src/skiasharp/_Shared.WinUI/ChartControl.cs b/src/skiasharp/_Shared.WinUI/ChartControl.cs index 89ad243ba..7c07f0553 100644 --- a/src/skiasharp/_Shared.WinUI/ChartControl.cs +++ b/src/skiasharp/_Shared.WinUI/ChartControl.cs @@ -42,7 +42,7 @@ namespace LiveChartsCore.SkiaSharpView.WinUI; /// public abstract partial class ChartControl : UserControl, IChartView { - private DateTime _lastPresed; + private DateTime _lastTouch; private readonly ThemeListener _themeListener; private readonly PointerController _pointerController; private static readonly bool s_isWebAssembly = RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); @@ -125,10 +125,12 @@ private void OnPressed(object? sender, Native.Events.PressedEventArgs args) if (PointerPressedCommand?.CanExecute(cArgs) == true) PointerPressedCommand.Execute(cArgs); - var isSecondary = (DateTime.Now - _lastPresed).TotalMilliseconds < 500; + var isSecondary = (DateTime.Now - _lastTouch).TotalMilliseconds < 500; CoreChart?.InvokePointerDown(args.Location, args.IsSecondaryPress || isSecondary); - _lastPresed = DateTime.Now; + + if (NativeHelpers.IsTouchDevice()) + _lastTouch = DateTime.Now; } private void OnMoved(object? sender, Native.Events.ScreenEventArgs args)