Browse Source

Merge remote-tracking branch 'origin/master' into android-text

pull/12270/head
Emmanuel Hansen 3 years ago
parent
commit
2b823fe3b4
  1. 6
      src/Avalonia.Base/Media/GlyphRun.cs
  2. 321
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  3. 7
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  4. 8
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  5. 2
      src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs
  6. 20
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  7. 2
      src/Avalonia.Base/Threading/Dispatcher.Timers.cs
  8. 3
      src/Avalonia.Base/Threading/Dispatcher.cs
  9. 8
      src/Avalonia.Base/Threading/DispatcherFrame.cs
  10. 2
      src/Avalonia.Base/Threading/DispatcherOperation.cs
  11. 12
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  12. 4
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  13. 4
      src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs
  14. 4
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  15. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  16. 10
      src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
  17. 2
      src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs
  18. 8
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  19. 5
      src/Avalonia.X11/X11Structs.cs
  20. 3
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
  21. 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  22. 116
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

6
src/Avalonia.Base/Media/GlyphRun.cs

@ -424,13 +424,13 @@ namespace Avalonia.Media
/// </returns> /// </returns>
public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{ {
var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
if (characterHit.TrailingLength != 0) if (characterHit.TrailingLength != 0)
{ {
return new CharacterHit(characterHit.FirstCharacterIndex); return previousCharacterHit;
} }
var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
return new CharacterHit(previousCharacterHit.FirstCharacterIndex); return new CharacterHit(previousCharacterHit.FirstCharacterIndex);
} }

