Browse Source

Merge branch 'master' into feature/windowstate-fullscreen

pull/3849/head
danwalmsley 7 years ago
committed by GitHub
parent
commit
acf18cf134
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 46
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  2. 6
      src/Avalonia.Visuals/Media/FormattedText.cs
  3. 26
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  4. 9
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  5. 12
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  6. 2
      tests/Avalonia.RenderTests/TestBase.cs

46
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -74,16 +74,15 @@ namespace Avalonia.Controls.Presenters
static TextPresenter() static TextPresenter()
{ {
AffectsRender<TextPresenter>(PasswordCharProperty, AffectsRender<TextPresenter>(SelectionBrushProperty);
SelectionBrushProperty, SelectionForegroundBrushProperty,
SelectionStartProperty, SelectionEndProperty);
Observable.Merge( Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed,
TextProperty.Changed, TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
SelectionStartProperty.Changed, TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed,
SelectionEndProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed,
PasswordCharProperty.Changed SelectionStartProperty.Changed, SelectionEndProperty.Changed,
).AddClassHandler<TextPresenter>((x,_) => x.InvalidateFormattedText()); SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed
).AddClassHandler<TextPresenter>((x, _) => x.InvalidateFormattedText());
CaretIndexProperty.Changed.AddClassHandler<TextPresenter>((x, e) => x.CaretIndexChanged((int)e.NewValue)); CaretIndexProperty.Changed.AddClassHandler<TextPresenter>((x, e) => x.CaretIndexChanged((int)e.NewValue));
} }
@ -184,7 +183,7 @@ namespace Avalonia.Controls.Presenters
{ {
get get
{ {
return _formattedText ?? (_formattedText = CreateFormattedText(Bounds.Size, Text)); return _formattedText ?? (_formattedText = CreateFormattedText());
} }
} }
@ -219,7 +218,7 @@ namespace Avalonia.Controls.Presenters
get => GetValue(SelectionForegroundBrushProperty); get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value); set => SetValue(SelectionForegroundBrushProperty, value);
} }
public IBrush CaretBrush public IBrush CaretBrush
{ {
get => GetValue(CaretBrushProperty); get => GetValue(CaretBrushProperty);
@ -284,13 +283,9 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
protected void InvalidateFormattedText() protected void InvalidateFormattedText()
{ {
if (_formattedText != null) _formattedText = null;
{
_constraint = _formattedText.Constraint;
_formattedText = null;
}
InvalidateVisual(); InvalidateMeasure();
} }
/// <summary> /// <summary>
@ -307,6 +302,7 @@ namespace Avalonia.Controls.Presenters
} }
FormattedText.Constraint = Bounds.Size; FormattedText.Constraint = Bounds.Size;
context.DrawText(Foreground, new Point(), FormattedText); context.DrawText(Foreground, new Point(), FormattedText);
} }
@ -424,20 +420,20 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Creates the <see cref="FormattedText"/> used to render the text. /// Creates the <see cref="FormattedText"/> used to render the text.
/// </summary> /// </summary>
/// <param name="constraint">The constraint of the text.</param>
/// <param name="text">The text to generated the <see cref="FormattedText"/> for.</param>
/// <returns>A <see cref="FormattedText"/> object.</returns> /// <returns>A <see cref="FormattedText"/> object.</returns>
protected virtual FormattedText CreateFormattedText(Size constraint, string text) protected virtual FormattedText CreateFormattedText()
{ {
FormattedText result = null; FormattedText result = null;
var text = Text;
if (PasswordChar != default(char)) if (PasswordChar != default(char))
{ {
result = CreateFormattedTextInternal(constraint, new string(PasswordChar, text?.Length ?? 0)); result = CreateFormattedTextInternal(_constraint, new string(PasswordChar, text?.Length ?? 0));
} }
else else
{ {
result = CreateFormattedTextInternal(constraint, text); result = CreateFormattedTextInternal(_constraint, text);
} }
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
@ -467,13 +463,15 @@ namespace Avalonia.Controls.Presenters
{ {
if (TextWrapping == TextWrapping.Wrap) if (TextWrapping == TextWrapping.Wrap)
{ {
FormattedText.Constraint = new Size(availableSize.Width, double.PositiveInfinity); _constraint = new Size(availableSize.Width, double.PositiveInfinity);
} }
else else
{ {
FormattedText.Constraint = Size.Infinity; _constraint = Size.Infinity;
} }
_formattedText = null;
return FormattedText.Bounds.Size; return FormattedText.Bounds.Size;
} }

6
src/Avalonia.Visuals/Media/FormattedText.cs

@ -200,7 +200,13 @@ namespace Avalonia.Media
private void Set<T>(ref T field, T value) private void Set<T>(ref T field, T value)
{ {
if (field != null && field.Equals(value))
{
return;
}
field = value; field = value;
_platformImpl = null; _platformImpl = null;
} }
} }

