diff --git a/Avalonia.sln b/Avalonia.sln
index 3a2c619d5b..f6dc039c2f 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
@@ -211,8 +211,8 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
- src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
- src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
+ src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index 1cf3bc75b0..6800ff7d68 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -278,6 +278,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
+ virtual HRESULT TakeFocusFromChildren() = 0;
};
AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
@@ -493,8 +494,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
- virtual void MoveTo(float x, float y, float width, float height) = 0;
- virtual void Hide() = 0;
+ virtual void ShowInBounds(float x, float y, float width, float height) = 0;
+ virtual void HideWithSize(float width, float height) = 0;
virtual void ReleaseChild() = 0;
};
diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm
index 315ec2f310..5ee2344ac7 100644
--- a/native/Avalonia.Native/src/OSX/controlhost.mm
+++ b/native/Avalonia.Native/src/OSX/controlhost.mm
@@ -97,7 +97,7 @@ public:
return S_OK;
};
- virtual void MoveTo(float x, float y, float width, float height) override
+ virtual void ShowInBounds(float x, float y, float width, float height) override
{
if(_child == nil)
return;
@@ -106,7 +106,7 @@ public:
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
- slf->MoveTo(x, y, width, height);
+ slf->ShowInBounds(x, y, width, height);
slf->Release();
});
return;
@@ -122,9 +122,24 @@ public:
[[_holder superview] setNeedsDisplay:true];
}
- virtual void Hide() override
+ virtual void HideWithSize(float width, float height) override
{
+ if(_child == nil)
+ return;
+ if(AvnInsidePotentialDeadlock::IsInside())
+ {
+ IAvnNativeControlHostTopLevelAttachment* slf = this;
+ slf->AddRef();
+ dispatch_async(dispatch_get_main_queue(), ^{
+ slf->HideWithSize(width, height);
+ slf->Release();
+ });
+ return;
+ }
+
+ NSRect frame = {0, 0, width, height};
[_holder setHidden: true];
+ [_child setFrame: frame];
}
virtual void ReleaseChild() override
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 86b3584681..7f8a6e1393 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -116,10 +116,15 @@ public:
{
SetPosition(lastPositionSet);
UpdateStyle();
-
- [Window makeKeyAndOrderFront:Window];
- [NSApp activateIgnoringOtherApps:YES];
-
+ if(ShouldTakeFocusOnShow())
+ {
+ [Window makeKeyAndOrderFront:Window];
+ [NSApp activateIgnoringOtherApps:YES];
+ }
+ else
+ {
+ [Window orderFront: Window];
+ }
[Window setTitle:_lastTitle];
_shown = true;
@@ -128,6 +133,11 @@ public:
}
}
+ virtual bool ShouldTakeFocusOnShow()
+ {
+ return true;
+ }
+
virtual HRESULT Hide () override
{
@autoreleasepool
@@ -774,6 +784,15 @@ private:
}
}
+ virtual HRESULT TakeFocusFromChildren () override
+ {
+ if(Window == nil)
+ return S_OK;
+ if([Window isKeyWindow])
+ [Window makeFirstResponder: View];
+ return S_OK;
+ }
+
void EnterFullScreenMode ()
{
_fullScreenActive = true;
@@ -1858,7 +1877,6 @@ private:
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
}
-
protected:
virtual NSWindowStyleMask GetStyle() override
{
@@ -1876,6 +1894,11 @@ protected:
return S_OK;
}
}
+public:
+ virtual bool ShouldTakeFocusOnShow() override
+ {
+ return false;
+ }
};
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 488062f5b6..ec7a11067c 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -29,6 +29,9 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
+
+
+
@@ -54,6 +57,7 @@
+
diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
new file mode 100644
index 0000000000..af6b6e8605
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
@@ -0,0 +1,123 @@
+
+
+ DatePicker and TimePicker
+
+
+
+
+ A simple DatePicker with a header
+
+
+
+
+
+
+
+
+ <DatePicker Header="Pick a date" />
+
+
+
+
+
+
+ A DatePicker with day formatted and year hidden.
+
+
+
+
+
+
+
+
+ <DatePicker DayFormat="d (ddd)" YearVisible="False" />
+
+
+
+
+
+
+
+
+ A simple TimePicker.
+
+
+
+
+
+
+
+
+ <TimePicker />
+
+
+
+
+
+
+ A TimePicker with a header and minute increments specified.
+
+
+
+
+
+
+
+
+ <TimePicker Header="Arrival time" MinuteIncrement="15" />
+
+
+
+
+
+
+ A TimePicker using a 12-hour clock.
+
+
+
+
+
+
+
+
+ <TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" />
+
+
+
+
+
+
+ A TimePicker using a 24-hour clock.
+
+
+
+
+
+
+
+
+ <TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" />
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
new file mode 100644
index 0000000000..6c7ae3437e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
@@ -0,0 +1,30 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class DateTimePickerPage : UserControl
+ {
+ public DateTimePickerPage()
+ {
+ this.InitializeComponent();
+ this.FindControl("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
+ "for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
+ "These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
+ "Order of month, day, and year is dynamically set based on user date settings";
+
+ this.FindControl("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
+ "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
+ "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
+ "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";
+
+
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml
index 13bae59805..2ec0b48c76 100644
--- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml
+++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml
@@ -1,29 +1,19 @@
-
+
ProgressBar
A progress bar control
-
-
-
+
+
+
-
-
+
-
-
+
-
-
+
+
diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml
index c6f5521e60..ea31ed0050 100644
--- a/samples/ControlCatalog/Pages/SliderPage.xaml
+++ b/samples/ControlCatalog/Pages/SliderPage.xaml
@@ -6,11 +6,22 @@
A control that lets the user select from a range of values by moving a Thumb control along a Track.
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Inline
+ CompactInline
+ Overlay
+ CompactOverlay
+
+
+
+
+ SystemControlBackgroundChromeMediumLowBrush
+ Red
+ Blue
+ Green
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs b/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs
new file mode 100644
index 0000000000..cbf217c94a
--- /dev/null
+++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml.cs
@@ -0,0 +1,21 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
+
+namespace ControlCatalog.Pages
+{
+ public class SplitViewPage : UserControl
+ {
+ public SplitViewPage()
+ {
+ this.InitializeComponent();
+ DataContext = new SplitViewPageViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs
new file mode 100644
index 0000000000..f27f605a8b
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs
@@ -0,0 +1,46 @@
+using System;
+using Avalonia.Controls;
+using ReactiveUI;
+
+namespace ControlCatalog.ViewModels
+{
+ public class SplitViewPageViewModel : ReactiveObject
+ {
+ private bool _isLeft = true;
+ private int _displayMode = 3; //CompactOverlay
+
+ public bool IsLeft
+ {
+ get => _isLeft;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _isLeft, value);
+ this.RaisePropertyChanged(nameof(PanePlacement));
+ }
+ }
+
+ public int DisplayMode
+ {
+ get => _displayMode;
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _displayMode, value);
+ this.RaisePropertyChanged(nameof(CurrentDisplayMode));
+ }
+ }
+
+ public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right;
+
+ public SplitViewDisplayMode CurrentDisplayMode
+ {
+ get
+ {
+ if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode))
+ {
+ return (SplitViewDisplayMode)_displayMode;
+ }
+ return SplitViewDisplayMode.CompactOverlay;
+ }
+ }
+ }
+}
diff --git a/samples/interop/NativeEmbedSample/MainWindow.xaml b/samples/interop/NativeEmbedSample/MainWindow.xaml
index dcec9035e0..f2161a1bea 100644
--- a/samples/interop/NativeEmbedSample/MainWindow.xaml
+++ b/samples/interop/NativeEmbedSample/MainWindow.xaml
@@ -20,7 +20,16 @@
+
+
+
+ Text
+
+
+ Tooltip
+
+
diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs
index 5b0dea6c60..e006459652 100644
--- a/src/Avalonia.Animation/Easing/Easing.cs
+++ b/src/Avalonia.Animation/Easing/Easing.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Globalization;
using System.Linq;
namespace Avalonia.Animation.Easings
@@ -25,6 +26,11 @@ namespace Avalonia.Animation.Easings
/// Returns the instance of the parsed type.
public static Easing Parse(string e)
{
+ if (e.Contains(','))
+ {
+ return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture));
+ }
+
if (_easingTypes == null)
{
_easingTypes = new Dictionary();
diff --git a/src/Avalonia.Animation/Easing/SplineEasing.cs b/src/Avalonia.Animation/Easing/SplineEasing.cs
new file mode 100644
index 0000000000..975fcc4746
--- /dev/null
+++ b/src/Avalonia.Animation/Easing/SplineEasing.cs
@@ -0,0 +1,85 @@
+namespace Avalonia.Animation.Easings
+{
+ ///
+ /// Eases a value
+ /// using a user-defined cubic bezier curve.
+ /// Good for custom easing functions that doesn't quite
+ /// fit with the built-in ones.
+ ///
+ public class SplineEasing : Easing
+ {
+ ///
+ /// X coordinate of the first control point
+ ///
+ public double X1
+ {
+ get => _internalKeySpline.ControlPointX1;
+ set
+ {
+ _internalKeySpline.ControlPointX1 = value;
+ }
+ }
+
+ ///
+ /// Y coordinate of the first control point
+ ///
+ public double Y1
+ {
+ get => _internalKeySpline.ControlPointY1;
+ set
+ {
+ _internalKeySpline.ControlPointY1 = value;
+ }
+ }
+
+ ///
+ /// X coordinate of the second control point
+ ///
+ public double X2
+ {
+ get => _internalKeySpline.ControlPointX2;
+ set
+ {
+ _internalKeySpline.ControlPointX2 = value;
+ }
+ }
+
+ ///
+ /// Y coordinate of the second control point
+ ///
+ public double Y2
+ {
+ get => _internalKeySpline.ControlPointY2;
+ set
+ {
+ _internalKeySpline.ControlPointY2 = value;
+ }
+ }
+
+ private readonly KeySpline _internalKeySpline;
+
+ public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d)
+ {
+ _internalKeySpline = new KeySpline();
+
+ this.X1 = x1;
+ this.Y1 = y1;
+ this.X2 = x2;
+ this.Y1 = y2;
+ }
+
+ public SplineEasing(KeySpline keySpline)
+ {
+ _internalKeySpline = keySpline;
+ }
+
+ public SplineEasing()
+ {
+ _internalKeySpline = new KeySpline();
+ }
+
+ ///
+ public override double Ease(double progress) =>
+ _internalKeySpline.GetSplineProgress(progress);
+ }
+}
diff --git a/src/Avalonia.Animation/KeySpline.cs b/src/Avalonia.Animation/KeySpline.cs
index 5a4f7a15a3..a6e9769186 100644
--- a/src/Avalonia.Animation/KeySpline.cs
+++ b/src/Avalonia.Animation/KeySpline.cs
@@ -81,7 +81,10 @@ namespace Avalonia.Animation
/// A with the appropriate values set
public static KeySpline Parse(string value, CultureInfo culture)
{
- using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline."))
+ if (culture is null)
+ culture = CultureInfo.InvariantCulture;
+
+ using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."))
{
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
@@ -98,6 +101,7 @@ namespace Avalonia.Animation
if (IsValidXValue(value))
{
_controlPointX1 = value;
+ _isDirty = true;
}
else
{
@@ -112,7 +116,11 @@ namespace Avalonia.Animation
public double ControlPointY1
{
get => _controlPointY1;
- set => _controlPointY1 = value;
+ set
+ {
+ _controlPointY1 = value;
+ _isDirty = true;
+ }
}
///
@@ -126,6 +134,7 @@ namespace Avalonia.Animation
if (IsValidXValue(value))
{
_controlPointX2 = value;
+ _isDirty = true;
}
else
{
@@ -140,7 +149,11 @@ namespace Avalonia.Animation
public double ControlPointY2
{
get => _controlPointY2;
- set => _controlPointY2 = value;
+ set
+ {
+ _controlPointY2 = value;
+ _isDirty = true;
+ }
}
///
@@ -330,20 +343,4 @@ namespace Avalonia.Animation
}
}
}
-
- ///
- /// Converts string values to values
- ///
- public class KeySplineTypeConverter : TypeConverter
- {
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- return sourceType == typeof(string);
- }
-
- public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
- {
- return KeySpline.Parse((string)value, culture);
- }
- }
}
diff --git a/src/Avalonia.Animation/KeySplineTypeConverter.cs b/src/Avalonia.Animation/KeySplineTypeConverter.cs
new file mode 100644
index 0000000000..cd7427a37d
--- /dev/null
+++ b/src/Avalonia.Animation/KeySplineTypeConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+// Ported from WPF open-source code.
+// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Converts string values to values
+ ///
+ public class KeySplineTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return KeySpline.Parse((string)value, culture);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
index d915887e4c..b52829a60f 100644
--- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
@@ -140,6 +140,7 @@ namespace Avalonia.Collections
}
}
+ [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
public static IAvaloniaReadOnlyList CreateDerivedList(
this IAvaloniaReadOnlyList collection,
Func select)
diff --git a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs
index 7c402f42f6..6d09befeba 100644
--- a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs
+++ b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs
@@ -12,8 +12,19 @@ namespace Avalonia.Data.Core
base.NextValueChanged(Negate(value));
}
- private static object Negate(object v)
+ private static object Negate(object value)
{
+ var notification = value as BindingNotification;
+ var v = BindingNotification.ExtractValue(value);
+
+ BindingNotification GenerateError(Exception e)
+ {
+ notification ??= new BindingNotification(AvaloniaProperty.UnsetValue);
+ notification.AddError(e, BindingErrorType.Error);
+ notification.ClearValue();
+ return notification;
+ }
+
if (v != AvaloniaProperty.UnsetValue)
{
var s = v as string;
@@ -28,9 +39,7 @@ namespace Avalonia.Data.Core
}
else
{
- return new BindingNotification(
- new InvalidCastException($"Unable to convert '{s}' to bool."),
- BindingErrorType.Error);
+ return GenerateError(new InvalidCastException($"Unable to convert '{s}' to bool."));
}
}
else
@@ -38,24 +47,31 @@ namespace Avalonia.Data.Core
try
{
var boolean = Convert.ToBoolean(v, CultureInfo.InvariantCulture);
- return !boolean;
+
+ if (notification is object)
+ {
+ notification.SetValue(!boolean);
+ return notification;
+ }
+ else
+ {
+ return !boolean;
+ }
}
catch (InvalidCastException)
{
// The error message here is "Unable to cast object of type 'System.Object'
// to type 'System.IConvertible'" which is kinda useless so provide our own.
- return new BindingNotification(
- new InvalidCastException($"Unable to convert '{v}' to bool."),
- BindingErrorType.Error);
+ return GenerateError(new InvalidCastException($"Unable to convert '{v}' to bool."));
}
catch (Exception e)
{
- return new BindingNotification(e, BindingErrorType.Error);
+ return GenerateError(e);
}
}
}
- return AvaloniaProperty.UnsetValue;
+ return notification ?? AvaloniaProperty.UnsetValue;
}
public object Transform(object value)
diff --git a/src/Avalonia.Base/Utilities/StyleClassParser.cs b/src/Avalonia.Base/Utilities/StyleClassParser.cs
new file mode 100644
index 0000000000..2db58f73d9
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/StyleClassParser.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Globalization;
+
+namespace Avalonia.Utilities
+{
+#if !BUILDTASK
+ public
+#endif
+ static class StyleClassParser
+ {
+ public static ReadOnlySpan ParseStyleClass(this ref CharacterReader r)
+ {
+ if (IsValidIdentifierStart(r.Peek))
+ {
+ return r.TakeWhile(c => IsValidIdentifierChar(c));
+ }
+ else
+ {
+ return ReadOnlySpan.Empty;
+ }
+ }
+
+ private static bool IsValidIdentifierStart(char c)
+ {
+ return char.IsLetter(c) || c == '_';
+ }
+
+ private static bool IsValidIdentifierChar(char c)
+ {
+ if (IsValidIdentifierStart(c) || c == '-')
+ {
+ return true;
+ }
+ else
+ {
+ var cat = CharUnicodeInfo.GetUnicodeCategory(c);
+ return cat == UnicodeCategory.NonSpacingMark ||
+ cat == UnicodeCategory.SpacingCombiningMark ||
+ cat == UnicodeCategory.ConnectorPunctuation ||
+ cat == UnicodeCategory.Format ||
+ cat == UnicodeCategory.DecimalDigitNumber;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 582e4499c5..5b2484382e 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -42,7 +42,10 @@
Markup/%(RecursiveDir)%(FileName)%(Extension)
-
+
+ Markup/%(RecursiveDir)%(FileName)%(Extension)
+
+
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
new file mode 100644
index 0000000000..5d3311e8c6
--- /dev/null
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -0,0 +1,412 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+using Avalonia.Controls.Templates;
+using Avalonia.Interactivity;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// A control to allow the user to select a date
+ ///
+ public class DatePicker : TemplatedControl
+ {
+ ///
+ /// Define the Property
+ ///
+ public static readonly DirectProperty DayFormatProperty =
+ AvaloniaProperty.RegisterDirect(nameof(DayFormat),
+ x => x.DayFormat, (x, v) => x.DayFormat = v);
+
+ ///
+ /// Defines the Property
+ ///
+ public static readonly DirectProperty DayVisibleProperty =
+ AvaloniaProperty.RegisterDirect(nameof(DayVisible),
+ x => x.DayVisible, (x, v) => x.DayVisible = v);
+
+ ///
+ /// Defines the Property
+ ///
+ public static readonly StyledProperty
public class ProgressBar : RangeBase
{
+ public class ProgressBarTemplateProperties : AvaloniaObject
+ {
+ private double _container2Width;
+ private double _containerWidth;
+ private double _containerAnimationStartPosition;
+ private double _containerAnimationEndPosition;
+ private double _container2AnimationStartPosition;
+ private double _container2AnimationEndPosition;
+
+ public static readonly DirectProperty ContainerAnimationStartPositionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(ContainerAnimationStartPosition),
+ p => p.ContainerAnimationStartPosition,
+ (p, o) => p.ContainerAnimationStartPosition = o, 0d);
+
+ public static readonly DirectProperty ContainerAnimationEndPositionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(ContainerAnimationEndPosition),
+ p => p.ContainerAnimationEndPosition,
+ (p, o) => p.ContainerAnimationEndPosition = o, 0d);
+
+ public static readonly DirectProperty Container2AnimationStartPositionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Container2AnimationStartPosition),
+ p => p.Container2AnimationStartPosition,
+ (p, o) => p.Container2AnimationStartPosition = o, 0d);
+
+ public static readonly DirectProperty Container2AnimationEndPositionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Container2AnimationEndPosition),
+ p => p.Container2AnimationEndPosition,
+ (p, o) => p.Container2AnimationEndPosition = o);
+
+ public static readonly DirectProperty Container2WidthProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Container2Width),
+ p => p.Container2Width,
+ (p, o) => p.Container2Width = o);
+
+ public static readonly DirectProperty ContainerWidthProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(ContainerWidth),
+ p => p.ContainerWidth,
+ (p, o) => p.ContainerWidth = o);
+
+ public double ContainerAnimationStartPosition
+ {
+ get => _containerAnimationStartPosition;
+ set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value);
+ }
+
+ public double ContainerAnimationEndPosition
+ {
+ get => _containerAnimationEndPosition;
+ set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value);
+ }
+
+ public double Container2AnimationStartPosition
+ {
+ get => _container2AnimationStartPosition;
+ set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value);
+ }
+
+ public double Container2Width
+ {
+ get => _container2Width;
+ set => SetAndRaise(Container2WidthProperty, ref _container2Width, value);
+ }
+
+ public double ContainerWidth
+ {
+ get => _containerWidth;
+ set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value);
+ }
+
+ public double Container2AnimationEndPosition
+ {
+ get => _container2AnimationEndPosition;
+ set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value);
+ }
+ }
+
+ private double _indeterminateStartingOffset;
+ private double _indeterminateEndingOffset;
+ private Border _indicator;
+
public static readonly StyledProperty IsIndeterminateProperty =
AvaloniaProperty.Register(nameof(IsIndeterminate));
@@ -20,19 +105,33 @@ namespace Avalonia.Controls
public static readonly StyledProperty OrientationProperty =
AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal);
- private static readonly DirectProperty IndeterminateStartingOffsetProperty =
+ [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+ public static readonly DirectProperty IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect(
nameof(IndeterminateStartingOffset),
p => p.IndeterminateStartingOffset,
(p, o) => p.IndeterminateStartingOffset = o);
- private static readonly DirectProperty IndeterminateEndingOffsetProperty =
+ [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+ public static readonly DirectProperty IndeterminateEndingOffsetProperty =
AvaloniaProperty.RegisterDirect(
nameof(IndeterminateEndingOffset),
p => p.IndeterminateEndingOffset,
(p, o) => p.IndeterminateEndingOffset = o);
- private Border _indicator;
+ [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+ public double IndeterminateStartingOffset
+ {
+ get => _indeterminateStartingOffset;
+ set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
+ }
+
+ [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+ public double IndeterminateEndingOffset
+ {
+ get => _indeterminateEndingOffset;
+ set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
+ }
static ProgressBar()
{
@@ -45,6 +144,8 @@ namespace Avalonia.Controls
UpdatePseudoClasses(IsIndeterminate, Orientation);
}
+ public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties();
+
public bool IsIndeterminate
{
get => GetValue(IsIndeterminateProperty);
@@ -62,19 +163,6 @@ namespace Avalonia.Controls
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
- private double _indeterminateStartingOffset;
- private double IndeterminateStartingOffset
- {
- get => _indeterminateStartingOffset;
- set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
- }
-
- private double _indeterminateEndingOffset;
- private double IndeterminateEndingOffset
- {
- get => _indeterminateEndingOffset;
- set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
- }
///
protected override Size ArrangeOverride(Size finalSize)
@@ -111,21 +199,33 @@ namespace Avalonia.Controls
{
if (IsIndeterminate)
{
- if (Orientation == Orientation.Horizontal)
- {
- var width = bounds.Width / 5.0;
- IndeterminateStartingOffset = -width;
- _indicator.Width = width;
- IndeterminateEndingOffset = bounds.Width;
+ // Pulled from ModernWPF.
- }
- else
- {
- var height = bounds.Height / 5.0;
- IndeterminateStartingOffset = -bounds.Height;
- _indicator.Height = height;
- IndeterminateEndingOffset = height;
- }
+ var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height;
+ var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
+ var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
+
+ TemplateProperties.ContainerWidth = barIndicatorWidth;
+ TemplateProperties.Container2Width = barIndicatorWidth2;
+
+ TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
+ TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
+
+ TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
+ TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
+
+ // Remove these properties when we switch to fluent as default and removed the old one.
+ IndeterminateStartingOffset = -(dim / 5d);
+ IndeterminateEndingOffset = dim;
+
+ var padding = Padding;
+ var rectangle = new RectangleGeometry(
+ new Rect(
+ padding.Left,
+ padding.Top,
+ bounds.Width - (padding.Right + padding.Left),
+ bounds.Height - (padding.Bottom + padding.Top)
+ ));
}
else
{
diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs
index ff1c0260bb..aa6552579f 100644
--- a/src/Avalonia.Controls/SelectionModel.cs
+++ b/src/Avalonia.Controls/SelectionModel.cs
@@ -189,8 +189,6 @@ namespace Avalonia.Controls
}
set
{
- var isSelected = IsSelectedWithPartialAt(value);
-
if (!IsSelectedAt(value) || SelectedItems.Count > 1)
{
using var operation = new Operation(this);
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index fe1a4f5ac1..293cbac82f 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Collections;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@@ -64,6 +65,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty TickPlacementProperty =
AvaloniaProperty.Register(nameof(TickPlacement), 0d);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty> TicksProperty =
+ TickBar.TicksProperty.AddOwner();
+
// Slider required parts
private bool _isDragging = false;
private Track _track;
@@ -83,7 +90,8 @@ namespace Avalonia.Controls
PressedMixin.Attach();
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
- Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
+ Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e),
+ RoutingStrategies.Bubble);
}
///
@@ -94,6 +102,15 @@ namespace Avalonia.Controls
UpdatePseudoClasses(Orientation);
}
+ ///
+ /// Defines the ticks to be drawn on the tick bar.
+ ///
+ public AvaloniaList Ticks
+ {
+ get => GetValue(TicksProperty);
+ set => SetValue(TicksProperty, value);
+ }
+
///
/// Gets or sets the orientation of a .
///
@@ -240,19 +257,50 @@ namespace Avalonia.Controls
/// Value that want to snap to closest Tick.
private double SnapToTick(double value)
{
- var previous = Minimum;
- var next = Maximum;
-
- if (TickFrequency > 0.0)
+ if (IsSnapToTickEnabled)
{
- previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency);
- next = Math.Min(Maximum, previous + TickFrequency);
+ double previous = Minimum;
+ double next = Maximum;
+
+ // This property is rarely set so let's try to avoid the GetValue
+ var ticks = Ticks;
+
+ // If ticks collection is available, use it.
+ // Note that ticks may be unsorted.
+ if ((ticks != null) && (ticks.Count > 0))
+ {
+ for (int i = 0; i < ticks.Count; i++)
+ {
+ double tick = ticks[i];
+ if (MathUtilities.AreClose(tick, value))
+ {
+ return value;
+ }
+
+ if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous))
+ {
+ previous = tick;
+ }
+ else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next))
+ {
+ next = tick;
+ }
+ }
+ }
+ else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
+ {
+ previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency);
+ next = Math.Min(Maximum, previous + TickFrequency);
+ }
+
+ // Choose the closest value between previous and next. If tie, snap to 'next'.
+ value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
}
- // Choose the closest value between previous and next. If tie, snap to 'next'.
- return MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
+ return value;
}
+
private void UpdatePseudoClasses(Orientation o)
{
PseudoClasses.Set(":vertical", o == Orientation.Vertical);
diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs
new file mode 100644
index 0000000000..fc3ff51f24
--- /dev/null
+++ b/src/Avalonia.Controls/SplitView.cs
@@ -0,0 +1,487 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+using System;
+using System.Reactive.Disposables;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Defines constants for how the SplitView Pane should display
+ ///
+ public enum SplitViewDisplayMode
+ {
+ ///
+ /// Pane is displayed next to content, and does not auto collapse
+ /// when tapped outside
+ ///
+ Inline,
+ ///
+ /// Pane is displayed next to content. When collapsed, pane is still
+ /// visible according to CompactPaneLength. Pane does not auto collapse
+ /// when tapped outside
+ ///
+ CompactInline,
+ ///
+ /// Pane is displayed above content. Pane collapses when tapped outside
+ ///
+ Overlay,
+ ///
+ /// Pane is displayed above content. When collapsed, pane is still
+ /// visible according to CompactPaneLength. Pane collapses when tapped outside
+ ///
+ CompactOverlay
+ }
+
+ ///
+ /// Defines constants for where the Pane should appear
+ ///
+ public enum SplitViewPanePlacement
+ {
+ Left,
+ Right
+ }
+
+ public class SplitViewTemplateSettings : AvaloniaObject
+ {
+ internal SplitViewTemplateSettings() { }
+
+ public static readonly StyledProperty ClosedPaneWidthProperty =
+ AvaloniaProperty.Register(nameof(ClosedPaneWidth), 0d);
+
+ public static readonly StyledProperty PaneColumnGridLengthProperty =
+ AvaloniaProperty.Register(nameof(PaneColumnGridLength));
+
+ public double ClosedPaneWidth
+ {
+ get => GetValue(ClosedPaneWidthProperty);
+ internal set => SetValue(ClosedPaneWidthProperty, value);
+ }
+
+ public GridLength PaneColumnGridLength
+ {
+ get => GetValue(PaneColumnGridLengthProperty);
+ internal set => SetValue(PaneColumnGridLengthProperty, value);
+ }
+ }
+
+ ///
+ /// A control with two views: A collapsible pane and an area for content
+ ///
+ public class SplitView : TemplatedControl
+ {
+ /*
+ Pseudo classes & combos
+ :open / :closed
+ :compactoverlay :compactinline :overlay :inline
+ :left :right
+ */
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty ContentProperty =
+ AvaloniaProperty.Register(nameof(Content));
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty CompactPaneLengthProperty =
+ AvaloniaProperty.Register(nameof(CompactPaneLength), defaultValue: 48);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty DisplayModeProperty =
+ AvaloniaProperty.Register(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly DirectProperty IsPaneOpenProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IsPaneOpen),
+ x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty OpenPaneLengthProperty =
+ AvaloniaProperty.Register(nameof(OpenPaneLength), defaultValue: 320);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty PaneBackgroundProperty =
+ AvaloniaProperty.Register(nameof(PaneBackground));
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty PanePlacementProperty =
+ AvaloniaProperty.Register(nameof(PanePlacement));
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty PaneProperty =
+ AvaloniaProperty.Register(nameof(Pane));
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty UseLightDismissOverlayModeProperty =
+ AvaloniaProperty.Register(nameof(UseLightDismissOverlayMode));
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty TemplateSettingsProperty =
+ AvaloniaProperty.Register(nameof(TemplateSettings));
+
+ private bool _isPaneOpen;
+ private Panel _pane;
+ private CompositeDisposable _pointerDisposables;
+
+ public SplitView()
+ {
+ PseudoClasses.Add(":overlay");
+ PseudoClasses.Add(":left");
+
+ TemplateSettings = new SplitViewTemplateSettings();
+ }
+
+ static SplitView()
+ {
+ UseLightDismissOverlayModeProperty.Changed.AddClassHandler((x, v) => x.OnUseLightDismissChanged(v));
+ CompactPaneLengthProperty.Changed.AddClassHandler((x, v) => x.OnCompactPaneLengthChanged(v));
+ PanePlacementProperty.Changed.AddClassHandler((x, v) => x.OnPanePlacementChanged(v));
+ DisplayModeProperty.Changed.AddClassHandler((x, v) => x.OnDisplayModeChanged(v));
+ }
+
+ ///
+ /// Gets or sets the content of the SplitView
+ ///
+ [Content]
+ public IControl Content
+ {
+ get => GetValue(ContentProperty);
+ set => SetValue(ContentProperty, value);
+ }
+
+ ///
+ /// Gets or sets the length of the pane when in
+ /// or mode
+ ///
+ public double CompactPaneLength
+ {
+ get => GetValue(CompactPaneLengthProperty);
+ set => SetValue(CompactPaneLengthProperty, value);
+ }
+
+ ///
+ /// Gets or sets the for the SplitView
+ ///
+ public SplitViewDisplayMode DisplayMode
+ {
+ get => GetValue(DisplayModeProperty);
+ set => SetValue(DisplayModeProperty, value);
+ }
+
+ ///
+ /// Gets or sets whether the pane is open or closed
+ ///
+ public bool IsPaneOpen
+ {
+ get => _isPaneOpen;
+ set
+ {
+ if (value == _isPaneOpen)
+ {
+ return;
+ }
+
+ if (value)
+ {
+ OnPaneOpening(this, null);
+ SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value);
+
+ PseudoClasses.Add(":open");
+ PseudoClasses.Remove(":closed");
+ OnPaneOpened(this, null);
+ }
+ else
+ {
+ SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false);
+ OnPaneClosing(this, args);
+ if (!args.Cancel)
+ {
+ SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value);
+
+ PseudoClasses.Add(":closed");
+ PseudoClasses.Remove(":open");
+ OnPaneClosed(this, null);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the length of the pane when open
+ ///
+ public double OpenPaneLength
+ {
+ get => GetValue(OpenPaneLengthProperty);
+ set => SetValue(OpenPaneLengthProperty, value);
+ }
+
+ ///
+ /// Gets or sets the background of the pane
+ ///
+ public IBrush PaneBackground
+ {
+ get => GetValue(PaneBackgroundProperty);
+ set => SetValue(PaneBackgroundProperty, value);
+ }
+
+ ///
+ /// Gets or sets the for the SplitView
+ ///
+ public SplitViewPanePlacement PanePlacement
+ {
+ get => GetValue(PanePlacementProperty);
+ set => SetValue(PanePlacementProperty, value);
+ }
+
+ ///
+ /// Gets or sets the Pane for the SplitView
+ ///
+ public IControl Pane
+ {
+ get => GetValue(PaneProperty);
+ set => SetValue(PaneProperty, value);
+ }
+
+ ///
+ /// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
+ /// When enabled, and the pane is open in Overlay or CompactOverlay mode,
+ /// the contents of the splitview are darkened to visually separate the open pane
+ /// and the rest of the SplitView
+ ///
+ public bool UseLightDismissOverlayMode
+ {
+ get => GetValue(UseLightDismissOverlayModeProperty);
+ set => SetValue(UseLightDismissOverlayModeProperty, value);
+ }
+
+ ///
+ /// Gets or sets the TemplateSettings for the SplitView
+ ///
+ public SplitViewTemplateSettings TemplateSettings
+ {
+ get => GetValue(TemplateSettingsProperty);
+ set => SetValue(TemplateSettingsProperty, value);
+ }
+
+ ///
+ /// Fired when the pane is closed
+ ///
+ public event EventHandler PaneClosed;
+
+ ///
+ /// Fired when the pane is closing
+ ///
+ public event EventHandler PaneClosing;
+
+ ///
+ /// Fired when the pane is opened
+ ///
+ public event EventHandler PaneOpened;
+
+ ///
+ /// Fired when the pane is opening
+ ///
+ public event EventHandler PaneOpening;
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ _pane = e.NameScope.Find("PART_PaneRoot");
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ var topLevel = this.VisualRoot;
+ if (topLevel is Window window)
+ {
+ //Logic adapted from Popup
+ //Basically if we're using an overlay DisplayMode, close the pane if we don't click on the pane
+ IDisposable subscribeToEventHandler(T target, TEventHandler handler,
+ Action subscribe, Action unsubscribe)
+ {
+ subscribe(target, handler);
+ return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler));
+ }
+
+ _pointerDisposables = new CompositeDisposable(
+ window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel),
+ InputManager.Instance?.Process.Subscribe(OnNonClientClick),
+ subscribeToEventHandler(window, Window_Deactivated,
+ (x, handler) => x.Deactivated += handler, (x, handler) => x.Deactivated -= handler),
+ subscribeToEventHandler(window.PlatformImpl, OnWindowLostFocus,
+ (x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler));
+ }
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+ _pointerDisposables?.Dispose();
+ }
+
+ private void OnWindowLostFocus()
+ {
+ if (IsPaneOpen && ShouldClosePane())
+ {
+ IsPaneOpen = false;
+ }
+ }
+
+ private void PointerPressedOutside(object sender, PointerPressedEventArgs e)
+ {
+ if (!IsPaneOpen)
+ {
+ return;
+ }
+
+ //If we click within the Pane, don't do anything
+ //Otherwise, ClosePane if open & using an overlay display mode
+ bool closePane = ShouldClosePane();
+ if (!closePane)
+ {
+ return;
+ }
+
+ var src = e.Source as IVisual;
+ while (src != null)
+ {
+ if (src == _pane)
+ {
+ closePane = false;
+ break;
+ }
+
+ src = src.VisualParent;
+ }
+ if (closePane)
+ {
+ IsPaneOpen = false;
+ e.Handled = true;
+ }
+ }
+
+ private void OnNonClientClick(RawInputEventArgs obj)
+ {
+ if (!IsPaneOpen)
+ {
+ return;
+ }
+
+ var mouse = obj as RawPointerEventArgs;
+ if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
+
+ {
+ if (ShouldClosePane())
+ IsPaneOpen = false;
+ }
+ }
+
+ private void Window_Deactivated(object sender, EventArgs e)
+ {
+ if (IsPaneOpen && ShouldClosePane())
+ {
+ IsPaneOpen = false;
+ }
+ }
+
+ private bool ShouldClosePane()
+ {
+ return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay);
+ }
+
+ protected virtual void OnPaneOpening(SplitView sender, EventArgs args)
+ {
+ PaneOpening?.Invoke(sender, args);
+ }
+
+ protected virtual void OnPaneOpened(SplitView sender, EventArgs args)
+ {
+ PaneOpened?.Invoke(sender, args);
+ }
+
+ protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args)
+ {
+ PaneClosing?.Invoke(sender, args);
+ }
+
+ protected virtual void OnPaneClosed(SplitView sender, EventArgs args)
+ {
+ PaneClosed?.Invoke(sender, args);
+ }
+
+ private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var newLen = (double)e.NewValue;
+ var displayMode = DisplayMode;
+ if (displayMode == SplitViewDisplayMode.CompactInline)
+ {
+ TemplateSettings.ClosedPaneWidth = newLen;
+ }
+ else if (displayMode == SplitViewDisplayMode.CompactOverlay)
+ {
+ TemplateSettings.ClosedPaneWidth = newLen;
+ TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel);
+ }
+ }
+
+ private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var oldState = e.OldValue.ToString().ToLower();
+ var newState = e.NewValue.ToString().ToLower();
+ PseudoClasses.Remove($":{oldState}");
+ PseudoClasses.Add($":{newState}");
+ }
+
+ private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var oldState = e.OldValue.ToString().ToLower();
+ var newState = e.NewValue.ToString().ToLower();
+
+ PseudoClasses.Remove($":{oldState}");
+ PseudoClasses.Add($":{newState}");
+
+ var (closedPaneWidth, paneColumnGridLength) = (SplitViewDisplayMode)e.NewValue switch
+ {
+ SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)),
+ SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)),
+ SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)),
+ SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)),
+ _ => throw new NotImplementedException(),
+ };
+ TemplateSettings.ClosedPaneWidth = closedPaneWidth;
+ TemplateSettings.PaneColumnGridLength = paneColumnGridLength;
+ }
+
+ private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var mode = (bool)e.NewValue;
+ PseudoClasses.Set(":lightdismiss", mode);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs b/src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs
new file mode 100644
index 0000000000..46fb2d161b
--- /dev/null
+++ b/src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ public class SplitViewPaneClosingEventArgs : EventArgs
+ {
+ public bool Cancel { get; set; }
+
+ public SplitViewPaneClosingEventArgs(bool cancel)
+ {
+ Cancel = cancel;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs
index 16e063beb3..22145d8742 100644
--- a/src/Avalonia.Controls/TickBar.cs
+++ b/src/Avalonia.Controls/TickBar.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Controls.Primitives;
-using Avalonia.Data;
-using Avalonia.Data.Converters;
+using Avalonia.Collections;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
@@ -135,15 +131,15 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty> TicksProperty =
- AvaloniaProperty.Register>(nameof(Ticks));
+ public static readonly StyledProperty> TicksProperty =
+ AvaloniaProperty.Register>(nameof(Ticks));
///
/// The Ticks property contains collection of value of type Double which
/// are the logical positions use to draw the ticks.
/// The property value is a .
///
- public List Ticks
+ public AvaloniaList Ticks
{
get { return GetValue(TicksProperty); }
set { SetValue(TicksProperty, value); }
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 6c3a2d4a03..f058942116 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -340,6 +340,9 @@ namespace Avalonia.Controls
_globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
}
+ Renderer?.Dispose();
+ Renderer = null;
+
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
@@ -349,8 +352,7 @@ namespace Avalonia.Controls
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
OnClosed(EventArgs.Empty);
- Renderer?.Dispose();
- Renderer = null;
+
LayoutManager?.Dispose();
}
diff --git a/src/Avalonia.Controls/Utils/IEnumerableUtils.cs b/src/Avalonia.Controls/Utils/IEnumerableUtils.cs
index 9b6444fc66..9614d079d9 100644
--- a/src/Avalonia.Controls/Utils/IEnumerableUtils.cs
+++ b/src/Avalonia.Controls/Utils/IEnumerableUtils.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Controls.Utils
@@ -15,12 +16,14 @@ namespace Avalonia.Controls.Utils
{
if (items != null)
{
- var collection = items as ICollection;
-
- if (collection != null)
+ if (items is ICollection collection)
{
return collection.Count;
}
+ else if (items is IReadOnlyCollection readOnly)
+ {
+ return readOnly.Count;
+ }
else
{
return Enumerable.Count(items.Cast());
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index b3a8f4745e..4899be2955 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -45,7 +45,15 @@ namespace Avalonia.Diagnostics
window.Closed += DevToolsClosed;
s_open.Add(root, window);
- window.Show();
+
+ if (root is Window inspectedWindow)
+ {
+ window.Show(inspectedWindow);
+ }
+ else
+ {
+ window.Show();
+ }
}
return Disposable.Create(() => window?.Close());
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
index a7c2997346..38788ef8ee 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.LogicalTree;
@@ -9,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
public LogicalTreeNode(ILogical logical, TreeNode parent)
: base((Control)logical, parent)
{
- Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
+ Children = new LogicalTreeNodeCollection(this, logical);
}
public static LogicalTreeNode[] Create(object control)
@@ -17,5 +18,31 @@ namespace Avalonia.Diagnostics.ViewModels
var logical = control as ILogical;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null;
}
+
+ internal class LogicalTreeNodeCollection : TreeNodeCollection
+ {
+ private readonly ILogical _control;
+ private IDisposable _subscription;
+
+ public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
+ : base(owner)
+ {
+ _control = control;
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+ _subscription?.Dispose();
+ }
+
+ protected override void Initialize(AvaloniaList nodes)
+ {
+ _subscription = _control.LogicalChildren.ForEachItem(
+ (i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)),
+ (i, item) => nodes.RemoveAt(i),
+ () => nodes.Clear());
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index 1d19e1a346..acc3ef16c2 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
@@ -12,6 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly TreePageViewModel _logicalTree;
private readonly TreePageViewModel _visualTree;
private readonly EventsPageViewModel _events;
+ private readonly IDisposable _pointerOverSubscription;
private ViewModelBase _content;
private int _selectedTab;
private string _focusedControl;
@@ -25,16 +27,9 @@ namespace Avalonia.Diagnostics.ViewModels
_events = new EventsPageViewModel(root);
UpdateFocusedControl();
- KeyboardDevice.Instance.PropertyChanged += (s, e) =>
- {
- if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
- {
- UpdateFocusedControl();
- }
- };
-
+ KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
SelectedTab = 0;
- root.GetObservable(TopLevel.PointerOverElementProperty)
+ _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x?.GetType().Name);
Console = new ConsoleViewModel(UpdateConsoleContext);
}
@@ -129,6 +124,8 @@ namespace Avalonia.Diagnostics.ViewModels
public void Dispose()
{
+ KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged;
+ _pointerOverSubscription.Dispose();
_logicalTree.Dispose();
_visualTree.Dispose();
}
@@ -137,5 +134,13 @@ namespace Avalonia.Diagnostics.ViewModels
{
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
}
+
+ private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
+ {
+ UpdateFocusedControl();
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
index aa27538abc..cb5f5b1fda 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
@@ -3,15 +3,15 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Linq;
-using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
- internal class TreeNode : ViewModelBase
+ internal class TreeNode : ViewModelBase, IDisposable
{
+ private IDisposable _classesSubscription;
private string _classes;
private bool _isExpanded;
@@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels
x => control.Classes.CollectionChanged -= x)
.TakeUntil(removed);
- classesChanged.Select(_ => Unit.Default)
+ _classesSubscription = classesChanged.Select(_ => Unit.Default)
.StartWith(Unit.Default)
.Subscribe(_ =>
{
@@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public IAvaloniaReadOnlyList Children
+ public TreeNodeCollection Children
{
get;
protected set;
@@ -104,6 +104,12 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
+ public void Dispose()
+ {
+ _classesSubscription.Dispose();
+ Children.Dispose();
+ }
+
private static int IndexOf(IReadOnlyList collection, TreeNode item)
{
var count = collection.Count;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
new file mode 100644
index 0000000000..8b4f03bd23
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Avalonia.Collections;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+ internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList, IDisposable
+ {
+ private AvaloniaList _inner;
+
+ public TreeNodeCollection(TreeNode owner) => Owner = owner;
+
+ public TreeNode this[int index]
+ {
+ get
+ {
+ EnsureInitialized();
+ return _inner[index];
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ EnsureInitialized();
+ return _inner.Count;
+ }
+ }
+
+ protected TreeNode Owner { get; }
+
+ public event NotifyCollectionChangedEventHandler CollectionChanged
+ {
+ add => _inner.CollectionChanged += value;
+ remove => _inner.CollectionChanged -= value;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged
+ {
+ add => _inner.PropertyChanged += value;
+ remove => _inner.PropertyChanged -= value;
+ }
+
+ public virtual void Dispose()
+ {
+ if (_inner is object)
+ {
+ foreach (var node in _inner)
+ {
+ node.Dispose();
+ }
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ EnsureInitialized();
+ return _inner.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ protected abstract void Initialize(AvaloniaList nodes);
+
+ private void EnsureInitialized()
+ {
+ if (_inner is null)
+ {
+ _inner = new AvaloniaList();
+ Initialize(_inner);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
index 38ac88a83c..ec48cff399 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
@@ -62,7 +62,15 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public void Dispose() => _details?.Dispose();
+ public void Dispose()
+ {
+ foreach (var node in Nodes)
+ {
+ node.Dispose();
+ }
+
+ _details?.Dispose();
+ }
public TreeNode FindNode(IControl control)
{
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
index 5383cb2b68..bc40edf477 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
@@ -1,3 +1,4 @@
+using System;
using Avalonia.Collections;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -9,16 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
public VisualTreeNode(IVisual visual, TreeNode parent)
: base(visual, parent)
{
- var host = visual as IVisualTreeHost;
-
- if (host?.Root == null)
- {
- Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
- }
- else
- {
- Children = new AvaloniaList(new[] { new VisualTreeNode(host.Root, this) });
- }
+ Children = new VisualTreeNodeCollection(this, visual);
if ((Visual is IStyleable styleable))
{
@@ -33,5 +25,30 @@ namespace Avalonia.Diagnostics.ViewModels
var visual = control as IVisual;
return visual != null ? new[] { new VisualTreeNode(visual, null) } : null;
}
+
+ internal class VisualTreeNodeCollection : TreeNodeCollection
+ {
+ private readonly IVisual _control;
+ private IDisposable _subscription;
+
+ public VisualTreeNodeCollection(TreeNode owner, IVisual control)
+ : base(owner)
+ {
+ _control = control;
+ }
+
+ public override void Dispose()
+ {
+ _subscription?.Dispose();
+ }
+
+ protected override void Initialize(AvaloniaList nodes)
+ {
+ _subscription = _control.VisualChildren.ForEachItem(
+ (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
+ (i, item) => nodes.RemoveAt(i),
+ () => nodes.Clear());
+ }
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
index 3abdb5034a..10861538ae 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
@@ -14,8 +14,8 @@ namespace Avalonia.Diagnostics.Views
{
internal class MainWindow : Window, IStyleHost
{
+ private readonly IDisposable _keySubscription;
private TopLevel _root;
- private IDisposable _keySubscription;
public MainWindow()
{
@@ -33,8 +33,22 @@ namespace Avalonia.Diagnostics.Views
{
if (_root != value)
{
+ if (_root != null)
+ {
+ _root.Closed -= RootClosed;
+ }
+
_root = value;
- DataContext = new MainViewModel(value);
+
+ if (_root != null)
+ {
+ _root.Closed += RootClosed;
+ DataContext = new MainViewModel(value);
+ }
+ else
+ {
+ DataContext = null;
+ }
}
}
}
@@ -45,6 +59,8 @@ namespace Avalonia.Diagnostics.Views
{
base.OnClosed(e);
_keySubscription.Dispose();
+ _root.Closed -= RootClosed;
+ _root = null;
((MainViewModel)DataContext)?.Dispose();
}
@@ -70,5 +86,7 @@ namespace Avalonia.Diagnostics.Views
}
}
}
+
+ private void RootClosed(object sender, EventArgs e) => Close();
}
}
diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs
index 792de774d1..fc988a8d6c 100644
--- a/src/Avalonia.Layout/LayoutManager.cs
+++ b/src/Avalonia.Layout/LayoutManager.cs
@@ -106,8 +106,6 @@ namespace Avalonia.Layout
if (!_running)
{
- _running = true;
-
Stopwatch? stopwatch = null;
const LogEventLevel timingLogLevel = LogEventLevel.Information;
@@ -128,15 +126,24 @@ namespace Avalonia.Layout
_toMeasure.BeginLoop(MaxPasses);
_toArrange.BeginLoop(MaxPasses);
- for (var pass = 0; pass < MaxPasses; ++pass)
+ try
{
- InnerLayoutPass();
+ _running = true;
- if (!RaiseEffectiveViewportChanged())
+ for (var pass = 0; pass < MaxPasses; ++pass)
{
- break;
+ InnerLayoutPass();
+
+ if (!RaiseEffectiveViewportChanged())
+ {
+ break;
+ }
}
}
+ finally
+ {
+ _running = false;
+ }
_toMeasure.EndLoop();
_toArrange.EndLoop();
@@ -221,23 +228,16 @@ namespace Avalonia.Layout
private void InnerLayoutPass()
{
- try
+ for (var pass = 0; pass < MaxPasses; ++pass)
{
- for (var pass = 0; pass < MaxPasses; ++pass)
- {
- ExecuteMeasurePass();
- ExecuteArrangePass();
+ ExecuteMeasurePass();
+ ExecuteArrangePass();
- if (_toMeasure.Count == 0)
- {
- break;
- }
+ if (_toMeasure.Count == 0)
+ {
+ break;
}
}
- finally
- {
- _running = false;
- }
}
private void ExecuteMeasurePass()
@@ -362,7 +362,7 @@ namespace Avalonia.Layout
}
}
- return startCount != _toMeasure.Count + _toMeasure.Count;
+ return startCount != _toMeasure.Count + _toArrange.Count;
}
private Rect CalculateEffectiveViewport(IVisual control)
diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs
index 0777c6416b..a46528dc48 100644
--- a/src/Avalonia.Native/NativeControlHostImpl.cs
+++ b/src/Avalonia.Native/NativeControlHostImpl.cs
@@ -114,19 +114,18 @@ namespace Avalonia.Native
public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
- public void Hide()
+ public void HideWithSize(Size size)
{
- _native?.Hide();
+ _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
}
- public void ShowInBounds(TransformedBounds transformedBounds)
+ public void ShowInBounds(Rect bounds)
{
if (_attachedTo == null)
throw new InvalidOperationException("Native control isn't attached to a toplevel");
- var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
Math.Max(1, bounds.Height));
- _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
+ _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
}
public void InitWithChild(IPlatformHandle handle)
diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs
index c41be1723b..b0da5fdc43 100644
--- a/src/Avalonia.Native/PopupImpl.cs
+++ b/src/Avalonia.Native/PopupImpl.cs
@@ -10,6 +10,7 @@ namespace Avalonia.Native
private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature;
+ private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
@@ -19,6 +20,7 @@ namespace Avalonia.Native
_factory = factory;
_opts = opts;
_glFeature = glFeature;
+ _parent = parent;
using (var e = new PopupEvents(this))
{
var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
@@ -58,6 +60,16 @@ namespace Avalonia.Native
}
}
+ public override void Show()
+ {
+ var parent = _parent;
+ while (parent is PopupImpl p)
+ parent = p._parent;
+ if (parent is WindowImpl w)
+ w.Native.TakeFocusFromChildren();
+ base.Show();
+ }
+
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
public void SetWindowManagerAddShadowHint(bool enabled)
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 930b5800ba..9a90f65d1b 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -319,7 +319,7 @@ namespace Avalonia.Native
}
- public void Show()
+ public virtual void Show()
{
_native.Show();
}
diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml
index d3c2f0c784..43a0752bc8 100644
--- a/src/Avalonia.Themes.Default/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Default/ProgressBar.xaml
@@ -1,4 +1,12 @@
+
+
+
+
+
+
+
+
-
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
index b12a639d31..8647e7f0dc 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
@@ -2,35 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
- #FF0078D7
- #FF000000
- #FF000000
- #FF000000
- #FF000000
- #FF000000
- #FFFFFFFF
- #FF333333
- #FF9A9A9A
- #FFB4B4B4
- #FF676767
- #FFB4B4B4
- #FF000000
- #FFB4B4B4
- #FF000000
- #FF000000
- #FF333333
- #FF808080
- #FF808080
- #FF151515
- #FF1D1D1D
- #FF2C2C2C
- #FFFFFFFF
- #FF1D1D1D
- #FF333333
- #CC000000
- #FF333333
- #FF1D1D1D
- #FF333333
+ #18FFFFFF
+ #30FFFFFF
@@ -67,13 +40,6 @@
1,1,1,1
1
-
- #FF005A9E
- #FF004275
- #FF002642
- #FF429CE3
- #FF76B9ED
- #FFA6D8FF
#FF000000
@@ -218,6 +184,11 @@
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
index 31c2f592b9..2b960259d0 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
@@ -2,36 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
- #FF0078D7
- #FFFFFFFF
- #FFFFFFFF
- #FFFFFFFF
- #FFFFFFFF
- #FFFFFFFF
- #FF000000
- #FFCCCCCC
- #FF898989
- #FF5D5D5D
- #FF737373
- #FF5D5D5D
- #FF000000
- #FFCCCCCC
- #FF5D5D5D
- #FF898989
- #FFCCCCCC
- #FF898989
- #FF737373
- #FFCCCCCC
- #FFECECEC
- #FFE6E6E6
- #FFECECEC
- #FFFFFFFF
- #FFE6E6E6
- #FFCCCCCC
- #CCFFFFFF
- #FFCCCCCC
- #FFE6E6E6
- #FFCCCCCC
+ #17000000
+ #2E000000
@@ -68,13 +40,6 @@
1,1,1,1
1
-
- #FF005A9E
- #FF004275
- #FF002642
- #FF429CE3
- #FF76B9ED
- #FFA6D8FF
#FFFFFFFF
@@ -222,6 +187,12 @@
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
index 4fe0d52cc4..5ab50f5b73 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
@@ -336,6 +336,15 @@
-->
+
+ 0.6
+ 4
+ 0
+
+
+
+
+
@@ -658,6 +667,67 @@
8,5,8,7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
XamlAutoFontFamily
XamlAutoFontFamily
@@ -819,5 +889,7 @@
1
32
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
index b5de500093..88040a69e7 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
@@ -248,6 +248,14 @@
+
+ 0.6
+ 4
+ 0
+
+
+
+
1
@@ -656,6 +664,67 @@
8,5,8,7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
XamlAutoFontFamily
XamlAutoFontFamily
@@ -817,5 +886,7 @@
1
32
+
+
diff --git a/src/Avalonia.Themes.Fluent/DatePicker.xaml b/src/Avalonia.Themes.Fluent/DatePicker.xaml
new file mode 100644
index 0000000000..ce29869ed3
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/DatePicker.xaml
@@ -0,0 +1,338 @@
+
+
+
+
+ 0,0,0,4
+ 40
+ 40
+ 41
+ 296
+ 456
+ 0,3,0,6
+ 9,3,0,6
+ 0,3,0,6
+ 9,3,0,6
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml
index 49b2d9561b..0cbbb77d38 100644
--- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml
@@ -53,4 +53,7 @@
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/FocusAdorner.xaml b/src/Avalonia.Themes.Fluent/FocusAdorner.xaml
index 2d5e369573..f20dc2e650 100644
--- a/src/Avalonia.Themes.Fluent/FocusAdorner.xaml
+++ b/src/Avalonia.Themes.Fluent/FocusAdorner.xaml
@@ -1,10 +1,34 @@
-
+
+
+ 0
+ 2
+ 1
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/ProgressBar.xaml
index d3c2f0c784..ac5dd7c0ed 100644
--- a/src/Avalonia.Themes.Fluent/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Fluent/ProgressBar.xaml
@@ -1,81 +1,176 @@
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Slider.xaml b/src/Avalonia.Themes.Fluent/Slider.xaml
index 539c448e0f..099c2000b8 100644
--- a/src/Avalonia.Themes.Fluent/Slider.xaml
+++ b/src/Avalonia.Themes.Fluent/Slider.xaml
@@ -182,6 +182,7 @@
diff --git a/src/Avalonia.Themes.Fluent/SplitView.xaml b/src/Avalonia.Themes.Fluent/SplitView.xaml
new file mode 100644
index 0000000000..71e92459f1
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/SplitView.xaml
@@ -0,0 +1,219 @@
+
+
+
+ 320
+ 48
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/TimePicker.xaml b/src/Avalonia.Themes.Fluent/TimePicker.xaml
new file mode 100644
index 0000000000..4a411796d8
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/TimePicker.xaml
@@ -0,0 +1,284 @@
+
+
+
+
+ 40
+ 1
+ 1
+ 0,0,0,4
+ 40
+ 41
+ 242
+ 456
+ 0,3,0,6
+ 0,3,0,6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/ToolTip.xaml
index cf6f32f9bc..47ad494bbf 100644
--- a/src/Avalonia.Themes.Fluent/ToolTip.xaml
+++ b/src/Avalonia.Themes.Fluent/ToolTip.xaml
@@ -1,11 +1,14 @@
-
+
+
@@ -19,7 +22,7 @@
@@ -34,6 +37,10 @@
+
+ 320
+
+
+
+
+
+
+
";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var foo = window.FindControl("foo");
+
+ Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
+ }
+ }
+
+ [Fact]
+ public void Style_Can_Use_Pseudolass_Selector_With_Dash()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var foo = window.FindControl("foo");
+
+ Assert.Null(foo.Background);
+
+ ((IPseudoClasses)foo.Classes).Add(":foo-bar");
+
+ Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
index 90c1e2cee1..814ce15d0a 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
@@ -1,33 +1,34 @@
- OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ
-OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
-CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % %
-GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % %
-NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % %
-PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % %
-B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ %
-ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _
-CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % %
-H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ %
-H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ %
-JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ %
-JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ %
-JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ %
-RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ %
-EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % %
-EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ %
-ZWJ _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ % _ % % _ _ ^ # ^ _ _ _ _ _ _ % % %
+ OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB
+OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
+CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
+GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
+NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % _
+PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % _
+B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % _
+ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ _
+CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
+H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _
+H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _
+JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % _
+JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _
+JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _
+RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % _
+EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % _
+EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+ZWJ % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
+CB _ ^ ^ % % _ ^ ^ ^ _ _ _ _ _ _ _ _ _ _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
index 94ab615130..c074e5a1f1 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using Avalonia.Media.TextFormatting.Unicode;
@@ -12,6 +11,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
public static void Execute()
{
+ if (!Directory.Exists("Generated"))
+ {
+ Directory.CreateDirectory("Generated");
+ }
+
using (var stream = File.Create("Generated\\GraphemeBreak.trie"))
{
var trie = GenerateBreakTypeTrie();
@@ -22,48 +26,29 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
private static UnicodeTrie GenerateBreakTypeTrie()
{
- var graphemeBreakClassValues = UnicodeEnumsGenerator.GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)");
-
- var graphemeBreakClassMapping = graphemeBreakClassValues.Select(x => x.name).ToList();
-
var trieBuilder = new UnicodeTrieBuilder();
- var graphemeBreakData = ReadBreakData(
- "https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt");
-
- foreach (var (start, end, graphemeBreakType) in graphemeBreakData)
- {
- if (!graphemeBreakClassMapping.Contains(graphemeBreakType))
- {
- continue;
- }
-
- if (start == end)
- {
- trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
- }
- else
- {
- trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
- }
- }
+ var graphemeBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakProperty.txt"));
- var emojiBreakData = ReadBreakData("https://unicode.org/Public/emoji/12.0/emoji-data.txt");
+ var emojiBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "emoji/emoji-data.txt"));
- foreach (var (start, end, graphemeBreakType) in emojiBreakData)
+ foreach (var breakData in new [] { graphemeBreakData, emojiBreakData })
{
- if (!graphemeBreakClassMapping.Contains(graphemeBreakType))
+ foreach (var (start, end, graphemeBreakType) in breakData)
{
- continue;
- }
+ if (!Enum.TryParse(graphemeBreakType, out var value))
+ {
+ continue;
+ }
- if (start == end)
- {
- trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
- }
- else
- {
- trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
+ if (start == end)
+ {
+ trieBuilder.Set(start, (uint)value);
+ }
+ else
+ {
+ trieBuilder.SetRange(start, end, (uint)value);
+ }
}
}
@@ -113,7 +98,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
end = Convert.ToInt32(match.Groups[2].Value, 16);
}
- data.Add((start, end, match.Groups[3].Value));
+ var breakType = match.Groups[3].Value;
+
+ data.Add((start, end, breakType));
}
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
index d9a9c82f85..848b60b341 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
@@ -1,9 +1,4 @@
using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
@@ -16,10 +11,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public class GraphemeBreakClassTrieGeneratorTests
{
[Theory(Skip = "Only run when we update the trie.")]
- [ClassData(typeof(GraphemeEnumeratorTestDataGenerator))]
+ [ClassData(typeof(GraphemeBreakTestDataGenerator))]
public void Should_Enumerate(string text, int expectedLength)
{
- var enumerator = new GraphemeEnumerator(text.AsMemory());
+ var textMemory = text.AsMemory();
+
+ var enumerator = new GraphemeEnumerator(textMemory);
Assert.True(enumerator.MoveNext());
@@ -31,7 +28,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
const string text = "ABCDEFGHIJ";
- var enumerator = new GraphemeEnumerator(text.AsMemory());
+ var textMemory = text.AsMemory();
+
+ var enumerator = new GraphemeEnumerator(textMemory);
var count = 0;
@@ -51,73 +50,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
GraphemeBreakClassTrieGenerator.Execute();
}
- public class GraphemeEnumeratorTestDataGenerator : IEnumerable
+ private class GraphemeBreakTestDataGenerator : TestDataGenerator
{
- private readonly List _testData;
-
- public GraphemeEnumeratorTestDataGenerator()
- {
- _testData = ReadTestData();
- }
-
- public IEnumerator GetEnumerator()
- {
- return _testData.GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- private static List ReadTestData()
+ public GraphemeBreakTestDataGenerator()
+ : base("auxiliary/GraphemeBreakTest.txt")
{
- var testData = new List();
-
- using (var client = new HttpClient())
- {
- using (var result = client.GetAsync("https://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt").GetAwaiter().GetResult())
- {
- if (!result.IsSuccessStatusCode)
- return testData;
-
- using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
- using (var reader = new StreamReader(stream))
- {
- while (!reader.EndOfStream)
- {
- var line = reader.ReadLine();
-
- if (line == null)
- {
- break;
- }
-
- if (line.StartsWith("#") || string.IsNullOrEmpty(line))
- {
- continue;
- }
-
- var elements = line.Split('#')[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
-
- var chars = elements[0].Replace(" × ", " ").Split(' ');
-
- var codepoints = chars.Where(x => x != "" && x != "×")
- .Select(x => Convert.ToInt32(x, 16)).ToArray();
-
- var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
-
- var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
-
- var data = new object[] { text, length };
-
- testData.Add(data);
- }
- }
- }
- }
-
- return testData;
}
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
index fe7d7adc17..3ed5cfb0b2 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
@@ -3,7 +3,7 @@ using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utility;
using Xunit;
-namespace Avalonia.Visuals.UnitTests.Media.Text
+namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
public class LineBreakerTests
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
new file mode 100644
index 0000000000..058de909df
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+
+namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
+{
+ public abstract class TestDataGenerator : IEnumerable
+ {
+ private readonly string _fileName;
+ private readonly List _testData;
+
+ protected TestDataGenerator(string fileName)
+ {
+ _fileName = fileName;
+ _testData = ReadTestData();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _testData.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ private List ReadTestData()
+ {
+ var testData = new List();
+
+ using (var client = new HttpClient())
+ {
+ var url = Path.Combine(UnicodeDataGenerator.Ucd, _fileName);
+
+ using (var result = client.GetAsync(url).GetAwaiter().GetResult())
+ {
+ if (!result.IsSuccessStatusCode)
+ return testData;
+
+ using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
+ using (var reader = new StreamReader(stream))
+ {
+ while (!reader.EndOfStream)
+ {
+ var line = reader.ReadLine();
+
+ if (line == null)
+ {
+ break;
+ }
+
+ if (line.StartsWith("#") || string.IsNullOrEmpty(line))
+ {
+ continue;
+ }
+
+ var elements = line.Split('#');
+
+ elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
+
+ var chars = elements[0].Replace(" × ", " ").Split(' ');
+
+ var codepoints = chars.Where(x => x != "" && x != "×")
+ .Select(x => Convert.ToInt32(x, 16)).ToArray();
+
+ var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
+
+ var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
+
+ var data = new object[] { text, length };
+
+ testData.Add(data);
+ }
+ }
+ }
+ }
+
+ return testData;
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
index f7b8b68ab3..cbe8edefb6 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
@@ -9,13 +9,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
internal static class UnicodeDataGenerator
{
+ public const string Ucd = "https://www.unicode.org/Public/13.0.0/ucd/";
+
public static void Execute()
{
var codepoints = new Dictionary();
- var generalCategoryValues = UnicodeEnumsGenerator.CreateGeneralCategoryEnum();
+ var generalCategoryEntries =
+ UnicodeEnumsGenerator.CreateGeneralCategoryEnum();
- var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryValues);
+ var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryEntries);
var generalCategoryData = ReadGeneralCategoryData();
@@ -26,23 +29,23 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddGeneralCategoryRange(codepoints, range, generalCategory);
}
- var scriptValues = UnicodeEnumsGenerator.CreateScriptEnum();
+ var scriptEntries = UnicodeEnumsGenerator.CreateScriptEnum();
- var scriptMappings = CreateNameToIndexMappings(scriptValues);
+ var scriptMappings = CreateNameToIndexMappings(scriptEntries);
var scriptData = ReadScriptData();
foreach (var (range, name) in scriptData)
{
- var script = scriptMappings[name.Replace("_", "")];
+ var script = scriptMappings[name];
AddScriptRange(codepoints, range, script);
-
}
- var biDiClassValues = UnicodeEnumsGenerator.CreateBiDiClassEnum();
+ var biDiClassEntries =
+ UnicodeEnumsGenerator.CreateBiDiClassEnum();
- var biDiClassMappings = CreateTagToIndexMappings(biDiClassValues);
+ var biDiClassMappings = CreateTagToIndexMappings(biDiClassEntries);
var biDiData = ReadBiDiData();
@@ -53,9 +56,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddBiDiClassRange(codepoints, range, biDiClass);
}
- var lineBreakClassValues = UnicodeEnumsGenerator.CreateLineBreakClassEnum();
+ var lineBreakClassEntries =
+ UnicodeEnumsGenerator.CreateLineBreakClassEnum();
- var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassValues);
+ var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassEntries);
var lineBreakClassData = ReadLineBreakClassData();
@@ -66,11 +70,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddLineBreakClassRange(codepoints, range, lineBreakClass);
}
- const int initialValue = ((int)LineBreakClass.Unknown << UnicodeData.LINEBREAK_SHIFT) |
- ((int)BiDiClass.LeftToRight << UnicodeData.BIDI_SHIFT) |
- ((int)Script.Unknown << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
+ //const int initialValue = (0 << UnicodeData.LINEBREAK_SHIFT) |
+ // (0 << UnicodeData.BIDI_SHIFT) |
+ // (0 << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
- var builder = new UnicodeTrieBuilder(initialValue);
+ var builder = new UnicodeTrieBuilder(/*initialValue*/);
foreach (var properties in codepoints.Values)
{
@@ -88,27 +92,30 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
trie.Save(stream);
}
+
+ UnicodeEnumsGenerator.CreatePropertyValueAliasHelper(scriptEntries, generalCategoryEntries,
+ biDiClassEntries, lineBreakClassEntries);
}
- private static Dictionary CreateTagToIndexMappings(List<(string name, string tag, string comment)> values)
+ private static Dictionary CreateTagToIndexMappings(List entries)
{
var mappings = new Dictionary();
- for (var i = 0; i < values.Count; i++)
+ for (var i = 0; i < entries.Count; i++)
{
- mappings.Add(values[i].tag, i);
+ mappings.Add(entries[i].Tag, i);
}
return mappings;
}
- private static Dictionary CreateNameToIndexMappings(List<(string name, string tag, string comment)> values)
+ private static Dictionary CreateNameToIndexMappings(List entries)
{
var mappings = new Dictionary();
- for (var i = 0; i < values.Count; i++)
+ for (var i = 0; i < entries.Count; i++)
{
- mappings.Add(values[i].name, i);
+ mappings.Add(entries[i].Name, i);
}
return mappings;
@@ -180,24 +187,22 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public static List<(CodepointRange, string)> ReadGeneralCategoryData()
{
- return ReadUnicodeData(
- "https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt");
+ return ReadUnicodeData("extracted/DerivedGeneralCategory.txt");
}
public static List<(CodepointRange, string)> ReadScriptData()
{
- return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt");
+ return ReadUnicodeData("Scripts.txt");
}
public static List<(CodepointRange, string)> ReadBiDiData()
{
- return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt");
+ return ReadUnicodeData("extracted/DerivedBidiClass.txt");
}
public static List<(CodepointRange, string)> ReadLineBreakClassData()
{
- return ReadUnicodeData(
- "https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedLineBreak.txt");
+ return ReadUnicodeData("extracted/DerivedLineBreak.txt");
}
private static List<(CodepointRange, string)> ReadUnicodeData(string file)
@@ -208,7 +213,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
using (var client = new HttpClient())
{
- using (var result = client.GetAsync(file).GetAwaiter().GetResult())
+ var url = Path.Combine(Ucd, file);
+
+ using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{
if (!result.IsSuccessStatusCode)
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
index 47aef84533..5c705ba0c7 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
@@ -1,4 +1,6 @@
-using Xunit;
+using System;
+using Avalonia.Media.TextFormatting.Unicode;
+using Xunit;
namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
@@ -13,5 +15,26 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
UnicodeDataGenerator.Execute();
}
+ [Theory(Skip = "Only run when we update the trie.")]
+ [ClassData(typeof(LineBreakTestDataGenerator))]
+
+ public void Should_Enumerate_LineBreaks(string text, int expectedLength)
+ {
+ var textMemory = text.AsMemory();
+
+ var enumerator = new LineBreakEnumerator(textMemory);
+
+ Assert.True(enumerator.MoveNext());
+
+ Assert.Equal(expectedLength, enumerator.Current.PositionWrap);
+ }
+
+ private class LineBreakTestDataGenerator : TestDataGenerator
+ {
+ public LineBreakTestDataGenerator()
+ : base("auxiliary/LineBreakTest.txt")
+ {
+ }
+ }
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
index e141204d4c..3a936ff3b0 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
@@ -8,9 +8,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
internal static class UnicodeEnumsGenerator
{
- public static List<(string name, string tag, string comment)> CreateScriptEnum()
+ public static List CreateScriptEnum()
{
- var scriptValues = GetPropertyValueAliases("# Script (sc)");
+ var entries = new List
+ {
+ new DataEntry("Unknown", "Zzzz", string.Empty),
+ new DataEntry("Common", "Zyyy", string.Empty),
+ new DataEntry("Inherited", "Zinh", string.Empty)
+ };
+
+ ParseDataEntries("# Script (sc)", entries);
using (var stream = File.Create("Generated\\Script.cs"))
using (var writer = new StreamWriter(stream))
@@ -20,22 +27,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum Script");
writer.WriteLine(" {");
- foreach (var (name, tag, comment) in scriptValues)
+ foreach (var entry in entries)
{
- writer.WriteLine(" " + name + ", //" + tag +
- (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+ writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+ (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" }");
writer.WriteLine("}");
}
- return scriptValues;
+ return entries;
}
- public static List<(string name, string tag, string comment)> CreateGeneralCategoryEnum()
+ public static List CreateGeneralCategoryEnum()
{
- var generalCategoryValues = GetPropertyValueAliases("# General_Category (gc)");
+ var entries = new List { new DataEntry("Other", "C", " Cc | Cf | Cn | Co | Cs") };
+
+ ParseDataEntries("# General_Category (gc)", entries);
using (var stream = File.Create("Generated\\GeneralCategory.cs"))
using (var writer = new StreamWriter(stream))
@@ -45,22 +54,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GeneralCategory");
writer.WriteLine(" {");
- foreach (var (name, tag, comment) in generalCategoryValues)
+ foreach (var entry in entries)
{
- writer.WriteLine(" " + name + ", //" + tag +
- (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+ writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+ (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" }");
writer.WriteLine("}");
}
- return generalCategoryValues;
+ return entries;
}
- public static List<(string name, string tag, string comment)> CreateGraphemeBreakTypeEnum()
+ public static List CreateGraphemeBreakTypeEnum()
{
- var graphemeClusterBreakValues = GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)");
+ var entries = new List { new DataEntry("Other", "XX", string.Empty) };
+
+ ParseDataEntries("# Grapheme_Cluster_Break (GCB)", entries);
using (var stream = File.Create("Generated\\GraphemeBreakClass.cs"))
using (var writer = new StreamWriter(stream))
@@ -70,10 +81,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GraphemeBreakClass");
writer.WriteLine(" {");
- foreach (var (name, tag, comment) in graphemeClusterBreakValues)
+ foreach (var entry in entries)
{
- writer.WriteLine(" " + name + ", //" + tag +
- (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+ writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+ (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" ExtendedPictographic");
@@ -82,7 +93,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("}");
}
- return graphemeClusterBreakValues;
+ return entries;
}
private static List GenerateBreakPairTable()
@@ -185,20 +196,32 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
}
}
- public static List<(string name, string tag, string comment)> CreateLineBreakClassEnum()
+ public static List CreateLineBreakClassEnum()
{
var usedLineBreakClasses = GenerateBreakPairTable();
- var lineBreakValues = GetPropertyValueAliases("# Line_Break (lb)");
+ var entries = new List { new DataEntry("Unknown", "XX", string.Empty) };
+
+ ParseDataEntries("# Line_Break (lb)", entries);
- var lineBreakClassMappings = lineBreakValues.ToDictionary(x => x.tag, x => (x.name, x.tag, x.comment));
+ var orderedLineBreakEntries = new Dictionary();
- var orderedLineBreakValues = usedLineBreakClasses.Select(x =>
+ foreach (var tag in usedLineBreakClasses)
{
- var value = lineBreakClassMappings[x];
- lineBreakClassMappings.Remove(x);
- return value;
- }).ToList();
+ var entry = entries.Single(x => x.Tag == tag);
+
+ orderedLineBreakEntries.Add(tag, entry);
+ }
+
+ foreach (var entry in entries)
+ {
+ if (orderedLineBreakEntries.ContainsKey(entry.Tag))
+ {
+ continue;
+ }
+
+ orderedLineBreakEntries.Add(entry.Tag, entry);
+ }
using (var stream = File.Create("Generated\\LineBreakClass.cs"))
using (var writer = new StreamWriter(stream))
@@ -208,32 +231,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum LineBreakClass");
writer.WriteLine(" {");
- foreach (var (name, tag, comment) in orderedLineBreakValues)
- {
- writer.WriteLine(" " + name + ", //" + tag +
- (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
- }
-
- writer.WriteLine();
-
- foreach (var (name, tag, comment) in lineBreakClassMappings.Values)
+ foreach (var entry in orderedLineBreakEntries.Values)
{
- writer.WriteLine(" " + name + ", //" + tag +
- (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+ writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+ (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" }");
writer.WriteLine("}");
}
- orderedLineBreakValues.AddRange(lineBreakClassMappings.Values);
-
- return orderedLineBreakValues;
+ return orderedLineBreakEntries.Values.ToList();
}
- public static List<(string name, string tag, string comment)> CreateBiDiClassEnum()
+ public static List CreateBiDiClassEnum()
{
- var biDiClassValues = GetPropertyValueAliases("# Bidi_Class (bc)");
+ var entries = new List { new DataEntry("Left_To_Right", "L", string.Empty) };
+
+ ParseDataEntries("# Bidi_Class (bc)", entries);
using (var stream = File.Create("Generated\\BiDiClass.cs"))
using (var writer = new StreamWriter(stream))
@@ -243,23 +258,21 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum BiDiClass");
writer.WriteLine(" {");
- foreach (var (name, tag, comment) in biDiClassValues)
+ foreach (var entry in entries)
{
- writer.WriteLine(" " + name + ", //" + tag +
- (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
+ writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
+ (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" }");
writer.WriteLine("}");
}
- return biDiClassValues;
+ return entries;
}
- public static void CreatePropertyValueAliasHelper(List<(string name, string tag, string comment)> scriptValues,
- List<(string name, string tag, string comment)> generalCategoryValues,
- List<(string name, string tag, string comment)> biDiClassValues,
- List<(string name, string tag, string comment)> lineBreakValues)
+ public static void CreatePropertyValueAliasHelper(List scriptEntries, IEnumerable generalCategoryEntries,
+ IEnumerable biDiClassEntries, IEnumerable lineBreakClassEntries)
{
using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs"))
using (var writer = new StreamWriter(stream))
@@ -269,35 +282,35 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode");
writer.WriteLine("{");
- writer.WriteLine(" public static class PropertyValueAliasHelper");
+ writer.WriteLine(" internal static class PropertyValueAliasHelper");
writer.WriteLine(" {");
- WritePropertyValueAliasGetTag(writer, scriptValues, "Script", "Zzzz");
+ WritePropertyValueAliasGetTag(writer, scriptEntries, "Script", "Zzzz");
- WritePropertyValueAlias(writer, scriptValues, "Script", "Unknown");
+ WritePropertyValueAlias(writer, scriptEntries, "Script", "Unknown");
- WritePropertyValueAlias(writer, generalCategoryValues, "GeneralCategory", "Other");
+ WritePropertyValueAlias(writer, generalCategoryEntries, "GeneralCategory", "Other");
- WritePropertyValueAlias(writer, biDiClassValues, "BiDiClass", "LeftToRight");
+ WritePropertyValueAlias(writer, biDiClassEntries, "BiDiClass", "LeftToRight");
- WritePropertyValueAlias(writer, lineBreakValues, "LineBreakClass", "Unknown");
+ WritePropertyValueAlias(writer, lineBreakClassEntries, "LineBreakClass", "Unknown");
writer.WriteLine(" }");
writer.WriteLine("}");
}
}
- public static List<(string name, string tag, string comment)> GetPropertyValueAliases(string property)
+ public static void ParseDataEntries(string property, List entries)
{
- var data = new List<(string name, string tag, string comment)>();
-
using (var client = new HttpClient())
{
- using (var result = client.GetAsync("https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt").GetAwaiter().GetResult())
+ var url = Path.Combine(UnicodeDataGenerator.Ucd, "PropertyValueAliases.txt");
+
+ using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{
if (!result.IsSuccessStatusCode)
{
- return data;
+ return;
}
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
@@ -337,7 +350,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
elements = elements[2].Split('#');
- var name = elements[0].Trim().Replace("_", string.Empty);
+ var name = elements[0].Trim();
+
+ if (entries.Any(x => x.Name == name))
+ {
+ continue;
+ }
var comment = string.Empty;
@@ -346,24 +364,25 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
comment = elements[1];
}
- data.Add((name, tag, comment));
+ var entry = new DataEntry(name, tag, comment);
+
+ entries.Add(entry);
}
}
}
}
-
- return data;
}
- private static void WritePropertyValueAliasGetTag(TextWriter writer,
- IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue)
+ private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable entries,
+ string typeName, string defaultValue)
{
- writer.WriteLine($" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = ");
+ writer.WriteLine(
+ $" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = ");
writer.WriteLine($" new Dictionary<{typeName}, string>{{");
- foreach (var (name, tag, comment) in values)
+ foreach (var entry in entries)
{
- writer.WriteLine($" {{ {typeName}.{name}, \"{tag}\"}},");
+ writer.WriteLine($" {{ {typeName}.{entry.Name.Replace("_", "")}, \"{entry.Tag}\"}},");
}
writer.WriteLine(" };");
@@ -382,15 +401,15 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine();
}
- private static void WritePropertyValueAlias(TextWriter writer,
- IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue)
+ private static void WritePropertyValueAlias(TextWriter writer, IEnumerable entries, string typeName,
+ string defaultValue)
{
writer.WriteLine($" private static readonly Dictionary s_tagTo{typeName} = ");
writer.WriteLine($" new Dictionary{{");
- foreach (var (name, tag, comment) in values)
+ foreach (var entry in entries)
{
- writer.WriteLine($" {{ \"{tag}\", {typeName}.{name}}},");
+ writer.WriteLine($" {{ \"{entry.Tag}\", {typeName}.{entry.Name.Replace("_", "")}}},");
}
writer.WriteLine(" };");
@@ -409,4 +428,18 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine();
}
}
+
+ public readonly struct DataEntry
+ {
+ public DataEntry(string name, string tag, string comment)
+ {
+ Name = name;
+ Tag = tag;
+ Comment = comment;
+ }
+
+ public string Name { get; }
+ public string Tag { get; }
+ public string Comment { get; }
+ }
}