321
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -9,7 +9,7 @@ namespace Avalonia.Media.TextFormatting
internal static Comparer<TextBounds> TextBoundsComparer { get; } = internal static Comparer<TextBounds> TextBoundsComparer { get; } =
Comparer<TextBounds>.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left)); Comparer<TextBounds>.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left));
private IReadOnlyList<IndexedTextRun>? _indexedTextRuns; internal IReadOnlyList<IndexedTextRun>? _indexedTextRuns;
private readonly TextRun[] _textRuns; private readonly TextRun[] _textRuns;
private readonly double _paragraphWidth; private readonly double _paragraphWidth;
private readonly TextParagraphProperties _paragraphProperties; private readonly TextParagraphProperties _paragraphProperties;
@ -512,38 +512,45 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit) public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
{ {
if (_textRuns.Length == 0) if (_textRuns.Length == 0 || _indexedTextRuns is null)
{ {
return new CharacterHit(); return new CharacterHit();
} }
if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit)) var currentCharacterrHit = characterHit;
{ var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
return nextCharacterHit;
}
var lastTextPosition = FirstTextSourceIndex + Length;
// Can't move, we're after the last character var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Forward, out var currentPosition);
var runIndex = GetRunIndexAtCharacterIndex(lastTextPosition, LogicalDirection.Forward, out var currentPosition);
var currentRun = _textRuns[runIndex]; var nextCharacterHit = characterHit;
switch (currentRun) switch (currentRun)
{ {
case ShapedTextRun shapedRun: case ShapedTextRun shapedRun:
{ {
nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(characterHit); var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster - characterHit.TrailingLength);
if (offset > 0)
{
currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
}
nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(currentCharacterrHit);
if (offset > 0)
{
nextCharacterHit = new CharacterHit(nextCharacterHit.FirstCharacterIndex + offset, nextCharacterHit.TrailingLength);
}
break; break;
} }
default: case TextRun:
{ {
nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length); nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
break; break;
} }
} }
if (characterHit.FirstCharacterIndex + characterHit.TrailingLength == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength) if (characterIndex == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
{ {
return characterHit; return characterHit;
} }
@ -554,17 +561,75 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{ {
if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit)) if (_textRuns.Length == 0 || _indexedTextRuns is null)
{
return new CharacterHit();
}
if (characterHit.TrailingLength > 0 && characterHit.FirstCharacterIndex <= FirstTextSourceIndex)
{
return new CharacterHit(FirstTextSourceIndex);
}
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (characterIndex <= FirstTextSourceIndex)
{ {
return previousCharacterHit; return new CharacterHit(FirstTextSourceIndex);
}
var currentCharacterrHit = characterHit;
var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
if (currentPosition == characterHit.FirstCharacterIndex)
{
currentRun = GetRunAtCharacterIndex(characterHit.FirstCharacterIndex, LogicalDirection.Backward, out currentPosition);
}
var previousCharacterHit = characterHit;
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster);
if (offset > 0)
{
currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
}
previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(currentCharacterrHit);
if (offset > 0)
{
previousCharacterHit = new CharacterHit(previousCharacterHit.FirstCharacterIndex + offset, previousCharacterHit.TrailingLength);
}
break;
}
case TextRun:
{
if (characterHit.TrailingLength > 0)
{
previousCharacterHit = new CharacterHit(currentPosition, currentRun.Length);
}
else
{
previousCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
}
break;
}
} }
if (characterHit.FirstCharacterIndex <= FirstTextSourceIndex) if (characterIndex == previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength)
{ {
characterHit = new CharacterHit(FirstTextSourceIndex); return characterHit;
} }
return characterHit; // Can't move, we're before the first character return previousCharacterHit;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -1009,161 +1074,7 @@ namespace Avalonia.Media.TextFormatting
if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine) if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine)
{ {
_textLineBreak = new TextLineBreak(textEndOfLine); _textLineBreak = new TextLineBreak(textEndOfLine);
}
}
/// <summary>
/// Tries to find the next character hit.
/// </summary>
/// <param name="characterHit">The current character hit.</param>
/// <param name="nextCharacterHit">The next character hit.</param>
/// <returns></returns>
private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit)
{
nextCharacterHit = characterHit;
var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var lastCodepointIndex = FirstTextSourceIndex + Length;
if (codepointIndex >= lastCodepointIndex)
{
return false; // Cannot go forward anymore
}
if (codepointIndex < FirstTextSourceIndex)
{
codepointIndex = FirstTextSourceIndex;
}
var runIndex = GetRunIndexAtCharacterIndex(codepointIndex, LogicalDirection.Forward, out var currentPosition);
while (runIndex < _textRuns.Length)
{
var currentRun = _textRuns[runIndex];
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == FirstTextSourceIndex + Length;
if (isAtEnd && !shapedRun.GlyphRun.IsLeftToRight)
{
nextCharacterHit = foundCharacterHit;
return true;
}
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
foundCharacterHit :
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
{
return true;
}
break;
}
default:
{
var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (textPosition == currentPosition)
{
nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
return true;
}
break;
}
}
currentPosition += currentRun.Length;
runIndex++;
}
return false;
}
/// <summary>
/// Tries to find the previous character hit.
/// </summary>
/// <param name="characterHit">The current character hit.</param>
/// <param name="previousCharacterHit">The previous character hit.</param>
/// <returns></returns>
private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit)
{
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (characterIndex == FirstTextSourceIndex)
{
previousCharacterHit = new CharacterHit(FirstTextSourceIndex);
return true;
}
previousCharacterHit = characterHit;
if (characterIndex < FirstTextSourceIndex)
{
return false; // Cannot go backward anymore.
}
var runIndex = GetRunIndexAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
while (runIndex >= 0)
{
var currentRun = _textRuns[runIndex];
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
if (foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength < characterIndex)
{
previousCharacterHit = foundCharacterHit;
return true;
}
var previousPosition = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength;
if (foundCharacterHit.TrailingLength > 0 && previousPosition == characterIndex)
{
previousCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex);
}
if (previousCharacterHit != characterHit)
{
return true;
}
break;
}
default:
{
if (characterIndex == currentPosition + currentRun.Length)
{
previousCharacterHit = new CharacterHit(currentPosition);
return true;
}
break;
}
}
currentPosition -= currentRun.Length;
runIndex--;
} }
return false;
} }
/// <summary> /// <summary>
@ -1173,15 +1084,23 @@ namespace Avalonia.Media.TextFormatting
/// <param name="direction">The logical direction.</param> /// <param name="direction">The logical direction.</param>
/// <param name="textPosition">The text position of the found run index.</param> /// <param name="textPosition">The text position of the found run index.</param>
/// <returns>The text run index.</returns> /// <returns>The text run index.</returns>
private int GetRunIndexAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition) private TextRun? GetRunAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition)
{ {
var runIndex = 0; var runIndex = 0;
textPosition = FirstTextSourceIndex; textPosition = FirstTextSourceIndex;
if (_indexedTextRuns is null)
{
return null;
}
TextRun? currentRun = null;
TextRun? previousRun = null; TextRun? previousRun = null;
while (runIndex < _textRuns.Length) while (runIndex < _indexedTextRuns.Count)
{ {
var currentRun = _textRuns[runIndex]; var indexedRun = _indexedTextRuns[runIndex];
currentRun = indexedRun.TextRun;
switch (currentRun) switch (currentRun)
{ {
@ -1189,64 +1108,49 @@ namespace Avalonia.Media.TextFormatting
{ {
var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster; var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster;
if (firstCluster > codepointIndex) firstCluster += Math.Max(0, indexedRun.TextSourceCharacterIndex - firstCluster);
{
break;
}
if (previousRun is ShapedTextRun previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight)
{
if (shapedRun.ShapedBuffer.IsLeftToRight)
{
if (firstCluster >= codepointIndex)
{
return --runIndex;
}
}
else
{
if (codepointIndex > firstCluster + currentRun.Length)
{
return --runIndex;
}
}
}
if (direction == LogicalDirection.Forward) if (direction == LogicalDirection.Forward)
{ {
if (codepointIndex >= firstCluster && codepointIndex <= firstCluster + currentRun.Length) if (codepointIndex >= firstCluster && codepointIndex < firstCluster + currentRun.Length)
{ {
return runIndex; return currentRun;
} }
} }
else else
{ {
if (codepointIndex > firstCluster && if (previousRun is not null && previousRun is not ShapedTextRun && codepointIndex == textPosition + firstCluster)
codepointIndex <= firstCluster + currentRun.Length) {
textPosition -= previousRun.Length;
return previousRun;
}
if (codepointIndex > firstCluster && codepointIndex <= firstCluster + currentRun.Length)
{ {
return runIndex; return currentRun;
} }
} }
if (runIndex + 1 >= _textRuns.Length) if (runIndex + 1 >= _textRuns.Length)
{ {
return runIndex; return currentRun;
} }
textPosition += currentRun.Length; textPosition += currentRun.Length;
break; break;
} }
default: case TextRun:
{ {
if (codepointIndex == textPosition) if (codepointIndex == textPosition)
{ {
return runIndex; return currentRun;
} }
if (runIndex + 1 >= _textRuns.Length) if (runIndex + 1 >= _textRuns.Length)
{ {
return runIndex; return currentRun;
} }
textPosition += currentRun.Length; textPosition += currentRun.Length;
@ -1257,10 +1161,11 @@ namespace Avalonia.Media.TextFormatting
} }
runIndex++; runIndex++;
previousRun = currentRun; previousRun = currentRun;
} }
return runIndex; return currentRun;
} }
private TextLineMetrics CreateLineMetrics() private TextLineMetrics CreateLineMetrics()

7
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -99,14 +99,15 @@ namespace Avalonia.Threading
} }
} }
public static RestoreContext Ensure(DispatcherPriority priority) public static RestoreContext Ensure(DispatcherPriority priority) => Ensure(Dispatcher.UIThread, priority);
public static RestoreContext Ensure(Dispatcher dispatcher, DispatcherPriority priority)
{ {
if (Current is AvaloniaSynchronizationContext avaloniaContext if (Current is AvaloniaSynchronizationContext avaloniaContext
&& avaloniaContext.Priority == priority) && avaloniaContext.Priority == priority)
return default; return default;
var oldContext = Current; var oldContext = Current;
Dispatcher.UIThread.VerifyAccess(); dispatcher.VerifyAccess();
SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(priority)); SetSynchronizationContext(dispatcher.GetContextWithPriority(priority));
return new RestoreContext(oldContext); return new RestoreContext(oldContext);
} }
} }