26
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -35,6 +35,7 @@ namespace Avalonia.Rendering
private IRef<IDrawOperation> _currentDraw; private IRef<IDrawOperation> _currentDraw;
private readonly IDeferredRendererLock _lock; private readonly IDeferredRendererLock _lock;
private readonly object _sceneLock = new object(); private readonly object _sceneLock = new object();
private readonly Action _updateSceneIfNeededDelegate;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class. /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@ -49,7 +50,7 @@ namespace Avalonia.Rendering
IRenderLoop renderLoop, IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null, ISceneBuilder sceneBuilder = null,
IDispatcher dispatcher = null, IDispatcher dispatcher = null,
IDeferredRendererLock rendererLock = null) IDeferredRendererLock rendererLock = null) : base(true)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
@ -59,6 +60,7 @@ namespace Avalonia.Rendering
Layers = new RenderLayers(); Layers = new RenderLayers();
_renderLoop = renderLoop; _renderLoop = renderLoop;
_lock = rendererLock ?? new ManagedDeferredRendererLock(); _lock = rendererLock ?? new ManagedDeferredRendererLock();
_updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
} }
/// <summary> /// <summary>
@ -73,7 +75,7 @@ namespace Avalonia.Rendering
public DeferredRenderer( public DeferredRenderer(
IVisual root, IVisual root,
IRenderTarget renderTarget, IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null) ISceneBuilder sceneBuilder = null) : base(true)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null); Contract.Requires<ArgumentNullException>(renderTarget != null);
@ -83,6 +85,7 @@ namespace Avalonia.Rendering
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers(); Layers = new RenderLayers();
_lock = new ManagedDeferredRendererLock(); _lock = new ManagedDeferredRendererLock();
_updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -261,7 +264,8 @@ namespace Avalonia.Rendering
try try
{ {
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context); var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context);
if (updated)
FpsTick();
using (scene) using (scene)
{ {
if (scene?.Item != null) if (scene?.Item != null)
@ -318,17 +322,25 @@ namespace Avalonia.Rendering
_lastSceneId = scene.Generation; _lastSceneId = scene.Generation;
var isUiThread = Dispatcher.UIThread.CheckAccess();
// We have consumed the previously available scene, but there might be some dirty // 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 // rects since the last update. *If* we are on UI thread, we can force immediate scene
// rebuild before rendering anything on-screen // rebuild before rendering anything on-screen
// We are calling the same method recursively here // We are calling the same method recursively here
if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate) if (!recursiveCall && isUiThread && NeedsUpdate)
{ {
UpdateScene(); UpdateScene();
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true); var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true);
return (rs, 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 // Indicate that we have updated the layers
return (sceneRef.Clone(), true); return (sceneRef.Clone(), true);
} }
@ -534,6 +546,12 @@ namespace Avalonia.Rendering
context = RenderTarget.CreateDrawingContext(this); context = RenderTarget.CreateDrawingContext(this);
} }
private void UpdateSceneIfNeeded()
{
if(NeedsUpdate)
UpdateScene();
}
private void UpdateScene() private void UpdateScene()
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();

9
src/Avalonia.Visuals/Rendering/RendererBase.cs

@ -7,6 +7,7 @@ namespace Avalonia.Rendering
{ {
public class RendererBase public class RendererBase
{ {
private readonly bool _useManualFpsCounting;
private static int s_fontSize = 18; private static int s_fontSize = 18;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private int _framesThisSecond; private int _framesThisSecond;
@ -14,8 +15,9 @@ namespace Avalonia.Rendering
private FormattedText _fpsText; private FormattedText _fpsText;
private TimeSpan _lastFpsUpdate; private TimeSpan _lastFpsUpdate;
public RendererBase() public RendererBase(bool useManualFpsCounting = false)
{ {
_useManualFpsCounting = useManualFpsCounting;
_fpsText = new FormattedText _fpsText = new FormattedText
{ {
Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily.Default), 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) protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount)
{ {
var now = _stopwatch.Elapsed; var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate; var elapsed = now - _lastFpsUpdate;
++_framesThisSecond; if (!_useManualFpsCounting)
++_framesThisSecond;
if (elapsed.TotalSeconds > 1) if (elapsed.TotalSeconds > 1)
{ {

12
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -149,7 +149,17 @@ namespace Avalonia.Skia
if (index >= Text.Length || index < 0) if (index >= Text.Length || index < 0)
{ {
var r = rects.LastOrDefault(); 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]; return rects[index];
} }

2
tests/Avalonia.RenderTests/TestBase.cs

@ -184,7 +184,7 @@ namespace Avalonia.Direct2D1.RenderTests
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)
{ {
throw new NotImplementedException(); // No-op
} }
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)

Loading…
Cancel
Save