diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index f93436d157..e83bf53331 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -101,7 +101,7 @@ public: virtual bool GetCurrentThreadIsLoopThread() override { - return [[NSThread currentThread] isMainThread]; + return [NSThread isMainThread]; } virtual void SetSignaledCallback(IAvnSignaledCallback* cb) override { diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 93a33bbbb0..00b6dab219 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -2,6 +2,7 @@ #include "rendertarget.h" #import #import +#import #include #include @@ -143,13 +144,17 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta return _layer; } -- (void)resize:(AvnPixelSize)size withScale: (float) scale;{ +- (void)resize:(AvnPixelSize)size withScale: (float) scale{ @synchronized (lock) { if(surface == nil || surface->size.Width != size.Width || surface->size.Height != size.Height || surface->scale != scale) + { surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()]; + + [self updateLayer]; + } } } @@ -159,12 +164,15 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta @synchronized (lock) { if(_layer == nil) return; + [CATransaction begin]; [_layer setContents: nil]; if(surface != nil) { [_layer setContentsScale: surface->scale]; [_layer setContents: (__bridge IOSurface*) surface->surface]; } + [CATransaction commit]; + [CATransaction flush]; } } else diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index 8e4c97b7f0..5251a2fa55 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -6,25 +6,21 @@ - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 97bd88f5e4..6a70bb082f 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -16,47 +16,39 @@ - - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + - - - - - - - + + + + + diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs index 0238446892..6e52b6770a 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace Avalonia.Utilities { @@ -9,12 +12,14 @@ namespace Avalonia.Utilities /// Stored value type. internal sealed class AvaloniaPropertyValueStore { + // The last item in the list is always int.MaxValue. + private static readonly Entry[] s_emptyEntries = { new Entry { PropertyId = int.MaxValue, Value = default! } }; + private Entry[] _entries; public AvaloniaPropertyValueStore() { - // The last item in the list is always int.MaxValue - _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = default } }; + _entries = s_emptyEntries; } private (int, bool) TryFindEntry(int propertyId) @@ -86,7 +91,7 @@ namespace Avalonia.Utilities return (0, false); } - public bool TryGetValue(AvaloniaProperty property, out TValue value) + public bool TryGetValue(AvaloniaProperty property, [MaybeNull] out TValue value) { (int index, bool found) = TryFindEntry(property.Id); if (!found) @@ -132,7 +137,18 @@ namespace Avalonia.Utilities if (found) { - Entry[] entries = new Entry[_entries.Length - 1]; + var newLength = _entries.Length - 1; + + // Special case - one element left means that value store is empty so we can just reuse our "empty" array. + if (newLength == 1) + { + _entries = s_emptyEntries; + + return; + } + + var entries = new Entry[newLength]; + int ix = 0; for (int i = 0; i < _entries.Length; ++i) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index bfac1ac1e1..90f6abc873 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -1,7 +1,7 @@  netstandard2.0 - netstandard2.0;netcoreapp2.0 + netstandard2.0;netcoreapp3.1 exe false tools @@ -81,6 +81,15 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index d5c406293e..f8960f56ec 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -1,3 +1,4 @@ +#if !NETCOREAPP3_1 namespace System { // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory @@ -63,6 +64,8 @@ namespace System } public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); + + public static implicit operator ReadOnlySpan(char[] arr) => new ReadOnlySpan(new string(arr)); } static class SpanCompatExtensions @@ -71,3 +74,4 @@ namespace System } } +#endif diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index 57f308d59f..b8418949d9 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -8,7 +8,10 @@ namespace Avalonia.Controls /// /// Defines the valid units for a . /// - public enum GridUnitType +#if !BUILDTASK + public +#endif + enum GridUnitType { /// /// The row or column is auto-sized to fit its content. @@ -29,7 +32,10 @@ namespace Avalonia.Controls /// /// Holds the width or height of a 's column and row definitions. /// - public struct GridLength : IEquatable +#if !BUILDTASK + public +#endif + struct GridLength : IEquatable { private readonly GridUnitType _type; diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index 63bb39108f..5d2e1087a7 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -1,7 +1,6 @@ using System; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; -using Avalonia.Styling; namespace Avalonia.Controls { @@ -16,7 +15,7 @@ namespace Avalonia.Controls EnableMenuItemClickForwardingProperty.Changed.Subscribe(args => { var item = (MenuItem)args.Sender; - if (args.NewValue.Equals(true)) + if (args.NewValue.GetValueOrDefault()) item.Click += OnMenuItemClick; else item.Click -= OnMenuItemClick; diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index a0fec9e677..76197b62ad 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -2,6 +2,7 @@ using System; using System.Windows.Input; using Avalonia.Input; using Avalonia.Media.Imaging; +using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Controls @@ -62,6 +63,7 @@ namespace Avalonia.Controls public static readonly DirectProperty MenuProperty = AvaloniaProperty.RegisterDirect(nameof(Menu), o => o.Menu, (o, v) => o.Menu = v); + [Content] public NativeMenu Menu { get => _menu; @@ -151,7 +153,7 @@ namespace Avalonia.Controls IsEnabled = _command?.CanExecute(null) ?? true; } - public bool HasClickHandlers => Clicked != null; + public bool HasClickHandlers => Click != null; public ICommand Command { @@ -182,11 +184,21 @@ namespace Avalonia.Controls set { SetValue(CommandParameterProperty, value); } } - public event EventHandler Clicked; + /// + /// Occurs when a is clicked. + /// + public event EventHandler Click; + + [Obsolete("Use Click event.")] + public event EventHandler Clicked + { + add => Click += value; + remove => Click -= value; + } void INativeMenuItemExporterEventsImplBridge.RaiseClicked() { - Clicked?.Invoke(this, new EventArgs()); + Click?.Invoke(this, new EventArgs()); if (Command?.CanExecute(CommandParameter) == true) { diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index a54d1ce308..5cbd3698b6 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls.Platform { var direction = e.Key.ToNavigationDirection(); - if (direction.HasValue) + if (direction?.IsDirectional() == true) { if (item == null && _isContextMenu) { diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 6e08e78813..fe4b24099f 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -3,6 +3,7 @@ using Avalonia.Collections; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; @@ -94,6 +95,8 @@ namespace Avalonia.Controls Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); + + ValueProperty.OverrideMetadata(new DirectPropertyMetadata(enableDataValidation: true)); } /// @@ -225,6 +228,14 @@ namespace Avalonia.Controls Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue; } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) + { + if (property == ValueProperty) + { + DataValidationErrors.SetError(this, value.Error); + } + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Input/NavigationDirection.cs b/src/Avalonia.Input/NavigationDirection.cs index 9b9af0b0a6..9bd6a8bb42 100644 --- a/src/Avalonia.Input/NavigationDirection.cs +++ b/src/Avalonia.Input/NavigationDirection.cs @@ -83,7 +83,7 @@ namespace Avalonia.Input /// public static bool IsDirectional(this NavigationDirection direction) { - return direction > NavigationDirection.Previous || + return direction > NavigationDirection.Previous && direction <= NavigationDirection.PageDown; } diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index b192de95de..2e3408eca5 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -60,7 +60,7 @@ namespace Avalonia.Native Header = "About Avalonia", }; - aboutItem.Clicked += async (sender, e) => + aboutItem.Click += async (sender, e) => { var dialog = new AboutAvaloniaDialog(); diff --git a/src/Avalonia.Native/Extensions.cs b/src/Avalonia.Native/Extensions.cs new file mode 100644 index 0000000000..c0d90f7a85 --- /dev/null +++ b/src/Avalonia.Native/Extensions.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Native +{ + internal static class Extensions + { + public static int AsComBool(this bool b) => b ? 1 : 0; + public static bool FromComBool(this int b) => b != 0; + } +} diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs index e2feffaa33..4c0c81394f 100644 --- a/src/Avalonia.Native/IAvnMenuItem.cs +++ b/src/Avalonia.Native/IAvnMenuItem.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native.Interop.Impl private void UpdateTitle(string title) => SetTitle(title ?? ""); - private void UpdateIsChecked(bool isChecked) => SetIsChecked(isChecked); + private void UpdateIsChecked(bool isChecked) => SetIsChecked(isChecked.AsComBool()); private void UpdateToggleType(NativeMenuItemToggleType toggleType) { diff --git a/src/Avalonia.Native/PlatformThreadingInterface.cs b/src/Avalonia.Native/PlatformThreadingInterface.cs index df69f2eafb..882d5a2ed3 100644 --- a/src/Avalonia.Native/PlatformThreadingInterface.cs +++ b/src/Avalonia.Native/PlatformThreadingInterface.cs @@ -33,9 +33,9 @@ namespace Avalonia.Native _parent = parent; } - public void Signaled(int priority, bool priorityContainsMeaningfulValue) + public void Signaled(int priority, int priorityContainsMeaningfulValue) { - _parent.Signaled?.Invoke(priorityContainsMeaningfulValue ? (DispatcherPriority?)priority : null); + _parent.Signaled?.Invoke(priorityContainsMeaningfulValue.FromComBool() ? (DispatcherPriority?)priority : null); } } @@ -50,7 +50,7 @@ namespace Avalonia.Native _native.SetSignaledCallback(cb); } - public bool CurrentThreadIsLoopThread => _native.CurrentThreadIsLoopThread; + public bool CurrentThreadIsLoopThread => _native.CurrentThreadIsLoopThread.FromComBool(); public event Action Signaled; diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 2f98385038..60c552a937 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -50,9 +50,9 @@ namespace Avalonia.Native // NOP on Popup } - bool IAvnWindowEvents.Closing() + int IAvnWindowEvents.Closing() { - return true; + return true.AsComBool(); } void IAvnWindowEvents.WindowStateChanged(AvnWindowState state) diff --git a/src/Avalonia.Native/PredicateCallback.cs b/src/Avalonia.Native/PredicateCallback.cs index 1ed2ae36af..19c470bcb3 100644 --- a/src/Avalonia.Native/PredicateCallback.cs +++ b/src/Avalonia.Native/PredicateCallback.cs @@ -12,9 +12,9 @@ namespace Avalonia.Native _predicate = predicate; } - bool IAvnPredicateCallback.Evaluate() + int IAvnPredicateCallback.Evaluate() { - return _predicate(); + return _predicate().AsComBool(); } } } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index ae6da01388..7b4a001486 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.Native screen.PixelDensity, screen.Bounds.ToAvaloniaPixelRect(), screen.WorkingArea.ToAvaloniaPixelRect(), - screen.Primary); + screen.Primary.FromComBool()); } return result; diff --git a/src/Avalonia.Native/SystemDialogs.cs b/src/Avalonia.Native/SystemDialogs.cs index 0239fc680d..8012813644 100644 --- a/src/Avalonia.Native/SystemDialogs.cs +++ b/src/Avalonia.Native/SystemDialogs.cs @@ -26,7 +26,7 @@ namespace Avalonia.Native if (dialog is OpenFileDialog ofd) { _native.OpenFileDialog(nativeParent, - events, ofd.AllowMultiple, + events, ofd.AllowMultiple.AsComBool(), ofd.Title ?? "", ofd.Directory ?? "", ofd.InitialFileName ?? "", diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index b42831854d..f60e83efe3 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -42,14 +42,14 @@ namespace Avalonia.Native _parent = parent; } - bool IAvnWindowEvents.Closing() + int IAvnWindowEvents.Closing() { if (_parent.Closing != null) { - return _parent.Closing(); + return _parent.Closing().AsComBool(); } - return true; + return true.AsComBool(); } void IAvnWindowEvents.WindowStateChanged(AvnWindowState state) @@ -69,7 +69,7 @@ namespace Avalonia.Native public void CanResize(bool value) { - _native.SetCanResize(value); + _native.SetCanResize(value.AsComBool()); } public void SetSystemDecorations(Controls.SystemDecorations enabled) @@ -145,7 +145,7 @@ namespace Avalonia.Native { _isExtended = extendIntoClientAreaHint; - _native.SetExtendClientArea(extendIntoClientAreaHint); + _native.SetExtendClientArea(extendIntoClientAreaHint.AsComBool()); InvalidateExtendedMargins(); } @@ -198,7 +198,7 @@ namespace Avalonia.Native public void SetEnabled(bool enable) { - _native.SetEnabled(enable); + _native.SetEnabled(enable.AsComBool()); } } } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 150ab2703e..88c3144884 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -192,14 +192,14 @@ namespace Avalonia.Native _parent.RawMouseEvent(type, timeStamp, modifiers, point, delta); } - bool IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) + int IAvnWindowBaseEvents.RawKeyEvent(AvnRawKeyEventType type, uint timeStamp, AvnInputModifiers modifiers, uint key) { - return _parent.RawKeyEvent(type, timeStamp, modifiers, key); + return _parent.RawKeyEvent(type, timeStamp, modifiers, key).AsComBool(); } - bool IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text) + int IAvnWindowBaseEvents.RawTextInputEvent(uint timeStamp, string text) { - return _parent.RawTextInputEvent(timeStamp, text); + return _parent.RawTextInputEvent(timeStamp, text).AsComBool(); } @@ -388,7 +388,7 @@ namespace Avalonia.Native public void SetTopmost(bool value) { - _native.SetTopMost(value); + _native.SetTopMost(value.AsComBool()); } public double RenderScaling => _native?.Scaling ?? 1; @@ -456,7 +456,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled((TransparencyLevel >= WindowTransparencyLevel.Blur).AsComBool()); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 1d36cce20d..3f33476c3d 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -1,5 +1,6 @@ @clr-namespace Avalonia.Native.Interop @clr-access internal +@clr-map bool int @cpp-preamble @@ #include "com.h" #include "key.h" diff --git a/src/Avalonia.Styling/ApiCompatBaseline.txt b/src/Avalonia.Styling/ApiCompatBaseline.txt new file mode 100644 index 0000000000..001e165830 --- /dev/null +++ b/src/Avalonia.Styling/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Styling: +MembersMustExist : Member 'public System.IObservable Avalonia.Controls.ResourceNodeExtensions.GetResourceObservable(Avalonia.IStyledElement, System.Object, System.Func)' does not exist in the implementation but it does exist in the contract. +Total Issues: 1 diff --git a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs index 250274c39b..be1069a4da 100644 --- a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls } public static IObservable GetResourceObservable( - this IStyledElement control, + this IResourceHost control, object key, Func? converter = null) { @@ -83,11 +83,11 @@ namespace Avalonia.Controls private class ResourceObservable : LightweightObservableBase { - private readonly IStyledElement _target; + private readonly IResourceHost _target; private readonly object _key; private readonly Func? _converter; - public ResourceObservable(IStyledElement target, object key, Func? converter) + public ResourceObservable(IResourceHost target, object key, Func? converter) { _target = target; _key = key; @@ -147,13 +147,16 @@ namespace Avalonia.Controls { if (_target.Owner is object) { - observer.OnNext(Convert(_target.Owner?.FindResource(_key))); + observer.OnNext(Convert(_target.Owner.FindResource(_key))); } } private void PublishNext() { - PublishNext(Convert(_target.Owner?.FindResource(_key))); + if (_target.Owner is object) + { + PublishNext(Convert(_target.Owner.FindResource(_key))); + } } private void OwnerChanged(object sender, EventArgs e) diff --git a/src/Avalonia.Themes.Default/PathIcon.xaml b/src/Avalonia.Themes.Default/PathIcon.xaml index a2d01f7b5b..039f0ef10c 100644 --- a/src/Avalonia.Themes.Default/PathIcon.xaml +++ b/src/Avalonia.Themes.Default/PathIcon.xaml @@ -2,16 +2,19 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/src/Avalonia.Themes.Default/ToggleSwitch.xaml b/src/Avalonia.Themes.Default/ToggleSwitch.xaml index 9d1c024eb9..f11f77df5a 100644 --- a/src/Avalonia.Themes.Default/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Default/ToggleSwitch.xaml @@ -5,7 +5,7 @@ 0,0,0,6 6 6 - 154 + 0 0 1 diff --git a/src/Avalonia.Themes.Fluent/PathIcon.xaml b/src/Avalonia.Themes.Fluent/PathIcon.xaml index 0a2c747514..d4952b3571 100644 --- a/src/Avalonia.Themes.Fluent/PathIcon.xaml +++ b/src/Avalonia.Themes.Fluent/PathIcon.xaml @@ -10,16 +10,19 @@ diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml index 2491225a44..45537e41ad 100644 --- a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml @@ -5,7 +5,7 @@ 0,0,0,6 6 6 - 154 + 0 diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 2d00d82a46..62f4def701 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -1,39 +1,5 @@ Compat issues with assembly Avalonia.Visuals: -MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.Geometry.GetRenderBounds(Avalonia.Media.Pen)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public System.Boolean Avalonia.Media.Geometry.StrokeContains(Avalonia.Media.Pen, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Media.GlyphRunDrawing.BaselineOrigin.set(Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. -TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract. -CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract. -CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract. -TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.DrawableTextRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size.get()' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.ShapedTextCharacters.Bounds.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextCharacters.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract. -CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract. -MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. -CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IDrawingContextLayerImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public Avalonia.Platform.IRenderTargetBitmapImpl Avalonia.Platform.IDrawingContextImpl.CreateLayer(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. -MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. -MembersMustExist : Member 'public Avalonia.Utilities.IRef Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. -Total Issues: 37 +MembersMustExist : Member 'public Avalonia.StyledProperty> Avalonia.StyledProperty> Avalonia.Media.DashStyle.DashesProperty' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Collections.Generic.IReadOnlyList Avalonia.Media.DashStyle.Dashes.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.DashStyle.Dashes.set(System.Collections.Generic.IReadOnlyList)' does not exist in the implementation but it does exist in the contract. +Total Issues: 3 diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index 16b4f90d57..a57a962db4 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -1,17 +1,24 @@ using System; using System.Globalization; +#if !BUILDTASK using Avalonia.Animation.Animators; +#endif namespace Avalonia.Media { /// /// An ARGB color. /// - public readonly struct Color : IEquatable +#if !BUILDTASK + public +#endif + readonly struct Color : IEquatable { static Color() { +#if !BUILDTASK Animation.Animation.RegisterAnimator(prop => typeof(Color).IsAssignableFrom(prop.PropertyType)); +#endif } /// @@ -223,7 +230,12 @@ namespace Avalonia.Media if (input.Length == 3 || input.Length == 4) { var extendedLength = 2 * input.Length; + +#if !BUILDTASK Span extended = stackalloc char[extendedLength]; +#else + char[] extended = new char[extendedLength]; +#endif for (int i = 0; i < input.Length; i++) { diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index 1e813edc13..acba2d3615 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -1,11 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using Avalonia.Animation; +using Avalonia.Collections; +using Avalonia.Media.Immutable; + +#nullable enable + namespace Avalonia.Media { - using System; - using System.Collections.Generic; - using System.Linq; - using Avalonia.Animation; - using Avalonia.Media.Immutable; - /// /// Represents the sequence of dashes and gaps that will be applied by a . /// @@ -14,8 +17,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> DashesProperty = - AvaloniaProperty.Register>(nameof(Dashes)); + public static readonly StyledProperty> DashesProperty = + AvaloniaProperty.Register>(nameof(Dashes)); /// /// Defines the property. @@ -23,10 +26,10 @@ namespace Avalonia.Media public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register(nameof(Offset)); - private static ImmutableDashStyle s_dash; - private static ImmutableDashStyle s_dot; - private static ImmutableDashStyle s_dashDot; - private static ImmutableDashStyle s_dashDotDot; + private static ImmutableDashStyle? s_dash; + private static ImmutableDashStyle? s_dot; + private static ImmutableDashStyle? s_dashDot; + private static ImmutableDashStyle? s_dashDotDot; /// /// Initializes a new instance of the class. @@ -41,9 +44,9 @@ namespace Avalonia.Media /// /// The dashes collection. /// The dash sequence offset. - public DashStyle(IEnumerable dashes, double offset) + public DashStyle(IEnumerable? dashes, double offset) { - Dashes = (IReadOnlyList)dashes?.ToList() ?? Array.Empty(); + Dashes = (dashes as AvaloniaList) ?? new AvaloniaList(dashes ?? Array.Empty()); Offset = offset; } @@ -61,31 +64,27 @@ namespace Avalonia.Media /// /// Represents a dashed . /// - public static IDashStyle Dash => - s_dash ?? (s_dash = new ImmutableDashStyle(new double[] { 2, 2 }, 1)); + public static IDashStyle Dash => s_dash ??= new ImmutableDashStyle(new double[] { 2, 2 }, 1); /// /// Represents a dotted . /// - public static IDashStyle Dot => - s_dot ?? (s_dot = new ImmutableDashStyle(new double[] { 0, 2 }, 0)); + public static IDashStyle Dot => s_dot ??= new ImmutableDashStyle(new double[] { 0, 2 }, 0); /// /// Represents a dashed dotted . /// - public static IDashStyle DashDot => - s_dashDot ?? (s_dashDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2 }, 1)); + public static IDashStyle DashDot => s_dashDot ??= new ImmutableDashStyle(new double[] { 2, 2, 0, 2 }, 1); /// /// Represents a dashed double dotted . /// - public static IDashStyle DashDotDot => - s_dashDotDot ?? (s_dashDotDot = new ImmutableDashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1)); + public static IDashStyle DashDotDot => s_dashDotDot ??= new ImmutableDashStyle(new double[] { 2, 2, 0, 2, 0, 2 }, 1); /// /// Gets or sets the length of alternating dashes and gaps. /// - public IReadOnlyList Dashes + public AvaloniaList Dashes { get => GetValue(DashesProperty); set => SetValue(DashesProperty, value); @@ -100,15 +99,43 @@ namespace Avalonia.Media set => SetValue(OffsetProperty, value); } + IReadOnlyList IDashStyle.Dashes => Dashes; + /// /// Raised when the dash style changes. /// - public event EventHandler Invalidated; + public event EventHandler? Invalidated; /// /// Returns an immutable clone of the . /// /// public ImmutableDashStyle ToImmutable() => new ImmutableDashStyle(Dashes, Offset); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == DashesProperty) + { + var oldValue = change.OldValue.GetValueOrDefault>(); + var newValue = change.NewValue.GetValueOrDefault>(); + + if (oldValue is object) + { + oldValue.CollectionChanged -= DashesChanged; + } + + if (newValue is object) + { + newValue.CollectionChanged += DashesChanged; + } + } + } + + private void DashesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + Invalidated?.Invoke(this, e); + } } } diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs index fd73eee69a..bae6dc3d8c 100644 --- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs +++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs @@ -12,10 +12,28 @@ namespace Avalonia.Media /// public static readonly StyledProperty RectProperty = AvaloniaProperty.Register(nameof(Rect)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RadiusXProperty = + AvaloniaProperty.Register(nameof(RadiusX)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RadiusYProperty = + AvaloniaProperty.Register(nameof(RadiusY)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterProperty = + AvaloniaProperty.Register(nameof(Center)); static EllipseGeometry() { - AffectsGeometry(RectProperty); + AffectsGeometry(RectProperty, RadiusXProperty, RadiusYProperty, CenterProperty); } /// @@ -43,6 +61,33 @@ namespace Avalonia.Media set => SetValue(RectProperty, value); } + /// + /// Gets or sets a double that defines the radius in the X-axis of the ellipse. + /// + public double RadiusX + { + get => GetValue(RadiusXProperty); + set => SetValue(RadiusXProperty, value); + } + + /// + /// Gets or sets a double that defines the radius in the Y-axis of the ellipse. + /// + public double RadiusY + { + get => GetValue(RadiusYProperty); + set => SetValue(RadiusYProperty, value); + } + + /// + /// Gets or sets a point that defines the center of the ellipse. + /// + public Point Center + { + get => GetValue(CenterProperty); + set => SetValue(CenterProperty, value); + } + /// public override Geometry Clone() { @@ -54,7 +99,14 @@ namespace Avalonia.Media { var factory = AvaloniaLocator.Current.GetService(); - return factory.CreateEllipseGeometry(Rect); + if (Rect != default) return factory.CreateEllipseGeometry(Rect); + + var originX = Center.X - RadiusX; + var originY = Center.Y - RadiusY; + var width = RadiusX * 2; + var height = RadiusY * 2; + + return factory.CreateEllipseGeometry(new Rect(originX, originY, width, height)); } } } diff --git a/src/Avalonia.Visuals/Media/KnownColors.cs b/src/Avalonia.Visuals/Media/KnownColors.cs index 0887d2c913..fe09f5f538 100644 --- a/src/Avalonia.Visuals/Media/KnownColors.cs +++ b/src/Avalonia.Visuals/Media/KnownColors.cs @@ -8,7 +8,9 @@ namespace Avalonia.Media { private static readonly IReadOnlyDictionary _knownColorNames; private static readonly IReadOnlyDictionary _knownColors; +#if !BUILDTASK private static readonly Dictionary _knownBrushes; +#endif static KnownColors() { @@ -32,14 +34,19 @@ namespace Avalonia.Media _knownColorNames = knownColorNames; _knownColors = knownColors; + +#if !BUILDTASK _knownBrushes = new Dictionary(); +#endif } +#if !BUILDTASK public static ISolidColorBrush GetKnownBrush(string s) { var color = GetKnownColor(s); return color != KnownColor.None ? color.ToBrush() : null; } +#endif public static KnownColor GetKnownColor(string s) { @@ -61,6 +68,7 @@ namespace Avalonia.Media return Color.FromUInt32((uint)color); } +#if !BUILDTASK public static ISolidColorBrush ToBrush(this KnownColor color) { lock (_knownBrushes) @@ -74,6 +82,7 @@ namespace Avalonia.Media return brush; } } +#endif } internal enum KnownColor : uint diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs index d0eb67ba39..caf86cb234 100644 --- a/src/Avalonia.Visuals/Media/PathFigure.cs +++ b/src/Avalonia.Visuals/Media/PathFigure.cs @@ -1,3 +1,7 @@ +#nullable enable +using System; +using System.Linq; +using Avalonia.Collections; using Avalonia.Metadata; namespace Avalonia.Media @@ -8,22 +12,36 @@ namespace Avalonia.Media /// Defines the property. /// public static readonly StyledProperty IsClosedProperty - = AvaloniaProperty.Register(nameof(IsClosed), true); + = AvaloniaProperty.Register(nameof(IsClosed), true); + /// /// Defines the property. /// public static readonly StyledProperty IsFilledProperty - = AvaloniaProperty.Register(nameof(IsFilled), true); + = AvaloniaProperty.Register(nameof(IsFilled), true); + /// /// Defines the property. /// - public static readonly DirectProperty SegmentsProperty - = AvaloniaProperty.RegisterDirect(nameof(Segments), f => f.Segments, (f, s) => f.Segments = s); + public static readonly DirectProperty SegmentsProperty + = AvaloniaProperty.RegisterDirect( + nameof(Segments), + f => f.Segments, + (f, s) => f.Segments = s); + /// /// Defines the property. /// public static readonly StyledProperty StartPointProperty - = AvaloniaProperty.Register(nameof(StartPoint)); + = AvaloniaProperty.Register(nameof(StartPoint)); + + internal event EventHandler? SegmentsInvalidated; + + private PathSegments? _segments; + + private IDisposable? _segmentsDisposable; + + private IDisposable? _segmentsPropertiesDisposable; /// /// Initializes a new instance of the class. @@ -33,6 +51,31 @@ namespace Avalonia.Media Segments = new PathSegments(); } + static PathFigure() + { + SegmentsProperty.Changed.AddClassHandler( + (s, e) => + s.OnSegmentsChanged()); + } + + private void OnSegmentsChanged() + { + _segmentsDisposable?.Dispose(); + _segmentsPropertiesDisposable?.Dispose(); + + _segmentsDisposable = _segments?.ForEachItem( + _ => InvalidateSegments(), + _ => InvalidateSegments(), + InvalidateSegments); + + _segmentsPropertiesDisposable = _segments?.TrackItemPropertyChanged(_ => InvalidateSegments()); + } + + private void InvalidateSegments() + { + SegmentsInvalidated?.Invoke(this, EventArgs.Empty); + } + /// /// Gets or sets a value indicating whether this instance is closed. /// @@ -64,7 +107,7 @@ namespace Avalonia.Media /// The segments. /// [Content] - public PathSegments Segments + public PathSegments? Segments { get { return _segments; } set { SetAndRaise(SegmentsProperty, ref _segments, value); } @@ -81,22 +124,23 @@ namespace Avalonia.Media get { return GetValue(StartPointProperty); } set { SetValue(StartPointProperty, value); } } + + public override string ToString() + => $"M {StartPoint} {string.Join(" ", _segments ?? Enumerable.Empty())}{(IsClosed ? "Z" : "")}"; internal void ApplyTo(StreamGeometryContext ctx) { ctx.BeginFigure(StartPoint, IsFilled); - foreach (var segment in Segments) + if (Segments != null) { - segment.ApplyTo(ctx); + foreach (var segment in Segments) + { + segment.ApplyTo(ctx); + } } ctx.EndFigure(IsClosed); } - - private PathSegments _segments; - - public override string ToString() - => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}"; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index fbc29aedc8..3d11c19b7d 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -104,12 +104,26 @@ namespace Avalonia.Media _figuresPropertiesObserver?.Dispose(); _figuresObserver = figures?.ForEachItem( - _ => InvalidateGeometry(), - _ => InvalidateGeometry(), - () => InvalidateGeometry()); + s => + { + s.SegmentsInvalidated += InvalidateGeometryFromSegments; + InvalidateGeometry(); + }, + s => + { + s.SegmentsInvalidated -= InvalidateGeometryFromSegments; + InvalidateGeometry(); + }, + InvalidateGeometry); + _figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry()); + } + private void InvalidateGeometryFromSegments(object _, EventArgs __) + { + InvalidateGeometry(); + } public override string ToString() => $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}"; diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index b3a24e6c37..8cdf24cc7b 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -351,7 +351,7 @@ namespace Avalonia.X11 if (state.HasFlag(XModifierMask.Mod4Mask)) Modifiers |= RawInputModifiers.Meta; - Modifiers = ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask); + Modifiers |= ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask); Valuators = new Dictionary(); Position = new Point(ev->event_x, ev->event_y); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs new file mode 100644 index 0000000000..218c49512c --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlGridLengthAstNode.cs @@ -0,0 +1,34 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes +{ + class AvaloniaXamlIlGridLengthAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode + { + private readonly AvaloniaXamlIlWellKnownTypes _types; + private readonly GridLength _gridLength; + + public AvaloniaXamlIlGridLengthAstNode(IXamlLineInfo lineInfo, AvaloniaXamlIlWellKnownTypes types, GridLength gridLength) : base(lineInfo) + { + _types = types; + _gridLength = gridLength; + + Type = new XamlAstClrTypeReference(lineInfo, types.GridLength, false); + } + + public IXamlAstTypeReference Type { get; } + + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen + .Ldc_R8(_gridLength.Value) + .Ldc_I4((int)_gridLength.GridUnitType) + .Newobj(_types.GridLengthConstructorValueType); + + return XamlILNodeEmitResult.Type(0, Type.GetClrType()); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs index c3d9534828..40cfd21a76 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Avalonia.Controls; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using Avalonia.Media; using XamlX; using XamlX.Ast; using XamlX.Emit; @@ -205,67 +207,152 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions result = new AvaloniaXamlIlFontFamilyAstNode(types, text, node); return true; } - + if (type.Equals(types.Thickness)) { - var thickness = Thickness.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Thickness, types.ThicknessFullConstructor, - new[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom }); + try + { + var thickness = Thickness.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Thickness, types.ThicknessFullConstructor, + new[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node); + } } if (type.Equals(types.Point)) { - var point = Point.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor, - new[] { point.X, point.Y }); + try + { + var point = Point.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor, + new[] { point.X, point.Y }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node); + } } if (type.Equals(types.Vector)) { - var vector = Vector.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor, - new[] { vector.X, vector.Y }); + try + { + var vector = Vector.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor, + new[] { vector.X, vector.Y }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node); + } } if (type.Equals(types.Size)) { - var size = Size.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Size, types.SizeFullConstructor, - new[] { size.Width, size.Height }); + try + { + var size = Size.Parse(text); - return true; + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Size, types.SizeFullConstructor, + new[] { size.Width, size.Height }); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node); + } } if (type.Equals(types.Matrix)) { - var matrix = Matrix.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Matrix, types.MatrixFullConstructor, - new[] { matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31, matrix.M32 }); + try + { + var matrix = Matrix.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Matrix, types.MatrixFullConstructor, + new[] { matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31, matrix.M32 }); - return true; + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node); + } } if (type.Equals(types.CornerRadius)) { - var cornerRadius = CornerRadius.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.CornerRadius, types.CornerRadiusFullConstructor, - new[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft }); + try + { + var cornerRadius = CornerRadius.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.CornerRadius, types.CornerRadiusFullConstructor, + new[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft }); + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node); + } + } + + if (type.Equals(types.Color)) + { + if (!Color.TryParse(text, out Color color)) + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a color", node); + } + + result = new XamlStaticOrTargetedReturnMethodCallNode(node, + type.GetMethod( + new FindMethodMethodSignature("FromUInt32", type, types.UInt) { IsStatic = true }), + new[] { new XamlConstantNode(node, types.UInt, color.ToUint32()) }); + return true; } + if (type.Equals(types.GridLength)) + { + try + { + var gridLength = GridLength.Parse(text); + + result = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a grid length", node); + } + } + + if (type.Equals(types.Cursor)) + { + if (TypeSystemHelpers.TryGetEnumValueNode(types.StandardCursorType, text, node, out var enumConstantNode)) + { + var cursorTypeRef = new XamlAstClrTypeReference(node, types.Cursor, false); + + result = new XamlAstNewClrObjectNode(node, cursorTypeRef, types.CursorTypeConstructor, new List { enumConstantNode }); + + return true; + } + } + if (type.FullName == "Avalonia.AvaloniaProperty") { var scope = context.ParentNodes().OfType().FirstOrDefault(); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 05b13b61d3..f046778429 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -49,6 +49,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType ReflectionBindingExtension { get; } public IXamlType RelativeSource { get; } + public IXamlType UInt { get; } public IXamlType Long { get; } public IXamlType Uri { get; } public IXamlType FontFamily { get; } @@ -65,6 +66,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlConstructor MatrixFullConstructor { get; } public IXamlType CornerRadius { get; } public IXamlConstructor CornerRadiusFullConstructor { get; } + public IXamlType GridLength { get; } + public IXamlConstructor GridLengthConstructorValueType { get; } + public IXamlType Color { get; } + public IXamlType StandardCursorType { get; } + public IXamlType Cursor { get; } + public IXamlConstructor CursorTypeConstructor { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -122,6 +129,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater"); ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension"); RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource"); + UInt = cfg.TypeSystem.GetType("System.UInt32"); Long = cfg.TypeSystem.GetType("System.Int64"); Uri = cfg.TypeSystem.GetType("System.Uri"); FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); @@ -141,6 +149,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers (Size, SizeFullConstructor) = GetNumericTypeInfo("Avalonia.Size", XamlIlTypes.Double, 2); (Matrix, MatrixFullConstructor) = GetNumericTypeInfo("Avalonia.Matrix", XamlIlTypes.Double, 6); (CornerRadius, CornerRadiusFullConstructor) = GetNumericTypeInfo("Avalonia.CornerRadius", XamlIlTypes.Double, 4); + + GridLength = cfg.TypeSystem.GetType("Avalonia.Controls.GridLength"); + GridLengthConstructorValueType = GridLength.GetConstructor(new List { XamlIlTypes.Double, cfg.TypeSystem.GetType("Avalonia.Controls.GridUnitType") }); + Color = cfg.TypeSystem.GetType("Avalonia.Media.Color"); + StandardCursorType = cfg.TypeSystem.GetType("Avalonia.Input.StandardCursorType"); + Cursor = cfg.TypeSystem.GetType("Avalonia.Input.Cursor"); + CursorTypeConstructor = Cursor.GetConstructor(new List { StandardCursorType }); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 03fd2e60dd..95380be65f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -10,8 +10,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { public class DynamicResourceExtension : IBinding { - private IStyledElement? _anchor; - private IResourceProvider? _resourceProvider; + private object? _anchor; public DynamicResourceExtension() { @@ -30,12 +29,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions if (!(provideTarget.TargetObject is IStyledElement)) { - _anchor = serviceProvider.GetFirstParent(); - - if (_anchor is null) - { - _resourceProvider = serviceProvider.GetFirstParent(); - } + _anchor = serviceProvider.GetFirstParent() ?? + serviceProvider.GetFirstParent() ?? + (object?)serviceProvider.GetFirstParent(); } return this; @@ -52,16 +48,16 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions return null; } - var control = target as IStyledElement ?? _anchor as IStyledElement; + var control = target as IResourceHost ?? _anchor as IResourceHost; if (control != null) { var source = control.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); return InstancedBinding.OneWay(source); } - else if (_resourceProvider is object) + else if (_anchor is IResourceProvider resourceProvider) { - var source = _resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); + var source = resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty)); return InstancedBinding.OneWay(source); } diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 7e1f97ab84..2aa82436f6 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -134,7 +134,7 @@ namespace Avalonia.Win32.WinRT.Composition public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle) { const int majorRequired = 10; - const int buildRequired = 16299; + const int buildRequired = 17134; var majorInstalled = Win32Platform.WindowsVersion.Major; var buildInstalled = Win32Platform.WindowsVersion.Build; diff --git a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs index 7170f6d7d4..3493dd0f53 100644 --- a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs @@ -17,7 +17,8 @@ namespace Avalonia.Benchmarks.Layout _app = UnitTestApplication.Start( TestServices.StyledWindow.With( renderInterface: new NullRenderingPlatform(), - threadingInterface: new NullThreadingPlatform())); + threadingInterface: new NullThreadingPlatform(), + standardCursorFactory: new NullCursorFactory())); _root = new TestRoot(true, null) { diff --git a/tests/Avalonia.Benchmarks/NullCursorFactory.cs b/tests/Avalonia.Benchmarks/NullCursorFactory.cs new file mode 100644 index 0000000000..012adce0f2 --- /dev/null +++ b/tests/Avalonia.Benchmarks/NullCursorFactory.cs @@ -0,0 +1,14 @@ +using System; +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.Benchmarks +{ + internal class NullCursorFactory : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + return new PlatformHandle(IntPtr.Zero, "null"); + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 47a73cb360..9152131ab3 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -345,6 +345,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + [Fact] + public void DynamicResource_Can_Be_Assigned_To_Resource_Property_In_Application() + { + var xaml = @" + + + #ff506070 + + +"; + + var application = (Application)AvaloniaRuntimeXamlLoader.Load(xaml); + var brush = (SolidColorBrush)application.Resources["brush"]; + + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } [Fact] public void DynamicResource_Can_Be_Assigned_To_ItemTemplate_Property() diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs new file mode 100644 index 0000000000..bd577e84f8 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ShapeTests.cs @@ -0,0 +1,24 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class ShapeTests : XamlTestBase + { + [Fact] + public void Can_Specify_DashStyle_In_XAML() + { + var xaml = @" + + + + +"; + + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); + + Assert.NotNull(target); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs new file mode 100644 index 0000000000..0737b4dc88 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathSegmentTests.cs @@ -0,0 +1,34 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class PathSegmentTests + { + [Fact] + public void PathSegment_Triggers_Invalidation_On_Property_Change() + { + var targetSegment = new ArcSegment() + { + Size = new Size(10, 10), + Point = new Point(5, 5) + }; + + var target = new PathGeometry + { + Figures = new PathFigures + { + new PathFigure { IsClosed = false, Segments = new PathSegments { targetSegment } } + } + }; + + var changed = false; + + target.Changed += (s, e) => changed = true; + + targetSegment.Size = new Size(20, 20); + + Assert.True(changed); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs index 418ac7576b..c2a1a5f9e4 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PenTests.cs @@ -1,4 +1,5 @@ -using Avalonia.Media; +using Avalonia.Collections; +using Avalonia.Media; using Avalonia.Media.Immutable; using Xunit; @@ -39,7 +40,20 @@ namespace Avalonia.Visuals.UnitTests.Media var raised = false; target.Invalidated += (s, e) => raised = true; - dashes.Dashes = new[] { 0.1, 0.2 }; + dashes.Dashes = new AvaloniaList { 0.1, 0.2 }; + + Assert.True(raised); + } + + [Fact] + public void Adding_DashStyle_Dashes_Raises_Invalidated() + { + var dashes = new DashStyle(); + var target = new Pen { DashStyle = dashes }; + var raised = false; + + target.Invalidated += (s, e) => raised = true; + dashes.Dashes.Add(0.3); Assert.True(raised); }