8
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -98,7 +98,7 @@ public partial class Dispatcher
if (timeout.TotalMilliseconds < 0 && if (timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1)) timeout != TimeSpan.FromMilliseconds(-1))
{ {
throw new ArgumentOutOfRangeException("timeout"); throw new ArgumentOutOfRangeException(nameof(timeout));
} }
// Fast-Path: if on the same thread, and invoking at Send priority, // Fast-Path: if on the same thread, and invoking at Send priority,
@ -106,7 +106,7 @@ public partial class Dispatcher
// call the callback directly. // call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess()) if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{ {
using (AvaloniaSynchronizationContext.Ensure(priority)) using (AvaloniaSynchronizationContext.Ensure(this, priority))
callback(); callback();
return; return;
} }
@ -220,7 +220,7 @@ public partial class Dispatcher
if (timeout.TotalMilliseconds < 0 && if (timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1)) timeout != TimeSpan.FromMilliseconds(-1))
{ {
throw new ArgumentOutOfRangeException("timeout"); throw new ArgumentOutOfRangeException(nameof(timeout));
} }
// Fast-Path: if on the same thread, and invoking at Send priority, // Fast-Path: if on the same thread, and invoking at Send priority,
@ -228,7 +228,7 @@ public partial class Dispatcher
// call the callback directly. // call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess()) if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{ {
using (AvaloniaSynchronizationContext.Ensure(priority)) using (AvaloniaSynchronizationContext.Ensure(this, priority))
return callback(); return callback();
} }

