diff --git a/.gitmodules b/.gitmodules
index 9dbc50ef61..2d11fdfa9e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,5 +2,5 @@
path = nukebuild/Numerge
url = https://github.com/kekekeks/Numerge.git
[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
- path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
+ path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
url = https://github.com/kekekeks/XamlX.git
diff --git a/Avalonia.sln b/Avalonia.sln
index 4ab647a25e..ccaa41f552 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -123,10 +123,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
+ build\AndroidWorkarounds.props = build\AndroidWorkarounds.props
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
- build\BuildTargets.targets = build\BuildTargets.targets
+ build\CoreLibraries.props = build\CoreLibraries.props
+ build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
+ build\iOSWorkarounds.props = build\iOSWorkarounds.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@@ -136,17 +139,24 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props
build\ReactiveUI.props = build\ReactiveUI.props
+ build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props
build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props
+ build\SharedVersion.props = build\SharedVersion.props
build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.props = build\SkiaSharp.props
+ build\SourceLink.props = build\SourceLink.props
+ build\System.Drawing.Common.props = build\System.Drawing.Common.props
build\System.Memory.props = build\System.Memory.props
+ build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
ProjectSection(SolutionItems) = preProject
build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
+ build\BuildTargets.targets = build\BuildTargets.targets
+ build\LegacyProject.targets = build\LegacyProject.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"
@@ -211,6 +221,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Av
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1998,6 +2010,30 @@ Global
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.Build.0 = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.Build.0 = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2056,6 +2092,7 @@ Global
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+ {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution
index a2208a9a91..bef7e45524 100644
--- a/Avalonia.v3.ncrunchsolution
+++ b/Avalonia.v3.ncrunchsolution
@@ -3,6 +3,7 @@
tests\TestFiles\**.*
src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll
+ src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll
True
.ncrunch
diff --git a/Directory.Build.props b/Directory.Build.props
index 1f26df9bbc..b41f8c488e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,6 @@
$(MSBuildThisFileDirectory)build-intermediate/nuget
+ $(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll
diff --git a/dirs.proj b/dirs.proj
index 4b3b1183f0..26c8f54b23 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -6,7 +6,7 @@
-
+
diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm
index 278daf9a18..455cfa2e41 100644
--- a/native/Avalonia.Native/src/OSX/Screens.mm
+++ b/native/Avalonia.Native/src/OSX/Screens.mm
@@ -4,6 +4,14 @@ class Screens : public ComSingleObject
{
public:
FORWARD_IUNKNOWN()
+
+ private:
+ CGFloat PrimaryDisplayHeight()
+ {
+ return NSMaxY([[[NSScreen screens] firstObject] frame]);
+ }
+
+public:
virtual HRESULT GetScreenCount (int* ret) override
{
@autoreleasepool
@@ -25,15 +33,15 @@ class Screens : public ComSingleObject
auto screen = [[NSScreen screens] objectAtIndex:index];
- ret->Bounds.X = [screen frame].origin.x;
- ret->Bounds.Y = [screen frame].origin.y;
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
+ ret->Bounds.X = [screen frame].origin.x;
+ ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
- ret->WorkingArea.X = [screen visibleFrame].origin.x;
- ret->WorkingArea.Y = [screen visibleFrame].origin.y;
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
+ ret->WorkingArea.X = [screen visibleFrame].origin.x;
+ ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
ret->PixelDensity = [screen backingScaleFactor];
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 872269bb26..2d0ffbe4f0 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
- _lastPixelSize.Width = (int)fsize.width;
- _lastPixelSize.Height = (int)fsize.height;
- [self updateRenderTarget];
- _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
+
+ if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
+ {
+ _lastPixelSize.Width = (int)fsize.width;
+ _lastPixelSize.Height = (int)fsize.height;
+ [self updateRenderTarget];
+
+ _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
+ }
}
- (void)updateLayer
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 0baa6589c6..3f1ec289d1 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -29,11 +29,19 @@ namespace ControlCatalog
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
- Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
- Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
+ },
+ new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml")
+ },
+ new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
},
};
@@ -41,11 +49,19 @@ namespace ControlCatalog
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
- Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
+ },
+ new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
+ },
+ new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
+ {
+ Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
- Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
+ Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
},
};
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index fa4fd7dd07..af95e3c356 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -2,7 +2,6 @@
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.MainView"
- Background="Black"
Foreground="{DynamicResource ThemeForegroundBrush}"
FontSize="{DynamicResource FontSizeNormal}">
diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
index 08bb1584a9..304782dbf9 100644
--- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
+++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
@@ -1,6 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ItemsRepeater
@@ -12,8 +36,6 @@
Stack - Horizontal
UniformGrid - Vertical
UniformGrid - Horizontal
- WrapLayout - Horizontal
- WrapLayout - Veritcal
@@ -25,20 +47,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
index a4003cadc9..9e898c4536 100644
--- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
@@ -38,6 +38,12 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
+ public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
+ {
+ var item = (ItemsRepeaterPageViewModel.Item)e.DataContext;
+ e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
+ }
+
private void LayoutChanged(object sender, SelectionChangedEventArgs e)
{
if (_repeater == null)
diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml
index 47b4ce7151..f4d81418ac 100644
--- a/samples/ControlCatalog/Pages/ListBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml
@@ -10,7 +10,13 @@
HorizontalAlignment="Center"
Spacing="16">
-
+
diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml
index de9ea34e80..e9d2301e89 100644
--- a/samples/ControlCatalog/Pages/MenuPage.xaml
+++ b/samples/ControlCatalog/Pages/MenuPage.xaml
@@ -20,7 +20,9 @@
@@ -52,6 +54,33 @@
+
+
+
+ Mixed
+
+
diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
index 73aaeff994..f893a6e28e 100644
--- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
@@ -62,13 +62,9 @@ namespace ControlCatalog.ViewModels
public class Item : ReactiveObject
{
private double _height = double.NaN;
- private int _index;
-
- public Item(int index)
- {
- _index = index;
- }
+ public Item(int index) => Index = index;
+ public int Index { get; }
public string Text { get; set; }
public double Height
@@ -76,8 +72,6 @@ namespace ControlCatalog.ViewModels
get => _height;
set => this.RaiseAndSetIfChanged(ref _height, value);
}
-
- public IBrush Background => ((_index % 2) == 0) ? Brushes.Yellow : Brushes.Wheat;
}
}
}
diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
index dc9c4a8f49..9e7ae8b716 100644
--- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
@@ -17,6 +17,23 @@ namespace ControlCatalog.ViewModels
SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false));
OpenRecentCommand = ReactiveCommand.Create(OpenRecent);
+ var recentItems = new[]
+ {
+ new MenuItemViewModel
+ {
+ Header = "File1.txt",
+ Command = OpenRecentCommand,
+ CommandParameter = @"c:\foo\File1.txt"
+ },
+ new MenuItemViewModel
+ {
+ Header = "File2.txt",
+ Command = OpenRecentCommand,
+ CommandParameter = @"c:\foo\File2.txt"
+ },
+ };
+
+ RecentItems = recentItems;
MenuItems = new[]
{
new MenuItemViewModel
@@ -24,27 +41,13 @@ namespace ControlCatalog.ViewModels
Header = "_File",
Items = new[]
{
- new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
+ new MenuItemViewModel { Header = "O_pen...", Command = OpenCommand },
new MenuItemViewModel { Header = "Save", Command = SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
- Items = new[]
- {
- new MenuItemViewModel
- {
- Header = "File1.txt",
- Command = OpenRecentCommand,
- CommandParameter = @"c:\foo\File1.txt"
- },
- new MenuItemViewModel
- {
- Header = "File2.txt",
- Command = OpenRecentCommand,
- CommandParameter = @"c:\foo\File2.txt"
- },
- }
+ Items = recentItems
},
}
},
@@ -61,6 +64,7 @@ namespace ControlCatalog.ViewModels
}
public IReadOnlyList MenuItems { get; set; }
+ public IReadOnlyList RecentItems { get; set; }
public ReactiveCommand OpenCommand { get; }
public ReactiveCommand SaveCommand { get; }
public ReactiveCommand OpenRecentCommand { get; }
diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml
index ccc3f54cc0..61e4d2385b 100644
--- a/samples/RenderDemo/App.xaml
+++ b/samples/RenderDemo/App.xaml
@@ -3,8 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.App">
-
-
+
-
\ No newline at end of file
+
diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml
index 07fdb91a16..fd23067f61 100644
--- a/samples/RenderDemo/SideBar.xaml
+++ b/samples/RenderDemo/SideBar.xaml
@@ -1,65 +1,68 @@
-
+
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 69fd2a9f13..71dce93ce7 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Visibility = ViewStates.Visible;
}
- public double Scaling => 1;
+ public double RenderScaling => 1;
void Draw()
{
diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
index 38a23f918f..40cf81358f 100644
--- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
+++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Runtime.ConstrainedExecution;
using System.Threading;
namespace Avalonia.Threading
@@ -7,6 +9,20 @@ namespace Avalonia.Threading
///
public class AvaloniaSynchronizationContext : SynchronizationContext
{
+ public interface INonPumpingPlatformWaitProvider
+ {
+ int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
+ }
+
+ private readonly INonPumpingPlatformWaitProvider _waitProvider;
+
+ public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider)
+ {
+ _waitProvider = waitProvider;
+ if (_waitProvider != null)
+ SetWaitNotificationRequired();
+ }
+
///
/// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer.
///
@@ -22,7 +38,8 @@ namespace Avalonia.Threading
return;
}
- SetSynchronizationContext(new AvaloniaSynchronizationContext());
+ SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current
+ .GetService()));
}
///
@@ -39,5 +56,13 @@ namespace Avalonia.Threading
else
Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
}
+
+ [PrePrepareMethod]
+ public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+ {
+ if (_waitProvider != null)
+ return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout);
+ return base.Wait(waitHandles, waitAll, millisecondsTimeout);
+ }
}
}
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 7117fd51a2..490364c0d8 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -17,14 +17,14 @@
Shared/AvaloniaResourceXamlInfo.cs
-
+
XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)
-
+
XamlIl/%(RecursiveDir)%(FileName)%(Extension)
-
+
XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)
@@ -57,8 +57,8 @@
Markup/%(RecursiveDir)%(FileName)%(Extension)
-
-
+
+
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index fe9656a00b..7e921944ea 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -3922,6 +3922,12 @@ namespace Avalonia.Controls
dataGridCell: editingCell);
EditingRow.InvalidateDesiredHeight();
+ var column = editingCell.OwningColumn;
+ if (column.Width.IsSizeToCells || column.Width.IsAuto)
+ {// Invalidate desired width and force recalculation
+ column.SetWidthDesiredValue(0);
+ EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width);
+ }
}
// We're done, so raise the CellEditEnded event
diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs
index 75d6c366b8..a86cbc271b 100644
--- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs
+++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs
@@ -38,12 +38,10 @@ namespace Avalonia.Controls.Chrome
{
if (_disposables != null)
{
- var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
-
- layer?.Children.Remove(this);
-
_disposables.Dispose();
_disposables = null;
+
+ _hostWindow = null;
}
}
diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs
index 78b49d2a03..c0c8076dd8 100644
--- a/src/Avalonia.Controls/Chrome/TitleBar.cs
+++ b/src/Avalonia.Controls/Chrome/TitleBar.cs
@@ -12,106 +12,86 @@ namespace Avalonia.Controls.Chrome
public class TitleBar : TemplatedControl
{
private CompositeDisposable? _disposables;
- private readonly Window? _hostWindow;
private CaptionButtons? _captionButtons;
- public TitleBar(Window hostWindow)
+ private void UpdateSize(Window window)
{
- _hostWindow = hostWindow;
- }
-
- public TitleBar()
- {
-
- }
-
- public void Attach()
- {
- if (_disposables == null)
- {
- var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
-
- layer?.Children.Add(this);
-
- if (_hostWindow != null)
- {
- _disposables = new CompositeDisposable
- {
- _hostWindow.GetObservable(Window.WindowDecorationMarginProperty)
- .Subscribe(x => UpdateSize()),
-
- _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
- .Subscribe(x => UpdateSize()),
-
- _hostWindow.GetObservable(Window.OffScreenMarginProperty)
- .Subscribe(x => UpdateSize()),
-
- _hostWindow.GetObservable(Window.WindowStateProperty)
- .Subscribe(x =>
- {
- PseudoClasses.Set(":minimized", x == WindowState.Minimized);
- PseudoClasses.Set(":normal", x == WindowState.Normal);
- PseudoClasses.Set(":maximized", x == WindowState.Maximized);
- PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
- })
- };
-
- _captionButtons?.Attach(_hostWindow);
- }
-
- UpdateSize();
- }
- }
-
- private void UpdateSize()
- {
- if (_hostWindow != null)
+ if (window != null)
{
Margin = new Thickness(
- _hostWindow.OffScreenMargin.Left,
- _hostWindow.OffScreenMargin.Top,
- _hostWindow.OffScreenMargin.Right,
- _hostWindow.OffScreenMargin.Bottom);
+ window.OffScreenMargin.Left,
+ window.OffScreenMargin.Top,
+ window.OffScreenMargin.Right,
+ window.OffScreenMargin.Bottom);
- if (_hostWindow.WindowState != WindowState.FullScreen)
+ if (window.WindowState != WindowState.FullScreen)
{
- Height = _hostWindow.WindowDecorationMargin.Top;
+ Height = window.WindowDecorationMargin.Top;
if (_captionButtons != null)
{
_captionButtons.Height = Height;
}
}
+
+ IsVisible = window.PlatformImpl.NeedsManagedDecorations;
}
}
- public void Detach()
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
- if (_disposables != null)
- {
- var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
-
- layer?.Children.Remove(this);
+ base.OnApplyTemplate(e);
- _disposables.Dispose();
- _disposables = null;
+ _captionButtons?.Detach();
+
+ _captionButtons = e.NameScope.Get("PART_CaptionButtons");
- _captionButtons?.Detach();
+ if (VisualRoot is Window window)
+ {
+ _captionButtons?.Attach(window);
+
+ UpdateSize(window);
}
}
- protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
- base.OnApplyTemplate(e);
-
- _captionButtons = e.NameScope.Get("PART_CaptionButtons");
+ base.OnAttachedToVisualTree(e);
- if (_hostWindow != null)
+ if (VisualRoot is Window window)
{
- _captionButtons.Attach(_hostWindow);
+ _disposables = new CompositeDisposable
+ {
+ window.GetObservable(Window.WindowDecorationMarginProperty)
+ .Subscribe(x => UpdateSize(window)),
+ window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
+ .Subscribe(x => UpdateSize(window)),
+ window.GetObservable(Window.OffScreenMarginProperty)
+ .Subscribe(x => UpdateSize(window)),
+ window.GetObservable(Window.ExtendClientAreaChromeHintsProperty)
+ .Subscribe(x => UpdateSize(window)),
+ window.GetObservable(Window.WindowStateProperty)
+ .Subscribe(x =>
+ {
+ PseudoClasses.Set(":minimized", x == WindowState.Minimized);
+ PseudoClasses.Set(":normal", x == WindowState.Normal);
+ PseudoClasses.Set(":maximized", x == WindowState.Maximized);
+ PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
+ }),
+ window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty)
+ .Subscribe(x => UpdateSize(window))
+ };
}
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
- UpdateSize();
+ _disposables?.Dispose();
+
+ _captionButtons?.Detach();
+ _captionButtons = null;
}
}
}
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 5d3311e8c6..a41c159980 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -88,7 +88,7 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect(nameof(SelectedDate),
x => x.SelectedDate, (x, v) => x.SelectedDate = v);
- //Template Items
+ // Template Items
private Button _flyoutButton;
private TextBlock _dayText;
private TextBlock _monthText;
@@ -359,10 +359,14 @@ namespace Avalonia.Controls
}
}
- Grid.SetColumn(_spacer1, 1);
- Grid.SetColumn(_spacer2, 3);
- _spacer1.IsVisible = columnIndex > 1;
- _spacer2.IsVisible = columnIndex > 2;
+ var isSpacer1Visible = columnIndex > 1;
+ var isSpacer2Visible = columnIndex > 2;
+ // ternary conditional operator is used to make sure grid cells will be validated
+ Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
+ Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+
+ _spacer1.IsVisible = isSpacer1Visible;
+ _spacer2.IsVisible = isSpacer2Visible;
}
private void SetSelectedDateText()
@@ -398,7 +402,7 @@ namespace Avalonia.Controls
var deltaY = _presenter.GetOffsetForPopup();
- //The extra 5 px I think is related to default popup placement behavior
+ // The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
index 8b86e46e88..31527ccb16 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
@@ -77,7 +77,7 @@ namespace Avalonia.Controls
DatePicker.YearVisibleProperty.AddOwner(x =>
x.YearVisible, (x, v) => x.YearVisible = v);
- //Template Items
+ // Template Items
private Grid _pickerContainer;
private Button _acceptButton;
private Button _dismissButton;
@@ -107,7 +107,7 @@ namespace Avalonia.Controls
private bool _yearVisible = true;
private DateTimeOffset _syncDate;
- private GregorianCalendar _calendar;
+ private readonly GregorianCalendar _calendar;
private bool _suppressUpdateSelection;
public DatePickerPresenter()
@@ -234,7 +234,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
- //These are requirements, so throw if not found
+ // These are requirements, so throw if not found
_pickerContainer = e.NameScope.Get("PickerContainer");
_monthHost = e.NameScope.Get("MonthHost");
_dayHost = e.NameScope.Get("DayHost");
@@ -326,7 +326,7 @@ namespace Avalonia.Controls
///
private void InitPicker()
{
- //OnApplyTemplate must've been called before we can init here...
+ // OnApplyTemplate must've been called before we can init here...
if (_pickerContainer == null)
return;
@@ -344,12 +344,11 @@ namespace Avalonia.Controls
SetGrid();
- //Date should've been set when we reach this point
+ // Date should've been set when we reach this point
var dt = Date;
if (DayVisible)
{
- GregorianCalendar gc = new GregorianCalendar();
- var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
+ var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month);
_daySelector.MaximumValue = maxDays;
_daySelector.MinimumValue = 1;
_daySelector.SelectedValue = dt.Day;
@@ -407,10 +406,14 @@ namespace Avalonia.Controls
}
}
- Grid.SetColumn(_spacer1, 1);
- Grid.SetColumn(_spacer2, 3);
- _spacer1.IsVisible = columnIndex > 1;
- _spacer2.IsVisible = columnIndex > 2;
+ var isSpacer1Visible = columnIndex > 1;
+ var isSpacer2Visible = columnIndex > 2;
+ // ternary conditional operator is used to make sure grid cells will be validated
+ Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
+ Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+
+ _spacer1.IsVisible = isSpacer1Visible;
+ _spacer2.IsVisible = isSpacer2Visible;
}
private void SetInitialFocus()
@@ -433,12 +436,12 @@ namespace Avalonia.Controls
}
}
- private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
{
OnDismiss();
}
- private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
{
Date = _syncDate;
OnConfirmed();
@@ -471,7 +474,7 @@ namespace Avalonia.Controls
_syncDate = newDate;
- //We don't need to update the days if not displaying day, not february
+ // We don't need to update the days if not displaying day, not february
if (!DayVisible || _syncDate.Month != 2)
return;
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
index 3fa426b1d3..e54da1fb3a 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Defines the property
///
public static readonly DirectProperty MinuteIncrementProperty =
- AvaloniaProperty.RegisterDirect(nameof(MinuteIncrement),
+ AvaloniaProperty.RegisterDirect(nameof(MinuteIncrement),
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
///
@@ -34,17 +34,17 @@ namespace Avalonia.Controls
/// Defines the property
///
public static readonly DirectProperty ClockIdentifierProperty =
- AvaloniaProperty.RegisterDirect(nameof(ClockIdentifier),
+ AvaloniaProperty.RegisterDirect(nameof(ClockIdentifier),
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
///
/// Defines the property
///
public static readonly DirectProperty SelectedTimeProperty =
- AvaloniaProperty.RegisterDirect(nameof(SelectedTime),
+ AvaloniaProperty.RegisterDirect(nameof(SelectedTime),
x => x.SelectedTime, (x, v) => x.SelectedTime = v);
- //Template Items
+ // Template Items
private TimePickerPresenter _presenter;
private Button _flyoutButton;
private Border _firstPickerHost;
@@ -52,7 +52,7 @@ namespace Avalonia.Controls
private Border _thirdPickerHost;
private TextBlock _hourText;
private TextBlock _minuteText;
- public TextBlock _periodText;
+ private TextBlock _periodText;
private Rectangle _firstSplitter;
private Rectangle _secondSplitter;
private Grid _contentGrid;
@@ -145,7 +145,7 @@ namespace Avalonia.Controls
if (_flyoutButton != null)
_flyoutButton.Click -= OnFlyoutButtonClicked;
- if(_presenter != null)
+ if (_presenter != null)
{
_presenter.Confirmed -= OnConfirmed;
_presenter.Dismissed -= OnDismissPicker;
@@ -170,7 +170,6 @@ namespace Avalonia.Controls
_popup = e.NameScope.Find("Popup");
_presenter = e.NameScope.Find("PickerPresenter");
-
if (_flyoutButton != null)
_flyoutButton.Click += OnFlyoutButtonClicked;
@@ -185,7 +184,6 @@ namespace Avalonia.Controls
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty];
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty];
}
-
}
private void SetGrid()
@@ -195,30 +193,19 @@ namespace Avalonia.Controls
bool use24HourClock = ClockIdentifier == "24HourClock";
- if (!use24HourClock)
- {
- _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
- _thirdPickerHost.IsVisible = true;
- _secondSplitter.IsVisible = true;
+ var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
+ _contentGrid.ColumnDefinitions = new ColumnDefinitions(columnsD);
- Grid.SetColumn(_firstPickerHost, 0);
- Grid.SetColumn(_secondPickerHost, 2);
- Grid.SetColumn(_thirdPickerHost, 4);
+ _thirdPickerHost.IsVisible = !use24HourClock;
+ _secondSplitter.IsVisible = !use24HourClock;
- Grid.SetColumn(_firstSplitter, 1);
- Grid.SetColumn(_secondSplitter, 3);
- }
- else
- {
- _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
- _thirdPickerHost.IsVisible = false;
- _secondSplitter.IsVisible = false;
+ Grid.SetColumn(_firstPickerHost, 0);
+ Grid.SetColumn(_secondPickerHost, 2);
- Grid.SetColumn(_firstPickerHost, 0);
- Grid.SetColumn(_secondPickerHost, 2);
+ Grid.SetColumn(_thirdPickerHost, use24HourClock ? 0 : 4);
- Grid.SetColumn(_firstSplitter, 1);
- }
+ Grid.SetColumn(_firstSplitter, 1);
+ Grid.SetColumn(_secondSplitter, use24HourClock ? 0 : 3);
}
private void SetSelectedTimeText()
@@ -237,14 +224,13 @@ namespace Avalonia.Controls
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
newTime = new TimeSpan(hr, newTime.Minutes, 0);
}
- _hourText.Text = newTime.ToString("%h");
+ _hourText.Text = newTime.ToString("%h");
_minuteText.Text = newTime.ToString("mm");
PseudoClasses.Set(":hasnotime", false);
_periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
-
}
else
{
@@ -262,7 +248,7 @@ namespace Avalonia.Controls
SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime));
}
- private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ private void OnFlyoutButtonClicked(object sender, Interactivity.RoutedEventArgs e)
{
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
@@ -270,7 +256,7 @@ namespace Avalonia.Controls
var deltaY = _presenter.GetOffsetForPopup();
- //The extra 5 px I think is related to default popup placement behavior
+ // The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
@@ -287,6 +273,5 @@ namespace Avalonia.Controls
_popup.Close();
SelectedTime = _presenter.Time;
}
-
}
}
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
index 5ab5b92e16..920eeeb406 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
@@ -39,7 +39,7 @@ namespace Avalonia.Controls
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
- //TemplateItems
+ // TemplateItems
private Grid _pickerContainer;
private Button _acceptButton;
private Button _dismissButton;
@@ -55,8 +55,8 @@ namespace Avalonia.Controls
private Button _minuteDownButton;
private Button _periodDownButton;
- //Backing Fields
- private TimeSpan _Time;
+ // Backing Fields
+ private TimeSpan _time;
private int _minuteIncrement = 1;
private string _clockIdentifier = "12HourClock";
@@ -83,7 +83,7 @@ namespace Avalonia.Controls
get => _clockIdentifier;
set
{
- if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock"))
+ if (string.IsNullOrEmpty(value) || !(value == "12HourClock" || value == "24HourClock"))
throw new ArgumentException("Invalid ClockIdentifier");
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
InitPicker();
@@ -95,10 +95,10 @@ namespace Avalonia.Controls
///
public TimeSpan Time
{
- get => _Time;
+ get => _time;
set
{
- SetAndRaise(TimeProperty, ref _Time, value);
+ SetAndRaise(TimeProperty, ref _time, value);
InitPicker();
}
}
@@ -213,26 +213,24 @@ namespace Avalonia.Controls
private void SetGrid()
{
- if (ClockIdentifier == "12HourClock")
- {
- _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
- _spacer2.IsVisible = true;
- _periodHost.IsVisible = true;
- }
- else
- {
- _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
- _spacer2.IsVisible = false;
- _periodHost.IsVisible = false;
- }
+ bool use24HourClock = ClockIdentifier == "24HourClock";
+
+ var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
+ _pickerContainer.ColumnDefinitions = new ColumnDefinitions(columnsD);
+
+ _spacer2.IsVisible = !use24HourClock;
+ _periodHost.IsVisible = !use24HourClock;
+
+ Grid.SetColumn(_spacer2, use24HourClock ? 0 : 3);
+ Grid.SetColumn(_periodHost, use24HourClock ? 0 : 4);
}
- private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
{
OnDismiss();
}
- private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
{
OnConfirmed();
}
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index f8bd2878d9..522103c7bd 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
}
}
- public double Scaling
+ public double RenderScaling
{
get { return _scaling; }
set
diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
index cd1ce3deae..9e65ef5f81 100644
--- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
@@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators
private readonly IDataTemplate _inner;
public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
public IControl Build(object param) => _inner.Build(param);
- public bool SupportsRecycling => _inner.SupportsRecycling;
public bool Match(object data) => _inner.Match(data);
public InstancedBinding ItemsSelector(object item) => null;
}
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
index 0d77cbf802..7514f214aa 100644
--- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
+++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
@@ -23,10 +23,10 @@ namespace Avalonia.Platform
Size ClientSize { get; }
///
- /// Gets the scaling factor for the toplevel.
+ /// Gets the scaling factor for the toplevel. This is used for rendering.
///
- double Scaling { get; }
-
+ double RenderScaling { get; }
+
///
/// The list of native platform's surfaces that can be consumed by rendering subsystems.
///
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index b190c4f2e7..ecaf87d1ed 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -13,6 +13,11 @@ namespace Avalonia.Platform
/// Hides the window.
///
void Hide();
+
+ ///
+ /// Gets the scaling factor for Window positioning and sizing.
+ ///
+ double DesktopScaling { get; }
///
/// Gets the position of the window in device pixels.
diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
index 70a7583daf..7888249bdd 100644
--- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
@@ -155,6 +155,11 @@ namespace Avalonia.Controls.Presenters
}
}
+ protected override void PanelCreated(IPanel panel)
+ {
+ ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
///
/// Moves to the selected page, animating if a is set.
///
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index c4571505ba..8837901816 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
- private IDataTemplate _dataTemplate;
+ private IRecyclingDataTemplate _recyclingDataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
///
@@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
- _dataTemplate = null;
+ _recyclingDataTemplate = null;
_createdChild = false;
InvalidateMeasure();
}
@@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters
{
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
- // We have content and it isn't a control, so if the new data template is the same
- // as the old data template, try to recycle the existing child control to display
- // the new data.
- if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
+ if (dataTemplate is IRecyclingDataTemplate rdt)
{
- newChild = oldChild;
+ var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
+ newChild = rdt.Build(content, toRecycle);
+ _recyclingDataTemplate = rdt;
}
else
{
- _dataTemplate = dataTemplate;
- newChild = _dataTemplate.Build(content);
+ newChild = dataTemplate.Build(content);
+ _recyclingDataTemplate = null;
}
}
else
{
- _dataTemplate = null;
+ _recyclingDataTemplate = null;
}
return newChild;
@@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters
LogicalChildren.Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null;
- _dataTemplate = null;
+ _recyclingDataTemplate = null;
}
InvalidateMeasure();
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
index 23846bcd2e..52f173fc71 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
@@ -229,8 +229,6 @@ namespace Avalonia.Controls.Presenters
}
PanelCreated(Panel);
-
- ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
///
diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
index ba0fdfd535..7171ecc302 100644
--- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Controls.Primitives
{
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest
{
- public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual)
+ public static Panel? GetOverlayLayer(IVisual visual)
{
foreach (var v in visual.GetVisualAncestors())
if (v is VisualLayerManager vlm)
@@ -24,6 +24,11 @@ namespace Avalonia.Controls.Primitives
return null;
}
+ public void Add(Control c)
+ {
+ base.Children.Add(c);
+ }
+
public bool HitTest(Point point) => Children.HitTestCustom(point);
}
}
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
index b0e3d1ab08..91ed5d975d 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
@@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
- _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
+ _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling);
}
- public virtual double Scaling => _parent.Scaling;
+ public virtual double Scaling => _parent.DesktopScaling;
}
}
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index c915dc70b6..59b7777b1b 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives
}
}
- foreach (var i in e.SelectedIndices)
+ if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0)
{
- Mark(i.GetAt(0), true);
- }
+ foreach (var i in e.SelectedIndices)
+ {
+ Mark(i.GetAt(0), true);
+ }
- foreach (var i in e.DeselectedIndices)
+ foreach (var i in e.DeselectedIndices)
+ {
+ Mark(i.GetAt(0), false);
+ }
+ }
+ else if (e.DeselectedItems.Count > 0)
{
- Mark(i.GetAt(0), false);
+ // (De)selected indices being empty means that a selected item was removed from
+ // the Items (it can't tell us the index of the item because the index is no longer
+ // valid). In this case, we just update the selection state of all containers.
+ UpdateContainerSelection();
}
var newSelectedIndex = SelectedIndex;
diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
index 3084d7fa72..ff83351190 100644
--- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
+++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Avalonia.LogicalTree;
+using Avalonia.Metadata;
namespace Avalonia.Controls.Primitives
{
@@ -11,10 +12,12 @@ namespace Avalonia.Controls.Primitives
private ILogicalRoot _logicalRoot;
private readonly List _layers = new List();
-
+
+ public static readonly StyledProperty ChromeOverlayLayerProperty =
+ AvaloniaProperty.Register(nameof(ChromeOverlayLayer));
public bool IsPopup { get; set; }
-
+
public AdornerLayer AdornerLayer
{
get
@@ -30,10 +33,19 @@ namespace Avalonia.Controls.Primitives
{
get
{
- var rv = FindLayer();
- if (rv == null)
- AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex);
- return rv;
+ var current = GetValue(ChromeOverlayLayerProperty);
+
+ if (current is null)
+ {
+ var chromeOverlayLayer = new ChromeOverlayLayer();
+ AddLayer(chromeOverlayLayer, ChromeZIndex);
+
+ SetValue(ChromeOverlayLayerProperty, chromeOverlayLayer);
+
+ current = chromeOverlayLayer;
+ }
+
+ return current;
}
}
@@ -44,7 +56,7 @@ namespace Avalonia.Controls.Primitives
if (IsPopup)
return null;
var rv = FindLayer();
- if(rv == null)
+ if (rv == null)
AddLayer(rv = new OverlayLayer(), OverlayZIndex);
return rv;
}
@@ -65,7 +77,8 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex;
VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree)
- ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
+ ((ILogical)layer).NotifyAttachedToLogicalTree(
+ new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
InvalidateArrange();
}
diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
index f93de5ca15..f64c26682b 100644
--- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
+++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Controls
AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
+ RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
}
///
diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs
index 033a5559f5..27f13a3f57 100644
--- a/src/Avalonia.Controls/RelativePanel.cs
+++ b/src/Avalonia.Controls/RelativePanel.cs
@@ -1,4 +1,4 @@
-/// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs
+// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs
using System;
using System.Collections.Generic;
using System.Linq;
@@ -14,25 +14,28 @@ namespace Avalonia.Controls
public RelativePanel() => _childGraph = new Graph();
- protected override Size MeasureOverride(Size availableSize)
+ private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child)
{
- foreach (var child in Children)
+ var dependency = child.GetValue(property);
+
+ if (dependency is Layoutable layoutable)
{
- child?.Measure(availableSize);
+ if (Children.Contains((ILayoutable)layoutable))
+ return layoutable;
+
+ throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}");
}
- return availableSize;
+ return null;
}
- protected override Size ArrangeOverride(Size arrangeSize)
+ protected override Size MeasureOverride(Size availableSize)
{
- _childGraph.Reset(arrangeSize);
-
- foreach (var child in Children.OfType())
+ _childGraph.Clear();
+ foreach (Layoutable child in Children)
{
if (child == null)
continue;
-
var node = _childGraph.AddNode(child);
node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child));
@@ -47,63 +50,44 @@ namespace Avalonia.Controls
node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child));
node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child));
- }
- if (_childGraph.CheckCyclic())
- {
- throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
}
+ _childGraph.Measure(availableSize);
- var size = new Size();
-
- foreach (var child in Children)
- {
- if (child.Bounds.Bottom > size.Height)
- {
- size = size.WithHeight(child.Bounds.Bottom);
- }
+ _childGraph.Reset(false);
+ var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN());
+ _childGraph.Reset();
+ _childGraph.Measure(boundingSize);
+ return boundingSize;
+ }
- if (child.Bounds.Right > size.Width)
- {
- size = size.WithWidth(child.Bounds.Right);
- }
- }
+ protected override Size ArrangeOverride(Size arrangeSize)
+ {
+ _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize));
+ return arrangeSize;
+ }
- if (VerticalAlignment == VerticalAlignment.Stretch)
- {
- size = size.WithHeight(arrangeSize.Height);
- }
+ private class GraphNode
+ {
+ public bool Measured { get; set; }
- if (HorizontalAlignment == HorizontalAlignment.Stretch)
- {
- size = size.WithWidth(arrangeSize.Width);
- }
+ public Layoutable Element { get; }
- return size;
- }
+ private bool HorizontalOffsetFlag { get; set; }
- private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child)
- {
- var dependency = child.GetValue(property);
+ private bool VerticalOffsetFlag { get; set; }
- if (dependency is Layoutable layoutable)
- {
- if (Children.Contains((ILayoutable)layoutable))
- return layoutable;
+ private Size BoundingSize { get; set; }
- throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}");
- }
+ public Size OriginDesiredSize { get; set; }
- return null;
- }
+ public double Left { get; set; } = double.NaN;
- private class GraphNode
- {
- public Point Position { get; set; }
+ public double Top { get; set; } = double.NaN;
- public bool Arranged { get; set; }
+ public double Right { get; set; } = double.NaN;
- public Layoutable Element { get; }
+ public double Bottom { get; set; } = double.NaN;
public HashSet OutgoingNodes { get; }
@@ -132,19 +116,106 @@ namespace Avalonia.Controls
OutgoingNodes = new HashSet();
Element = element;
}
+
+ public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0)));
+
+ public void Reset(bool clearPos)
+ {
+ if (clearPos)
+ {
+ Left = double.NaN;
+ Top = double.NaN;
+ Right = double.NaN;
+ Bottom = double.NaN;
+ }
+
+ Measured = false;
+ }
+
+ public Size GetBoundingSize()
+ {
+ if (Left < 0 || Top < 0) return default;
+ if (Measured)
+ return BoundingSize;
+
+ if (!OutgoingNodes.Any())
+ {
+ BoundingSize = Element.DesiredSize;
+ Measured = true;
+ }
+ else
+ {
+ BoundingSize = GetBoundingSize(this, Element.DesiredSize, OutgoingNodes);
+ Measured = true;
+ }
+
+ return BoundingSize;
+ }
+
+ private static Size GetBoundingSize(GraphNode prevNode, Size prevSize, IEnumerable nodes)
+ {
+ foreach (var node in nodes)
+ {
+ if (node.Measured || !node.OutgoingNodes.Any())
+ {
+ if (prevNode.LeftOfNode != null && prevNode.LeftOfNode == node ||
+ prevNode.RightOfNode != null && prevNode.RightOfNode == node)
+ {
+ prevSize = prevSize.WithWidth(prevSize.Width + node.BoundingSize.Width);
+ if (GetAlignHorizontalCenterWithPanel(node.Element) || node.HorizontalOffsetFlag)
+ {
+ prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width);
+ prevNode.HorizontalOffsetFlag = true;
+ }
+ if (node.VerticalOffsetFlag)
+ {
+ prevNode.VerticalOffsetFlag = true;
+ }
+ }
+
+ if (prevNode.AboveNode != null && prevNode.AboveNode == node ||
+ prevNode.BelowNode != null && prevNode.BelowNode == node)
+ {
+ prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height);
+ if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag)
+ {
+ prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height);
+ prevNode.VerticalOffsetFlag = true;
+ }
+ if (node.HorizontalOffsetFlag)
+ {
+ prevNode.HorizontalOffsetFlag = true;
+ }
+ }
+ }
+ else
+ {
+ return GetBoundingSize(node, prevSize, node.OutgoingNodes);
+ }
+ }
+
+ return prevSize;
+ }
}
private class Graph
{
private readonly Dictionary _nodeDic;
- private Size _arrangeSize;
+ private Size AvailableSize { get; set; }
+
+ public Graph() => _nodeDic = new Dictionary();
+
+ public IEnumerable GetNodes() => _nodeDic.Values;
- public Graph()
+ public void Clear()
{
- _nodeDic = new Dictionary();
+ AvailableSize = new Size();
+ _nodeDic.Clear();
}
+ public void Reset(bool clearPos = true) => _nodeDic.Values.Do(node => node.Reset(clearPos));
+
public GraphNode? AddLink(GraphNode from, Layoutable? to)
{
if (to == null)
@@ -177,177 +248,289 @@ namespace Avalonia.Controls
return _nodeDic[value];
}
- public void Reset(Size arrangeSize)
+ public void Measure(Size availableSize)
{
- _arrangeSize = arrangeSize;
- _nodeDic.Clear();
+ AvailableSize = availableSize;
+ Measure(_nodeDic.Values, null);
}
- public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null);
-
- private bool CheckCyclic(IEnumerable nodes, HashSet? set)
+ private void Measure(IEnumerable nodes, HashSet? set)
{
- set ??= new HashSet();
+ set ??= new HashSet();
foreach (var node in nodes)
{
- if (!node.Arranged && node.OutgoingNodes.Count == 0)
+ if (!node.Measured && !node.OutgoingNodes.Any())
{
- ArrangeChild(node, true);
+ MeasureChild(node);
continue;
}
-
- if (node.OutgoingNodes.All(item => item.Arranged))
+
+ if (node.OutgoingNodes.All(item => item.Measured))
{
- ArrangeChild(node);
+ MeasureChild(node);
continue;
}
-
+
if (!set.Add(node.Element))
- return true;
+ throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
+
+ Measure(node.OutgoingNodes, set);
- return CheckCyclic(node.OutgoingNodes, set);
+ if (!node.Measured)
+ {
+ MeasureChild(node);
+ }
}
-
- return false;
}
- private void ArrangeChild(GraphNode node, bool ignoneSibling = false)
+ private void MeasureChild(GraphNode node)
{
var child = node.Element;
- var childSize = child.DesiredSize;
- var childPos = new Point();
+ child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+ node.OriginDesiredSize = child.DesiredSize;
- if (GetAlignHorizontalCenterWithPanel(child))
+ var alignLeftWithPanel = GetAlignLeftWithPanel(child);
+ var alignTopWithPanel = GetAlignTopWithPanel(child);
+ var alignRightWithPanel = GetAlignRightWithPanel(child);
+ var alignBottomWithPanel = GetAlignBottomWithPanel(child);
+
+ if (alignLeftWithPanel)
+ node.Left = 0;
+ if (alignTopWithPanel)
+ node.Top = 0;
+ if (alignRightWithPanel)
+ node.Right = 0;
+ if (alignBottomWithPanel)
+ node.Bottom = 0;
+
+ if (node.AlignLeftWithNode != null)
{
- childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2);
+ node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5;
}
- if (GetAlignVerticalCenterWithPanel(child))
+ if (node.AlignTopWithNode != null)
{
- childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2);
+ node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5;
}
- var alignLeftWithPanel = GetAlignLeftWithPanel(child);
- var alignTopWithPanel = GetAlignTopWithPanel(child);
- var alignRightWithPanel = GetAlignRightWithPanel(child);
- var alignBottomWithPanel = GetAlignBottomWithPanel(child);
+ if (node.AlignRightWithNode != null)
+ {
+ node.Right = node.Right.IsNaN()
+ ? node.AlignRightWithNode.Right
+ : node.AlignRightWithNode.Right * 0.5;
+ }
- if (!ignoneSibling)
+ if (node.AlignBottomWithNode != null)
{
- if (node.LeftOfNode != null)
- {
- childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width);
- }
+ node.Bottom = node.Bottom.IsNaN()
+ ? node.AlignBottomWithNode.Bottom
+ : node.AlignBottomWithNode.Bottom * 0.5;
+ }
- if (node.AboveNode != null)
- {
- childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height);
- }
+ var availableHeight = AvailableSize.Height - node.Top - node.Bottom;
+ if (availableHeight.IsNaN())
+ {
+ availableHeight = AvailableSize.Height;
- if (node.RightOfNode != null)
+ if (!node.Top.IsNaN() && node.Bottom.IsNaN())
{
- childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width);
+ availableHeight -= node.Top;
}
-
- if (node.BelowNode != null)
+ else if (node.Top.IsNaN() && !node.Bottom.IsNaN())
{
- childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height);
+ availableHeight -= node.Bottom;
}
+ }
+
+ var availableWidth = AvailableSize.Width - node.Left - node.Right;
+ if (availableWidth.IsNaN())
+ {
+ availableWidth = AvailableSize.Width;
- if (node.AlignHorizontalCenterWith != null)
+ if (!node.Left.IsNaN() && node.Right.IsNaN())
{
- childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X +
- (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2);
+ availableWidth -= node.Left;
}
-
- if (node.AlignVerticalCenterWith != null)
+ else if (node.Left.IsNaN() && !node.Right.IsNaN())
{
- childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y +
- (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2);
+ availableWidth -= node.Right;
}
+ }
- if (node.AlignLeftWithNode != null)
+ child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0)));
+ var childSize = child.DesiredSize;
+
+ if (node.LeftOfNode != null && node.Left.IsNaN())
+ {
+ node.Left = node.LeftOfNode.Left - childSize.Width;
+ }
+
+ if (node.AboveNode != null && node.Top.IsNaN())
+ {
+ node.Top = node.AboveNode.Top - childSize.Height;
+ }
+
+ if (node.RightOfNode != null)
+ {
+ if (node.Right.IsNaN())
{
- childPos = childPos.WithX(node.AlignLeftWithNode.Position.X);
+ node.Right = node.RightOfNode.Right - childSize.Width;
}
- if (node.AlignTopWithNode != null)
+ if (node.Left.IsNaN())
{
- childPos = childPos.WithY(node.AlignTopWithNode.Position.Y);
+ node.Left = AvailableSize.Width - node.RightOfNode.Right;
}
+ }
- if (node.AlignRightWithNode != null)
+ if (node.BelowNode != null)
+ {
+ if (node.Bottom.IsNaN())
{
- childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width);
+ node.Bottom = node.BelowNode.Bottom - childSize.Height;
}
- if (node.AlignBottomWithNode != null)
+ if (node.Top.IsNaN())
{
- childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height);
+ node.Top = AvailableSize.Height - node.BelowNode.Bottom;
}
}
- if (alignLeftWithPanel)
+ if (node.AlignHorizontalCenterWith != null)
{
- if (node.AlignRightWithNode != null)
- {
- childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2);
- }
+ var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5;
+ var halfWidthRight = (AvailableSize.Width - node.AlignHorizontalCenterWith.Left + node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5;
+
+ if (node.Left.IsNaN())
+ node.Left = halfWidthLeft;
else
- {
- childPos = childPos.WithX(0);
- }
+ node.Left = (node.Left + halfWidthLeft) * 0.5;
+
+ if (node.Right.IsNaN())
+ node.Right = halfWidthRight;
+ else
+ node.Right = (node.Right + halfWidthRight) * 0.5;
}
- if (alignTopWithPanel)
+ if (node.AlignVerticalCenterWith != null)
{
- if (node.AlignBottomWithNode != null)
- {
- childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2);
- }
+ var halfHeightTop = (AvailableSize.Height + node.AlignVerticalCenterWith.Top - node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5;
+ var halfHeightBottom = (AvailableSize.Height - node.AlignVerticalCenterWith.Top + node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5;
+
+ if (node.Top.IsNaN())
+ node.Top = halfHeightTop;
else
- {
- childPos = childPos.WithY(0);
- }
+ node.Top = (node.Top + halfHeightTop) * 0.5;
+
+ if (node.Bottom.IsNaN())
+ node.Bottom = halfHeightBottom;
+ else
+ node.Bottom = (node.Bottom + halfHeightBottom) * 0.5;
}
- if (alignRightWithPanel)
+ if (GetAlignHorizontalCenterWithPanel(child))
{
- if (alignLeftWithPanel)
- {
- childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2);
- }
- else if (node.AlignLeftWithNode == null)
- {
- childPos = childPos.WithX(_arrangeSize.Width - childSize.Width);
- }
+ var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5;
+
+ if (node.Left.IsNaN())
+ node.Left = halfSubWidth;
else
- {
- childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2);
- }
+ node.Left = (node.Left + halfSubWidth) * 0.5;
+
+ if (node.Right.IsNaN())
+ node.Right = halfSubWidth;
+ else
+ node.Right = (node.Right + halfSubWidth) * 0.5;
}
- if (alignBottomWithPanel)
+ if (GetAlignVerticalCenterWithPanel(child))
{
- if (alignTopWithPanel)
- {
- childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2);
- }
- else if (node.AlignTopWithNode == null)
+ var halfSubHeight = (AvailableSize.Height - childSize.Height) * 0.5;
+
+ if (node.Top.IsNaN())
+ node.Top = halfSubHeight;
+ else
+ node.Top = (node.Top + halfSubHeight) * 0.5;
+
+ if (node.Bottom.IsNaN())
+ node.Bottom = halfSubHeight;
+ else
+ node.Bottom = (node.Bottom + halfSubHeight) * 0.5;
+ }
+
+ if (node.Left.IsNaN())
+ {
+ if (!node.Right.IsNaN())
+ node.Left = AvailableSize.Width - node.Right - childSize.Width;
+ else
{
- childPos = childPos.WithY(_arrangeSize.Height - childSize.Height);
+ node.Left = 0;
+ node.Right = AvailableSize.Width - childSize.Width;
}
+ }
+ else if (!node.Left.IsNaN() && node.Right.IsNaN())
+ {
+ node.Right = AvailableSize.Width - node.Left - childSize.Width;
+ }
+
+ if (node.Top.IsNaN())
+ {
+ if (!node.Bottom.IsNaN())
+ node.Top = AvailableSize.Height - node.Bottom - childSize.Height;
else
{
- childPos = childPos.WithY((_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2);
+ node.Top = 0;
+ node.Bottom = AvailableSize.Height - childSize.Height;
}
}
+ else if (!node.Top.IsNaN() && node.Bottom.IsNaN())
+ {
+ node.Bottom = AvailableSize.Height - node.Top - childSize.Height;
+ }
+
+ node.Measured = true;
+ }
+
+ public Size GetBoundingSize(bool calcWidth, bool calcHeight)
+ {
+ var boundingSize = new Size();
+
+ foreach (var node in _nodeDic.Values)
+ {
+ var size = node.GetBoundingSize();
+ boundingSize = boundingSize.WithWidth(Math.Max(boundingSize.Width, size.Width));
+ boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height));
+ }
+
+ boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width);
+ boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height);
+ return boundingSize;
+ }
+ }
+ }
+
+ internal static partial class Extensions
+ {
+ ///
+ /// Returns a value that indicates whether the specified value is not a number ().
+ ///
+ /// A double-precision floating-point number.
+ /// true if evaluates to ; otherwise, false.
+ public static bool IsNaN(this double d)
+ {
+ return double.IsNaN(d);
+ }
- child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height));
- node.Position = childPos;
- node.Arranged = true;
+ public static IEnumerable Do(this IEnumerable source, Action predicate)
+ {
+ var enumerable = source as IList ?? source.ToList();
+ foreach (var item in enumerable)
+ {
+ predicate.Invoke(item);
}
+
+ return enumerable;
}
}
}
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index f5c673d5f9..931c27c575 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
@@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server
GetAvaloniaInputModifiers(pressed.Modifiers)));
}, DispatcherPriority.Input);
}
- if (obj is PointerPressedEventMessage released)
+ if (obj is PointerReleasedEventMessage released)
{
Dispatcher.UIThread.Post(() =>
{
diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs
new file mode 100644
index 0000000000..644e077221
--- /dev/null
+++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs
@@ -0,0 +1,27 @@
+using Avalonia.Controls.Templates;
+
+namespace Avalonia.Controls
+{
+ public abstract class ElementFactory : IElementFactory
+ {
+ public IControl Build(object data)
+ {
+ return GetElementCore(new ElementFactoryGetArgs { Data = data });
+ }
+
+ public IControl GetElement(ElementFactoryGetArgs args)
+ {
+ return GetElementCore(args);
+ }
+
+ public bool Match(object data) => true;
+
+ public void RecycleElement(ElementFactoryRecycleArgs args)
+ {
+ RecycleElementCore(args);
+ }
+
+ protected abstract IControl GetElementCore(ElementFactoryGetArgs args);
+ protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args);
+ }
+}
diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs
new file mode 100644
index 0000000000..6a899a6f26
--- /dev/null
+++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs
@@ -0,0 +1,66 @@
+using Avalonia.Controls.Templates;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents the optional arguments to use when calling an implementation of the
+ /// 's method.
+ ///
+ public class ElementFactoryGetArgs
+ {
+ ///
+ /// Gets or sets the data item for which an appropriate element tree should be realized
+ /// when calling .
+ ///
+ public object Data { get; set; }
+
+ ///
+ /// Gets or sets the that is expected to be the parent of the
+ /// realized element from .
+ ///
+ public IControl Parent { get; set; }
+
+ ///
+ /// Gets or sets the index of the item that should be realized.
+ ///
+ public int Index { get; set; }
+ }
+
+ ///
+ /// Represents the optional arguments to use when calling an implementation of the
+ /// 's method.
+ ///
+ public class ElementFactoryRecycleArgs
+ {
+ ///
+ /// Gets or sets the to recycle when calling
+ /// .
+ ///
+ public IControl Element { get; set; }
+
+ ///
+ /// Gets or sets the that is expected to be the parent of the
+ /// realized element from .
+ ///
+ public IControl Parent { get; set; }
+ }
+
+ ///
+ /// A data template that supports creating and recyling elements for an .
+ ///
+ public interface IElementFactory : IDataTemplate
+ {
+ ///
+ /// Gets an .
+ ///
+ /// The element args.
+ public IControl GetElement(ElementFactoryGetArgs args);
+
+ ///
+ /// Recycles an that was previously retrieved using
+ /// .
+ ///
+ /// The recycle args.
+ public void RecycleElement(ElementFactoryRecycleArgs args);
+ }
+}
diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
index 04d859c742..dd97cde218 100644
--- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
+++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
@@ -7,13 +7,26 @@ using Avalonia.Controls.Templates;
namespace Avalonia.Controls
{
- internal class ItemTemplateWrapper
+ internal class ItemTemplateWrapper : IElementFactory
{
private readonly IDataTemplate _dataTemplate;
public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate;
- public IControl GetElement(IControl parent, object data)
+ public IControl Build(object param) => GetElement(null, param);
+ public bool Match(object data) => _dataTemplate.Match(data);
+
+ public IControl GetElement(ElementFactoryGetArgs args)
+ {
+ return GetElement(args.Parent, args.Data);
+ }
+
+ public void RecycleElement(ElementFactoryRecycleArgs args)
+ {
+ RecycleElement(args.Parent, args.Element);
+ }
+
+ private IControl GetElement(IControl parent, object data)
{
var selectedTemplate = _dataTemplate;
var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate);
@@ -37,7 +50,7 @@ namespace Avalonia.Controls
return element;
}
- public void RecycleElement(IControl parent, IControl element)
+ private void RecycleElement(IControl parent, IControl element)
{
var selectedTemplate = _dataTemplate;
var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate);
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index 87f4760156..8bc356bdec 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -141,7 +141,7 @@ namespace Avalonia.Controls
///
public ItemsSourceView ItemsSourceView { get; private set; }
- internal ItemTemplateWrapper ItemTemplateShim { get; set; }
+ internal IElementFactory ItemTemplateShim { get; set; }
internal Point LayoutOrigin { get; set; }
internal object LayoutState { get; set; }
internal IControl MadeAnchor => _viewportManager.MadeAnchor;
@@ -664,7 +664,7 @@ namespace Avalonia.Controls
}
}
- ItemTemplateShim = new ItemTemplateWrapper(newValue);
+ ItemTemplateShim = newValue as IElementFactory ?? new ItemTemplateWrapper(newValue);
InvalidateMeasure();
}
diff --git a/src/Avalonia.Controls/Repeater/RecyclePool.cs b/src/Avalonia.Controls/Repeater/RecyclePool.cs
index 4e5950bdc5..28f299043c 100644
--- a/src/Avalonia.Controls/Repeater/RecyclePool.cs
+++ b/src/Avalonia.Controls/Repeater/RecyclePool.cs
@@ -11,10 +11,13 @@ using Avalonia.Controls.Templates;
namespace Avalonia.Controls
{
- internal class RecyclePool
+ public class RecyclePool
{
- public static readonly AttachedProperty OriginTemplateProperty =
- AvaloniaProperty.RegisterAttached("OriginTemplate", typeof(RecyclePool));
+ internal static readonly AttachedProperty OriginTemplateProperty =
+ AvaloniaProperty.RegisterAttached("OriginTemplate");
+
+ internal static readonly AttachedProperty ReuseKeyProperty =
+ AvaloniaProperty.RegisterAttached("ReuseKey", string.Empty);
private static ConditionalWeakTable s_pools = new ConditionalWeakTable();
private readonly Dictionary> _elements = new Dictionary>();
@@ -77,6 +80,9 @@ namespace Avalonia.Controls
return null;
}
+ internal string GetReuseKey(IControl element) => element.GetValue(ReuseKeyProperty);
+ internal void SetReuseKey(IControl element, string value) => element.SetValue(ReuseKeyProperty, value);
+
private IPanel EnsureOwnerIsPanelOrNull(IControl owner)
{
if (owner is IPanel panel)
diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs
new file mode 100644
index 0000000000..9503239e34
--- /dev/null
+++ b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls.Templates;
+
+#nullable enable
+
+namespace Avalonia.Controls
+{
+ public class SelectTemplateEventArgs : EventArgs
+ {
+ public string? TemplateKey { get; set; }
+ public object? DataContext { get; internal set; }
+ public IControl? Owner { get; internal set; }
+ }
+
+ public class RecyclingElementFactory : ElementFactory
+ {
+ private RecyclePool? _recyclePool;
+ private IDictionary? _templates;
+ private SelectTemplateEventArgs? _args;
+
+ public RecyclingElementFactory()
+ {
+ Templates = new Dictionary();
+ }
+
+ public RecyclePool RecyclePool
+ {
+ get => _recyclePool ??= new RecyclePool();
+ set => _recyclePool = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ public IDictionary Templates
+ {
+ get => _templates ??= new Dictionary();
+ set => _templates = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ public event EventHandler? SelectTemplateKey;
+
+ protected override IControl GetElementCore(ElementFactoryGetArgs args)
+ {
+ if (_templates == null || _templates.Count == 0)
+ {
+ throw new InvalidOperationException("Templates cannot be empty.");
+ }
+
+ var templateKey = Templates.Count == 1 ?
+ Templates.First().Key :
+ OnSelectTemplateKeyCore(args.Data, args.Parent);
+
+ if (string.IsNullOrEmpty(templateKey))
+ {
+ // Note: We could allow null/whitespace, which would work as long as
+ // the recycle pool is not shared. in order to make this work in all cases
+ // currently we validate that a valid template key is provided.
+ throw new InvalidOperationException("Template key cannot be null or empty.");
+ }
+
+ // Get an element from the Recycle Pool or create one
+ var element = RecyclePool.TryGetElement(templateKey, args.Parent);
+
+ if (element is null)
+ {
+ // No need to call HasKey if there is only one template.
+ if (Templates.Count > 1 && !Templates.ContainsKey(templateKey))
+ {
+ var message = $"No templates of key '{templateKey}' were found in the templates collection.";
+ throw new InvalidOperationException(message);
+ }
+
+ var dataTemplate = Templates[templateKey];
+ element = dataTemplate.Build(args.Data);
+
+ // Associate ReuseKey with element
+ RecyclePool.SetReuseKey(element, templateKey);
+ }
+
+ return element;
+ }
+
+ protected override void RecycleElementCore(ElementFactoryRecycleArgs args)
+ {
+ var element = args.Element;
+ var key = RecyclePool.GetReuseKey(element);
+ RecyclePool.PutElement(element, key, args.Parent);
+ }
+
+ protected virtual string OnSelectTemplateKeyCore(object dataContext, IControl owner)
+ {
+ if (SelectTemplateKey is object)
+ {
+ _args ??= new SelectTemplateEventArgs();
+ _args.TemplateKey = null;
+ _args.DataContext = dataContext;
+ _args.Owner = owner;
+
+ try
+ {
+ SelectTemplateKey(this, _args);
+ }
+ finally
+ {
+ _args.DataContext = null;
+ _args.Owner = null;
+ }
+ }
+
+ if (string.IsNullOrEmpty(_args?.TemplateKey))
+ {
+ throw new InvalidOperationException(
+ "Please provide a valid template identifier in the handler for the SelectTemplateKey event.");
+ }
+
+ return _args!.TemplateKey!;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs
index eff51804b9..416b1e2824 100644
--- a/src/Avalonia.Controls/Repeater/ViewManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewManager.cs
@@ -6,11 +6,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Linq;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
-using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.VisualTree;
@@ -26,6 +24,8 @@ namespace Avalonia.Controls
private readonly UniqueIdElementPool _resetPool;
private IControl _lastFocusedElement;
private bool _isDataSourceStableResetPending;
+ private ElementFactoryGetArgs _elementFactoryGetArgs;
+ private ElementFactoryRecycleArgs _elementFactoryRecycleArgs;
private int _firstRealizedElementIndexHeldByLayout = FirstRealizedElementIndexDefault;
private int _lastRealizedElementIndexHeldByLayout = LastRealizedElementIndexDefault;
private bool _eventsSubscribed;
@@ -134,7 +134,14 @@ namespace Avalonia.Controls
if (_owner.ItemTemplateShim != null)
{
- _owner.ItemTemplateShim.RecycleElement(_owner, element);
+ var context = _elementFactoryRecycleArgs ??= new ElementFactoryRecycleArgs();
+ context.Element = element;
+ context.Parent = _owner;
+
+ _owner.ItemTemplateShim.RecycleElement(context);
+
+ context.Element = null;
+ context.Parent = null;
}
else
{
@@ -579,7 +586,7 @@ namespace Avalonia.Controls
var data = _owner.ItemsSourceView.GetAt(index);
var providedElementFactory = _owner.ItemTemplateShim;
- ItemTemplateWrapper GetElementFactory()
+ IElementFactory GetElementFactory()
{
if (providedElementFactory == null)
{
@@ -602,7 +609,20 @@ namespace Avalonia.Controls
}
var elementFactory = GetElementFactory();
- return elementFactory.GetElement(_owner, data);
+ var args = _elementFactoryGetArgs ??= new ElementFactoryGetArgs();
+
+ try
+ {
+ args.Data = data;
+ args.Parent = _owner;
+ args.Index = index;
+ return elementFactory.GetElement(args);
+ }
+ finally
+ {
+ args.Data = null;
+ args.Parent = null;
+ }
}
var element = GetElement();
@@ -732,6 +752,7 @@ namespace Avalonia.Controls
{
_owner.GotFocus += OnFocusChanged;
_owner.LostFocus += OnFocusChanged;
+ _eventsSubscribed = true;
}
}
diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs
index d454a29021..1afd86a11e 100644
--- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs
+++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates
///
/// Builds a control for a piece of data.
///
- public class FuncDataTemplate : FuncTemplate
diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml
index 893d64f505..ded121f5f6 100644
--- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml
+++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml
@@ -43,7 +43,8 @@
-
+
+
diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml
index 739887fb35..1856eec206 100644
--- a/src/Avalonia.Themes.Default/Window.xaml
+++ b/src/Avalonia.Themes.Default/Window.xaml
@@ -1,23 +1,25 @@
@@ -145,6 +148,8 @@
@@ -155,8 +160,7 @@
-
+
+
+
@@ -212,14 +221,15 @@
-
-
-
diff --git a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/NumericUpDown.xaml
index d1610b343c..cc5e2234f8 100644
--- a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml
+++ b/src/Avalonia.Themes.Fluent/NumericUpDown.xaml
@@ -43,7 +43,7 @@
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml
index 45798d3fa1..4dba5b4ba4 100644
--- a/src/Avalonia.Themes.Fluent/TitleBar.xaml
+++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml
@@ -15,7 +15,7 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
index e7f8fb1641..4309edefe3 100644
--- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
+++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml
@@ -43,7 +43,8 @@
-
+
+
diff --git a/src/Avalonia.Themes.Fluent/Window.xaml b/src/Avalonia.Themes.Fluent/Window.xaml
index 794f516f71..1856eec206 100644
--- a/src/Avalonia.Themes.Fluent/Window.xaml
+++ b/src/Avalonia.Themes.Fluent/Window.xaml
@@ -8,16 +8,18 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index 052ee5e1b7..16b4f90d57 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -89,6 +89,11 @@ namespace Avalonia.Media
/// The .
public static Color Parse(string s)
{
+ if (s is null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
if (TryParse(s, out Color color))
{
return color;
@@ -120,14 +125,16 @@ namespace Avalonia.Media
/// The status of the operation.
public static bool TryParse(string s, out Color color)
{
- if (s == null)
+ color = default;
+
+ if (s is null)
{
- throw new ArgumentNullException(nameof(s));
+ return false;
}
if (s.Length == 0)
{
- throw new FormatException();
+ return false;
}
if (s[0] == '#' && TryParseInternal(s.AsSpan(), out color))
@@ -144,8 +151,6 @@ namespace Avalonia.Media
return true;
}
- color = default;
-
return false;
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
index c4302aecec..8e7d934bca 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
@@ -4,14 +4,12 @@
{
private TextAlignment _textAlignment;
private TextWrapping _textWrapping;
- private TextTrimming _textTrimming;
private double _lineHeight;
public GenericTextParagraphProperties(
TextRunProperties defaultTextRunProperties,
TextAlignment textAlignment = TextAlignment.Left,
- TextWrapping textWrapping = TextWrapping.WrapWithOverflow,
- TextTrimming textTrimming = TextTrimming.None,
+ TextWrapping textWrapping = TextWrapping.NoWrap,
double lineHeight = 0)
{
DefaultTextRunProperties = defaultTextRunProperties;
@@ -20,8 +18,6 @@
_textWrapping = textWrapping;
- _textTrimming = textTrimming;
-
_lineHeight = lineHeight;
}
@@ -31,8 +27,6 @@
public override TextWrapping TextWrapping => _textWrapping;
- public override TextTrimming TextTrimming => _textTrimming;
-
public override double LineHeight => _lineHeight;
///
@@ -50,13 +44,6 @@
{
_textWrapping = textWrapping;
}
- ///
- /// Set text trimming
- ///
- internal void SetTextTrimming(TextTrimming textTrimming)
- {
- _textTrimming = textTrimming;
- }
///
/// Set line height
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
index b71fe5bc3c..9e67a03f45 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
@@ -1,5 +1,4 @@
-using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Utilities;
+using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
new file mode 100644
index 0000000000..ffd65423a3
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
@@ -0,0 +1,23 @@
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// Properties of text collapsing
+ ///
+ public abstract class TextCollapsingProperties
+ {
+ ///
+ /// Gets the width in which the collapsible range is constrained to
+ ///
+ public abstract double Width { get; }
+
+ ///
+ /// Gets the text run that is used as collapsing symbol
+ ///
+ public abstract TextRun Symbol { get; }
+
+ ///
+ /// Gets the style of collapsing
+ ///
+ public abstract TextCollapsingStyle Style { get; }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
new file mode 100644
index 0000000000..1523cc4d9a
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
@@ -0,0 +1,18 @@
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// Text collapsing style
+ ///
+ public enum TextCollapsingStyle
+ {
+ ///
+ /// Collapse trailing characters
+ ///
+ TrailingCharacter,
+
+ ///
+ /// Collapse trailing words
+ ///
+ TrailingWord,
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
index 3ad23f3504..9318fcc68e 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
@@ -1,52 +1,194 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Platform;
-using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
internal class TextFormatterImpl : TextFormatter
{
- private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' });
-
///
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
{
- var textTrimming = paragraphProperties.TextTrimming;
var textWrapping = paragraphProperties.TextWrapping;
- TextLine textLine = null;
var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak);
var textRange = GetTextRange(textRuns);
- if (textTrimming != TextTrimming.None)
+ TextLine textLine;
+
+ switch (textWrapping)
+ {
+ case TextWrapping.NoWrap:
+ {
+ var textLineMetrics =
+ TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);
+
+ textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
+ break;
+ }
+ case TextWrapping.WrapWithOverflow:
+ case TextWrapping.Wrap:
+ {
+ textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
+ break;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return textLine;
+ }
+
+ ///
+ /// Measures the number of characters that fits into available width.
+ ///
+ /// The text run.
+ /// The available width.
+ ///
+ internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
+ {
+ var glyphRun = textCharacters.GlyphRun;
+
+ if (glyphRun.Bounds.Width < availableWidth)
{
- textLine = PerformTextTrimming(textRuns, textRange, paragraphWidth, paragraphProperties);
+ return glyphRun.Characters.Length;
+ }
+
+ var glyphCount = 0;
+
+ var currentWidth = 0.0;
+
+ if (glyphRun.GlyphAdvances.IsEmpty)
+ {
+ var glyphTypeface = glyphRun.GlyphTypeface;
+
+ for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+ {
+ var glyph = glyphRun.GlyphIndices[i];
+
+ var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
+
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
+
+ currentWidth += advance;
+
+ glyphCount++;
+ }
}
else
{
- switch (textWrapping)
+ foreach (var advance in glyphRun.GlyphAdvances)
{
- case TextWrapping.NoWrap:
- {
- var textLineMetrics =
- TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);
+ if (currentWidth + advance > availableWidth)
+ {
+ break;
+ }
- textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
- break;
+ currentWidth += advance;
+
+ glyphCount++;
+ }
+ }
+
+ if (glyphCount == glyphRun.GlyphIndices.Length)
+ {
+ return glyphRun.Characters.Length;
+ }
+
+ if (glyphRun.GlyphClusters.IsEmpty)
+ {
+ return glyphCount;
+ }
+
+ var firstCluster = glyphRun.GlyphClusters[0];
+
+ var lastCluster = glyphRun.GlyphClusters[glyphCount];
+
+ return lastCluster - firstCluster;
+ }
+
+ ///
+ /// Split a sequence of runs into two segments at specified length.
+ ///
+ /// The text run's.
+ /// The length to split at.
+ /// The split text runs.
+ internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length)
+ {
+ var currentLength = 0;
+
+ for (var i = 0; i < textRuns.Count; i++)
+ {
+ var currentRun = textRuns[i];
+
+ if (currentLength + currentRun.GlyphRun.Characters.Length < length)
+ {
+ currentLength += currentRun.GlyphRun.Characters.Length;
+ continue;
+ }
+
+ var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
+
+ var first = new ShapedTextCharacters[firstCount];
+
+ if (firstCount > 1)
+ {
+ for (var j = 0; j < i; j++)
+ {
+ first[j] = textRuns[j];
+ }
+ }
+
+ var secondCount = textRuns.Count - firstCount;
+
+ if (currentLength + currentRun.GlyphRun.Characters.Length == length)
+ {
+ var second = new ShapedTextCharacters[secondCount];
+
+ var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
+
+ if (secondCount > 0)
+ {
+ for (var j = 0; j < secondCount; j++)
+ {
+ second[j] = textRuns[i + j + offset];
}
- case TextWrapping.WrapWithOverflow:
- case TextWrapping.Wrap:
+ }
+
+ first[i] = currentRun;
+
+ return new SplitTextRunsResult(first, second);
+ }
+ else
+ {
+ secondCount++;
+
+ var second = new ShapedTextCharacters[secondCount];
+
+ if (secondCount > 0)
+ {
+ for (var j = 1; j < secondCount; j++)
{
- textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
- break;
+ second[j] = textRuns[i + j];
}
+ }
+
+ var split = currentRun.Split(length - currentLength);
+
+ first[i] = split.First;
+
+ second[0] = split.Second;
+
+ return new SplitTextRunsResult(first, second);
}
}
- return textLine;
+ return new SplitTextRunsResult(textRuns, null);
}
///
@@ -174,87 +316,6 @@ namespace Avalonia.Media.TextFormatting
return false;
}
- ///
- /// Performs text trimming and returns a trimmed line.
- ///
- /// The text runs to perform the trimming on.
- /// The text range that is covered by the text runs.
- /// A value that specifies the width of the paragraph that the line fills.
- /// A value that represents paragraph properties,
- /// such as TextWrapping, TextAlignment, or TextStyle.
- ///
- private static TextLine PerformTextTrimming(IReadOnlyList textRuns, TextRange textRange,
- double paragraphWidth, TextParagraphProperties paragraphProperties)
- {
- var textTrimming = paragraphProperties.TextTrimming;
- var availableWidth = paragraphWidth;
- var currentWidth = 0.0;
- var runIndex = 0;
-
- while (runIndex < textRuns.Count)
- {
- var currentRun = textRuns[runIndex];
-
- currentWidth += currentRun.GlyphRun.Bounds.Width;
-
- if (currentWidth > availableWidth)
- {
- var ellipsisRun = CreateEllipsisRun(currentRun.Properties);
-
- var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width);
-
- if (textTrimming == TextTrimming.WordEllipsis)
- {
- if (measuredLength < textRange.End)
- {
- var currentBreakPosition = 0;
-
- var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
- while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
- {
- var nextBreakPosition = lineBreaker.Current.PositionWrap;
-
- if (nextBreakPosition == 0)
- {
- break;
- }
-
- if (nextBreakPosition > measuredLength)
- {
- break;
- }
-
- currentBreakPosition = nextBreakPosition;
- }
-
- measuredLength = currentBreakPosition;
- }
- }
-
- var splitResult = SplitTextRuns(textRuns, measuredLength);
-
- var trimmedRuns = new List(splitResult.First.Count + 1);
-
- trimmedRuns.AddRange(splitResult.First);
-
- trimmedRuns.Add(ellipsisRun);
-
- var textLineMetrics =
- TextLineMetrics.Create(trimmedRuns, textRange, paragraphWidth, paragraphProperties);
-
- return new TextLineImpl(trimmedRuns, textLineMetrics);
- }
-
- availableWidth -= currentRun.GlyphRun.Bounds.Width;
-
- runIndex++;
- }
-
- return new TextLineImpl(textRuns,
- TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
- }
-
///
/// Performs text wrapping returns a list of text lines.
///
@@ -269,7 +330,7 @@ namespace Avalonia.Media.TextFormatting
var availableWidth = paragraphWidth;
var currentWidth = 0.0;
var runIndex = 0;
- var length = 0;
+ var currentLength = 0;
while (runIndex < textRuns.Count)
{
@@ -277,60 +338,55 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
{
- var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);
+ var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
+
+ var breakFound = false;
+
+ var currentBreakPosition = 0;
if (measuredLength < currentRun.Text.Length)
{
- if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
- {
- var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(measuredLength));
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text);
- if (lineBreaker.MoveNext())
- {
- measuredLength += lineBreaker.Current.PositionWrap;
- }
- else
- {
- measuredLength = currentRun.Text.Length;
- }
- }
- else
+ while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
- var currentBreakPosition = -1;
+ var nextBreakPosition = lineBreaker.Current.PositionWrap;
- var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
- while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+ if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
{
- var nextBreakPosition = lineBreaker.Current.PositionWrap;
+ break;
+ }
- if (nextBreakPosition == 0)
- {
- break;
- }
+ breakFound = lineBreaker.Current.Required ||
+ lineBreaker.Current.PositionWrap != currentRun.Text.Length;
- if (nextBreakPosition > measuredLength)
- {
- break;
- }
+ currentBreakPosition = nextBreakPosition;
+ }
+ }
- currentBreakPosition = nextBreakPosition;
- }
+ if (breakFound)
+ {
+ measuredLength = currentBreakPosition;
+ }
+ else
+ {
+ if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
+ {
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(currentBreakPosition));
- if (currentBreakPosition != -1)
+ if (lineBreaker.MoveNext())
{
- measuredLength = currentBreakPosition;
+ measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap;
}
-
}
}
- length += measuredLength;
+ currentLength += measuredLength;
- var splitResult = SplitTextRuns(textRuns, length);
+ var splitResult = SplitTextRuns(textRuns, currentLength);
var textLineMetrics = TextLineMetrics.Create(splitResult.First,
- new TextRange(textRange.Start, length), paragraphWidth, paragraphProperties);
+ new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties);
var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
new TextLineBreak(splitResult.Second) :
@@ -341,7 +397,7 @@ namespace Avalonia.Media.TextFormatting
currentWidth += currentRun.GlyphRun.Bounds.Width;
- length += currentRun.GlyphRun.Characters.Length;
+ currentLength += currentRun.GlyphRun.Characters.Length;
runIndex++;
}
@@ -350,94 +406,6 @@ namespace Avalonia.Media.TextFormatting
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
}
- ///
- /// Measures the number of characters that fits into available width.
- ///
- /// The text run.
- /// The available width.
- ///
- private static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth)
- {
- var glyphRun = textCharacters.GlyphRun;
-
- if (glyphRun.Bounds.Width < availableWidth)
- {
- return glyphRun.Characters.Length;
- }
-
- var glyphCount = 0;
-
- var currentWidth = 0.0;
-
- if (glyphRun.GlyphAdvances.IsEmpty)
- {
- var glyphTypeface = glyphRun.GlyphTypeface;
-
- for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
- {
- var glyph = glyphRun.GlyphIndices[i];
-
- var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
-
- if (currentWidth + advance > availableWidth)
- {
- break;
- }
-
- currentWidth += advance;
-
- glyphCount++;
- }
- }
- else
- {
- for (var i = 0; i < glyphRun.GlyphAdvances.Length; i++)
- {
- var advance = glyphRun.GlyphAdvances[i];
-
- if (currentWidth + advance > availableWidth)
- {
- break;
- }
-
- currentWidth += advance;
-
- glyphCount++;
- }
- }
-
- if (glyphCount == glyphRun.GlyphIndices.Length)
- {
- return glyphRun.Characters.Length;
- }
-
- if (glyphRun.GlyphClusters.IsEmpty)
- {
- return glyphCount;
- }
-
- var firstCluster = glyphRun.GlyphClusters[0];
-
- var lastCluster = glyphRun.GlyphClusters[glyphCount];
-
- return lastCluster - firstCluster;
- }
-
- ///
- /// Creates an ellipsis.
- ///
- /// The text run properties.
- ///
- private static ShapedTextCharacters CreateEllipsisRun(TextRunProperties properties)
- {
- var formatterImpl = AvaloniaLocator.Current.GetService();
-
- var glyphRun = formatterImpl.ShapeText(s_ellipsis, properties.Typeface, properties.FontRenderingEmSize,
- properties.CultureInfo);
-
- return new ShapedTextCharacters(glyphRun, properties);
- }
-
///
/// Gets the text range that is covered by the text runs.
///
@@ -464,86 +432,7 @@ namespace Avalonia.Media.TextFormatting
return new TextRange(start, end - start);
}
- ///
- /// Split a sequence of runs into two segments at specified length.
- ///
- /// The text run's.
- /// The length to split at.
- /// The split text runs.
- private static SplitTextRunsResult SplitTextRuns(IReadOnlyList textRuns, int length)
- {
- var currentLength = 0;
-
- for (var i = 0; i < textRuns.Count; i++)
- {
- var currentRun = textRuns[i];
-
- if (currentLength + currentRun.GlyphRun.Characters.Length < length)
- {
- currentLength += currentRun.GlyphRun.Characters.Length;
- continue;
- }
-
- var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
-
- var first = new ShapedTextCharacters[firstCount];
-
- if (firstCount > 1)
- {
- for (var j = 0; j < i; j++)
- {
- first[j] = textRuns[j];
- }
- }
-
- var secondCount = textRuns.Count - firstCount;
-
- if (currentLength + currentRun.GlyphRun.Characters.Length == length)
- {
- var second = new ShapedTextCharacters[secondCount];
-
- var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
-
- if (secondCount > 0)
- {
- for (var j = 0; j < secondCount; j++)
- {
- second[j] = textRuns[i + j + offset];
- }
- }
-
- first[i] = currentRun;
-
- return new SplitTextRunsResult(first, second);
- }
- else
- {
- secondCount++;
-
- var second = new ShapedTextCharacters[secondCount];
-
- if (secondCount > 0)
- {
- for (var j = 1; j < secondCount; j++)
- {
- second[j] = textRuns[i + j];
- }
- }
-
- var split = currentRun.Split(length - currentLength);
-
- first[i] = split.First;
-
- second[0] = split.Second;
-
- return new SplitTextRunsResult(first, second);
- }
- }
-
- return new SplitTextRunsResult(textRuns, null);
- }
-
- private readonly struct SplitTextRunsResult
+ internal readonly struct SplitTextRunsResult
{
public SplitTextRunsResult(IReadOnlyList first, IReadOnlyList second)
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
index 54745144c8..14602a2560 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
-using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
{
@@ -17,6 +15,7 @@ namespace Avalonia.Media.TextFormatting
private readonly ReadOnlySlice _text;
private readonly TextParagraphProperties _paragraphProperties;
private readonly IReadOnlyList> _textStyleOverrides;
+ private readonly TextTrimming _textTrimming;
///
/// Initializes a new instance of the class.
@@ -54,9 +53,11 @@ namespace Avalonia.Media.TextFormatting
new ReadOnlySlice(text.AsMemory());
_paragraphProperties =
- CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming,
+ CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, lineHeight);
+ _textTrimming = textTrimming;
+
_textStyleOverrides = textStyleOverrides;
LineHeight = lineHeight;
@@ -143,18 +144,16 @@ namespace Avalonia.Media.TextFormatting
/// The foreground.
/// The text alignment.
/// The text wrapping.
- /// The text trimming.
/// The text decorations.
/// The height of each line of text.
///
private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
- IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextTrimming textTrimming,
+ IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping,
TextDecorationCollection textDecorations, double lineHeight)
{
var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
- return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming,
- lineHeight);
+ return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, lineHeight);
}
///
@@ -214,25 +213,44 @@ namespace Avalonia.Media.TextFormatting
var textSource = new FormattedTextSource(_text,
_paragraphProperties.DefaultTextRunProperties, _textStyleOverrides);
- TextLineBreak previousLineBreak = null;
+ TextLine previousLine = null;
- while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines))
+ while (currentPosition < _text.Length)
{
var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
- _paragraphProperties, previousLineBreak);
+ _paragraphProperties, previousLine?.LineBreak);
- previousLineBreak = textLine.LineBreak;
+ currentPosition += textLine.TextRange.Length;
- textLines.Add(textLine);
+ if (textLines.Count > 0)
+ {
+ if (textLines.Count == MaxLines || !double.IsPositiveInfinity(MaxHeight) &&
+ height + textLine.LineMetrics.Size.Height > MaxHeight)
+ {
+ if (previousLine?.LineBreak != null && _textTrimming != TextTrimming.None)
+ {
+ var collapsedLine =
+ previousLine.Collapse(GetCollapsingProperties(MaxWidth));
- UpdateBounds(textLine, ref width, ref height);
+ textLines[textLines.Count - 1] = collapsedLine;
+ }
+
+ break;
+ }
+ }
+
+ var hasOverflowed = textLine.LineMetrics.HasOverflowed;
- if (!double.IsPositiveInfinity(MaxHeight) && height > MaxHeight)
+ if (hasOverflowed && _textTrimming != TextTrimming.None)
{
- break;
+ textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth));
}
- currentPosition += textLine.TextRange.Length;
+ textLines.Add(textLine);
+
+ UpdateBounds(textLine, ref width, ref height);
+
+ previousLine = textLine;
if (currentPosition != _text.Length || textLine.LineBreak == null)
{
@@ -250,6 +268,23 @@ namespace Avalonia.Media.TextFormatting
}
}
+ ///
+ /// Gets the for current text trimming mode.
+ ///
+ /// The collapsing width.
+ /// The .
+ private TextCollapsingProperties GetCollapsingProperties(double width)
+ {
+ return _textTrimming switch
+ {
+ TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width,
+ _paragraphProperties.DefaultTextRunProperties),
+ TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width,
+ _paragraphProperties.DefaultTextRunProperties),
+ _ => throw new ArgumentOutOfRangeException(),
+ };
+ }
+
private readonly struct FormattedTextSource : ITextSource
{
private readonly ReadOnlySlice _text;
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
index c3b7dfc77a..423ca9fb7f 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
@@ -39,6 +39,14 @@ namespace Avalonia.Media.TextFormatting
///
public abstract TextLineBreak LineBreak { get; }
+ ///
+ /// Gets a value that indicates whether the line is collapsed.
+ ///
+ ///
+ /// true, if the line is collapsed; otherwise, false.
+ ///
+ public abstract bool HasCollapsed { get; }
+
///
/// Draws the at the given origin.
///
@@ -47,40 +55,49 @@ namespace Avalonia.Media.TextFormatting
public abstract void Draw(DrawingContext drawingContext, Point origin);
///
- /// Client to get the character hit corresponding to the specified
- /// distance from the beginning of the line.
+ /// Create a collapsed line based on collapsed text properties.
+ ///
+ /// A list of
+ /// objects that represent the collapsed text properties.
+ ///
+ /// A value that represents a collapsed line that can be displayed.
+ ///
+ public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
+
+ ///
+ /// Gets the character hit corresponding to the specified distance from the beginning of the line.
///
- /// distance in text flow direction from the beginning of the line
- /// The
+ /// A value that represents the distance from the beginning of the line.
+ /// The object at the specified distance from the beginning of the line.
public abstract CharacterHit GetCharacterHitFromDistance(double distance);
///
- /// Client to get the distance from the beginning of the line from the specified
+ /// Gets the distance from the beginning of the line to the specified character hit.
/// .
///
- /// of the character to query the distance.
- /// Distance in text flow direction from the beginning of the line.
+ /// The object whose distance you want to query.
+ /// A that represents the distance from the beginning of the line.
public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit);
///
- /// Client to get the next for caret navigation.
+ /// Gets the next character hit for caret navigation.
///
/// The current .
/// The next .
public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit);
///
- /// Client to get the previous character hit for caret navigation
+ /// Gets the previous character hit for caret navigation.
///
- /// the current character hit
- /// The previous
+ /// The current .
+ /// The previous .
public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit);
///
- /// Client to get the previous character hit after backspacing
+ /// Gets the previous character hit after backspacing.
///
- /// the current character hit
- /// The after backspacing
+ /// The current .
+ /// The after backspacing.
public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit);
///
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
index a1a9b50793..f73a7be759 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -1,4 +1,6 @@
using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
+using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting
{
@@ -7,11 +9,12 @@ namespace Avalonia.Media.TextFormatting
private readonly IReadOnlyList _textRuns;
public TextLineImpl(IReadOnlyList textRuns, TextLineMetrics lineMetrics,
- TextLineBreak lineBreak = null)
+ TextLineBreak lineBreak = null, bool hasCollapsed = false)
{
_textRuns = textRuns;
LineMetrics = lineMetrics;
LineBreak = lineBreak;
+ HasCollapsed = hasCollapsed;
}
///
@@ -26,6 +29,9 @@ namespace Avalonia.Media.TextFormatting
///
public override TextLineBreak LineBreak { get; }
+ ///
+ public override bool HasCollapsed { get; }
+
///
public override void Draw(DrawingContext drawingContext, Point origin)
{
@@ -41,6 +47,99 @@ namespace Avalonia.Media.TextFormatting
}
}
+ ///
+ public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
+ {
+ if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0)
+ {
+ return this;
+ }
+
+ var collapsingProperties = collapsingPropertiesList[0];
+ var runIndex = 0;
+ var currentWidth = 0.0;
+ var textRange = TextRange;
+ var collapsedLength = 0;
+ TextLineMetrics textLineMetrics;
+
+ var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol);
+
+ var availableWidth = collapsingProperties.Width - shapedSymbol.Bounds.Width;
+
+ while (runIndex < _textRuns.Count)
+ {
+ var currentRun = _textRuns[runIndex];
+
+ currentWidth += currentRun.GlyphRun.Bounds.Width;
+
+ if (currentWidth > availableWidth)
+ {
+ var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
+
+ var currentBreakPosition = 0;
+
+ if (measuredLength < textRange.End)
+ {
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text);
+
+ while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+ {
+ var nextBreakPosition = lineBreaker.Current.PositionWrap;
+
+ if (nextBreakPosition == 0)
+ {
+ break;
+ }
+
+ if (nextBreakPosition > measuredLength)
+ {
+ break;
+ }
+
+ currentBreakPosition = nextBreakPosition;
+ }
+ }
+
+ if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
+ {
+ measuredLength = currentBreakPosition;
+ }
+
+ collapsedLength += measuredLength;
+
+ var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);
+
+ var shapedTextCharacters = new List(splitResult.First.Count + 1);
+
+ shapedTextCharacters.AddRange(splitResult.First);
+
+ shapedTextCharacters.Add(shapedSymbol);
+
+ textRange = new TextRange(textRange.Start, collapsedLength);
+
+ var shapedWidth = GetShapedWidth(shapedTextCharacters);
+
+ textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
+ LineMetrics.TextBaseline, textRange, false);
+
+ return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true);
+ }
+
+ availableWidth -= currentRun.GlyphRun.Bounds.Width;
+
+ collapsedLength += currentRun.GlyphRun.Characters.Length;
+
+ runIndex++;
+ }
+
+ textLineMetrics =
+ new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Bounds.Width),
+ LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed);
+
+ return new TextLineImpl(new List(_textRuns) { shapedSymbol }, textLineMetrics, null,
+ true);
+ }
+
///
public override CharacterHit GetCharacterHitFromDistance(double distance)
{
@@ -82,7 +181,7 @@ namespace Avalonia.Media.TextFormatting
return nextCharacterHit;
}
- return new CharacterHit(TextRange.End); // Can't move, we're after the last character
+ return characterHit; // Can't move, we're after the last character
}
///
@@ -93,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
return previousCharacterHit;
}
- return new CharacterHit(TextRange.Start); // Can't move, we're before the first character
+ return characterHit; // Can't move, we're before the first character
}
///
@@ -148,9 +247,13 @@ namespace Avalonia.Media.TextFormatting
{
var run = _textRuns[runIndex];
- nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
+ var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
- if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
+ nextCharacterHit = characterHit.TrailingLength != 0 ?
+ foundCharacterHit :
+ new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
+
+ if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
{
return true;
}
@@ -184,9 +287,13 @@ namespace Avalonia.Media.TextFormatting
{
var run = _textRuns[runIndex];
- previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
+ var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
+
+ previousCharacterHit = characterHit.TrailingLength != 0 ?
+ foundCharacterHit :
+ new CharacterHit(foundCharacterHit.FirstCharacterIndex);
- if (previousCharacterHit.FirstCharacterIndex < codepointIndex)
+ if (previousCharacterHit.FirstCharacterIndex < characterHit.FirstCharacterIndex)
{
return true;
}
@@ -230,5 +337,41 @@ namespace Avalonia.Media.TextFormatting
return runIndex;
}
+
+ ///
+ /// Creates a shaped symbol.
+ ///
+ /// The symbol run to shape.
+ ///
+ /// The shaped symbol.
+ ///
+ internal static ShapedTextCharacters CreateShapedSymbol(TextRun textRun)
+ {
+ var formatterImpl = AvaloniaLocator.Current.GetService();
+
+ var glyphRun = formatterImpl.ShapeText(textRun.Text, textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize,
+ textRun.Properties.CultureInfo);
+
+ return new ShapedTextCharacters(glyphRun, textRun.Properties);
+ }
+
+ ///
+ /// Gets the shaped width of specified shaped text characters.
+ ///
+ /// The shaped text characters.
+ ///
+ /// The shaped width.
+ ///
+ private static double GetShapedWidth(IReadOnlyList shapedTextCharacters)
+ {
+ var shapedWidth = 0.0;
+
+ for (var i = 0; i < shapedTextCharacters.Count; i++)
+ {
+ shapedWidth += shapedTextCharacters[i].Bounds.Width;
+ }
+
+ return shapedWidth;
+ }
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
index 2f7809ff35..6875cc1c04 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
@@ -9,11 +9,12 @@ namespace Avalonia.Media.TextFormatting
///
public readonly struct TextLineMetrics
{
- public TextLineMetrics(Size size, double textBaseline, TextRange textRange)
+ public TextLineMetrics(Size size, double textBaseline, TextRange textRange, bool hasOverflowed)
{
Size = size;
TextBaseline = textBaseline;
TextRange = textRange;
+ HasOverflowed = hasOverflowed;
}
///
@@ -37,6 +38,12 @@ namespace Avalonia.Media.TextFormatting
///
public double TextBaseline { get; }
+ ///
+ /// Gets a boolean value that indicates whether content of the line overflows
+ /// the specified paragraph width.
+ ///
+ public bool HasOverflowed { get; }
+
///
/// Creates the text line metrics.
///
@@ -83,7 +90,7 @@ namespace Avalonia.Media.TextFormatting
descent - ascent + lineGap :
paragraphProperties.LineHeight);
- return new TextLineMetrics(size, -ascent, textRange);
+ return new TextLineMetrics(size, -ascent, textRange, size.Width > paragraphWidth);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
index 39eb695404..3ecd1aafd9 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
@@ -26,11 +26,6 @@
///
public abstract TextWrapping TextWrapping { get; }
- ///
- /// Gets the text trimming.
- ///
- public abstract TextTrimming TextTrimming { get; }
-
///
/// Paragraph's line height
///
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
index bbcdfe2d8e..c4f9443c3d 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
@@ -4,12 +4,11 @@ using System.Globalization;
namespace Avalonia.Media.TextFormatting
{
///
- /// Properties that can change from one run to the next, such as typeface or foreground brush.
+ /// Provides a set of properties, such as typeface or foreground brush, that can be applied to a TextRun object. This is an abstract class.
///
///
- /// The client provides a concrete implementation of this abstract run properties class. This
- /// allows client to implement their run properties the way that fits with their run formatting
- /// store.
+ /// The text layout client provides a concrete implementation of this abstract class.
+ /// This enables the client to implement text run properties in a way that corresponds with the associated formatting store.
///
public abstract class TextRunProperties : IEquatable
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
new file mode 100644
index 0000000000..4bd46e8c75
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
@@ -0,0 +1,33 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// a collapsing properties to collapse whole line toward the end
+ /// at character granularity and with ellipsis being the collapsing symbol
+ ///
+ public class TextTrailingCharacterEllipsis : TextCollapsingProperties
+ {
+ private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' });
+
+ ///
+ /// Construct a text trailing character ellipsis collapsing properties
+ ///
+ /// width in which collapsing is constrained to
+ /// text run properties of ellispis symbol
+ public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties)
+ {
+ Width = width;
+ Symbol = new TextCharacters(s_ellipsis, textRunProperties);
+ }
+
+ ///
+ public sealed override double Width { get; }
+
+ ///
+ public sealed override TextRun Symbol { get; }
+
+ ///
+ public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter;
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
new file mode 100644
index 0000000000..9dffddd207
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
@@ -0,0 +1,37 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+ ///
+ /// a collapsing properties to collapse whole line toward the end
+ /// at word granularity and with ellipsis being the collapsing symbol
+ ///
+ public class TextTrailingWordEllipsis : TextCollapsingProperties
+ {
+ private static readonly ReadOnlySlice s_ellipsis = new ReadOnlySlice(new[] { '\u2026' });
+
+ ///
+ /// Construct a text trailing word ellipsis collapsing properties
+ ///
+ /// width in which collapsing is constrained to
+ /// text run properties of ellispis symbol
+ public TextTrailingWordEllipsis(
+ double width,
+ TextRunProperties textRunProperties
+ )
+ {
+ Width = width;
+ Symbol = new TextCharacters(s_ellipsis, textRunProperties);
+ }
+
+
+ ///
+ public sealed override double Width { get; }
+
+ ///
+ public sealed override TextRun Symbol { get; }
+
+ ///
+ public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord;
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
index 20fe345d93..2f46fdd9d0 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
@@ -112,7 +112,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
count = 1;
- if (index > text.End)
+ if (index > text.Length)
{
return ReplacementCodepoint;
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
index 26f7721128..76bb9ac44f 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
@@ -109,7 +109,6 @@ 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/TextWrapping.cs b/src/Avalonia.Visuals/Media/TextWrapping.cs
index d649bda23f..b7915e5612 100644
--- a/src/Avalonia.Visuals/Media/TextWrapping.cs
+++ b/src/Avalonia.Visuals/Media/TextWrapping.cs
@@ -5,13 +5,6 @@ namespace Avalonia.Media
///
public enum TextWrapping
{
- ///
- /// Line-breaking occurs if the line overflows the available block width.
- /// However, a line may overflow the block width if the line breaking algorithm
- /// cannot determine a break opportunity, as in the case of a very long word.
- ///
- WrapWithOverflow,
-
///
/// Text should not wrap.
///
@@ -20,6 +13,13 @@ namespace Avalonia.Media
///
/// Text can wrap.
///
- Wrap
+ Wrap,
+
+ ///
+ /// Line-breaking occurs if the line overflows the available block width.
+ /// However, a line may overflow the block width if the line breaking algorithm
+ /// cannot determine a break opportunity, as in the case of a very long word.
+ ///
+ WrapWithOverflow
}
}
diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs
index 82cf3c934f..77e410162f 100644
--- a/src/Avalonia.X11/NativeDialogs/Gtk.cs
+++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs
@@ -204,6 +204,9 @@ namespace Avalonia.X11.NativeDialogs
[DllImport(GtkName)]
public static extern IntPtr gtk_file_chooser_add_filter(IntPtr chooser, IntPtr filter);
+
+ [DllImport(GtkName)]
+ public static extern IntPtr gtk_file_chooser_get_filter(IntPtr chooser);
[DllImport(GtkName)]
public static extern void gtk_widget_realize(IntPtr gtkWidget);
diff --git a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
index 287a541bc8..6cf6c6f35f 100644
--- a/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
+++ b/src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
@@ -16,7 +16,7 @@ namespace Avalonia.X11.NativeDialogs
{
private Task _initialized;
private unsafe Task ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
- bool multiSelect, string initialFileName, IEnumerable filters)
+ bool multiSelect, string initialFileName, IEnumerable filters, string defaultExtension)
{
IntPtr dlg;
using (var name = new Utf8Buffer(title))
@@ -35,11 +35,13 @@ namespace Avalonia.X11.NativeDialogs
foreach (var d in disposables) d.Dispose();
disposables.Clear();
}
-
+
+ var filtersDic = new Dictionary();
if(filters != null)
foreach (var f in filters)
{
var filter = gtk_file_filter_new();
+ filtersDic[filter] = f;
using (var b = new Utf8Buffer(f.Name))
gtk_file_filter_set_name(filter, b);
@@ -74,6 +76,15 @@ namespace Avalonia.X11.NativeDialogs
}
g_slist_free(gs);
result = resultList.ToArray();
+
+ // GTK doesn't auto-append the extension, so we need to do that manually
+ if (action == GtkFileChooserAction.Save)
+ {
+ var currentFilter = gtk_file_chooser_get_filter(dlg);
+ filtersDic.TryGetValue(currentFilter, out var selectedFilter);
+ for (var c = 0; c < result.Length; c++)
+ result[c] = NameWithExtension(result[c], defaultExtension, selectedFilter);
+ }
}
gtk_widget_hide(dlg);
@@ -101,7 +112,30 @@ namespace Avalonia.X11.NativeDialogs
gtk_window_present(dlg);
return tcs.Task;
}
-
+
+ string NameWithExtension(string path, string defaultExtension, FileDialogFilter filter)
+ {
+ var name = Path.GetFileName(path);
+ if (name != null && !name.Contains("."))
+ {
+ if (filter?.Extensions?.Count > 0)
+ {
+ if (defaultExtension != null
+ && filter.Extensions.Contains(defaultExtension))
+ return path + "." + defaultExtension.TrimStart('.');
+
+ var ext = filter.Extensions.FirstOrDefault(x => x != "*");
+ if (ext != null)
+ return path + "." + ext.TrimStart('.');
+ }
+
+ if (defaultExtension != null)
+ path += "." + defaultExtension.TrimStart('.');
+ }
+
+ return path;
+ }
+
public async Task ShowFileDialogAsync(FileDialog dialog, Window parent)
{
await EnsureInitialized();
@@ -113,7 +147,8 @@ namespace Avalonia.X11.NativeDialogs
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.Directory) ? "" : dialog.Directory,
- string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters));
+ string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), dialog.Filters,
+ (dialog as SaveFileDialog)?.DefaultExtension));
}
public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
@@ -125,7 +160,7 @@ namespace Avalonia.X11.NativeDialogs
return await await RunOnGlibThread(async () =>
{
var res = await ShowDialog(dialog.Title, platformImpl,
- GtkFileChooserAction.SelectFolder, false, dialog.Directory, null);
+ GtkFileChooserAction.SelectFolder, false, dialog.Directory, null, null);
return res?.FirstOrDefault();
});
}
diff --git a/src/Avalonia.X11/X11NativeControlHost.cs b/src/Avalonia.X11/X11NativeControlHost.cs
index 23fb27f72b..6c4eb81c84 100644
--- a/src/Avalonia.X11/X11NativeControlHost.cs
+++ b/src/Avalonia.X11/X11NativeControlHost.cs
@@ -167,7 +167,7 @@ namespace Avalonia.X11
XUnmapWindow(_display, _holder.Handle);
}
- size *= _attachedTo.Window.Scaling;
+ size *= _attachedTo.Window.RenderScaling;
XResizeWindow(_display, _child.Handle,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
}
@@ -179,7 +179,7 @@ namespace Avalonia.X11
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
- bounds *= _attachedTo.Window.Scaling;
+ bounds *= _attachedTo.Window.RenderScaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 2a13999e8d..c24abcd230 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -163,7 +163,7 @@ namespace Avalonia.X11
var surfaces = new List
{
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
- depth, () => Scaling)
+ depth, () => RenderScaling)
};
if (egl != null)
@@ -217,7 +217,7 @@ namespace Avalonia.X11
}
}
- public double Scaling => _window.Scaling;
+ public double Scaling => _window.RenderScaling;
}
void UpdateMotifHints()
@@ -284,9 +284,9 @@ namespace Avalonia.X11
XSetWMNormalHints(_x11.Display, _handle, ref hints);
}
- public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling);
+ public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling);
- public double Scaling
+ public double RenderScaling
{
get
{
@@ -296,6 +296,8 @@ namespace Avalonia.X11
}
private set => _scaling = value;
}
+
+ public double DesktopScaling => RenderScaling;
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
@@ -538,14 +540,14 @@ namespace Avalonia.X11
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
- newScaling = monitor?.PixelDensity ?? Scaling;
+ newScaling = monitor?.PixelDensity ?? RenderScaling;
}
- if (Scaling != newScaling)
+ if (RenderScaling != newScaling)
{
var oldScaledSize = ClientSize;
- Scaling = newScaling;
- ScalingChanged?.Invoke(Scaling);
+ RenderScaling = newScaling;
+ ScalingChanged?.Invoke(RenderScaling);
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize)
Resize(oldScaledSize, true);
@@ -707,9 +709,9 @@ namespace Avalonia.X11
private void ScheduleInput(RawInputEventArgs args)
{
if (args is RawPointerEventArgs mouse)
- mouse.Position = mouse.Position / Scaling;
+ mouse.Position = mouse.Position / RenderScaling;
if (args is RawDragEvent drag)
- drag.Location = drag.Location / Scaling;
+ drag.Location = drag.Location / RenderScaling;
_lastEvent = new InputEventContainer() {Event = args};
_inputQueue.Enqueue(_lastEvent);
@@ -760,11 +762,7 @@ namespace Avalonia.X11
public void Dispose()
{
- if (_handle != IntPtr.Zero)
- {
- XDestroyWindow(_x11.Display, _handle);
- Cleanup();
- }
+ Cleanup();
}
void Cleanup()
@@ -787,8 +785,7 @@ namespace Avalonia.X11
}
if (_useRenderWindow && _renderHandle != IntPtr.Zero)
- {
- XDestroyWindow(_x11.Display, _renderHandle);
+ {
_renderHandle = IntPtr.Zero;
}
}
@@ -821,11 +818,11 @@ namespace Avalonia.X11
public void Hide() => XUnmapWindow(_x11.Display, _handle);
- public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
+ public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling);
public PixelPoint PointToScreen(Point point) => new PixelPoint(
- (int)(point.X * Scaling + Position.X),
- (int)(point.Y * Scaling + Position.Y));
+ (int)(point.X * RenderScaling + Position.X),
+ (int)(point.Y * RenderScaling + Position.Y));
public void SetSystemDecorations(SystemDecorations enabled)
{
@@ -845,7 +842,7 @@ namespace Avalonia.X11
Resize(size, true);
}
- PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
+ PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling));
void Resize(Size clientSize, bool force)
{
@@ -1025,13 +1022,13 @@ namespace Avalonia.X11
{
_scaledMinMaxSize = (minSize, maxSize);
var min = new PixelSize(
- (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
- (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
+ (int)(minSize.Width < 1 ? 1 : minSize.Width * RenderScaling),
+ (int)(minSize.Height < 1 ? 1 : minSize.Height * RenderScaling));
const int maxDim = MaxWindowDimension;
var max = new PixelSize(
- (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)),
- (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling)));
+ (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * RenderScaling)),
+ (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * RenderScaling)));
_minMaxSize = (min, max);
UpdateSizeHints(null);
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index b8ae2eb4d8..0a101eec7a 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -65,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer
public IMouseDevice MouseDevice => new MouseDevice();
public IPopupImpl CreatePopup() => null;
- public double Scaling => _outputBackend.Scaling;
+ public double RenderScaling => _outputBackend.Scaling;
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
public Action Paint { get; set; }
@@ -77,7 +77,7 @@ namespace Avalonia.LinuxFramebuffer
public Action Closed { get; set; }
public Action LostFocus { get; set; }
- public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);
+ public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling);
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
new file mode 100644
index 0000000000..768545eb7e
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
new file mode 100644
index 0000000000..4569970d01
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
@@ -0,0 +1,42 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using Avalonia.Markup.Xaml.XamlIl;
+// ReSharper disable CheckNamespace
+
+namespace Avalonia.Markup.Xaml
+{
+ public static class AvaloniaRuntimeXamlLoader
+ {
+ ///
+ /// Loads XAML from a string.
+ ///
+ /// The string containing the XAML.
+ /// Default assembly for clr-namespace:
+ ///
+ /// The optional instance into which the XAML should be loaded.
+ ///
+ /// The loaded object.
+ public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false)
+ {
+ Contract.Requires(xaml != null);
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+ {
+ return Load(stream, localAssembly, rootInstance, uri, designMode);
+ }
+ }
+
+ public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null,
+ bool designMode = false)
+ => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode);
+
+ public static object Parse(string xaml, Assembly localAssembly = null)
+ => Load(xaml, localAssembly);
+
+ public static T Parse(string xaml, Assembly localAssembly = null)
+ => (T)Parse(xaml, localAssembly);
+
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
similarity index 93%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index 5a0d6bac8d..241976241f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -1,11 +1,6 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Text;
-using Avalonia.Markup.Parsers;
-using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
-using Avalonia.Utilities;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
@@ -129,12 +124,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (itemsCollectionType != null)
{
- var elementType = itemsCollectionType
- .GetAllInterfaces()
- .FirstOrDefault(i =>
- i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
- .GenericArguments[0];
- return new AvaloniaXamlIlDataContextTypeMetadataNode(on, elementType);
+ foreach (var i in GetAllInterfacesIncludingSelf(itemsCollectionType))
+ {
+ if (i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
+ {
+ return new AvaloniaXamlIlDataContextTypeMetadataNode(on, i.GenericArguments[0]);
+ }
+ }
}
// We can't infer the collection type and the currently calculated type is definitely wrong.
// Notify the user that we were unable to infer the data context type if they use a compiled binding.
@@ -165,6 +161,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
}
+
+ private static IEnumerable GetAllInterfacesIncludingSelf(IXamlType type)
+ {
+ if (type.IsInterface)
+ yield return type;
+
+ foreach (var i in type.GetAllInterfaces())
+ yield return i;
+ }
}
[DebuggerDisplay("DataType = {DataContextType}")]
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
rename to src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props b/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
new file mode 100644
index 0000000000..c902fa956a
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
similarity index 100%
rename from src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
rename to src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 3979312ce0..24428253aa 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -45,44 +45,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -96,8 +62,6 @@
-
-
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
index 5c21037924..e5c6b72d12 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
@@ -10,10 +10,13 @@ namespace Avalonia.Markup.Xaml
///
/// Loads XAML for a avalonia application.
///
- public class AvaloniaXamlLoader
+ public static class AvaloniaXamlLoader
{
- public bool IsDesignMode { get; set; }
-
+ public interface IRuntimeXamlLoader
+ {
+ object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode);
+ }
+
///
/// Loads the XAML into a Avalonia component.
///
@@ -32,7 +35,7 @@ namespace Avalonia.Markup.Xaml
/// A base URI to use if is relative.
///
/// The loaded object.
- public object Load(Uri uri, Uri baseUri = null)
+ public static object Load(Uri uri, Uri baseUri = null)
{
Contract.Requires(uri != null);
@@ -55,52 +58,22 @@ namespace Avalonia.Markup.Xaml
if (compiledResult != null)
return compiledResult;
}
-
-
- var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
- using (var stream = asset.stream)
- {
- var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
- return Load(stream, asset.assembly, null, absoluteUri);
- }
- }
-
- ///
- /// Loads XAML from a string.
- ///
- /// The string containing the XAML.
- /// Default assembly for clr-namespace:
- ///
- /// The optional instance into which the XAML should be loaded.
- ///
- /// The loaded object.
- public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null)
- {
- Contract.Requires(xaml != null);
- using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+ // This is intended for unit-tests only
+ var runtimeLoader = AvaloniaLocator.Current.GetService();
+ if (runtimeLoader != null)
{
- return Load(stream, localAssembly, rootInstance);
+ var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
+ using (var stream = asset.stream)
+ {
+ var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
+ return runtimeLoader.Load(stream, asset.assembly, null, absoluteUri, false);
+ }
}
- }
- ///
- /// Loads XAML from a stream.
- ///
- /// The stream containing the XAML.
- /// Default assembly for clr-namespace
- ///
- /// The optional instance into which the XAML should be loaded.
- ///
- /// The URI of the XAML
- /// The loaded object.
- public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
- => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
-
- public static object Parse(string xaml, Assembly localAssembly = null)
- => new AvaloniaXamlLoader().Load(xaml, localAssembly);
-
- public static T Parse(string xaml, Assembly localAssembly = null)
- => (T)Parse(xaml, localAssembly);
+ throw new XamlLoadException(
+ $"No precompiled XAML found for {uri} (baseUri: {baseUri}), make sure to specify x:Class and include your XAML file as AvaloniaResource");
+ }
+
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
index 0cedf4f364..b6137aa89f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
@@ -25,8 +25,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (_loaded == null)
{
_isLoading = true;
- var loader = new AvaloniaXamlLoader();
- _loaded = (IResourceDictionary)loader.Load(Source, _baseUri);
+ _loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(Source, _baseUri);
_isLoading = false;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
index ea9042f779..607b552c28 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
@@ -51,8 +51,7 @@ namespace Avalonia.Markup.Xaml.Styling
if (_loaded == null)
{
_isLoading = true;
- var loader = new AvaloniaXamlLoader();
- var loaded = (IStyle)loader.Load(Source, _baseUri);
+ var loaded = (IStyle)AvaloniaXamlLoader.Load(Source, _baseUri);
_loaded = new[] { loaded };
_isLoading = false;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index 5663d08412..07c5451135 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -5,7 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
- public class DataTemplate : IDataTemplate
+ public class DataTemplate : IRecyclingDataTemplate
{
public Type DataType { get; set; }
@@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object Content { get; set; }
- public bool SupportsRecycling { get; set; } = true;
-
public bool Match(object data)
{
if (DataType == null)
@@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates
}
}
- public IControl Build(object data) => TemplateContent.Load(Content).Control;
+ public IControl Build(object data) => Build(data, null);
+
+ public IControl Build(object data, IControl existing)
+ {
+ return existing ?? TemplateContent.Load(Content).Control;
+ }
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index b96486235a..b8e1c2df80 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates
[AssignBinding]
public Binding ItemsSource { get; set; }
- public bool SupportsRecycling { get; set; } = true;
-
public bool Match(object data)
{
if (DataType == null)
diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs
index 7c4e7b5efe..3dbc83a7df 100644
--- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs
+++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs
@@ -137,9 +137,9 @@ namespace Avalonia.Data
{
Contract.Requires(target != null);
- if (!(target is IStyledElement))
+ if (!(target is IDataContextProvider))
{
- target = anchor as IStyledElement;
+ target = anchor as IDataContextProvider;
if (target == null)
{
diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
index d1f8d6a779..5e630e54a6 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -570,7 +570,7 @@ namespace Avalonia.Skia
float constraint = -1;
- if (_wrapping != TextWrapping.NoWrap)
+ if (_wrapping == TextWrapping.Wrap)
{
constraint = widthConstraint <= 0 ? MAX_LINE_WIDTH : widthConstraint;
if (constraint > MAX_LINE_WIDTH)
diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
index ffe1175567..b0384a1fdf 100644
--- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs
+++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
@@ -82,7 +82,7 @@ namespace Avalonia.Skia
if (codepoint.IsBreakChar)
{
- if (i < text.End)
+ if (i + 1 < text.Length)
{
var nextCodepoint = Codepoint.ReadAt(text, i + 1, out _);
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index f5d83611bb..3467a33d16 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -75,7 +75,7 @@ namespace Avalonia.Win32.Interop.Wpf
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
if (msg == (int)UnmanagedMethods.WindowsMessage.WM_DPICHANGED)
- _ttl.ScalingChanged?.Invoke(_ttl.Scaling);
+ _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling);
return IntPtr.Zero;
}
@@ -84,7 +84,7 @@ namespace Avalonia.Win32.Interop.Wpf
_currentHwndSource?.RemoveHook(_hook);
_currentHwndSource = e.NewSource as HwndSource;
_currentHwndSource?.AddHook(_hook);
- _ttl.ScalingChanged?.Invoke(_ttl.Scaling);
+ _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling);
}
public IRenderer CreateRenderer(IRenderRoot root)
@@ -102,7 +102,7 @@ namespace Avalonia.Win32.Interop.Wpf
Size ITopLevelImpl.ClientSize => _finalSize;
IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
- double ITopLevelImpl.Scaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
+ double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;
IEnumerable ITopLevelImpl.Surfaces => _surfaces;
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 1aec4f0016..b7c68c4b95 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -1059,9 +1060,21 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
+ public static extern IntPtr GetFocus();
+ [DllImport("user32.dll")]
public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
+
+ public enum GetAncestorFlags
+ {
+ GA_PARENT = 1,
+ GA_ROOT = 2,
+ GA_ROOTOWNER = 3
+ }
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags gaFlags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow);
@@ -1382,6 +1395,22 @@ namespace Avalonia.Win32.Interop
throw new Exception("RtlGetVersion failed!");
}
}
+
+ [DllImport("kernel32", EntryPoint="WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable);
+
+ public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF);
+
+ internal static int WaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable)
+ {
+ int result = IntWaitForMultipleObjectsEx(nCount, pHandles, bWaitAll, dwMilliseconds, bAlertable);
+ if(result == WAIT_FAILED)
+ {
+ throw new Win32Exception();
+ }
+
+ return result;
+ }
[DllImport("user32.dll")]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
diff --git a/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs b/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs
new file mode 100644
index 0000000000..a0160fcfbd
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs
@@ -0,0 +1,15 @@
+using System;
+using Avalonia.Threading;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+ internal class NonPumpingWaitProvider : AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider
+ {
+ public int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+ {
+ return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll,
+ millisecondsTimeout, false);
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs
index cd25b32ed9..57da1c4d66 100644
--- a/src/Windows/Avalonia.Win32/PopupImpl.cs
+++ b/src/Windows/Avalonia.Win32/PopupImpl.cs
@@ -7,6 +7,7 @@ namespace Avalonia.Win32
{
class PopupImpl : WindowImpl, IPopupImpl
{
+ private readonly IWindowBaseImpl _parent;
private bool _dropShadowHint = true;
private Size? _maxAutoSize;
@@ -19,18 +20,25 @@ namespace Avalonia.Win32
public override void Show()
{
UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate);
- var parent = UnmanagedMethods.GetParent(Handle.Handle);
- if (parent != IntPtr.Zero)
- {
- IntPtr nextParent = parent;
- while (nextParent != IntPtr.Zero)
- {
- parent = nextParent;
- nextParent = UnmanagedMethods.GetParent(parent);
- }
- UnmanagedMethods.SetFocus(parent);
+ // We need to steal focus if it's held by a child window of our toplevel window
+ var parent = _parent;
+ while(parent != null)
+ {
+ if(parent is PopupImpl pi)
+ parent = pi._parent;
+ else
+ break;
}
+
+ if(parent == null)
+ return;
+
+ var focusOwner = UnmanagedMethods.GetFocus();
+ if (focusOwner != IntPtr.Zero &&
+ UnmanagedMethods.GetAncestor(focusOwner, UnmanagedMethods.GetAncestorFlags.GA_ROOT)
+ == parent.Handle.Handle)
+ UnmanagedMethods.SetFocus(parent.Handle.Handle);
}
protected override bool ShouldTakeFocusOnClick => false;
@@ -49,7 +57,7 @@ namespace Avalonia.Win32
{
var info = UnmanagedMethods.MONITORINFO.Create();
UnmanagedMethods.GetMonitorInfo(monitor, ref info);
- _maxAutoSize = info.rcWork.ToPixelRect().ToRect(Scaling).Size;
+ _maxAutoSize = info.rcWork.ToPixelRect().ToRect(RenderScaling).Size;
}
}
@@ -118,6 +126,7 @@ namespace Avalonia.Win32
private PopupImpl(IWindowBaseImpl parent, bool dummy) : base()
{
+ _parent = parent;
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
diff --git a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
index d7bb2c037e..8f62163d81 100644
--- a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
+++ b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
@@ -176,7 +176,7 @@ namespace Avalonia.Win32
UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
if (_attachedTo == null || _child == null)
return;
- size *= _attachedTo.Window.Scaling;
+ size *= _attachedTo.Window.RenderScaling;
UnmanagedMethods.MoveWindow(_child.Handle, 0, 0,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false);
}
@@ -186,7 +186,7 @@ namespace Avalonia.Win32
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
- bounds *= _attachedTo.Window.Scaling;
+ bounds *= _attachedTo.Window.RenderScaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index b7bb0e19ba..af6058d197 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -93,6 +93,7 @@ namespace Avalonia.Win32
.Bind().ToConstant(s_instance)
.Bind().ToSingleton()
.Bind().ToConstant(s_instance)
+ .Bind().ToConstant(new NonPumpingWaitProvider())
.Bind().ToConstant(new WindowsMountedVolumeInfoProvider());
if (options.AllowEglInitialization)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 0ba1d311bc..25a34561fc 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -343,7 +343,7 @@ namespace Avalonia.Win32
{
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
- var f = Scaling;
+ var f = RenderScaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
@@ -368,7 +368,7 @@ namespace Avalonia.Win32
size == SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
- Resized(clientSize / Scaling);
+ Resized(clientSize / RenderScaling);
}
var windowState = size == SizeCommand.Maximized ?
@@ -402,29 +402,31 @@ namespace Avalonia.Win32
case WindowsMessage.WM_GETMINMAXINFO:
{
MINMAXINFO mmi = Marshal.PtrToStructure(lParam);
+
+ _maxTrackSize = mmi.ptMaxTrackSize;
if (_minSize.Width > 0)
{
mmi.ptMinTrackSize.X =
- (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
+ (int)((_minSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right);
}
if (_minSize.Height > 0)
{
mmi.ptMinTrackSize.Y =
- (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
+ (int)((_minSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom);
}
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0)
{
mmi.ptMaxTrackSize.X =
- (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
+ (int)((_maxSize.Width * RenderScaling) + BorderThickness.Left + BorderThickness.Right);
}
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0)
{
mmi.ptMaxTrackSize.Y =
- (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
+ (int)((_maxSize.Height * RenderScaling) + BorderThickness.Top + BorderThickness.Bottom);
}
Marshal.StructureToPtr(mmi, lParam, true);
@@ -480,7 +482,7 @@ namespace Avalonia.Win32
private Point DipFromLParam(IntPtr lParam)
{
- return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;
+ return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / RenderScaling;
}
private PixelPoint PointFromLParam(IntPtr lParam)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
index 2badf99f7f..a3b7574369 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Win32
if (_extendTitleBarHint >= 0)
{
- border_thickness.top = (int)(_extendedMargins.Top * Scaling);
+ border_thickness.top = (int)(_extendedMargins.Top * RenderScaling);
}
// Determine if the hit test is for resizing. Default middle (1,1).
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 0ee1342d27..6f22f94056 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -164,7 +164,9 @@ namespace Avalonia.Win32
}
}
- public double Scaling => _scaling;
+ public double RenderScaling => _scaling;
+
+ public double DesktopScaling => RenderScaling;
public Size ClientSize
{
@@ -172,7 +174,7 @@ namespace Avalonia.Win32
{
GetClientRect(_hwnd, out var rect);
- return new Size(rect.right, rect.bottom) / Scaling;
+ return new Size(rect.right, rect.bottom) / RenderScaling;
}
}
@@ -180,7 +182,7 @@ namespace Avalonia.Win32
public IPlatformHandle Handle { get; private set; }
- public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / Scaling, _maxTrackSize.Y / Scaling);
+ public virtual Size MaxAutoSizeHint => new Size(_maxTrackSize.X / RenderScaling, _maxTrackSize.Y / RenderScaling);
public IMouseDevice MouseDevice => _mouseDevice;
@@ -342,8 +344,8 @@ namespace Avalonia.Win32
public void Resize(Size value)
{
- int requestedClientWidth = (int)(value.Width * Scaling);
- int requestedClientHeight = (int)(value.Height * Scaling);
+ int requestedClientWidth = (int)(value.Width * RenderScaling);
+ int requestedClientHeight = (int)(value.Height * RenderScaling);
GetClientRect(_hwnd, out var clientRect);
@@ -395,7 +397,7 @@ namespace Avalonia.Win32
public void Invalidate(Rect rect)
{
- var scaling = Scaling;
+ var scaling = RenderScaling;
var r = new RECT
{
left = (int)Math.Floor(rect.X * scaling),
@@ -411,12 +413,12 @@ namespace Avalonia.Win32
{
var p = new POINT { X = point.X, Y = point.Y };
UnmanagedMethods.ScreenToClient(_hwnd, ref p);
- return new Point(p.X, p.Y) / Scaling;
+ return new Point(p.X, p.Y) / RenderScaling;
}
public PixelPoint PointToScreen(Point point)
{
- point *= Scaling;
+ point *= RenderScaling;
var p = new POINT { X = (int)point.X, Y = (int)point.Y };
ClientToScreen(_hwnd, ref p);
return new PixelPoint(p.X, p.Y);
@@ -710,19 +712,19 @@ namespace Avalonia.Win32
if (_extendTitleBarHint != -1)
{
- borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling);
+ borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);
}
margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
if (WindowState == WindowState.Maximized)
{
- _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / Scaling, 0, 0);
- _offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling);
+ _extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / RenderScaling, 0, 0);
+ _offScreenMargin = new Thickness(borderThickness.left / RenderScaling, borderThickness.top / RenderScaling, borderThickness.right / RenderScaling, borderThickness.bottom / RenderScaling);
}
else
{
- _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / Scaling, 0, 0);
+ _extendedMargins = new Thickness(0, (borderCaptionThickness.top) / RenderScaling, 0, 0);
_offScreenMargin = new Thickness();
}
@@ -1034,6 +1036,8 @@ namespace Avalonia.Win32
}
}
+ double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => RenderScaling;
+
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle;
public void SetExtendClientAreaToDecorationsHint(bool hint)
diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
index 83a68990d7..5a85a5ea88 100644
--- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs
+++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
@@ -48,7 +48,7 @@ namespace Avalonia.iOS
public new IPlatformHandle Handle => null;
- public double Scaling => UIScreen.MainScreen.Scale;
+ public double RenderScaling => UIScreen.MainScreen.Scale;
public override void LayoutSubviews() => Resized?.Invoke(ClientSize);
diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
index 1c7077870a..51d18e55d1 100644
--- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
+++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
@@ -18,6 +18,10 @@
-
+
+
+
+
+
diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
new file mode 100644
index 0000000000..7af29a56a1
--- /dev/null
+++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
@@ -0,0 +1,16 @@
+using System;
+using System.IO;
+using System.Reflection;
+using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.XamlIl;
+
+namespace Avalonia.Designer.HostApp
+{
+ class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader
+ {
+ public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode)
+ {
+ return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAsm, o, baseUri, designMode);
+ }
+ }
+}
diff --git a/src/tools/Avalonia.Designer.HostApp/Program.cs b/src/tools/Avalonia.Designer.HostApp/Program.cs
index 3163e1fbc3..4472dac4e3 100644
--- a/src/tools/Avalonia.Designer.HostApp/Program.cs
+++ b/src/tools/Avalonia.Designer.HostApp/Program.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Reflection;
+using Avalonia.DesignerSupport;
+using Avalonia.Markup.Xaml;
namespace Avalonia.Designer.HostApp
{
@@ -40,8 +42,9 @@ namespace Avalonia.Designer.HostApp
public static void Main(string[] args)
#endif
{
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new DesignXamlLoader());
Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args);
}
-
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
index e533001242..58ddc8ca60 100644
--- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
@@ -1,6 +1,5 @@
using System;
-using System.Collections.Generic;
-using Avalonia.Threading;
+using Avalonia.Data;
using Avalonia.UnitTests;
using Xunit;
@@ -32,5 +31,20 @@ namespace Avalonia.Controls.UnitTests
Assert.True(raised);
}
}
+
+ [Fact]
+ public void Can_Bind_To_DataContext()
+ {
+ using (UnitTestApplication.Start())
+ {
+ var application = Application.Current;
+
+ application.DataContext = "Test";
+
+ application.Bind(Application.NameProperty, new Binding("."));
+
+ Assert.Equal("Test", Application.Current.Name);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
index 2ca93dcf56..19c4454d3d 100644
--- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
+++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index 9a81d19bb9..7a2109e5a7 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -191,8 +191,7 @@ namespace Avalonia.Controls.UnitTests
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target1 = window.Find("target1");
var target2 = window.Find("target2");
var mouse = new MouseTestHelper();
@@ -235,8 +234,7 @@ namespace Avalonia.Controls.UnitTests
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target1 = window.Find("target1");
var target2 = window.Find("target2");
var mouse = new MouseTestHelper();
@@ -298,7 +296,7 @@ namespace Avalonia.Controls.UnitTests
var windowImpl = MockWindowingPlatform.CreateWindowMock();
popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
- popupImpl.SetupGet(x => x.Scaling).Returns(1);
+ popupImpl.SetupGet(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
index dee7a84812..84f02aeda5 100644
--- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
@@ -191,7 +191,8 @@ namespace Avalonia.Controls.UnitTests
{
var windowImpl = new Mock();
windowImpl.SetupProperty(x => x.Closed);
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
var services = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
index fddc02f19c..fab57cec49 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
@@ -60,6 +60,25 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.IsType(target.Panel.Children[1]);
}
+ [Fact]
+ public void Should_Create_Containers_Only_Once()
+ {
+ var parent = new TestItemsControl();
+ var target = new ItemsPresenter
+ {
+ Items = new[] { "foo", "bar" },
+ [StyledElement.TemplatedParentProperty] = parent,
+ };
+ var raised = 0;
+
+ parent.ItemContainerGenerator.Materialized += (s, e) => ++raised;
+
+ target.ApplyTemplate();
+
+ Assert.Equal(2, target.Panel.Children.Count);
+ Assert.Equal(2, raised);
+ }
+
[Fact]
public void ItemContainerGenerator_Should_Be_Picked_Up_From_TemplatedControl()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index fe9c7b1261..9ef2750ff3 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
target.SelectedIndex = 1;
Assert.Equal(items[1], target.SelectedItem);
@@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.NotNull(receivedArgs);
Assert.Empty(receivedArgs.AddedItems);
Assert.Equal(new[] { removed }, receivedArgs.RemovedItems);
+ Assert.False(items.Single().IsSelected);
+ }
+
+ [Fact]
+ public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit()
+ {
+ var items = new AvaloniaList-
+ {
+ new Item(),
+ new Item(),
+ };
+
+ var target = new SelectingItemsControl();
+ target.BeginInit();
+ target.Items = items;
+ target.Template = Template();
+ target.EndInit();
+
+ target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
+ target.SelectedIndex = 0;
+
+ Assert.Equal(items[0], target.SelectedItem);
+ Assert.Equal(0, target.SelectedIndex);
+
+ SelectionChangedEventArgs receivedArgs = null;
+
+ target.SelectionChanged += (_, args) => receivedArgs = args;
+
+ var removed = items[0];
+
+ items.RemoveAt(0);
+
+ Assert.Null(target.SelectedItem);
+ Assert.Equal(-1, target.SelectedIndex);
+ Assert.NotNull(receivedArgs);
+ Assert.Empty(receivedArgs.AddedItems);
+ Assert.Equal(new[] { removed }, receivedArgs.RemovedItems);
+ Assert.False(items.Single().IsSelected);
}
[Fact]
diff --git a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs
index 4248e643eb..6e171a58e7 100644
--- a/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/RelativePanelTests.cs
@@ -31,6 +31,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(20, 0, 20, 20), target.Children[1].Bounds);
}
+ [Fact]
public void Lays_Out_1_Child_Below_the_other()
{
var rect1 = new Rectangle { Height = 20, Width = 20 };
@@ -55,5 +56,81 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds);
}
+
+ [Fact]
+ public void RelativePanel_Can_Center()
+ {
+ var rect1 = new Rectangle { Height = 20, Width = 20 };
+ var rect2 = new Rectangle { Height = 20, Width = 20 };
+
+ var target = new RelativePanel
+ {
+ VerticalAlignment = Layout.VerticalAlignment.Center,
+ HorizontalAlignment = Layout.HorizontalAlignment.Center,
+ Children =
+ {
+ rect1, rect2
+ }
+ };
+
+ RelativePanel.SetAlignLeftWithPanel(rect1, true);
+ RelativePanel.SetBelow(rect2, rect1);
+ target.Measure(new Size(400, 400));
+ target.Arrange(new Rect(target.DesiredSize));
+
+ Assert.Equal(new Size(20, 40), target.Bounds.Size);
+ Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
+ Assert.Equal(new Rect(0, 20, 20, 20), target.Children[1].Bounds);
+ }
+
+ [Fact]
+ public void LeftOf_Measures_Correctly()
+ {
+ var rect1 = new Rectangle { Height = 20, Width = 20 };
+ var rect2 = new Rectangle { Height = 20, Width = 20 };
+
+ var target = new RelativePanel
+ {
+ VerticalAlignment = Layout.VerticalAlignment.Center,
+ HorizontalAlignment = Layout.HorizontalAlignment.Center,
+ Children =
+ {
+ rect1, rect2
+ }
+ };
+
+ RelativePanel.SetLeftOf(rect2, rect1);
+ target.Measure(new Size(400, 400));
+ target.Arrange(new Rect(target.DesiredSize));
+
+ Assert.Equal(new Size(20, 20), target.Bounds.Size);
+ Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
+ Assert.Equal(new Rect(-20, 0, 20, 20), target.Children[1].Bounds);
+ }
+
+ [Fact]
+ public void Above_Measures_Correctly()
+ {
+ var rect1 = new Rectangle { Height = 20, Width = 20 };
+ var rect2 = new Rectangle { Height = 20, Width = 20 };
+
+ var target = new RelativePanel
+ {
+ VerticalAlignment = Layout.VerticalAlignment.Center,
+ HorizontalAlignment = Layout.HorizontalAlignment.Center,
+ Children =
+ {
+ rect1, rect2
+ }
+ };
+
+ RelativePanel.SetAbove(rect2, rect1);
+ target.Measure(new Size(400, 400));
+ target.Arrange(new Rect(target.DesiredSize));
+
+ Assert.Equal(new Size(20, 20), target.Bounds.Size);
+ Assert.Equal(new Rect(0, 0, 20, 20), target.Children[0].Bounds);
+ Assert.Equal(new Rect(0, -20, 20, 20), target.Children[1].Bounds);
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index db9211ac3c..fd52aeb9af 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -8,6 +8,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
@@ -338,8 +339,7 @@ namespace Avalonia.Controls.UnitTests
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
";
- var loader = new Markup.Xaml.AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var tabControl = window.FindControl("tabs");
tabControl.DataContext = new { Tabs = new List() };
diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
index 34b37e7635..9d7bc6af74 100644
--- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
@@ -30,6 +30,40 @@ namespace Avalonia.Controls.UnitTests
Assert.False(ToolTip.GetIsOpen(control));
}
+
+ [Fact]
+ public void Should_Close_When_Control_Detaches()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+
+ var panel = new Panel();
+
+ var target = new Decorator()
+ {
+ [ToolTip.TipProperty] = "Tip",
+ [ToolTip.ShowDelayProperty] = 0
+ };
+
+ panel.Children.Add(target);
+
+ window.Content = panel;
+
+ window.ApplyTemplate();
+ window.Presenter.ApplyTemplate();
+
+ Assert.True((target as IVisual).IsAttachedToVisualTree);
+
+ _mouseHelper.Enter(target);
+
+ Assert.True(ToolTip.GetIsOpen(target));
+
+ panel.Children.Remove(target);
+
+ Assert.False(ToolTip.GetIsOpen(target));
+ }
+ }
[Fact]
public void Should_Open_On_Pointer_Enter()
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index e49e273bec..6b30aed257 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -93,7 +93,7 @@ namespace Avalonia.Controls.UnitTests
{
var impl = new Mock();
impl.SetupProperty(x => x.Resized);
- impl.SetupGet(x => x.Scaling).Returns(1);
+ impl.SetupGet(x => x.RenderScaling).Returns(1);
var target = new TestTopLevel(impl.Object)
{
@@ -290,7 +290,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock();
- impl.SetupGet(x => x.Scaling).Returns(1);
+ impl.SetupGet(x => x.RenderScaling).Returns(1);
var child = new Border { Classes = { "foo" } };
var target = new TestTopLevel(impl.Object)
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index c1bd45bcad..c25ad19027 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -1273,8 +1273,6 @@ namespace Avalonia.Controls.UnitTests
return new TextBlock { Text = node.Value };
}
- public bool SupportsRecycling => false;
-
public InstancedBinding ItemsSelector(object item)
{
var obs = ExpressionObserver.Create(item, o => (o as Node).Children);
diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
index 697ea9cff8..84f212d1b3 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
@@ -110,7 +110,8 @@ namespace Avalonia.Controls.UnitTests
public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close()
{
var windowImpl = new Mock();
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.SetupProperty(x => x.Closed);
using (UnitTestApplication.Start(TestServices.StyledWindow))
@@ -128,7 +129,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_IsVisible_True_Shows_Window()
{
var windowImpl = new Mock();
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@@ -143,7 +145,8 @@ namespace Avalonia.Controls.UnitTests
public void Setting_IsVisible_False_Hides_Window()
{
var windowImpl = new Mock();
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@@ -208,7 +211,8 @@ namespace Avalonia.Controls.UnitTests
{
var renderer = new Mock();
var windowImpl = new Mock();
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object);
@@ -237,7 +241,7 @@ namespace Avalonia.Controls.UnitTests
public TestWindowBase(IRenderer renderer = null)
: base(Mock.Of(x =>
- x.Scaling == 1 &&
+ x.RenderScaling == 1 &&
x.CreateRenderer(It.IsAny()) == renderer))
{
}
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index e2b0def00b..ba29001cf3 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -100,7 +100,8 @@ namespace Avalonia.Controls.UnitTests
{
var windowImpl = new Mock();
windowImpl.SetupProperty(x => x.Closed);
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
var services = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
@@ -206,7 +207,8 @@ namespace Avalonia.Controls.UnitTests
var parent = new Mock();
var windowImpl = new Mock();
windowImpl.SetupProperty(x => x.Closed);
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog(parent.Object);
@@ -245,7 +247,8 @@ namespace Avalonia.Controls.UnitTests
var parent = new Mock();
var windowImpl = new Mock();
windowImpl.SetupProperty(x => x.Closed);
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog(parent.Object);
@@ -273,7 +276,8 @@ namespace Avalonia.Controls.UnitTests
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(screens.Object);
using (UnitTestApplication.Start(TestServices.StyledWindow))
@@ -298,12 +302,14 @@ namespace Avalonia.Controls.UnitTests
var parentWindowImpl = MockWindowingPlatform.CreateWindowMock();
parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));
parentWindowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080));
- parentWindowImpl.Setup(x => x.Scaling).Returns(1);
+ parentWindowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ parentWindowImpl.Setup(x => x.RenderScaling).Returns(1);
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200));
windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(new Size(1920, 1080));
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
var parentWindowServices = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object));
@@ -565,7 +571,7 @@ namespace Avalonia.Controls.UnitTests
private IWindowImpl CreateImpl(Mock renderer)
{
return Mock.Of(x =>
- x.Scaling == 1 &&
+ x.RenderScaling == 1 &&
x.CreateRenderer(It.IsAny()) == renderer.Object);
}
diff --git a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
index 25e8c82b1a..bf1322afbc 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Controls.UnitTests
public IWindowImpl CreateWindow()
{
- return _windowImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1);
+ return _windowImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1);
}
public IWindowImpl CreateEmbeddableWindow()
@@ -25,6 +25,6 @@ namespace Avalonia.Controls.UnitTests
throw new NotImplementedException();
}
- public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1);
+ public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1);
}
}
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 00ef503b8d..530b8fa20c 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -355,7 +355,7 @@ namespace Avalonia.LeakTests
var renderer = new Mock();
renderer.Setup(x => x.Dispose());
var impl = new Mock();
- impl.SetupGet(x => x.Scaling).Returns(1);
+ impl.SetupGet(x => x.RenderScaling).Returns(1);
impl.SetupProperty(x => x.Closed);
impl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object);
impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
index e8c4daa7bc..ad3592294d 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs
index b424003ed6..c9420f1696 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
public void Bug_2228_Relative_Uris_Should_Be_Correctly_Parsed()
{
var testClass = typeof(TestClassWithUri);
- var parsed = AvaloniaXamlLoader.Parse(
+ var parsed = AvaloniaRuntimeXamlLoader.Parse(
$"<{testClass.Name} xmlns='clr-namespace:{testClass.Namespace}' Uri='/test'/>", testClass.Assembly);
Assert.False(parsed.Uri.IsAbsoluteUri);
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs
index a77723afe1..466ae1bf7c 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/MultiValueConverterTests.cs
@@ -29,8 +29,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl("textBlock");
window.ApplyTemplate();
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
index cdd40ed80f..eb8851c80b 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
@@ -22,8 +22,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
xmlns='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Converters'
Thickness = '5' Orientation='Vertical'
>";
- var loader = new AvaloniaXamlLoader();
- var data = (ClassWithNullableProperties)loader.Load(xaml, typeof(ClassWithNullableProperties).Assembly);
+ var data = (ClassWithNullableProperties)AvaloniaRuntimeXamlLoader.Load(xaml, typeof(ClassWithNullableProperties).Assembly);
Assert.Equal(new Thickness(5), data.Thickness);
Assert.Equal(Orientation.Vertical, data.Orientation);
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
index b060905f38..3b729e9cd8 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/PointsListTypeConverterTests.cs
@@ -31,8 +31,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
public void Should_Parse_Points_in_Xaml(string input)
{
var xaml = $"";
- var loader = new AvaloniaXamlLoader();
- var polygon = (Polygon)loader.Load(xaml);
+ var polygon = (Polygon)AvaloniaRuntimeXamlLoader.Load(xaml);
var points = polygon.Points;
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs
index 5e698117c3..4d5983e276 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ValueConverterTests.cs
@@ -21,8 +21,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
xmlns:c='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Converters;assembly=Avalonia.Markup.Xaml.UnitTests'>
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl("textBlock");
window.ApplyTemplate();
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
index 6730e3134d..afc4a36fea 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
@@ -24,8 +24,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl("textBlock");
window.DataContext = "foo";
@@ -45,8 +44,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl("textBlock");
window.ApplyTemplate();
@@ -86,8 +84,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBox = window.FindControl("textBox");
window.ApplyTemplate();
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
index b2b4c4da1a..a7a004bd49 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
@@ -20,8 +20,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl";
- var grid = AvaloniaXamlLoader.Parse(xaml);
+ var grid = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Equal(4, grid.ColumnDefinitions.Count);
Assert.Equal(4, grid.RowDefinitions.Count);
@@ -259,7 +259,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
RowDefinitions='100,Auto,*,100*'>
";
- var grid = AvaloniaXamlLoader.Parse(xaml);
+ var grid = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Equal(4, grid.ColumnDefinitions.Count);
@@ -291,7 +291,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var template = AvaloniaXamlLoader.Parse(xaml);
+ var template = AvaloniaRuntimeXamlLoader.Parse(xaml);
var parent = (ContentControl)template.Build(new ContentControl()).Control;
@@ -314,7 +314,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var template = AvaloniaXamlLoader.Parse(xaml);
+ var template = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Equal(typeof(ContentControl), template.TargetType);
@@ -332,7 +332,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var template = AvaloniaXamlLoader.Parse(xaml);
+ var template = AvaloniaRuntimeXamlLoader.Parse(xaml);
var panel = (Panel)template.Build(new ContentControl()).Control;
@@ -354,7 +354,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Foo
";
- var control = AvaloniaXamlLoader.Parse(xaml);
+ var control = AvaloniaRuntimeXamlLoader.Parse(xaml);
var button = control.FindControl("button");
Assert.Equal("Foo", button.Content);
@@ -365,7 +365,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
var xaml = @"";
- var control = AvaloniaXamlLoader.Parse(xaml);
+ var control = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Equal(200.5, control.Width);
}
@@ -374,7 +374,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
var xaml = @"";
- var control = AvaloniaXamlLoader.Parse(xaml);
+ var control = AvaloniaRuntimeXamlLoader.Parse(xaml);
var bk = control.Background;
Assert.IsType(bk);
Assert.Equal(Colors.White, (bk as ISolidColorBrush).Color);
@@ -392,7 +392,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var styles = AvaloniaXamlLoader.Parse(xaml);
+ var styles = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Single(styles);
@@ -420,7 +420,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var styles = AvaloniaXamlLoader.Parse(xaml);
+ var styles = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Single(styles);
@@ -474,7 +474,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var styles = AvaloniaXamlLoader.Parse(xaml);
+ var styles = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Single(styles);
@@ -505,7 +505,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var style = AvaloniaXamlLoader.Parse";
- var styles = AvaloniaXamlLoader.Parse(xaml);
+ var styles = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.True(styles.Count == 1);
@@ -554,7 +554,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml =
@"";
- var target = AvaloniaXamlLoader.Parse(xaml);
+ var target = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Null(target.Content);
@@ -573,7 +573,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml =
@"";
- var target = AvaloniaXamlLoader.Parse(xaml);
+ var target = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Null(target.Content);
@@ -596,7 +596,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var target = AvaloniaXamlLoader.Parse(xaml);
+ var target = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.NotNull(target.Content);
@@ -622,7 +622,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var target = AvaloniaXamlLoader.Parse(xaml);
+ var target = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Equal(2, target.Bindings.Count);
@@ -651,7 +651,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var target = AvaloniaXamlLoader.Parse(xaml);
+ var target = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.NotNull(target.Template);
@@ -681,7 +681,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var style = AvaloniaXamlLoader.Parse
";
- var loader = new AvaloniaXamlLoader();
- var userControl = (UserControl)loader.Load(xaml);
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var color = (Color)((Style)userControl.Styles[0]).Resources["color"];
Assert.Equal(0xff506070, color.ToUint32());
@@ -52,8 +51,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var userControl = (UserControl)loader.Load(xaml);
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var dataTemplate = (DataTemplate)((Style)userControl.Styles[0]).Resources["dataTemplate"];
Assert.NotNull(dataTemplate);
@@ -78,8 +76,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var userControl = (UserControl)loader.Load(xaml);
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var controlTemplate = (ControlTemplate)((Style)userControl.Styles[0]).Resources["controlTemplate"];
Assert.NotNull(controlTemplate);
@@ -103,8 +100,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var userControl = (UserControl)loader.Load(xaml);
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var brush = (SolidColorBrush)((Style)userControl.Styles[0]).Resources["brush"];
Assert.Equal(0xff506070, brush.Color.ToUint32());
@@ -124,7 +120,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var window = AvaloniaXamlLoader.Parse(xaml);
+ var window = AvaloniaRuntimeXamlLoader.Parse(xaml);
Assert.Single(window.Styles);
@@ -157,8 +153,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.Find("target");
Assert.IsType(target.Content);
@@ -189,8 +184,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.Find("target");
Assert.NotNull(target.FocusAdorner);
@@ -213,8 +207,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = (TextBlock)window.Content;
window.ApplyTemplate();
@@ -239,8 +232,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var ex = Assert.Throws(() => loader.Load(xaml));
+ var ex = Assert.Throws(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.Equal(
"Property 'Button.IsDefault' is not registered on 'Avalonia.Controls.TextBlock'.",
@@ -266,8 +258,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var foo = window.FindControl("foo");
var notFoo = window.FindControl("notFoo");
@@ -295,8 +286,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var foo = window.FindControl("foo");
var bar = window.FindControl("bar");
var baz = window.FindControl("baz");
@@ -326,8 +316,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl("button");
var carousel = window.FindControl("carousel");
var listBox = window.FindControl("listBox");
@@ -364,8 +353,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = (Border)window.Content;
Assert.Equal(1, border.Transitions.Count);
@@ -400,8 +388,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var foo = window.FindControl("foo");
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
@@ -425,8 +412,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
";
- var loader = new AvaloniaXamlLoader();
- var window = (Window)loader.Load(xaml);
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var foo = window.FindControl("foo");
Assert.Null(foo.Background);
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
index ed99156fad..3fdac49f31 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs
@@ -15,8 +15,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
var xaml = "";
- var loader = new AvaloniaXamlLoader();
- var templates = (DataTemplates)loader.Load(xaml);
+ var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
var template = (TreeDataTemplate)(templates.First());
Assert.IsType(template.ItemsSource);
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
index ddeb558754..67e46d25c3 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
[Fact]
public void Binding_Button_IsPressed_ShouldWork()
{
- var parsed = (Button)AvaloniaXamlLoader.Parse(@"
+ var parsed = (Button)AvaloniaRuntimeXamlLoader.Parse(@"
");
var ctx = new XamlIlBugTestsDataContext();
parsed.DataContext = ctx;
@@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
[Fact]
public void Transitions_Should_Be_Properly_Parsed()
{
- var parsed = (Grid)AvaloniaXamlLoader.Parse(@"
+ var parsed = (Grid)AvaloniaRuntimeXamlLoader.Parse(@"
@@ -55,7 +55,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
var precompiled = new XamlIlClassWithPrecompiledXaml();
Assert.Equal(Brushes.Red, precompiled.Background);
Assert.Equal(1, precompiled.Opacity);
- var loaded = (XamlIlClassWithPrecompiledXaml)AvaloniaXamlLoader.Parse(@"
+ var loaded = (XamlIlClassWithPrecompiledXaml)AvaloniaRuntimeXamlLoader.Parse(@"
",
null, Application.Current);
- var parsed = (Window)AvaloniaXamlLoader.Parse(@"
+ var parsed = (Window)AvaloniaRuntimeXamlLoader.Parse(@"
new AvaloniaXamlLoader() {IsDesignMode = true}
+ AssertThrows(() => AvaloniaRuntimeXamlLoader
.Load(@"
", typeof(XamlIlTests).Assembly),
+ xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly,
+ designMode: true),
e => e.Message.Contains("Unable to resolve ")
&& e.Message.Contains(" as static field, property, constant or enum value"));
@@ -200,14 +201,15 @@ namespace Avalonia.Markup.Xaml.UnitTests
{
SomeStaticProperty = "123";
- var loaded = (UserControl)new AvaloniaXamlLoader() {IsDesignMode = true}
+ var loaded = (UserControl)AvaloniaRuntimeXamlLoader
.Load(@"
", typeof(XamlIlTests).Assembly);
+ xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'/>", typeof(XamlIlTests).Assembly,
+ designMode: true);
Assert.Equal(Design.GetDataContext(loaded), SomeStaticProperty);
}
@@ -217,7 +219,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
- var parsed = (Window)AvaloniaXamlLoader.Parse(@"
+ var parsed = (Window)AvaloniaRuntimeXamlLoader.Parse(@"
(@"
+ var parsed = AvaloniaRuntimeXamlLoader.Parse(@"
(@"
+ var parsed = AvaloniaRuntimeXamlLoader.Parse(@"
() == null)
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new TestXamlLoaderShim());
+ }
+
+ class TestXamlLoaderShim : AvaloniaXamlLoader.IRuntimeXamlLoader
+ {
+ public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode)
+ => AvaloniaRuntimeXamlLoader.Load(stream, localAsm, o, baseUri, designMode);
}
}
}
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
index 32dc498781..d162a2abe0 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
+++ b/tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
@@ -8,7 +8,8 @@
+
-
+
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
index f1a1bd45ae..fda8503135 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
@@ -81,8 +81,7 @@ namespace Avalonia.ReactiveUI.UnitTests
private void InitializeComponent()
{
- var loader = new AvaloniaXamlLoader();
- loader.Load(@"
+ AvaloniaRuntimeXamlLoader.Load(@"
", null, this);
@@ -100,8 +99,7 @@ namespace Avalonia.ReactiveUI.UnitTests
private void InitializeComponent()
{
- var loader = new AvaloniaXamlLoader();
- loader.Load(@"
+ AvaloniaRuntimeXamlLoader.Load(@"
", null, this);
diff --git a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj
index e0bd082fb3..584eaf6fa9 100644
--- a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj
+++ b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj
@@ -1,6 +1,7 @@
netcoreapp3.1
+ latest
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
index 43a791b2cb..bf41381b52 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
@@ -490,10 +490,10 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
- [InlineData("0123456789\r0123456789", 2)]
- [InlineData("0123456789", 1)]
+ [InlineData("0123456789\r0123456789")]
+ [InlineData("0123456789")]
[Theory]
- public void Should_Include_Last_Line_When_Constraint_Is_Surpassed(string text, int numberOfLines)
+ public void Should_Include_First_Line_When_Constraint_Is_Surpassed(string text)
{
using (Start())
{
@@ -508,11 +508,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Typeface.Default,
12,
Brushes.Black.ToImmutable(),
- maxHeight: lineHeight * numberOfLines - lineHeight * 0.5);
+ maxHeight: lineHeight - lineHeight * 0.5);
- Assert.Equal(numberOfLines, layout.TextLines.Count);
+ Assert.Equal(1, layout.TextLines.Count);
- Assert.Equal(numberOfLines * lineHeight, layout.Size.Height);
+ Assert.Equal(lineHeight, layout.Size.Height);
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index ed00d6aaed..09cbf3bf08 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -31,12 +31,37 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var nextCharacterHit = new CharacterHit(0);
- for (var i = 1; i < clusters.Length; i++)
+ for (var i = 0; i < clusters.Length; i++)
{
+ Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex);
+
nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
+ }
+
+ var lastCharacterHit = nextCharacterHit;
+
+ nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
+
+ Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
+
+ Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
+
+ nextCharacterHit = new CharacterHit(0, clusters[1] - clusters[0]);
+
+ for (var i = 0; i < clusters.Length; i++)
+ {
+ Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex);
- Assert.Equal(clusters[i], nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength);
+ nextCharacterHit = textLine.GetNextCaretCharacterHit(nextCharacterHit);
}
+
+ lastCharacterHit = nextCharacterHit;
+
+ nextCharacterHit = textLine.GetNextCaretCharacterHit(lastCharacterHit);
+
+ Assert.Equal(lastCharacterHit.FirstCharacterIndex, nextCharacterHit.FirstCharacterIndex);
+
+ Assert.Equal(lastCharacterHit.TrailingLength, nextCharacterHit.TrailingLength);
}
}
@@ -60,14 +85,41 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var clusters = textLine.TextRuns.Cast().SelectMany(x => x.GlyphRun.GlyphClusters)
.ToArray();
- var previousCharacterHit = new CharacterHit(clusters[^1]);
+ var previousCharacterHit = new CharacterHit(text.Length);
- for (var i = clusters.Length - 2; i > 0; i--)
+ for (var i = clusters.Length - 1; i >= 0; i--)
{
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit);
- Assert.Equal(clusters[i], previousCharacterHit.FirstCharacterIndex);
+ Assert.Equal(clusters[i],
+ previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
}
+
+ var firstCharacterHit = previousCharacterHit;
+
+ previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
+
+ Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
+
+ Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength);
+
+ previousCharacterHit = new CharacterHit(clusters[^1], text.Length - clusters[^1]);
+
+ for (var i = clusters.Length - 1; i > 0; i--)
+ {
+ previousCharacterHit = textLine.GetPreviousCaretCharacterHit(previousCharacterHit);
+
+ Assert.Equal(clusters[i],
+ previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
+ }
+
+ firstCharacterHit = previousCharacterHit;
+
+ previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
+
+ Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
+
+ Assert.Equal(firstCharacterHit.TrailingLength, previousCharacterHit.TrailingLength);
}
}
@@ -162,6 +214,64 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ [InlineData("01234 01234", 8, TextCollapsingStyle.TrailingCharacter, "01234 0\u2026")]
+ [InlineData("01234 01234", 8, TextCollapsingStyle.TrailingWord, "01234 \u2026")]
+ [Theory]
+ public void Should_Collapse_Line(string text, int numberOfCharacters, TextCollapsingStyle style, string expected)
+ {
+ using (Start())
+ {
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+
+ var textSource = new SingleBufferTextSource(text, defaultProperties);
+
+ var formatter = new TextFormatterImpl();
+
+ var textLine =
+ formatter.FormatLine(textSource, 0, double.PositiveInfinity,
+ new GenericTextParagraphProperties(defaultProperties));
+
+ Assert.False(textLine.HasCollapsed);
+
+ var glyphTypeface = Typeface.Default.GlyphTypeface;
+
+ var scale = defaultProperties.FontRenderingEmSize / glyphTypeface.DesignEmHeight;
+
+ var width = 1.0;
+
+ for (var i = 0; i < numberOfCharacters; i++)
+ {
+ var glyph = glyphTypeface.GetGlyph(text[i]);
+
+ width += glyphTypeface.GetGlyphAdvance(glyph) * scale;
+ }
+
+ TextCollapsingProperties collapsingProperties;
+
+ if (style == TextCollapsingStyle.TrailingCharacter)
+ {
+ collapsingProperties = new TextTrailingCharacterEllipsis(width, defaultProperties);
+ }
+ else
+ {
+ collapsingProperties = new TextTrailingWordEllipsis(width, defaultProperties);
+ }
+
+ var collapsedLine = textLine.Collapse(collapsingProperties);
+
+ Assert.True(collapsedLine.HasCollapsed);
+
+ var trimmedText = collapsedLine.TextRuns.SelectMany(x => x.Text).ToArray();
+
+ Assert.Equal(expected.Length, trimmedText.Length);
+
+ for (var i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i], trimmedText[i]);
+ }
+ }
+ }
+
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index 48a333dc54..67503ef0d0 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -29,7 +29,8 @@ namespace Avalonia.UnitTests
windowImpl.SetupAllProperties();
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
- windowImpl.Setup(x => x.Scaling).Returns(1);
+ windowImpl.Setup(x => x.DesktopScaling).Returns(1);
+ windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
SetupToplevel(windowImpl);
@@ -81,7 +82,7 @@ namespace Avalonia.UnitTests
popupImpl.SetupAllProperties();
popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
- popupImpl.Setup(x => x.Scaling).Returns(1);
+ popupImpl.Setup(x => x.RenderScaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
SetupToplevel(popupImpl);
diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs
index cc06ea19f0..012cab23dc 100644
--- a/tests/Avalonia.UnitTests/TestServices.cs
+++ b/tests/Avalonia.UnitTests/TestServices.cs
@@ -167,8 +167,7 @@ namespace Avalonia.UnitTests
new DefaultTheme(),
};
- var loader = new AvaloniaXamlLoader();
- var baseLight = (IStyle)loader.Load(
+ var baseLight = (IStyle)AvaloniaXamlLoader.Load(
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
result.Add(baseLight);
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs
index f3f3c9a4ca..d68c2fd5fd 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/ColorTests.cs
@@ -179,5 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Media
{
Assert.False(Color.TryParse("#ff808g80", out _));
}
+
+ [Fact]
+ public void Parse_Throws_ArgumentNullException_For_Null_Input()
+ {
+ Assert.Throws(() => Color.Parse((string)null));
+ }
+
+ [Fact]
+ public void Parse_Throws_FormatException_For_Invalid_Input()
+ {
+ Assert.Throws(() => Color.Parse(string.Empty));
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public void TryParse_Returns_False_For_Invalid_Input(string input)
+ {
+ Assert.False(Color.TryParse(input, out _));
+ }
}
}