diff --git a/.ncrunch/NativeEmbedSample.v3.ncrunchproject b/.ncrunch/NativeEmbedSample.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/NativeEmbedSample.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/Avalonia.sln b/Avalonia.sln
index 3a2c619d5b..f6dc039c2f 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
@@ -211,8 +211,8 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
- src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
- src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
diff --git a/nukebuild/Numerge b/nukebuild/Numerge
index 4464343aef..aef10ae67d 160000
--- a/nukebuild/Numerge
+++ b/nukebuild/Numerge
@@ -1 +1 @@
-Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8
+Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 2c5a09bee7..cd3ce9adcd 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -41,6 +41,10 @@
true
build\
+
+ true
+ build\
+
diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props
index 30bafa37ee..deea3aa391 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.props
+++ b/packages/Avalonia/AvaloniaBuildTasks.props
@@ -1,3 +1,11 @@
-
+
+
+
+
+
+
+
+
+
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 537495fcad..84a62bb5c0 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -4,6 +4,20 @@
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false
low
+
+
+
+
+ %(Filename)
+ Code
+
+
+ %(Filename)
+ Code
+
+
+
+
+ DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
+
+
+
+
diff --git a/packages/Avalonia/AvaloniaItemSchema.xaml b/packages/Avalonia/AvaloniaItemSchema.xaml
new file mode 100644
index 0000000000..a51ea3c0be
--- /dev/null
+++ b/packages/Avalonia/AvaloniaItemSchema.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/readme.md b/readme.md
index a9263d4816..6a04c7e31e 100644
--- a/readme.md
+++ b/readme.md
@@ -68,7 +68,7 @@ Avalonia is licenced under the [MIT licence](licence.md).
## Contributors
-This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
+This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing)].
### Backers
diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs
index ebafc8b946..56cde9738e 100644
--- a/src/Avalonia.Base/Threading/DispatcherTimer.cs
+++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority;
private TimeSpan _interval;
-
+
///
/// Initializes a new instance of the class.
///
@@ -154,6 +154,8 @@ namespace Avalonia.Threading
TimeSpan interval,
DispatcherPriority priority = DispatcherPriority.Normal)
{
+ interval = (interval != TimeSpan.Zero) ? interval : TimeSpan.FromTicks(1);
+
var timer = new DispatcherTimer(priority) { Interval = interval };
timer.Tick += (s, e) =>
@@ -197,7 +199,7 @@ namespace Avalonia.Threading
}
}
-
+
///
/// Raises the event on the dispatcher thread.
diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
index 406abe6f99..ae2bf99d1e 100644
--- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
+++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
@@ -107,7 +107,7 @@ namespace Avalonia.Build.Tasks
foreach (var s in sources.ToList())
{
- if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml"))
+ if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml"))
{
XamlFileInfo info;
try
@@ -150,7 +150,7 @@ namespace Avalonia.Build.Tasks
BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
- foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml")))
+ foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml")))
BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
"XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
var resources = BuildResourceSources();
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 3b69109e68..30e8f120d7 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -25,7 +25,8 @@ namespace Avalonia.Build.Tasks
public static partial class XamlCompilerTaskExecutor
{
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
- || r.Name.ToLowerInvariant().EndsWith(".paml");
+ || r.Name.ToLowerInvariant().EndsWith(".paml")
+ || r.Name.ToLowerInvariant().EndsWith(".axaml");
public class CompileResult
{
diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
index 8a21d7aa4b..5ff0fd1feb 100644
--- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
+++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Controls.Embedding
{
EnsureInitialized();
ApplyTemplate();
- LayoutManager.ExecuteInitialLayoutPass(this);
+ LayoutManager.ExecuteInitialLayoutPass();
}
private void EnsureInitialized()
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
index d326ab5734..b037dd9901 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
EnsureInitialized();
ApplyTemplate();
- LayoutManager.ExecuteInitialLayoutPass(this);
+ LayoutManager.ExecuteInitialLayoutPass();
}
private void EnsureInitialized()
diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs
index b705a518ff..0d22187b34 100644
--- a/src/Avalonia.Controls/Repeater/ViewportManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs
@@ -49,8 +49,8 @@ namespace Avalonia.Controls
// For non-virtualizing layouts, we do not need to keep
// updating viewports and invalidating measure often. So when
// a non virtualizing layout is used, we stop doing all that work.
- bool _managingViewportDisabled;
- private IDisposable _effectiveViewportChangedRevoker;
+ private bool _managingViewportDisabled;
+ private bool _effectiveViewportChangedSubscribed;
private bool _layoutUpdatedSubscribed;
public ViewportManager(ItemsRepeater owner)
@@ -228,11 +228,15 @@ namespace Avalonia.Controls
_pendingViewportShift = default;
_unshiftableShift = default;
- _effectiveViewportChangedRevoker?.Dispose();
-
- if (!_managingViewportDisabled)
+ if (_managingViewportDisabled && _effectiveViewportChangedSubscribed)
{
- _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
+ _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
+ _effectiveViewportChangedSubscribed = false;
+ }
+ else if (!_managingViewportDisabled && !_effectiveViewportChangedSubscribed)
+ {
+ _owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
+ _effectiveViewportChangedSubscribed = true;
}
}
@@ -340,6 +344,11 @@ namespace Avalonia.Controls
// Note that the element being brought into view could be a descendant.
var targetChild = GetImmediateChildOfRepeater((IControl)args.TargetObject);
+ if (targetChild is null)
+ {
+ return;
+ }
+
// Make sure that only the target child can be the anchor during the bring into view operation.
foreach (var child in _owner.Children)
{
@@ -373,7 +382,7 @@ namespace Avalonia.Controls
if (parent == null)
{
- throw new InvalidOperationException("OnBringIntoViewRequested called with args.target element not under the ItemsRepeater that recieved the call");
+ return null;
}
return targetChild;
@@ -415,15 +424,15 @@ namespace Avalonia.Controls
_scroller = null;
}
- _effectiveViewportChangedRevoker?.Dispose();
- _effectiveViewportChangedRevoker = null;
+ _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
+ _effectiveViewportChangedSubscribed = false;
_ensuredScroller = false;
}
- private void OnEffectiveViewportChanged(Rect effectiveViewport)
+ private void OnEffectiveViewportChanged(object sender, EffectiveViewportChangedEventArgs e)
{
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId);
- UpdateViewport(effectiveViewport);
+ UpdateViewport(e.EffectiveViewport);
_pendingViewportShift = default;
_unshiftableShift = default;
@@ -468,8 +477,8 @@ namespace Avalonia.Controls
}
else if (!_managingViewportDisabled)
{
- _effectiveViewportChangedRevoker?.Dispose();
- _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
+ _owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
+ _effectiveViewportChangedSubscribed = true;
}
_ensuredScroller = true;
@@ -529,38 +538,6 @@ namespace Avalonia.Controls
}
}
- private IDisposable SubscribeToEffectiveViewportChanged(IControl control)
- {
- // HACK: This is a bit of a hack. We need the effective viewport of the ItemsRepeater -
- // we can get this from TransformedBounds, but this property is updated after layout has
- // run, which is too late. Instead, for now lets just hook into an internal event on
- // ScrollContentPresenter to find out what the offset and viewport will be after arrange
- // and use those values. Note that this doesn't handle nested ScrollViewers at all, but
- // it's enough to get scrolling to non-uniformly sized items working for now.
- //
- // UWP uses the EffectiveViewportChanged event (which I think was implemented specially
- // for this case): we need to implement that in Avalonia, but the semantics of it aren't
- // clear to me. Hopefully the source for this event will be released with WinUI 3.
- if (control.VisualParent is ScrollContentPresenter scp)
- {
- scp.PreArrange += ScrollContentPresenterPreArrange;
- return Disposable.Create(() => scp.PreArrange -= ScrollContentPresenterPreArrange);
- }
-
- return Disposable.Empty;
- }
-
- private void ScrollContentPresenterPreArrange(object sender, VectorEventArgs e)
- {
- var scp = (ScrollContentPresenter)sender;
- var effectiveViewport = new Rect((Point)scp.Offset, new Size(e.Vector.X, e.Vector.Y));
-
- if (effectiveViewport != _visibleWindow)
- {
- OnEffectiveViewportChanged(effectiveViewport);
- }
- }
-
private class ScrollerInfo
{
public ScrollerInfo(ScrollViewer scroller)
diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs
index ff1c0260bb..aa6552579f 100644
--- a/src/Avalonia.Controls/SelectionModel.cs
+++ b/src/Avalonia.Controls/SelectionModel.cs
@@ -189,8 +189,6 @@ namespace Avalonia.Controls
}
set
{
- var isSelected = IsSelectedWithPartialAt(value);
-
if (!IsSelectedAt(value) || SelectedItems.Count > 1)
{
using var operation = new Operation(this);
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index c5491746a3..f058942116 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -318,7 +318,7 @@ namespace Avalonia.Controls
///
/// Creates the layout manager for this .
///
- protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager();
+ protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
///
/// Handles a paint notification from .
@@ -340,6 +340,9 @@ namespace Avalonia.Controls
_globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
}
+ Renderer?.Dispose();
+ Renderer = null;
+
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
@@ -349,8 +352,8 @@ namespace Avalonia.Controls
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
OnClosed(EventArgs.Empty);
- Renderer?.Dispose();
- Renderer = null;
+
+ LayoutManager?.Dispose();
}
///
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index ff7cc41e3b..cedd20ace5 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -519,7 +519,7 @@ namespace Avalonia.Controls
}
}
- LayoutManager.ExecuteInitialLayoutPass(this);
+ LayoutManager.ExecuteInitialLayoutPass();
using (BeginAutoSizing())
{
@@ -592,7 +592,7 @@ namespace Avalonia.Controls
}
}
- LayoutManager.ExecuteInitialLayoutPass(this);
+ LayoutManager.ExecuteInitialLayoutPass();
var result = new TaskCompletionSource();
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index afc01db506..eb6e7319f5 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -162,7 +162,7 @@ namespace Avalonia.Controls
if (!_hasExecutedInitialLayoutPass)
{
- LayoutManager.ExecuteInitialLayoutPass(this);
+ LayoutManager.ExecuteInitialLayoutPass();
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show();
diff --git a/src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs b/src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs
new file mode 100644
index 0000000000..1cdc775b13
--- /dev/null
+++ b/src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Avalonia.Layout
+{
+ ///
+ /// Provides data for the event.
+ ///
+ public class EffectiveViewportChangedEventArgs : EventArgs
+ {
+ public EffectiveViewportChangedEventArgs(Rect effectiveViewport)
+ {
+ EffectiveViewport = effectiveViewport;
+ }
+
+ ///
+ /// Gets the representing the effective viewport.
+ ///
+ ///
+ /// The viewport is expressed in coordinates relative to the control that the event is
+ /// raised on.
+ ///
+ public Rect EffectiveViewport { get; }
+ }
+}
diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs
index 6e63d3edbb..614670a53b 100644
--- a/src/Avalonia.Layout/ILayoutManager.cs
+++ b/src/Avalonia.Layout/ILayoutManager.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- public interface ILayoutManager
+ public interface ILayoutManager : IDisposable
{
///
/// Raised when the layout manager completes a layout pass.
@@ -35,6 +35,15 @@ namespace Avalonia.Layout
///
void ExecuteLayoutPass();
+ ///
+ /// Executes the initial layout pass on a layout root.
+ ///
+ ///
+ /// You should not usually need to call this method explictly, the layout root will call
+ /// it to carry out the initial layout of the control.
+ ///
+ void ExecuteInitialLayoutPass();
+
///
/// Executes the initial layout pass on a layout root.
///
@@ -43,6 +52,19 @@ namespace Avalonia.Layout
/// You should not usually need to call this method explictly, the layout root will call
/// it to carry out the initial layout of the control.
///
+ [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
void ExecuteInitialLayoutPass(ILayoutRoot root);
+
+ ///
+ /// Registers a control as wanting to receive effective viewport notifications.
+ ///
+ /// The control.
+ void RegisterEffectiveViewportListener(ILayoutable control);
+
+ ///
+ /// Registers a control as no longer wanting to receive effective viewport notifications.
+ ///
+ /// The control.
+ void UnregisterEffectiveViewportListener(ILayoutable control);
}
}
diff --git a/src/Avalonia.Layout/ILayoutable.cs b/src/Avalonia.Layout/ILayoutable.cs
index 316a017f1d..54d3ba6a11 100644
--- a/src/Avalonia.Layout/ILayoutable.cs
+++ b/src/Avalonia.Layout/ILayoutable.cs
@@ -111,5 +111,12 @@ namespace Avalonia.Layout
///
/// The child control.
void ChildDesiredSizeChanged(ILayoutable control);
+
+ ///
+ /// Used by the to notify the control that its effective
+ /// viewport is changed.
+ ///
+ /// The viewport information.
+ void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e);
}
}
diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs
index aefb319fd0..fc988a8d6c 100644
--- a/src/Avalonia.Layout/LayoutManager.cs
+++ b/src/Avalonia.Layout/LayoutManager.cs
@@ -1,7 +1,10 @@
using System;
+using System.Buffers;
+using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
+using Avalonia.VisualTree;
#nullable enable
@@ -10,16 +13,21 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- public class LayoutManager : ILayoutManager
+ public class LayoutManager : ILayoutManager, IDisposable
{
+ private const int MaxPasses = 3;
+ private readonly ILayoutRoot _owner;
private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid);
private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid);
private readonly Action _executeLayoutPass;
+ private List? _effectiveViewportChangedListeners;
+ private bool _disposed;
private bool _queued;
private bool _running;
- public LayoutManager()
+ public LayoutManager(ILayoutRoot owner)
{
+ _owner = owner ?? throw new ArgumentNullException(nameof(owner));
_executeLayoutPass = ExecuteLayoutPass;
}
@@ -31,6 +39,11 @@ namespace Avalonia.Layout
control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess();
+ if (_disposed)
+ {
+ return;
+ }
+
if (!control.IsAttachedToVisualTree)
{
#if DEBUG
@@ -41,6 +54,11 @@ namespace Avalonia.Layout
#endif
}
+ if (control.VisualRoot != _owner)
+ {
+ throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
+ }
+
_toMeasure.Enqueue(control);
_toArrange.Enqueue(control);
QueueLayoutPass();
@@ -52,6 +70,11 @@ namespace Avalonia.Layout
control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess();
+ if (_disposed)
+ {
+ return;
+ }
+
if (!control.IsAttachedToVisualTree)
{
#if DEBUG
@@ -62,6 +85,11 @@ namespace Avalonia.Layout
#endif
}
+ if (control.VisualRoot != _owner)
+ {
+ throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
+ }
+
_toArrange.Enqueue(control);
QueueLayoutPass();
}
@@ -69,14 +97,15 @@ namespace Avalonia.Layout
///
public virtual void ExecuteLayoutPass()
{
- const int MaxPasses = 3;
-
Dispatcher.UIThread.VerifyAccess();
- if (!_running)
+ if (_disposed)
{
- _running = true;
+ return;
+ }
+ if (!_running)
+ {
Stopwatch? stopwatch = null;
const LogEventLevel timingLogLevel = LogEventLevel.Information;
@@ -99,12 +128,13 @@ namespace Avalonia.Layout
try
{
+ _running = true;
+
for (var pass = 0; pass < MaxPasses; ++pass)
{
- ExecuteMeasurePass();
- ExecuteArrangePass();
+ InnerLayoutPass();
- if (_toMeasure.Count == 0)
+ if (!RaiseEffectiveViewportChanged())
{
break;
}
@@ -131,13 +161,18 @@ namespace Avalonia.Layout
}
///
- public virtual void ExecuteInitialLayoutPass(ILayoutRoot root)
+ public virtual void ExecuteInitialLayoutPass()
{
+ if (_disposed)
+ {
+ return;
+ }
+
try
{
_running = true;
- Measure(root);
- Arrange(root);
+ Measure(_owner);
+ Arrange(_owner);
}
finally
{
@@ -151,6 +186,60 @@ namespace Avalonia.Layout
ExecuteLayoutPass();
}
+ [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
+ public void ExecuteInitialLayoutPass(ILayoutRoot root)
+ {
+ if (root != _owner)
+ {
+ throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root.");
+ }
+
+ ExecuteInitialLayoutPass();
+ }
+
+ public void Dispose()
+ {
+ _disposed = true;
+ _toMeasure.Dispose();
+ _toArrange.Dispose();
+ }
+
+ void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control)
+ {
+ _effectiveViewportChangedListeners ??= new List();
+ _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener(
+ control,
+ CalculateEffectiveViewport(control)));
+ }
+
+ void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control)
+ {
+ if (_effectiveViewportChangedListeners is object)
+ {
+ for (var i = _effectiveViewportChangedListeners.Count - 1; i >= 0; --i)
+ {
+ if (_effectiveViewportChangedListeners[i].Listener == control)
+ {
+ _effectiveViewportChangedListeners.RemoveAt(i);
+ }
+ }
+ }
+ }
+
+ private void InnerLayoutPass()
+ {
+ for (var pass = 0; pass < MaxPasses; ++pass)
+ {
+ ExecuteMeasurePass();
+ ExecuteArrangePass();
+
+ if (_toMeasure.Count == 0)
+ {
+ break;
+ }
+ }
+ }
+
private void ExecuteMeasurePass()
{
while (_toMeasure.Count > 0)
@@ -234,5 +323,97 @@ namespace Avalonia.Layout
_queued = true;
}
}
+
+ private bool RaiseEffectiveViewportChanged()
+ {
+ var startCount = _toMeasure.Count + _toArrange.Count;
+
+ if (_effectiveViewportChangedListeners is object)
+ {
+ var count = _effectiveViewportChangedListeners.Count;
+ var pool = ArrayPool.Shared;
+ var listeners = pool.Rent(count);
+
+ _effectiveViewportChangedListeners.CopyTo(listeners);
+
+ try
+ {
+ for (var i = 0; i < count; ++i)
+ {
+ var l = _effectiveViewportChangedListeners[i];
+
+ if (!l.Listener.IsAttachedToVisualTree)
+ {
+ continue;
+ }
+
+ var viewport = CalculateEffectiveViewport(l.Listener);
+
+ if (viewport != l.Viewport)
+ {
+ l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport));
+ _effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport);
+ }
+ }
+ }
+ finally
+ {
+ pool.Return(listeners, clearArray: true);
+ }
+ }
+
+ return startCount != _toMeasure.Count + _toArrange.Count;
+ }
+
+ private Rect CalculateEffectiveViewport(IVisual control)
+ {
+ var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity);
+ CalculateEffectiveViewport(control, control, ref viewport);
+ return viewport;
+ }
+
+ private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport)
+ {
+ // Recurse until the top level control.
+ if (control.VisualParent is object)
+ {
+ CalculateEffectiveViewport(target, control.VisualParent, ref viewport);
+ }
+ else
+ {
+ viewport = new Rect(control.Bounds.Size);
+ }
+
+ // Apply the control clip bounds if it's not the target control. We don't apply it to
+ // the target control because it may itself be clipped to bounds and if so the viewport
+ // we calculate would be of no use.
+ if (control != target && control.ClipToBounds)
+ {
+ viewport = control.Bounds.Intersect(viewport);
+ }
+
+ // Translate the viewport into this control's coordinate space.
+ viewport = viewport.Translate(-control.Bounds.Position);
+
+ if (control != target && control.RenderTransform is object)
+ {
+ var origin = control.RenderTransformOrigin.ToPixels(control.Bounds.Size);
+ var offset = Matrix.CreateTranslation(origin);
+ var renderTransform = (-offset) * control.RenderTransform.Value.Invert() * (offset);
+ viewport = viewport.TransformToAABB(renderTransform);
+ }
+ }
+
+ private readonly struct EffectiveViewportChangedListener
+ {
+ public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport)
+ {
+ Listener = listener;
+ Viewport = viewport;
+ }
+
+ public ILayoutable Listener { get; }
+ public Rect Viewport { get; }
+ }
}
}
diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs
index e261a1b48e..1a9eb6b785 100644
--- a/src/Avalonia.Layout/LayoutQueue.cs
+++ b/src/Avalonia.Layout/LayoutQueue.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Avalonia.Layout
{
- internal class LayoutQueue : IReadOnlyCollection
+ internal class LayoutQueue : IReadOnlyCollection, IDisposable
{
private struct Info
{
@@ -84,5 +84,12 @@ namespace Avalonia.Layout
_notFinalizedBuffer.Clear();
}
+
+ public void Dispose()
+ {
+ _inner.Clear();
+ _loopQueueInfo.Clear();
+ _notFinalizedBuffer.Clear();
+ }
}
}
diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs
index 8d2a825fa0..e62e22f8ec 100644
--- a/src/Avalonia.Layout/Layoutable.cs
+++ b/src/Avalonia.Layout/Layoutable.cs
@@ -132,6 +132,7 @@ namespace Avalonia.Layout
private bool _measuring;
private Size? _previousMeasure;
private Rect? _previousArrange;
+ private EventHandler? _effectiveViewportChanged;
private EventHandler? _layoutUpdated;
///
@@ -152,6 +153,32 @@ namespace Avalonia.Layout
VerticalAlignmentProperty);
}
+ ///
+ /// Occurs when the element's effective viewport changes.
+ ///
+ public event EventHandler? EffectiveViewportChanged
+ {
+ add
+ {
+ if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+ {
+ r.LayoutManager.RegisterEffectiveViewportListener(this);
+ }
+
+ _effectiveViewportChanged += value;
+ }
+
+ remove
+ {
+ _effectiveViewportChanged -= value;
+
+ if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+ {
+ r.LayoutManager.UnregisterEffectiveViewportListener(this);
+ }
+ }
+ }
+
///
/// Occurs when a layout pass completes for the control.
///
@@ -384,13 +411,6 @@ namespace Avalonia.Layout
}
}
- ///
- /// Called by InvalidateMeasure
- ///
- protected virtual void OnMeasureInvalidated()
- {
- }
-
///
/// Invalidates the measurement of the control and queues a new layout pass.
///
@@ -436,6 +456,11 @@ namespace Avalonia.Layout
}
}
+ void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e)
+ {
+ _effectiveViewportChanged?.Invoke(this, e);
+ }
+
///
/// Marks a property as affecting the control's measurement.
///
@@ -717,9 +742,17 @@ namespace Avalonia.Layout
{
base.OnAttachedToVisualTreeCore(e);
- if (_layoutUpdated is object && e.Root is ILayoutRoot r)
+ if (e.Root is ILayoutRoot r)
{
- r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
+ if (_layoutUpdated is object)
+ {
+ r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
+ }
+
+ if (_effectiveViewportChanged is object)
+ {
+ r.LayoutManager.RegisterEffectiveViewportListener(this);
+ }
}
}
@@ -727,12 +760,27 @@ namespace Avalonia.Layout
{
base.OnDetachedFromVisualTreeCore(e);
- if (_layoutUpdated is object && e.Root is ILayoutRoot r)
+ if (e.Root is ILayoutRoot r)
{
- r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
+ if (_layoutUpdated is object)
+ {
+ r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
+ }
+
+ if (_effectiveViewportChanged is object)
+ {
+ r.LayoutManager.UnregisterEffectiveViewportListener(this);
+ }
}
}
+ ///
+ /// Called by InvalidateMeasure
+ ///
+ protected virtual void OnMeasureInvalidated()
+ {
+ }
+
///
protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
index b12a639d31..b5d502787d 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
@@ -2,35 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
- #FF0078D7
- #FF000000
- #FF000000
- #FF000000
- #FF000000
- #FF000000
- #FFFFFFFF
- #FF333333
- #FF9A9A9A
- #FFB4B4B4
- #FF676767
- #FFB4B4B4
- #FF000000
- #FFB4B4B4
- #FF000000
- #FF000000
- #FF333333
- #FF808080
- #FF808080
- #FF151515
- #FF1D1D1D
- #FF2C2C2C
- #FFFFFFFF
- #FF1D1D1D
- #FF333333
- #CC000000
- #FF333333
- #FF1D1D1D
- #FF333333
+ #18FFFFFF
+ #30FFFFFF
@@ -67,13 +40,6 @@
1,1,1,1
1
-
- #FF005A9E
- #FF004275
- #FF002642
- #FF429CE3
- #FF76B9ED
- #FFA6D8FF
#FF000000
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
index 31c2f592b9..0806a6e9ef 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
@@ -2,36 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
- #FF0078D7
- #FFFFFFFF
- #FFFFFFFF
- #FFFFFFFF
- #FFFFFFFF
- #FFFFFFFF
- #FF000000
- #FFCCCCCC
- #FF898989
- #FF5D5D5D
- #FF737373
- #FF5D5D5D
- #FF000000
- #FFCCCCCC
- #FF5D5D5D
- #FF898989
- #FFCCCCCC
- #FF898989
- #FF737373
- #FFCCCCCC
- #FFECECEC
- #FFE6E6E6
- #FFECECEC
- #FFFFFFFF
- #FFE6E6E6
- #FFCCCCCC
- #CCFFFFFF
- #FFCCCCCC
- #FFE6E6E6
- #FFCCCCCC
+ #17000000
+ #2E000000
@@ -68,13 +40,6 @@
1,1,1,1
1
-
- #FF005A9E
- #FF004275
- #FF002642
- #FF429CE3
- #FF76B9ED
- #FFA6D8FF
#FFFFFFFF
diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
index a4eab83e4a..84bf799d8d 100644
--- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
+++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
new file mode 100644
index 0000000000..f0f3e5ea16
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
index 8776d3a7b7..a8e618af27 100644
--- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
@@ -1,6 +1,5 @@
using System;
using System.Reactive.Disposables;
-using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Immutable;
@@ -11,9 +10,9 @@ namespace Avalonia.Animation.Animators
///
public class SolidColorBrushAnimator : Animator
{
- ColorAnimator _colorAnimator;
+ private ColorAnimator _colorAnimator;
- void InitializeColorAnimator()
+ private void InitializeColorAnimator()
{
_colorAnimator = new ColorAnimator();
@@ -27,46 +26,44 @@ namespace Avalonia.Animation.Animators
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete)
{
+ // Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
foreach (var keyframe in this)
{
- if (keyframe.Value as ISolidColorBrush == null)
- return Disposable.Empty;
-
- // Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
- if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
+ if (keyframe.Value is ISolidColorBrush colorBrush)
{
- keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color;
+ keyframe.Value = colorBrush.Color;
+ }
+ else
+ {
+ return Disposable.Empty;
}
}
- // Add SCB if the target prop is empty.
- if (control.GetValue(Property) == null)
- control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
-
+ SolidColorBrush finalTarget;
var targetVal = control.GetValue(Property);
-
- // Continue if target prop is not empty & is a SolidColorBrush derivative.
- if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType()))
+ if (targetVal is null)
+ {
+ finalTarget = new SolidColorBrush(Colors.Transparent);
+ control.SetValue(Property, finalTarget);
+ }
+ else if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush)
+ {
+ finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color);
+ control.SetValue(Property, finalTarget);
+ }
+ else if (targetVal is ISolidColorBrush)
{
- if (_colorAnimator == null)
- InitializeColorAnimator();
-
- SolidColorBrush finalTarget;
-
- // If it's ISCB, change it back to SCB.
- if (targetVal.GetType() == typeof(ImmutableSolidColorBrush))
- {
- var col = (ImmutableSolidColorBrush)targetVal;
- targetVal = new SolidColorBrush(col.Color);
- control.SetValue(Property, targetVal);
- }
-
finalTarget = targetVal as SolidColorBrush;
-
- return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
}
+ else
+ {
+ return Disposable.Empty;
+ }
+
+ if (_colorAnimator == null)
+ InitializeColorAnimator();
- return Disposable.Empty;
+ return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
}
public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;
diff --git a/src/Avalonia.Visuals/Assets/GraphemeBreak.trie b/src/Avalonia.Visuals/Assets/GraphemeBreak.trie
index 704dea4e86..482bf9b44d 100644
Binary files a/src/Avalonia.Visuals/Assets/GraphemeBreak.trie and b/src/Avalonia.Visuals/Assets/GraphemeBreak.trie differ
diff --git a/src/Avalonia.Visuals/Assets/UnicodeData.trie b/src/Avalonia.Visuals/Assets/UnicodeData.trie
index 2e39745646..f96106a5fa 100644
Binary files a/src/Avalonia.Visuals/Assets/UnicodeData.trie and b/src/Avalonia.Visuals/Assets/UnicodeData.trie differ
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
index 03576a4c40..ad3cc9141b 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
@@ -2,6 +2,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
public enum BiDiClass
{
+ LeftToRight, //L
ArabicLetter, //AL
ArabicNumber, //AN
ParagraphSeparator, //B
@@ -11,7 +12,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
EuropeanSeparator, //ES
EuropeanTerminator, //ET
FirstStrongIsolate, //FSI
- LeftToRight, //L
LeftToRightEmbedding, //LRE
LeftToRightIsolate, //LRI
LeftToRightOverride, //LRO
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
index c13074711e..86d39a4283 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
@@ -4,38 +4,39 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
private static readonly byte[][] s_breakPairTable =
{
- new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4},
- new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1},
- new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1},
- new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,1,0,1,1,0,0,4,2,4,0,0,0,0,0,0,1,1,1},
+ new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4},
+ new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0},
+ new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
+ new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
};
public static PairBreakType Map(LineBreakClass first, LineBreakClass second)
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
index 684baae51f..71e4bce106 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
@@ -2,24 +2,20 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
public enum GraphemeBreakClass
{
- Control, //CN
- CR, //CR
- EBase, //EB
- EBaseGAZ, //EBG
- EModifier, //EM
- Extend, //EX
- GlueAfterZwj, //GAZ
- L, //L
- LF, //LF
- LV, //LV
- LVT, //LVT
- Prepend, //PP
- RegionalIndicator, //RI
- SpacingMark, //SM
- T, //T
- V, //V
- Other, //XX
- ZWJ, //ZWJ
- ExtendedPictographic
+ Other,
+ CR,
+ LF,
+ Control,
+ Extend,
+ ZWJ,
+ RegionalIndicator,
+ Prepend,
+ SpacingMark,
+ L,
+ V,
+ T,
+ LV,
+ LVT,
+ ExtendedPictographic,
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
index 925706dd4f..8b2e3f41e3 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
@@ -34,10 +34,11 @@ namespace Avalonia.Media.TextFormatting.Unicode
EBase, //EB
EModifier, //EM
ZWJ, //ZWJ
+ ContingentBreak, //CB
+ Unknown, //XX
Ambiguous, //AI
MandatoryBreak, //BK
- ContingentBreak, //CB
ConditionalJapaneseStarter, //CJ
CarriageReturn, //CR
LineFeed, //LF
@@ -45,6 +46,5 @@ namespace Avalonia.Media.TextFormatting.Unicode
ComplexContext, //SA
Surrogate, //SG
Space, //SP
- Unknown, //XX
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
index a11c008409..25a32bb1a3 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
@@ -95,7 +95,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
if (_nextClass.Value == LineBreakClass.MandatoryBreak)
{
- Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos);
+ _lastPos = _pos;
+ Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
return true;
}
@@ -108,6 +109,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
case PairBreakType.DI: // Direct break
shouldBreak = true;
+ _lastPos = _pos;
break;
case PairBreakType.IN: // possible indirect break
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
new file mode 100644
index 0000000000..388a7d257d
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
@@ -0,0 +1,178 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Media.TextFormatting.Unicode
+{
+ internal static class PropertyValueAliasHelper
+ {
+ private static readonly Dictionary