2
src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs

@ -49,7 +49,7 @@ public partial class Dispatcher
try try
{ {
_frames.Push(frame); _frames.Push(frame);
using (AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Normal)) using (AvaloniaSynchronizationContext.Ensure(this, DispatcherPriority.Normal))
frame.Run(_controlledImpl); frame.Run(_controlledImpl);
} }
finally finally

20
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -9,7 +9,9 @@ public partial class Dispatcher
private readonly DispatcherPriorityQueue _queue = new(); private readonly DispatcherPriorityQueue _queue = new();
private bool _signaled; private bool _signaled;
private bool _explicitBackgroundProcessingRequested; private bool _explicitBackgroundProcessingRequested;
private const int MaximumTimeProcessingBackgroundJobs = 50; private const int MaximumInputStarvationTimeInFallbackMode = 50;
private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50;
private int _maximumInputStarvationTime;
void RequestBackgroundProcessing() void RequestBackgroundProcessing()
{ {
@ -35,8 +37,8 @@ public partial class Dispatcher
lock (InstanceLock) lock (InstanceLock)
{ {
_explicitBackgroundProcessingRequested = false; _explicitBackgroundProcessingRequested = false;
ExecuteJobsCore();
} }
ExecuteJobsCore(true);
} }
/// <summary> /// <summary>
@ -130,10 +132,10 @@ public partial class Dispatcher
lock (InstanceLock) lock (InstanceLock)
_signaled = false; _signaled = false;
ExecuteJobsCore(); ExecuteJobsCore(false);
} }
void ExecuteJobsCore() void ExecuteJobsCore(bool fromExplicitBackgroundProcessingCallback)
{ {
long? backgroundJobExecutionStartedAt = null; long? backgroundJobExecutionStartedAt = null;
while (true) while (true)
@ -151,7 +153,6 @@ public partial class Dispatcher
if (job.Priority > DispatcherPriority.Input) if (job.Priority > DispatcherPriority.Input)
{ {
ExecuteJob(job); ExecuteJob(job);
backgroundJobExecutionStartedAt = null;
} }
// If platform supports pending input query, ask the platform if we can continue running low priority jobs // If platform supports pending input query, ask the platform if we can continue running low priority jobs
else if (_pendingInputImpl?.CanQueryPendingInput == true) else if (_pendingInputImpl?.CanQueryPendingInput == true)
@ -164,6 +165,13 @@ public partial class Dispatcher
return; return;
} }
} }
// We can't ask if the implementation has pending input, so we should let it to call us back
// Once it thinks that input is handled
else if (_backgroundProcessingImpl != null && !fromExplicitBackgroundProcessingCallback)
{
RequestBackgroundProcessing();
return;
}
// We can't check if there is pending input, but still need to enforce interactivity // We can't check if there is pending input, but still need to enforce interactivity
// so we stop processing background jobs after some timeout and start a timer to continue later // so we stop processing background jobs after some timeout and start a timer to continue later
else else
@ -171,7 +179,7 @@ public partial class Dispatcher
if (backgroundJobExecutionStartedAt == null) if (backgroundJobExecutionStartedAt == null)
backgroundJobExecutionStartedAt = Now; backgroundJobExecutionStartedAt = Now;
if (Now - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs) if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime)
{ {
_signaled = true; _signaled = true;
RequestBackgroundProcessing(); RequestBackgroundProcessing();

2
src/Avalonia.Base/Threading/Dispatcher.Timers.cs

@ -127,7 +127,7 @@ public partial class Dispatcher
if (needToPromoteTimers) if (needToPromoteTimers)
PromoteTimers(); PromoteTimers();
if (needToProcessQueue) if (needToProcessQueue)
ExecuteJobsCore(); ExecuteJobsCore(false);
UpdateOSTimer(); UpdateOSTimer();
} }

