diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 905a14cfee..9cbde72f7f 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -74,16 +74,15 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(PasswordCharProperty, - SelectionBrushProperty, SelectionForegroundBrushProperty, - SelectionStartProperty, SelectionEndProperty); + AffectsRender(SelectionBrushProperty); - Observable.Merge( - TextProperty.Changed, - SelectionStartProperty.Changed, - SelectionEndProperty.Changed, - PasswordCharProperty.Changed - ).AddClassHandler((x,_) => x.InvalidateFormattedText()); + Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed, + TextAlignmentProperty.Changed, TextWrappingProperty.Changed, + TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, + TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, + SelectionStartProperty.Changed, SelectionEndProperty.Changed, + SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed + ).AddClassHandler((x, _) => x.InvalidateFormattedText()); CaretIndexProperty.Changed.AddClassHandler((x, e) => x.CaretIndexChanged((int)e.NewValue)); } @@ -184,7 +183,7 @@ namespace Avalonia.Controls.Presenters { get { - return _formattedText ?? (_formattedText = CreateFormattedText(Bounds.Size, Text)); + return _formattedText ?? (_formattedText = CreateFormattedText()); } } @@ -219,7 +218,7 @@ namespace Avalonia.Controls.Presenters get => GetValue(SelectionForegroundBrushProperty); set => SetValue(SelectionForegroundBrushProperty, value); } - + public IBrush CaretBrush { get => GetValue(CaretBrushProperty); @@ -284,13 +283,9 @@ namespace Avalonia.Controls.Presenters /// protected void InvalidateFormattedText() { - if (_formattedText != null) - { - _constraint = _formattedText.Constraint; - _formattedText = null; - } + _formattedText = null; - InvalidateVisual(); + InvalidateMeasure(); } /// @@ -307,6 +302,7 @@ namespace Avalonia.Controls.Presenters } FormattedText.Constraint = Bounds.Size; + context.DrawText(Foreground, new Point(), FormattedText); } @@ -424,20 +420,20 @@ namespace Avalonia.Controls.Presenters /// /// Creates the used to render the text. /// - /// The constraint of the text. - /// The text to generated the for. /// A object. - protected virtual FormattedText CreateFormattedText(Size constraint, string text) + protected virtual FormattedText CreateFormattedText() { FormattedText result = null; + var text = Text; + if (PasswordChar != default(char)) { - result = CreateFormattedTextInternal(constraint, new string(PasswordChar, text?.Length ?? 0)); + result = CreateFormattedTextInternal(_constraint, new string(PasswordChar, text?.Length ?? 0)); } else { - result = CreateFormattedTextInternal(constraint, text); + result = CreateFormattedTextInternal(_constraint, text); } var selectionStart = SelectionStart; @@ -467,13 +463,15 @@ namespace Avalonia.Controls.Presenters { if (TextWrapping == TextWrapping.Wrap) { - FormattedText.Constraint = new Size(availableSize.Width, double.PositiveInfinity); + _constraint = new Size(availableSize.Width, double.PositiveInfinity); } else { - FormattedText.Constraint = Size.Infinity; + _constraint = Size.Infinity; } + _formattedText = null; + return FormattedText.Bounds.Size; } diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index 69806b22f2..53231ee1dd 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -200,7 +200,13 @@ namespace Avalonia.Media private void Set(ref T field, T value) { + if (field != null && field.Equals(value)) + { + return; + } + field = value; + _platformImpl = null; } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index d5e361ca0e..0e6dda1710 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -35,6 +35,7 @@ namespace Avalonia.Rendering private IRef _currentDraw; private readonly IDeferredRendererLock _lock; private readonly object _sceneLock = new object(); + private readonly Action _updateSceneIfNeededDelegate; /// /// Initializes a new instance of the class. @@ -49,7 +50,7 @@ namespace Avalonia.Rendering IRenderLoop renderLoop, ISceneBuilder sceneBuilder = null, IDispatcher dispatcher = null, - IDeferredRendererLock rendererLock = null) + IDeferredRendererLock rendererLock = null) : base(true) { Contract.Requires(root != null); @@ -59,6 +60,7 @@ namespace Avalonia.Rendering Layers = new RenderLayers(); _renderLoop = renderLoop; _lock = rendererLock ?? new ManagedDeferredRendererLock(); + _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; } /// @@ -73,7 +75,7 @@ namespace Avalonia.Rendering public DeferredRenderer( IVisual root, IRenderTarget renderTarget, - ISceneBuilder sceneBuilder = null) + ISceneBuilder sceneBuilder = null) : base(true) { Contract.Requires(root != null); Contract.Requires(renderTarget != null); @@ -83,6 +85,7 @@ namespace Avalonia.Rendering _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _lock = new ManagedDeferredRendererLock(); + _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; } /// @@ -261,7 +264,8 @@ namespace Avalonia.Rendering try { var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context); - + if (updated) + FpsTick(); using (scene) { if (scene?.Item != null) @@ -318,17 +322,25 @@ namespace Avalonia.Rendering _lastSceneId = scene.Generation; + var isUiThread = Dispatcher.UIThread.CheckAccess(); // We have consumed the previously available scene, but there might be some dirty // rects since the last update. *If* we are on UI thread, we can force immediate scene // rebuild before rendering anything on-screen // We are calling the same method recursively here - if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate) + if (!recursiveCall && isUiThread && NeedsUpdate) { UpdateScene(); var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true); return (rs, true); } + // We are rendering a new scene version, so it's highly likely + // that there is already a pending update for animations + // So we are scheduling an update call so UI thread could prepare a scene before + // the next render timer tick + if (!recursiveCall && !isUiThread) + Dispatcher.UIThread.Post(_updateSceneIfNeededDelegate, DispatcherPriority.Render); + // Indicate that we have updated the layers return (sceneRef.Clone(), true); } @@ -534,6 +546,12 @@ namespace Avalonia.Rendering context = RenderTarget.CreateDrawingContext(this); } + private void UpdateSceneIfNeeded() + { + if(NeedsUpdate) + UpdateScene(); + } + private void UpdateScene() { Dispatcher.UIThread.VerifyAccess(); diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs index 1e7b5c2923..b37d5d660b 100644 --- a/src/Avalonia.Visuals/Rendering/RendererBase.cs +++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs @@ -7,6 +7,7 @@ namespace Avalonia.Rendering { public class RendererBase { + private readonly bool _useManualFpsCounting; private static int s_fontSize = 18; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private int _framesThisSecond; @@ -14,8 +15,9 @@ namespace Avalonia.Rendering private FormattedText _fpsText; private TimeSpan _lastFpsUpdate; - public RendererBase() + public RendererBase(bool useManualFpsCounting = false) { + _useManualFpsCounting = useManualFpsCounting; _fpsText = new FormattedText { Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default), @@ -23,12 +25,15 @@ namespace Avalonia.Rendering }; } + protected void FpsTick() => _framesThisSecond++; + protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount) { var now = _stopwatch.Elapsed; var elapsed = now - _lastFpsUpdate; - ++_framesThisSecond; + if (!_useManualFpsCounting) + ++_framesThisSecond; if (elapsed.TotalSeconds > 1) { diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 6022e7a552..5f876464e2 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -149,7 +149,17 @@ namespace Avalonia.Skia if (index >= Text.Length || index < 0) { var r = rects.LastOrDefault(); - return new Rect(r.X + r.Width, r.Y, 0, _lineHeight); + + var c = Text[Text.Length - 1]; + + switch (c) + { + case '\n': + case '\r': + return new Rect(r.X, r.Y, 0, _lineHeight); + default: + return new Rect(r.X + r.Width, r.Y, 0, _lineHeight); + } } return rects[index]; } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 1e2443aff8..a0fbf704cf 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -184,7 +184,7 @@ namespace Avalonia.Direct2D1.RenderTests public void Signal(DispatcherPriority prio) { - throw new NotImplementedException(); + // No-op } public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)