3
src/Avalonia.Base/Threading/Dispatcher.cs

@ -34,6 +34,9 @@ public partial class Dispatcher : IDispatcher
_controlledImpl = _impl as IControlledDispatcherImpl; _controlledImpl = _impl as IControlledDispatcherImpl;
_pendingInputImpl = _impl as IDispatcherImplWithPendingInput; _pendingInputImpl = _impl as IDispatcherImplWithPendingInput;
_backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing; _backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing;
_maximumInputStarvationTime = _backgroundProcessingImpl == null ?
MaximumInputStarvationTimeInFallbackMode :
MaximumInputStarvationTimeInExplicitProcessingExplicitMode;
if (_backgroundProcessingImpl != null) if (_backgroundProcessingImpl != null)
_backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing; _backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing;
} }

8
src/Avalonia.Base/Threading/DispatcherFrame.cs

@ -38,10 +38,14 @@ public class DispatcherFrame
/// for their important criteria to be met. These frames /// for their important criteria to be met. These frames
/// should have a timeout associated with them. /// should have a timeout associated with them.
/// </param> /// </param>
public DispatcherFrame(bool exitWhenRequested) public DispatcherFrame(bool exitWhenRequested) : this(Dispatcher.UIThread, exitWhenRequested)
{ {
Dispatcher = Dispatcher.UIThread;
Dispatcher.VerifyAccess(); Dispatcher.VerifyAccess();
}
internal DispatcherFrame(Dispatcher dispatcher, bool exitWhenRequested)
{
Dispatcher = dispatcher;
_exitWhenRequested = exitWhenRequested; _exitWhenRequested = exitWhenRequested;
_continue = true; _continue = true;
} }

2
src/Avalonia.Base/Threading/DispatcherOperation.cs

@ -258,7 +258,7 @@ public class DispatcherOperation
try try
{ {
using (AvaloniaSynchronizationContext.Ensure(Priority)) using (AvaloniaSynchronizationContext.Ensure(Dispatcher, Priority))
InvokeCore(); InvokeCore();
} }
finally finally

12
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -112,11 +112,11 @@ public partial class DispatcherTimer
bool updateOSTimer = false; bool updateOSTimer = false;
if (value.TotalMilliseconds < 0) if (value.TotalMilliseconds < 0)
throw new ArgumentOutOfRangeException("value", throw new ArgumentOutOfRangeException(nameof(value),
"TimeSpan period must be greater than or equal to zero."); "TimeSpan period must be greater than or equal to zero.");
if (value.TotalMilliseconds > Int32.MaxValue) if (value.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException("value", throw new ArgumentOutOfRangeException(nameof(value),
"TimeSpan period must be less than or equal to Int32.MaxValue."); "TimeSpan period must be less than or equal to Int32.MaxValue.");
lock (_instanceLock) lock (_instanceLock)
@ -259,14 +259,14 @@ public partial class DispatcherTimer
DispatcherPriority.Validate(priority, "priority"); DispatcherPriority.Validate(priority, "priority");
if (priority == DispatcherPriority.Inactive) if (priority == DispatcherPriority.Inactive)
{ {
throw new ArgumentException("Specified priority is not valid.", "priority"); throw new ArgumentException("Specified priority is not valid.", nameof(priority));
} }
if (interval.TotalMilliseconds < 0) if (interval.TotalMilliseconds < 0)
throw new ArgumentOutOfRangeException("interval", "TimeSpan period must be greater than or equal to zero."); throw new ArgumentOutOfRangeException(nameof(interval), "TimeSpan period must be greater than or equal to zero.");
if (interval.TotalMilliseconds > Int32.MaxValue) if (interval.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException("interval", throw new ArgumentOutOfRangeException(nameof(interval),
"TimeSpan period must be less than or equal to Int32.MaxValue."); "TimeSpan period must be less than or equal to Int32.MaxValue.");
@ -349,4 +349,4 @@ public partial class DispatcherTimer
// used by Dispatcher // used by Dispatcher
internal long DueTimeInMs { get; private set; } internal long DueTimeInMs { get; private set; }
} }

4
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -807,7 +807,7 @@ namespace Avalonia.Collections
{ {
if (value < 0) if (value < 0)
{ {
throw new ArgumentOutOfRangeException("PageSize cannot have a negative value."); throw new ArgumentOutOfRangeException(nameof(value), "PageSize cannot have a negative value.");
} }
// if the Refresh is currently deferred, cache the desired PageSize // if the Refresh is currently deferred, cache the desired PageSize
@ -1954,7 +1954,7 @@ namespace Avalonia.Collections
// for indices larger than the count // for indices larger than the count
if (index >= Count || index < 0) if (index >= Count || index < 0)
{ {
throw new ArgumentOutOfRangeException("index"); throw new ArgumentOutOfRangeException(nameof(index));
} }
if (IsGrouping) if (IsGrouping)

4
src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs

@ -146,12 +146,12 @@ namespace Avalonia.Automation.Peers
if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0)) if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0))
{ {
throw new ArgumentOutOfRangeException("horizontalPercent"); throw new ArgumentOutOfRangeException(nameof(horizontalPercent));
} }
if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0)) if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0))
{ {
throw new ArgumentOutOfRangeException("verticalPercent"); throw new ArgumentOutOfRangeException(nameof(verticalPercent));
} }
var offset = Owner.Offset; var offset = Owner.Offset;

4
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives
set set
{ {
if (value > MaximumValue || value < MinimumValue) if (value > MaximumValue || value < MinimumValue)
throw new ArgumentOutOfRangeException("SelectedValue"); throw new ArgumentOutOfRangeException(nameof(value));
var sel = CoerceSelected(value); var sel = CoerceSelected(value);
_selectedValue = sel; _selectedValue = sel;
@ -195,7 +195,7 @@ namespace Avalonia.Controls.Primitives
set set
{ {
if (value <= 0 || value > _range) if (value <= 0 || value > _range)
throw new ArgumentOutOfRangeException("Increment"); throw new ArgumentOutOfRangeException(nameof(value));
_increment = value; _increment = value;
UpdateHelperInfo(); UpdateHelperInfo();
var sel = CoerceSelected(SelectedValue); var sel = CoerceSelected(SelectedValue);

4
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -1113,11 +1113,11 @@ namespace Avalonia.Controls
} }
if (value < Minimum) if (value < Minimum)
{ {
throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be greater than Minimum value of {0}", Minimum)); throw new ArgumentOutOfRangeException(nameof(value), $"Value must be greater than Minimum value of {Minimum}");
} }
else if (value > Maximum) else if (value > Maximum)
{ {
throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be less than Maximum value of {0}", Maximum)); throw new ArgumentOutOfRangeException(nameof(value), $"Value must be less than Maximum value of {Maximum}");
} }
} }

10
src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs

@ -98,14 +98,14 @@ namespace Avalonia.Controls.PullToRefresh
if (_scrollViewer.Content == null) if (_scrollViewer.Content == null)
{ {
throw new ArgumentException(nameof(adaptee), "Adaptee's content property cannot be null."); throw new ArgumentException("Adaptee's content property cannot be null.", nameof(adaptee));
} }
var content = adaptee.Content as Visual; var content = adaptee.Content as Visual;
if (content == null) if (content == null)
{ {
throw new ArgumentException(nameof(adaptee), "Adaptee's content property must be a Visual"); throw new ArgumentException("Adaptee's content property must be a Visual", nameof(adaptee));
} }
if (content.GetVisualParent() == null) if (content.GetVisualParent() == null)
@ -118,7 +118,7 @@ namespace Avalonia.Controls.PullToRefresh
if (content.Parent is not InputElement) if (content.Parent is not InputElement)
{ {
throw new ArgumentException(nameof(adaptee), "Adaptee's content's parent must be a InputElement"); throw new ArgumentException("Adaptee's content's parent must be a InputElement", nameof(adaptee));
} }
} }
@ -194,12 +194,12 @@ namespace Avalonia.Controls.PullToRefresh
var content = _scrollViewer?.Content as Visual; var content = _scrollViewer?.Content as Visual;
if (content == null) if (content == null)
{ {
throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual"); throw new ArgumentException("Adaptee's content property must be a Visual", nameof(_scrollViewer));
} }
if (content.Parent is not InputElement parent) if (content.Parent is not InputElement parent)
{ {
throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement"); throw new ArgumentException("Adaptee's content parent must be an InputElement", nameof(_scrollViewer));
} }
MakeInteractionSource(parent); MakeInteractionSource(parent);

2
src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls.Utils
get get
{ {
if(index < 0 || index >= Count) if(index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index"); throw new ArgumentOutOfRangeException(nameof(index));
index += _start; index += _start;

8
src/Avalonia.Remote.Protocol/MetsysBson.cs

@ -752,7 +752,7 @@ namespace Metsys.Bson
if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert) if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert)
{ {
throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), nameof(lambdaExpression)); throw new ArgumentException($"Expression '{lambdaExpression}' must resolve to top-level member.", nameof(lambdaExpression));
} }
return memberExpression.Member.Name; return memberExpression.Member.Name;
default: default:
@ -942,7 +942,7 @@ namespace Metsys.Bson
return new ListWrapper(); return new ListWrapper();
} }
} }
throw new BsonException(string.Format("Collection of type {0} cannot be deserialized", type.FullName)); throw new BsonException($"Collection of type {type.FullName} cannot be deserialized");
} }
public abstract void Add(object value); public abstract void Add(object value);
@ -1514,7 +1514,7 @@ namespace Metsys.Bson.Configuration
result = Visit((MemberExpression)expression.Left); result = Visit((MemberExpression)expression.Left);
} }
var index = Expression.Lambda(expression.Right).Compile().DynamicInvoke(); var index = Expression.Lambda(expression.Right).Compile().DynamicInvoke();
return result + string.Format("[{0}]", index); return result + $"[{index}]";
} }
private string Visit(MemberExpression expression) private string Visit(MemberExpression expression)
@ -1540,7 +1540,7 @@ namespace Metsys.Bson.Configuration
if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1) if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1)
{ {
var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke(); var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke();
name += string.Format("[{0}]", index); name += $"[{index}]";
} }
return name; return name;
} }

5
src/Avalonia.X11/X11Structs.cs

@ -1109,7 +1109,7 @@ namespace Avalonia.X11 {
public override string ToString () public override string ToString ()
{ {
return string.Format("MotifWmHints <flags={0}, functions={1}, decorations={2}, input_mode={3}, status={4}", (MotifFlags) flags.ToInt32 (), (MotifFunctions) functions.ToInt32 (), (MotifDecorations) decorations.ToInt32 (), (MotifInputMode) input_mode.ToInt32 (), status.ToInt32 ()); return $"MotifWmHints <flags={(MotifFlags)flags.ToInt32()}, functions={(MotifFunctions)functions.ToInt32()}, decorations={(MotifDecorations)decorations.ToInt32()}, input_mode={(MotifInputMode)input_mode.ToInt32()}, status={status.ToInt32()}";
} }
} }
@ -1707,8 +1707,7 @@ namespace Avalonia.X11 {
public override string ToString () public override string ToString ()
{ {
return string.Format ("XCursorImage (version: {0}, size: {1}, width: {2}, height: {3}, xhot: {4}, yhot: {5}, delay: {6}, pixels: {7}", return $"XCursorImage (version: {version}, size: {size}, width: {width}, height: {height}, xhot: {xhot}, yhot: {yhot}, delay: {delay}, pixels: {pixels}";
version, size, width, height, xhot, yhot, delay, pixels);
} }
} ; } ;

3
src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs

@ -33,8 +33,7 @@ namespace Avalonia.Win32.Automation
return null; return null;
var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y)); var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y));
var peer = (WindowBaseAutomationPeer)Peer; var found = InvokeSync(() => Peer.GetPeerFromPoint(p));
var found = InvokeSync(() => peer.GetPeerFromPoint(p));
var result = GetOrCreate(found) as IRawElementProviderFragment; var result = GetOrCreate(found) as IRawElementProviderFragment;
return result; return result;
} }

2
tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs

@ -111,7 +111,7 @@ namespace Avalonia.Base.UnitTests.Media
using(UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) using(UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
using (var glyphRun = CreateGlyphRun(advances, clusters, bidiLevel)) using (var glyphRun = CreateGlyphRun(advances, clusters, bidiLevel))
{ {
var characterHit = glyphRun.GetPreviousCaretCharacterHit(new CharacterHit(currentIndex, currentLength)); var characterHit = glyphRun.GetPreviousCaretCharacterHit(new CharacterHit(currentIndex + currentLength));
Assert.Equal(previousIndex, characterHit.FirstCharacterIndex); Assert.Equal(previousIndex, characterHit.FirstCharacterIndex);

116
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -194,7 +194,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
for (var i = 0; i < clusters.Count; i++) for (var i = 0; i < clusters.Count; i++)
{ {
var expectedCluster = clusters[i]; var expectedCluster = clusters[i];
var actualCluster = nextCharacterHit.FirstCharacterIndex; var actualCluster = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength;
Assert.Equal(expectedCluster, actualCluster); Assert.Equal(expectedCluster, actualCluster);
@ -278,16 +278,6 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(clusters[i], Assert.Equal(clusters[i],
previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength); previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
} }
firstCharacterHit = previousCharacterHit;
firstCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
Assert.Equal(0, previousCharacterHit.TrailingLength);
} }
} }
@ -728,6 +718,110 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
} }
} }
[Fact]
public void Should_GetNextCaretCharacterHit_From_Mixed_TextBuffer()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new MixedTextBufferTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(9, 1));
Assert.Equal(10, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(characterHit);
Assert.Equal(11, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(19, 1));
Assert.Equal(20, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(10));
Assert.Equal(11, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(characterHit);
Assert.Equal(12, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(20));
Assert.Equal(21, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
}
}
[Fact]
public void Should_GetPreviousCaretCharacterHit_From_Mixed_TextBuffer()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new MixedTextBufferTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(20, 1));
Assert.Equal(19, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(10, 1));
Assert.Equal(9, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(characterHit);
Assert.Equal(8, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(21));
Assert.Equal(20, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(11));
Assert.Equal(10, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(characterHit);
Assert.Equal(9, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
}
}
private class MixedTextBufferTextSource : ITextSource private class MixedTextBufferTextSource : ITextSource
{ {
public TextRun? GetTextRun(int textSourceIndex) public TextRun? GetTextRun(int textSourceIndex)

Loading…
Cancel
Save