diff --git a/Avalonia.sln b/Avalonia.sln index 4e7b4cc318..0354e20d4f 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -60,20 +60,17 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DEF5-D50F-4975-8B72-124C9EB54066}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Markup\Avalonia.Markup\Avalonia.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingDemo", "samples\BindingDemo\BindingDemo.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skia", "Skia", "{3743B0F2-CC41-4F14-A8C8-267F579BF91E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F}" @@ -235,15 +232,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "sampl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport", "src\Avalonia.PlatformSupport\Avalonia.PlatformSupport.csproj", "{E8A597F0-2AB5-4BDA-A235-41162DAF53CF}" +EndProject 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*{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 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|iPhone = Ad-Hoc|iPhone @@ -2169,6 +2160,30 @@ Global {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhone.Build.0 = Release|Any CPU {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.AppStore|iPhone.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Debug|iPhone.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Release|Any CPU.Build.0 = Release|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Release|iPhone.ActiveCfg = Release|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Release|iPhone.Build.0 = Release|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E8A597F0-2AB5-4BDA-A235-41162DAF53CF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2187,11 +2202,9 @@ Global {EFB11458-9CDF-41C0-BE4F-44AF45A4CAB8} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {99135EAB-653D-47E4-A378-C96E1278CA44} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {3E53A01A-B331-47F3-B828-4A5717E77A24} = {8B6A8209-894F-4BA1-B880-965FD453982C} - {E4D9629C-F168-4224-3F51-A5E482FFBC42} = {A689DEF5-D50F-4975-8B72-124C9EB54066} {6417E941-21BC-467B-A771-0DE389353CE6} = {8B6A8209-894F-4BA1-B880-965FD453982C} {8EF392D5-1416-45AA-9956-7CBBC3229E8A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {3C4C0CB4-0C0F-4450-A37B-148C84FF905F} = {A689DEF5-D50F-4975-8B72-124C9EB54066} {7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} {FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} {4488AD85-1495-4809-9AA4-DDFE0A48527E} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props index fff00041c3..3fccad2641 100644 --- a/build/CoreLibraries.props +++ b/build/CoreLibraries.props @@ -17,5 +17,6 @@ + diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 7d75901288..7f24ef35bc 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -3,7 +3,7 @@ Avalonia 0.10.999 - Copyright 2021 © The AvaloniaUI Project + Copyright 2022 © The AvaloniaUI Project https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ true diff --git a/samples/ControlCatalog.Android/Assets/AboutAssets.txt b/samples/ControlCatalog.Android/Assets/AboutAssets.txt index ee39886295..a9b0638eb1 100644 --- a/samples/ControlCatalog.Android/Assets/AboutAssets.txt +++ b/samples/ControlCatalog.Android/Assets/AboutAssets.txt @@ -1,7 +1,7 @@ Any raw assets you want to be deployed with your application can be placed in this directory (and child directories) and given a Build Action of "AndroidAsset". -These files will be deployed with you package and will be accessible using Android's +These files will be deployed with your package and will be accessible using Android's AssetManager, like this: public class ReadAsset : Activity @@ -16,4 +16,4 @@ public class ReadAsset : Activity Additionally, some Android functions will automatically load asset files: -Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); \ No newline at end of file +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/samples/ControlCatalog/Pages/PointersPage.cs b/samples/ControlCatalog/Pages/PointersPage.cs index fddc503a90..2901013cea 100644 --- a/samples/ControlCatalog/Pages/PointersPage.cs +++ b/samples/ControlCatalog/Pages/PointersPage.cs @@ -1,15 +1,37 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using System.Threading; using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Threading; +using Avalonia.VisualTree; -namespace ControlCatalog.Pages +namespace ControlCatalog.Pages; + +public class PointersPage : Decorator { - public class PointersPage : Control + public PointersPage() + { + Child = new TabControl + { + Items = new[] + { + new TabItem() { Header = "Contacts", Content = new PointerContactsTab() }, + new TabItem() { Header = "IntermediatePoints", Content = new PointerIntermediatePointsTab() } + } + }; + } + + + class PointerContactsTab : Control { class PointerInfo { @@ -45,7 +67,7 @@ namespace ControlCatalog.Pages private Dictionary _pointers = new Dictionary(); - public PointersPage() + public PointerContactsTab() { ClipToBounds = true; } @@ -104,4 +126,196 @@ namespace ControlCatalog.Pages } } } + + public class PointerIntermediatePointsTab : Decorator + { + public PointerIntermediatePointsTab() + { + this[TextBlock.ForegroundProperty] = Brushes.Black; + var slider = new Slider + { + Margin = new Thickness(5), + Minimum = 0, + Maximum = 500 + }; + + var status = new TextBlock() + { + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + }; + Child = new Grid + { + Children = + { + new PointerCanvas(slider, status), + new Border + { + Background = Brushes.LightYellow, + Child = new StackPanel + { + Children = + { + new StackPanel + { + Orientation = Orientation.Horizontal, + Children = + { + new TextBlock { Text = "Thread sleep:" }, + new TextBlock() + { + [!TextBlock.TextProperty] =slider.GetObservable(Slider.ValueProperty) + .Select(x=>x.ToString()).ToBinding() + } + } + }, + slider + } + }, + + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Top, + Width = 300, + Height = 60 + }, + status + } + }; + } + + class PointerCanvas : Control + { + private readonly Slider _slider; + private readonly TextBlock _status; + private int _events; + private Stopwatch _stopwatch = Stopwatch.StartNew(); + private Dictionary _pointers = new(); + class PointerPoints + { + struct CanvasPoint + { + public IBrush Brush; + public Point Point; + public double Radius; + } + + readonly CanvasPoint[] _points = new CanvasPoint[1000]; + int _index; + + public void Render(DrawingContext context) + { + + CanvasPoint? prev = null; + for (var c = 0; c < _points.Length; c++) + { + var i = (c + _index) % _points.Length; + var pt = _points[i]; + if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null) + context.DrawLine(new Pen(Brushes.Black), prev.Value.Point, pt.Point); + prev = pt; + if (pt.Brush != null) + context.DrawEllipse(pt.Brush, null, pt.Point, pt.Radius, pt.Radius); + + } + + } + + void AddPoint(Point pt, IBrush brush, double radius) + { + _points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius }; + _index = (_index + 1) % _points.Length; + } + + public void HandleEvent(PointerEventArgs e, Visual v) + { + e.Handled = true; + if (e.RoutedEvent == PointerPressedEvent) + AddPoint(e.GetPosition(v), Brushes.Green, 10); + else if (e.RoutedEvent == PointerReleasedEvent) + AddPoint(e.GetPosition(v), Brushes.Red, 10); + else + { + var pts = e.GetIntermediatePoints(v); + for (var c = 0; c < pts.Count; c++) + { + var pt = pts[c]; + AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black, + c == pts.Count - 1 ? 5 : 2); + } + } + } + } + + public PointerCanvas(Slider slider, TextBlock status) + { + _slider = slider; + _status = status; + DispatcherTimer.Run(() => + { + if (_stopwatch.Elapsed.TotalSeconds > 1) + { + _status.Text = "Events per second: " + (_events / _stopwatch.Elapsed.TotalSeconds); + _stopwatch.Restart(); + _events = 0; + } + + return this.GetVisualRoot() != null; + }, TimeSpan.FromMilliseconds(10)); + } + + + void HandleEvent(PointerEventArgs e) + { + _events++; + Thread.Sleep((int)_slider.Value); + InvalidateVisual(); + + if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch) + { + _pointers.Remove(e.Pointer.Id); + return; + } + + if (!_pointers.TryGetValue(e.Pointer.Id, out var pt)) + _pointers[e.Pointer.Id] = pt = new PointerPoints(); + pt.HandleEvent(e, this); + + + } + + public override void Render(DrawingContext context) + { + context.FillRectangle(Brushes.White, Bounds); + foreach(var pt in _pointers.Values) + pt.Render(context); + base.Render(context); + } + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + if (e.ClickCount == 2) + { + _pointers.Clear(); + InvalidateVisual(); + return; + } + + HandleEvent(e); + base.OnPointerPressed(e); + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + HandleEvent(e); + base.OnPointerMoved(e); + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + HandleEvent(e); + base.OnPointerReleased(e); + } + } + + } } diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 4edb0f137a..caad8b0854 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -49,25 +50,33 @@ namespace ControlCatalog.Pages context.DrawRectangle(p, boundsRect); context.DrawRectangle(p, workingAreaRect); - var text = new FormattedText() { Typeface = new Typeface("Arial"), FontSize = 18 }; - text.Text = $"Bounds: {screen.Bounds.TopLeft} {screen.Bounds.Width}:{screen.Bounds.Height}"; - context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height), text); - - text.Text = $"WorkArea: {screen.WorkingArea.TopLeft} {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"; - context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text); + var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height)); - text.Text = $"Scaling: {screen.PixelDensity * 100}%"; - context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); - - text.Text = $"Primary: {screen.Primary}"; - context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); - - text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"; - context.DrawText(drawBrush, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text); + formattedText = + CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); + + formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); + + formattedText = CreateFormattedText($"Primary: {screen.Primary}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); + + formattedText = + CreateFormattedText( + $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80)); } context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10)); } + + private FormattedText CreateFormattedText(string textToFormat) + { + return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, + Typeface.Default, 12, Brushes.Green); + } } } diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml index 81e5046636..e7e3007d35 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -1,5 +1,6 @@ @@ -12,8 +13,8 @@ + Stretch="{Binding #StretchSelector.SelectedItem, FallbackValue={x:Static Stretch.Uniform}}" + StretchDirection="{Binding #StretchDirectionSelector.SelectedItem, FallbackValue={x:Static StretchDirection.Both}}"> @@ -25,9 +26,22 @@ - + + + Uniform + UniformToFill + Fill + None + + - + + + Both + DownOnly + UpOnly + + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs index 94b3f3ea14..12b5086e12 100644 --- a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; -using Avalonia.Media; namespace ControlCatalog.Pages { @@ -9,24 +8,6 @@ namespace ControlCatalog.Pages public ViewboxPage() { InitializeComponent(); - - var stretchSelector = this.FindControl("StretchSelector"); - - stretchSelector.Items = new[] - { - Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None - }; - - stretchSelector.SelectedIndex = 0; - - var stretchDirectionSelector = this.FindControl("StretchDirectionSelector"); - - stretchDirectionSelector.Items = new[] - { - StretchDirection.Both, StretchDirection.DownOnly, StretchDirection.UpOnly - }; - - stretchDirectionSelector.SelectedIndex = 0; } private void InitializeComponent() diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index a4c6299278..4a8fb819ca 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -57,6 +57,9 @@ + + + diff --git a/samples/RenderDemo/Pages/ClippingPage.xaml b/samples/RenderDemo/Pages/ClippingPage.xaml index 10225f7c49..698e18d496 100644 --- a/samples/RenderDemo/Pages/ClippingPage.xaml +++ b/samples/RenderDemo/Pages/ClippingPage.xaml @@ -19,30 +19,36 @@ - + + + + diff --git a/samples/RenderDemo/Pages/ClippingPage.xaml.cs b/samples/RenderDemo/Pages/ClippingPage.xaml.cs index 5357181838..c5b669343a 100644 --- a/samples/RenderDemo/Pages/ClippingPage.xaml.cs +++ b/samples/RenderDemo/Pages/ClippingPage.xaml.cs @@ -1,35 +1,18 @@ -using System; -using System.Reactive.Linq; -using Avalonia; -using Avalonia.Animation; using Avalonia.Controls; -using Avalonia.Data; using Avalonia.Markup.Xaml; -using Avalonia.Media; namespace RenderDemo.Pages { public class ClippingPage : UserControl { - private Geometry _clip; - public ClippingPage() { InitializeComponent(); - WireUpCheckbox(); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } - - private void WireUpCheckbox() - { - var useMask = this.FindControl("useMask"); - var clipped = this.FindControl("clipped"); - _clip = clipped.Clip; - useMask.Click += (s, e) => clipped.Clip = clipped.Clip == null ? _clip : null; - } } } diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs index 2e59d934a1..9c524a7932 100644 --- a/samples/RenderDemo/Pages/CustomSkiaPage.cs +++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Globalization; using Avalonia; using Avalonia.Controls; using Avalonia.Media; @@ -41,7 +42,10 @@ namespace RenderDemo.Pages { var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; if (canvas == null) - context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl); + using (var c = new DrawingContext(context, false)) + { + c.DrawText(_noSkia, new Point()); + } else { canvas.Save(); @@ -108,10 +112,9 @@ namespace RenderDemo.Pages public override void Render(DrawingContext context) { - var noSkia = new FormattedText() - { - Text = "Current rendering API is not Skia" - }; + var noSkia = new FormattedText("Current rendering API is not Skia", CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, Typeface.Default, 12, Brushes.Black); + context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia)); Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background); } diff --git a/samples/RenderDemo/Pages/FormattedTextPage.axaml b/samples/RenderDemo/Pages/FormattedTextPage.axaml new file mode 100644 index 0000000000..92775bec9e --- /dev/null +++ b/samples/RenderDemo/Pages/FormattedTextPage.axaml @@ -0,0 +1,7 @@ + + diff --git a/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs b/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs new file mode 100644 index 0000000000..25e29c67a9 --- /dev/null +++ b/samples/RenderDemo/Pages/FormattedTextPage.axaml.cs @@ -0,0 +1,60 @@ +using System.Globalization; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media; + +namespace RenderDemo.Pages +{ + public class FormattedTextPage : UserControl + { + public FormattedTextPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public override void Render(DrawingContext context) + { + const string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor"; + + // Create the initial formatted text string. + var formattedText = new FormattedText( + testString, + CultureInfo.GetCultureInfo("en-us"), + FlowDirection.LeftToRight, + new Typeface("Verdana"), + 32, + Brushes.Black) { MaxTextWidth = 300, MaxTextHeight = 240 }; + + // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears. + + // Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters. + // The font size is calculated in terms of points -- not as device-independent pixels. + formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5); + + // Use a Bold font weight beginning at the 6th character and continuing for 11 characters. + formattedText.SetFontWeight(FontWeight.Bold, 6, 11); + + var gradient = new LinearGradientBrush + { + GradientStops = + new GradientStops { new GradientStop(Colors.Orange, 0), new GradientStop(Colors.Teal, 1) }, + StartPoint = new RelativePoint(0,0, RelativeUnit.Relative), + EndPoint = new RelativePoint(0,1, RelativeUnit.Relative) + }; + + // Use a linear gradient brush beginning at the 6th character and continuing for 11 characters. + formattedText.SetForegroundBrush(gradient, 6, 11); + + // Use an Italic font style beginning at the 28th character and continuing for 28 characters. + formattedText.SetFontStyle(FontStyle.Italic, 28, 28); + + context.DrawText(formattedText, new Point(10, 0)); + } + } +} diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs index 857358f6b2..7f85606957 100644 --- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs +++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs @@ -13,6 +13,7 @@ namespace RenderDemo.Pages private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface; private readonly Random _rand = new Random(); private ushort[] _glyphIndices = new ushort[1]; + private char[] _characters = new char[1]; private float _fontSize = 20; private int _direction = 10; @@ -38,7 +39,7 @@ namespace RenderDemo.Pages private void UpdateGlyphRun() { - var c = (uint)_rand.Next(65, 90); + var c = (char)_rand.Next(65, 90); if (_fontSize + _direction > 200) { @@ -54,6 +55,8 @@ namespace RenderDemo.Pages _glyphIndices[0] = _glyphTypeface.GetGlyph(c); + _characters[0] = c; + var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight; var drawingGroup = new DrawingGroup(); @@ -61,7 +64,7 @@ namespace RenderDemo.Pages var glyphRunDrawing = new GlyphRunDrawing { Foreground = Brushes.Black, - GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices), + GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices) }; drawingGroup.Children.Add(glyphRunDrawing); diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 6a940a54f1..2d4f6a305f 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -10,7 +10,7 @@ using Avalonia.Input.Platform; using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Shared.PlatformSupport; +using Avalonia.PlatformSupport; using Avalonia.Skia; namespace Avalonia @@ -59,8 +59,7 @@ namespace Avalonia.Android .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) .Bind().ToConstant(new RenderLoop()) - .Bind().ToSingleton() - .Bind().ToConstant(new AssetLoader(appType.Assembly)); + .Bind().ToSingleton(); SkiaPlatform.Initialize(); diff --git a/src/Android/Avalonia.Android/AppBuilder.cs b/src/Android/Avalonia.Android/AppBuilder.cs index 805bb61655..04f1ff00d0 100644 --- a/src/Android/Avalonia.Android/AppBuilder.cs +++ b/src/Android/Avalonia.Android/AppBuilder.cs @@ -1,5 +1,5 @@ using Avalonia.Controls; -using Avalonia.Shared.PlatformSupport; +using Avalonia.PlatformSupport; namespace Avalonia { diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 8c6775733f..5c33dbcea6 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -5,9 +5,11 @@ + + TargetFramework=netstandard2.0 + - diff --git a/src/Android/Avalonia.Android/RuntimeInfo.cs b/src/Android/Avalonia.Android/RuntimeInfo.cs deleted file mode 100644 index bb2466c357..0000000000 --- a/src/Android/Avalonia.Android/RuntimeInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia.Platform; - -namespace Avalonia.Shared.PlatformSupport -{ - internal partial class StandardRuntimePlatform - { - public RuntimePlatformInfo GetRuntimeInfo() => new RuntimePlatformInfo - { - IsCoreClr = false, - IsDesktop = false, - IsMobile = true, - IsDotNetFramework = false, - IsMono = true, - IsUnix = true, - OperatingSystem = OperatingSystemType.Android - }; - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 0e027712e0..2fe68e824d 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -146,6 +146,7 @@ namespace Avalonia.Collections { if (_inner.TryGetValue(key, out var value)) { + _inner.Remove(key); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index a0d5d611b3..850757a1ee 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; namespace Avalonia.Platform { @@ -23,6 +22,7 @@ namespace Avalonia.Platform public OperatingSystemType OperatingSystem { get; set; } public bool IsDesktop { get; set; } public bool IsMobile { get; set; } + public bool IsBrowser { get; set; } public bool IsCoreClr { get; set; } public bool IsMono { get; set; } public bool IsDotNetFramework { get; set; } @@ -36,6 +36,7 @@ namespace Avalonia.Platform Linux, OSX, Android, - iOS + iOS, + Browser } } diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index 908f431776..49cee441d0 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -74,6 +74,13 @@ namespace Avalonia.Threading /// /// public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority); + + /// + /// Use this method to check if there are more prioritized tasks + /// + /// + public bool HasJobsWithPriority(DispatcherPriority minimumPriority) => + _jobRunner.HasJobsWithPriority(minimumPriority); /// public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index f2aef0414c..4b304d44f6 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -121,6 +121,21 @@ namespace Avalonia.Threading return null; } + public bool HasJobsWithPriority(DispatcherPriority minimumPriority) + { + for (int c = (int)minimumPriority; c < (int)DispatcherPriority.MaxValue; c++) + { + var q = _queues[c]; + lock (q) + { + if (q.Count > 0) + return true; + } + } + + return false; + } + private interface IJob { /// diff --git a/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs b/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs index 90d1c52ff5..251dfe4351 100644 --- a/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs +++ b/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Avalonia.Utilities { - public struct ImmutableReadOnlyListStructEnumerator : IEnumerator, IEnumerator + public struct ImmutableReadOnlyListStructEnumerator : IEnumerator { private readonly IReadOnlyList _readOnlyList; private int _pos; diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 95ee73be4e..5d71a499e3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2215,7 +2215,14 @@ namespace Avalonia.Controls /// PointerWheelEventArgs protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { - e.Handled = e.Handled || UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta); + if(UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta)) + { + e.Handled = true; + } + else + { + e.Handled = e.Handled || !ScrollViewer.GetIsScrollChainingEnabled(this); + } } internal bool UpdateScroll(Vector delta) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index a7560c37f2..2c206b53f6 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -43,6 +43,10 @@ MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.Off MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation. +MembersMustExist : Member 'protected Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.CreateFormattedText()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Media.FormattedText Avalonia.Controls.Presenters.TextPresenter.FormattedText.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Int32 Avalonia.Controls.Presenters.TextPresenter.GetCaretIndex(Avalonia.Point)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPresenter.InvalidateFormattedText()' does not exist in the implementation but it does exist in the contract. CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. @@ -63,4 +67,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. -Total Issues: 64 +Total Issues: 68 diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index d44b2ab0db..8779ae9122 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -14,9 +14,9 @@ namespace Avalonia.Controls public abstract class AppBuilderBase where TAppBuilder : AppBuilderBase, new() { private static bool s_setupWasAlreadyCalled; - private Action _optionsInitializers; - private Func _appFactory; - private IApplicationLifetime _lifetime; + private Action? _optionsInitializers; + private Func? _appFactory; + private IApplicationLifetime? _lifetime; /// /// Gets or sets the instance. @@ -31,32 +31,32 @@ namespace Avalonia.Controls /// /// Gets the instance being initialized. /// - public Application Instance { get; private set; } + public Application? Instance { get; private set; } /// /// Gets the type of the Instance (even if it's not created yet) /// - public Type ApplicationType { get; private set; } + public Type? ApplicationType { get; private set; } /// /// Gets or sets a method to call the initialize the windowing subsystem. /// - public Action WindowingSubsystemInitializer { get; private set; } + public Action? WindowingSubsystemInitializer { get; private set; } /// /// Gets the name of the currently selected windowing subsystem. /// - public string WindowingSubsystemName { get; private set; } + public string? WindowingSubsystemName { get; private set; } /// /// Gets or sets a method to call the initialize the windowing subsystem. /// - public Action RenderingSubsystemInitializer { get; private set; } + public Action? RenderingSubsystemInitializer { get; private set; } /// /// Gets the name of the currently selected rendering subsystem. /// - public string RenderingSubsystemName { get; private set; } + public string? RenderingSubsystemName { get; private set; } /// /// Gets or sets a method to call after the is setup. @@ -126,7 +126,7 @@ namespace Avalonia.Controls /// The window type. /// A delegate that will be called to create a data context for the window (optional). [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] - public void Start(Func dataContextProvider = null) + public void Start(Func? dataContextProvider = null) where TMainWindow : Window, new() { AfterSetup(builder => @@ -134,7 +134,7 @@ namespace Avalonia.Controls var window = new TMainWindow(); if (dataContextProvider != null) window.DataContext = dataContextProvider(); - ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime) + ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!) .MainWindow = window; }); @@ -155,7 +155,7 @@ namespace Avalonia.Controls public void Start(AppMainDelegate main, string[] args) { Setup(); - main(Instance, args); + main(Instance!, args); } /// @@ -226,8 +226,8 @@ namespace Avalonia.Controls var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform"; var platformClassFullName = assemblyName + "." + platformClassName; var platformClass = assembly.GetType(platformClassFullName); - var init = platformClass.GetRuntimeMethod("Initialize", Type.EmptyTypes); - init.Invoke(null, null); + var init = platformClass!.GetRuntimeMethod("Initialize", Type.EmptyTypes); + init!.Invoke(null, null); }; public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); @@ -251,7 +251,7 @@ namespace Avalonia.Controls where constructor.GetParameters().Length == 0 && !constructor.IsStatic select constructor).Single() into constructor select (Action)(() => constructor.Invoke(Array.Empty())); - Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke(); + Delegate.Combine(moduleInitializers.ToArray())!.DynamicInvoke(); } /// @@ -292,6 +292,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("No rendering system configured."); } + if (_appFactory == null) + { + throw new InvalidOperationException("No Application factory configured."); + } + if (s_setupWasAlreadyCalled && CheckSetup) { throw new InvalidOperationException("Setup was already called on one of AppBuilder instances"); diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 584c3db23b..69fd6cabf8 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -13,7 +13,6 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; -#nullable enable namespace Avalonia { @@ -177,13 +176,13 @@ namespace Avalonia /// public IApplicationLifetime? ApplicationLifetime { get; set; } - event Action> IGlobalStyles.GlobalStylesAdded + event Action>? IGlobalStyles.GlobalStylesAdded { add => _stylesAdded += value; remove => _stylesAdded -= value; } - event Action> IGlobalStyles.GlobalStylesRemoved + event Action>? IGlobalStyles.GlobalStylesRemoved { add => _stylesRemoved += value; remove => _stylesRemoved -= value; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index edddf31d45..76e2d3a161 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -15,26 +15,26 @@ namespace Avalonia.Controls.ApplicationLifetimes public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable { private int _exitCode; - private CancellationTokenSource _cts; + private CancellationTokenSource? _cts; private bool _isShuttingDown; private HashSet _windows = new HashSet(); - private static ClassicDesktopStyleApplicationLifetime _activeLifetime; + private static ClassicDesktopStyleApplicationLifetime? _activeLifetime; static ClassicDesktopStyleApplicationLifetime() { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); } - private static void WindowClosedEvent(object sender, RoutedEventArgs e) + private static void WindowClosedEvent(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Remove((Window)sender); - _activeLifetime?.HandleWindowClosed((Window)sender); + _activeLifetime?._windows.Remove((Window)sender!); + _activeLifetime?.HandleWindowClosed((Window)sender!); } - private static void OnWindowOpened(object sender, RoutedEventArgs e) + private static void OnWindowOpened(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Add((Window)sender); + _activeLifetime?._windows.Add((Window)sender!); } public ClassicDesktopStyleApplicationLifetime() @@ -46,24 +46,24 @@ namespace Avalonia.Controls.ApplicationLifetimes } /// - public event EventHandler Startup; + public event EventHandler? Startup; /// - public event EventHandler ShutdownRequested; + public event EventHandler? ShutdownRequested; /// - public event EventHandler Exit; + public event EventHandler? Exit; /// /// Gets the arguments passed to the AppBuilder Start method. /// - public string[] Args { get; set; } + public string[]? Args { get; set; } /// public ShutdownMode ShutdownMode { get; set; } /// - public Window MainWindow { get; set; } + public Window? MainWindow { get; set; } public IReadOnlyList Windows => _windows.ToList(); @@ -183,7 +183,7 @@ namespace Avalonia.Controls.ApplicationLifetimes return true; } - private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e) => DoShutdown(e); + private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e); } public class ClassicDesktopStyleApplicationLifetimeOptions diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index a83229b732..2bd5c1238d 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls.ApplicationLifetimes /// /// method. /// - string[] Args { get; } + string[]? Args { get; } /// /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. @@ -38,7 +38,7 @@ namespace Avalonia.Controls.ApplicationLifetimes /// /// The main window. /// - Window MainWindow { get; set; } + Window? MainWindow { get; set; } IReadOnlyList Windows { get; } @@ -58,6 +58,6 @@ namespace Avalonia.Controls.ApplicationLifetimes /// will try to close each non-owned open window, invoking the event on each and allowing /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown. /// - event EventHandler ShutdownRequested; + event EventHandler? ShutdownRequested; } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs index eb451f51af..e25815602e 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs @@ -2,6 +2,6 @@ namespace Avalonia.Controls.ApplicationLifetimes { public interface ISingleViewApplicationLifetime : IApplicationLifetime { - Control MainView { get; set; } + Control? MainView { get; set; } } } diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 0e946126ea..930e250334 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -69,7 +69,7 @@ namespace Avalonia.Controls /// /// The text that is used to determine which items to display in /// the . - public string Parameter { get; private set; } + public string? Parameter { get; private set; } /// /// Initializes a new instance of the @@ -79,7 +79,7 @@ namespace Avalonia.Controls /// /// property, which is used to filter items for the /// control. - public PopulatingEventArgs(string parameter) + public PopulatingEventArgs(string? parameter) { Parameter = parameter; } @@ -98,7 +98,7 @@ namespace Avalonia.Controls /// The type used for filtering the /// . This type can /// be either a string or an object. - public delegate bool AutoCompleteFilterPredicate(string search, T item); + public delegate bool AutoCompleteFilterPredicate(string? search, T item); /// /// Specifies how text in the text box portion of the @@ -245,7 +245,7 @@ namespace Avalonia.Controls /// . /// This type can be either a string or an object. /// - public delegate string AutoCompleteSelector(string search, T item); + public delegate string AutoCompleteSelector(string? search, T item); /// /// Represents a control that provides a text box for user input and a @@ -275,19 +275,19 @@ namespace Avalonia.Controls /// private const string ElementTextBox = "PART_TextBox"; - private IEnumerable _itemsEnumerable; + private IEnumerable? _itemsEnumerable; /// /// Gets or sets a local cached copy of the items data. /// - private List _items; + private List? _items; /// /// Gets or sets the observable collection that contains references to /// all of the items in the generated view of data that is provided to /// the selection-style control adapter. /// - private AvaloniaList _view; + private AvaloniaList? _view; /// /// Gets or sets a value to ignore a number of pending change handlers. @@ -338,7 +338,7 @@ namespace Avalonia.Controls /// Gets or sets the DispatcherTimer used for the MinimumPopulateDelay /// condition for auto completion. /// - private DispatcherTimer _delayTimer; + private DispatcherTimer? _delayTimer; /// /// Gets or sets a value indicating whether a read-only dependency @@ -351,47 +351,47 @@ namespace Avalonia.Controls /// /// The TextBox template part. /// - private TextBox _textBox; - private IDisposable _textBoxSubscriptions; + private TextBox? _textBox; + private IDisposable? _textBoxSubscriptions; /// /// The SelectionAdapter. /// - private ISelectionAdapter _adapter; + private ISelectionAdapter? _adapter; /// /// A control that can provide updated string values from a binding. /// - private BindingEvaluator _valueBindingEvaluator; + private BindingEvaluator? _valueBindingEvaluator; /// /// A weak subscription for the collection changed event. /// - private IDisposable _collectionChangeSubscription; + private IDisposable? _collectionChangeSubscription; - private Func>> _asyncPopulator; - private CancellationTokenSource _populationCancellationTokenSource; + private Func>>? _asyncPopulator; + private CancellationTokenSource? _populationCancellationTokenSource; private bool _itemTemplateIsFromValueMemberBinding = true; private bool _settingItemTemplateFromValueMemberBinding; - private object _selectedItem; + private object? _selectedItem; private bool _isDropDownOpen; private bool _isFocused = false; - private string _text = string.Empty; - private string _searchText = string.Empty; + private string? _text = string.Empty; + private string? _searchText = string.Empty; - private AutoCompleteFilterPredicate _itemFilter; - private AutoCompleteFilterPredicate _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith); + private AutoCompleteFilterPredicate? _itemFilter; + private AutoCompleteFilterPredicate? _textFilter = AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith); - private AutoCompleteSelector _itemSelector; - private AutoCompleteSelector _textSelector; + private AutoCompleteSelector? _itemSelector; + private AutoCompleteSelector? _textSelector; public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); - public static readonly StyledProperty WatermarkProperty = + public static readonly StyledProperty WatermarkProperty = TextBox.WatermarkProperty.AddOwner(); /// @@ -479,8 +479,8 @@ namespace Avalonia.Controls /// The identifier the /// /// dependency property. - public static readonly DirectProperty SelectedItemProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectedItemProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v, @@ -495,7 +495,7 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty TextProperty = + public static readonly DirectProperty TextProperty = TextBlock.TextProperty.AddOwnerWithDataValidation( o => o.Text, (o, v) => o.Text = v, @@ -510,8 +510,8 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty SearchTextProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SearchTextProperty = + AvaloniaProperty.RegisterDirect( nameof(SearchText), o => o.SearchText, unsetValue: string.Empty); @@ -535,8 +535,8 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty> ItemFilterProperty = - AvaloniaProperty.RegisterDirect>( + public static readonly DirectProperty?> ItemFilterProperty = + AvaloniaProperty.RegisterDirect?>( nameof(ItemFilter), o => o.ItemFilter, (o, v) => o.ItemFilter = v); @@ -549,8 +549,8 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty> TextFilterProperty = - AvaloniaProperty.RegisterDirect>( + public static readonly DirectProperty?> TextFilterProperty = + AvaloniaProperty.RegisterDirect?>( nameof(TextFilter), o => o.TextFilter, (o, v) => o.TextFilter = v, @@ -564,8 +564,8 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty> ItemSelectorProperty = - AvaloniaProperty.RegisterDirect>( + public static readonly DirectProperty?> ItemSelectorProperty = + AvaloniaProperty.RegisterDirect?>( nameof(ItemSelector), o => o.ItemSelector, (o, v) => o.ItemSelector = v); @@ -578,8 +578,8 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty> TextSelectorProperty = - AvaloniaProperty.RegisterDirect>( + public static readonly DirectProperty?> TextSelectorProperty = + AvaloniaProperty.RegisterDirect?>( nameof(TextSelector), o => o.TextSelector, (o, v) => o.TextSelector = v); @@ -592,14 +592,14 @@ namespace Avalonia.Controls /// The identifier for the /// /// dependency property. - public static readonly DirectProperty ItemsProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty ItemsProperty = + AvaloniaProperty.RegisterDirect( nameof(Items), o => o.Items, (o, v) => o.Items = v); - public static readonly DirectProperty>>> AsyncPopulatorProperty = - AvaloniaProperty.RegisterDirect>>>( + public static readonly DirectProperty>>?> AsyncPopulatorProperty = + AvaloniaProperty.RegisterDirect>>?>( nameof(AsyncPopulator), o => o.AsyncPopulator, (o, v) => o.AsyncPopulator = v); @@ -640,7 +640,7 @@ namespace Avalonia.Controls /// The event data. private void OnControlIsEnabledChanged(AvaloniaPropertyChangedEventArgs e) { - bool isEnabled = (bool)e.NewValue; + bool isEnabled = (bool)e.NewValue!; if (!isEnabled) { IsDropDownOpen = false; @@ -655,7 +655,7 @@ namespace Avalonia.Controls /// Event arguments. private void OnMinimumPopulateDelayChanged(AvaloniaPropertyChangedEventArgs e) { - var newValue = (TimeSpan)e.NewValue; + var newValue = (TimeSpan)e.NewValue!; // Stop any existing timer if (_delayTimer != null) @@ -695,8 +695,8 @@ namespace Avalonia.Controls return; } - bool oldValue = (bool)e.OldValue; - bool newValue = (bool)e.NewValue; + bool oldValue = (bool)e.OldValue!; + bool newValue = (bool)e.NewValue!; if (newValue) { @@ -750,7 +750,7 @@ namespace Avalonia.Controls /// Event arguments. private void OnTextPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - TextUpdated((string)e.NewValue, false); + TextUpdated((string?)e.NewValue, false); } private void OnSearchTextPropertyChanged(AvaloniaPropertyChangedEventArgs e) @@ -778,7 +778,7 @@ namespace Avalonia.Controls /// Event arguments. private void OnFilterModePropertyChanged(AvaloniaPropertyChangedEventArgs e) { - AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue; + AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue!; // Sets the filter predicate for the new value TextFilter = AutoCompleteSearch.GetFilter(mode); @@ -790,7 +790,7 @@ namespace Avalonia.Controls /// Event arguments. private void OnItemFilterPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - AutoCompleteFilterPredicate value = e.NewValue as AutoCompleteFilterPredicate; + var value = e.NewValue as AutoCompleteFilterPredicate; // If null, revert to the "None" predicate if (value == null) @@ -810,7 +810,7 @@ namespace Avalonia.Controls /// Event arguments. private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - OnItemsChanged((IEnumerable)e.NewValue); + OnItemsChanged((IEnumerable?)e.NewValue); } private void OnItemTemplatePropertyChanged(AvaloniaPropertyChangedEventArgs e) @@ -818,7 +818,7 @@ namespace Avalonia.Controls if (!_settingItemTemplateFromValueMemberBinding) _itemTemplateIsFromValueMemberBinding = false; } - private void OnValueMemberBindingChanged(IBinding value) + private void OnValueMemberBindingChanged(IBinding? value) { if(_itemTemplateIsFromValueMemberBinding) { @@ -828,7 +828,8 @@ namespace Avalonia.Controls (o, _) => { var control = new ContentControl(); - control.Bind(ContentControl.ContentProperty, value); + if (value is not null) + control.Bind(ContentControl.ContentProperty, value); return control; }); @@ -975,7 +976,7 @@ namespace Avalonia.Controls /// The object used /// when binding to a collection property. [AssignBinding] - public IBinding ValueMemberBinding + public IBinding? ValueMemberBinding { get { return _valueBindingEvaluator?.ValueBinding; } set @@ -998,7 +999,7 @@ namespace Avalonia.Controls /// then displayed in the text box, the SelectedItem property will be /// a null reference. /// - public object SelectedItem + public object? SelectedItem { get { return _selectedItem; } set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); } @@ -1010,7 +1011,7 @@ namespace Avalonia.Controls /// /// The text in the text box portion of the /// control. - public string Text + public string? Text { get { return _text; } set { SetAndRaise(TextProperty, ref _text, value); } @@ -1029,7 +1030,7 @@ namespace Avalonia.Controls /// Text property, but is set after the TextChanged event occurs /// and before the Populating event. /// - public string SearchText + public string? SearchText { get { return _searchText; } private set @@ -1071,7 +1072,7 @@ namespace Avalonia.Controls set { SetValue(FilterModeProperty, value); } } - public string Watermark + public string? Watermark { get { return GetValue(WatermarkProperty); } set { SetValue(WatermarkProperty, value); } @@ -1091,7 +1092,7 @@ namespace Avalonia.Controls /// The filter mode is automatically set to Custom if you set the /// ItemFilter property. /// - public AutoCompleteFilterPredicate ItemFilter + public AutoCompleteFilterPredicate? ItemFilter { get { return _itemFilter; } set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); } @@ -1111,7 +1112,7 @@ namespace Avalonia.Controls /// The search mode is automatically set to Custom if you set the /// TextFilter property. /// - public AutoCompleteFilterPredicate TextFilter + public AutoCompleteFilterPredicate? TextFilter { get { return _textFilter; } set { SetAndRaise(TextFilterProperty, ref _textFilter, value); } @@ -1127,7 +1128,7 @@ namespace Avalonia.Controls /// text and one of the items specified by the /// . /// - public AutoCompleteSelector ItemSelector + public AutoCompleteSelector? ItemSelector { get { return _itemSelector; } set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); } @@ -1145,13 +1146,13 @@ namespace Avalonia.Controls /// /// in a text-based way. /// - public AutoCompleteSelector TextSelector + public AutoCompleteSelector? TextSelector { get { return _textSelector; } set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); } } - public Func>> AsyncPopulator + public Func>>? AsyncPopulator { get { return _asyncPopulator; } set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); } @@ -1165,7 +1166,7 @@ namespace Avalonia.Controls /// The collection that is used to generate the items of the /// drop-down portion of the /// control. - public IEnumerable Items + public IEnumerable? Items { get { return _itemsEnumerable; } set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); } @@ -1174,12 +1175,12 @@ namespace Avalonia.Controls /// /// Gets or sets the drop down popup control. /// - private Popup DropDownPopup { get; set; } + private Popup? DropDownPopup { get; set; } /// /// Gets or sets the Text template part. /// - private TextBox TextBox + private TextBox? TextBox { get { return _textBox; } set @@ -1243,7 +1244,7 @@ namespace Avalonia.Controls /// use with AutoCompleteBox or deriving from AutoCompleteBox to /// create a custom control. /// - protected ISelectionAdapter SelectionAdapter + protected ISelectionAdapter? SelectionAdapter { get { return _adapter; } set @@ -1279,10 +1280,10 @@ namespace Avalonia.Controls /// A object, /// if possible. Otherwise, null. /// - protected virtual ISelectionAdapter GetSelectionAdapterPart(INameScope nameScope) + protected virtual ISelectionAdapter? GetSelectionAdapterPart(INameScope nameScope) { - ISelectionAdapter adapter = null; - SelectingItemsControl selector = nameScope.Find(ElementSelector); + ISelectionAdapter? adapter = null; + SelectingItemsControl? selector = nameScope.Find(ElementSelector); if (selector != null) { // Check if it is already an IItemsSelector @@ -1316,7 +1317,7 @@ namespace Avalonia.Controls // Set the template parts. Individual part setters remove and add // any event handlers. - Popup popup = e.NameScope.Find(ElementPopup); + Popup? popup = e.NameScope.Find(ElementPopup); if (popup != null) { DropDownPopup = popup; @@ -1358,7 +1359,7 @@ namespace Avalonia.Controls /// that contains the event data. protected override void OnKeyDown(KeyEventArgs e) { - Contract.Requires(e != null); + _ = e ?? throw new ArgumentNullException(nameof(e)); base.OnKeyDown(e); @@ -1453,7 +1454,7 @@ namespace Avalonia.Controls /// otherwise, false. protected bool HasFocus() { - IVisual focused = FocusManager.Instance.Current; + IVisual? focused = FocusManager.Instance?.Current; while (focused != null) { @@ -1464,11 +1465,11 @@ namespace Avalonia.Controls // This helps deal with popups that may not be in the same // visual tree - IVisual parent = focused.GetVisualParent(); + IVisual? parent = focused.GetVisualParent(); if (parent == null) { // Try the logical parent. - IControl element = focused as IControl; + IControl? element = focused as IControl; if (element != null) { parent = element.Parent; @@ -1519,7 +1520,7 @@ namespace Avalonia.Controls /// Occurs when the text in the text box portion of the /// changes. /// - public event EventHandler TextChanged; + public event EventHandler? TextChanged; /// /// Occurs when the @@ -1535,7 +1536,7 @@ namespace Avalonia.Controls /// In this case, if you want possible matches to appear, you must /// provide the logic for populating the selection adapter. /// - public event EventHandler Populating; + public event EventHandler? Populating; /// /// Occurs when the @@ -1544,35 +1545,35 @@ namespace Avalonia.Controls /// /// property. /// - public event EventHandler Populated; + public event EventHandler? Populated; /// /// Occurs when the value of the /// /// property is changing from false to true. /// - public event EventHandler DropDownOpening; + public event EventHandler? DropDownOpening; /// /// Occurs when the value of the /// /// property has changed from false to true and the drop-down is open. /// - public event EventHandler DropDownOpened; + public event EventHandler? DropDownOpened; /// /// Occurs when the /// /// property is changing from true to false. /// - public event EventHandler DropDownClosing; + public event EventHandler? DropDownClosing; /// /// Occurs when the /// /// property was changed from true to false and the drop-down is open. /// - public event EventHandler DropDownClosed; + public event EventHandler? DropDownClosed; /// /// Occurs when the selected item in the drop-down portion of the @@ -1740,7 +1741,7 @@ namespace Avalonia.Controls /// /// The source object. /// The event data. - private void DropDownPopup_Closed(object sender, EventArgs e) + private void DropDownPopup_Closed(object? sender, EventArgs e) { // Force the drop down dependency property to be false. if (IsDropDownOpen) @@ -1760,7 +1761,7 @@ namespace Avalonia.Controls /// /// The source object. /// The event arguments. - private void PopulateDropDown(object sender, EventArgs e) + private void PopulateDropDown(object? sender, EventArgs e) { if (_delayTimer != null) { @@ -1786,7 +1787,7 @@ namespace Avalonia.Controls PopulateComplete(); } } - private bool TryPopulateAsync(string searchText) + private bool TryPopulateAsync(string? searchText) { _populationCancellationTokenSource?.Cancel(false); _populationCancellationTokenSource?.Dispose(); @@ -1804,12 +1805,12 @@ namespace Avalonia.Controls return true; } - private async Task PopulateAsync(string searchText, CancellationToken cancellationToken) + private async Task PopulateAsync(string? searchText, CancellationToken cancellationToken) { try { - IEnumerable result = await _asyncPopulator.Invoke(searchText, cancellationToken); + IEnumerable result = await _asyncPopulator!.Invoke(searchText, cancellationToken); var resultList = result.ToList(); if (cancellationToken.IsCancellationRequested) @@ -1878,9 +1879,9 @@ namespace Avalonia.Controls /// A value indicating whether to clear /// the data context after the lookup is performed. /// Formatted Value. - private string FormatValue(object value, bool clearDataContext) + private string? FormatValue(object? value, bool clearDataContext) { - string result = FormatValue(value); + string? result = FormatValue(value); if(clearDataContext && _valueBindingEvaluator != null) { _valueBindingEvaluator.ClearDataContext(); @@ -1902,7 +1903,7 @@ namespace Avalonia.Controls /// /// Override this method to provide a custom string conversion. /// - protected virtual string FormatValue(object value) + protected virtual string? FormatValue(object? value) { if (_valueBindingEvaluator != null) { @@ -1923,7 +1924,7 @@ namespace Avalonia.Controls Dispatcher.UIThread.Post(() => { // Call the central updated text method as a user-initiated action - TextUpdated(_textBox.Text, true); + TextUpdated(_textBox!.Text, true); }); } @@ -1933,7 +1934,7 @@ namespace Avalonia.Controls /// text changed events when there is a change. /// /// The new string value. - private void UpdateTextValue(string value) + private void UpdateTextValue(string? value) { UpdateTextValue(value, null); } @@ -1949,7 +1950,7 @@ namespace Avalonia.Controls /// underlying text dependency property is updated. In a non-user /// interaction, the text box value is updated. When user initiated is /// null, all values are updated. - private void UpdateTextValue(string value, bool? userInitiated) + private void UpdateTextValue(string? value, bool? userInitiated) { bool callTextChanged = false; // Update the Text dependency property @@ -1987,7 +1988,7 @@ namespace Avalonia.Controls /// A value indicating whether the update /// is a user-initiated action. This should be a True value when the /// TextUpdated method is called from a TextBox event handler. - private void TextUpdated(string newText, bool userInitiated) + private void TextUpdated(string? newText, bool userInitiated) { // Only process this event if it is coming from someone outside // setting the Text dependency property directly. @@ -2087,7 +2088,7 @@ namespace Avalonia.Controls bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null; int view_index = 0; - int view_count = _view.Count; + int view_count = _view!.Count; List items = _items; foreach (object item in items) { @@ -2096,7 +2097,7 @@ namespace Avalonia.Controls { if (stringFiltering) { - inResults = TextFilter(text, FormatValue(item)); + inResults = TextFilter!(text, FormatValue(item)); } else { @@ -2166,7 +2167,7 @@ namespace Avalonia.Controls /// adapter's ItemsSource to the view if appropriate. /// /// The new enumerable reference. - private void OnItemsChanged(IEnumerable newValue) + private void OnItemsChanged(IEnumerable? newValue) { // Remove handler for oldValue.CollectionChanged (if present) _collectionChangeSubscription?.Dispose(); @@ -2198,28 +2199,28 @@ namespace Avalonia.Controls /// /// The object that raised the event. /// The event data. - private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { // Update the cache if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) { for (int index = 0; index < e.OldItems.Count; index++) { - _items.RemoveAt(e.OldStartingIndex); + _items!.RemoveAt(e.OldStartingIndex); } } - if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items.Count >= e.NewStartingIndex) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items!.Count >= e.NewStartingIndex) { for (int index = 0; index < e.NewItems.Count; index++) { - _items.Insert(e.NewStartingIndex + index, e.NewItems[index]); + _items.Insert(e.NewStartingIndex + index, e.NewItems[index]!); } } if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null) { for (int index = 0; index < e.NewItems.Count; index++) { - _items[e.NewStartingIndex] = e.NewItems[index]; + _items![e.NewStartingIndex] = e.NewItems[index]!; } } @@ -2228,7 +2229,7 @@ namespace Avalonia.Controls { for (int index = 0; index < e.OldItems.Count; index++) { - _view.Remove(e.OldItems[index]); + _view!.Remove(e.OldItems[index]!); } } @@ -2270,7 +2271,7 @@ namespace Avalonia.Controls RefreshView(); // Fire the Populated event containing the read-only view data. - PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection(_view)); + PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection(_view!)); OnPopulated(populated); if (SelectionAdapter != null && SelectionAdapter.Items != _view) @@ -2278,7 +2279,7 @@ namespace Avalonia.Controls SelectionAdapter.Items = _view; } - bool isDropDownOpen = _userCalledPopulate && (_view.Count > 0); + bool isDropDownOpen = _userCalledPopulate && (_view!.Count > 0); if (isDropDownOpen != IsDropDownOpen) { _ignorePropertyChange = true; @@ -2306,20 +2307,20 @@ namespace Avalonia.Controls private void UpdateTextCompletion(bool userInitiated) { // By default this method will clear the selected value - object newSelectedItem = null; - string text = Text; + object? newSelectedItem = null; + string? text = Text; // Text search is StartsWith explicit and only when enabled, in // line with WPF's ComboBox lookup. When in use it will associate // a Value with the Text if it is found in ItemsSource. This is // only valid when there is data and the user initiated the action. - if (_view.Count > 0) + if (_view!.Count > 0) { if (IsTextCompletionEnabled && TextBox != null && userInitiated) { int currentLength = TextBox.Text?.Length ?? 0; int selectionStart = TextBoxSelectionStart; - if (selectionStart == text.Length && selectionStart > _textSelectionStart) + if (selectionStart == text?.Length && selectionStart > _textSelectionStart) { // When the FilterMode dependency property is set to // either StartsWith or StartsWithCaseSensitive, the @@ -2327,7 +2328,7 @@ namespace Avalonia.Controls // performance on the lookup. It assumes that the // FilterMode the user has selected is an acceptable // case sensitive matching function for their scenario. - object top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive + object? top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive ? _view[0] : TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); @@ -2335,18 +2336,18 @@ namespace Avalonia.Controls if (top != null) { newSelectedItem = top; - string topString = FormatValue(top, true); + string? topString = FormatValue(top, true); // Only replace partially when the two words being the same - int minLength = Math.Min(topString.Length, Text.Length); - if (AutoCompleteSearch.Equals(Text.Substring(0, minLength), topString.Substring(0, minLength))) + int minLength = Math.Min(topString?.Length ?? 0, Text?.Length ?? 0); + if (AutoCompleteSearch.Equals(Text?.Substring(0, minLength), topString?.Substring(0, minLength))) { // Update the text UpdateTextValue(topString); // Select the text past the user's caret TextBox.SelectionStart = currentLength; - TextBox.SelectionEnd = topString.Length; + TextBox.SelectionEnd = topString?.Length ?? 0; } } } @@ -2392,8 +2393,11 @@ namespace Avalonia.Controls /// The predicate to use for the partial or /// exact match. /// Returns the object or null. - private object TryGetMatch(string searchText, AvaloniaList view, AutoCompleteFilterPredicate predicate) + private object? TryGetMatch(string? searchText, AvaloniaList view, AutoCompleteFilterPredicate? predicate) { + if (predicate is null) + return null; + if (view != null && view.Count > 0) { foreach (object o in view) @@ -2428,9 +2432,9 @@ namespace Avalonia.Controls /// that is displayed in the text box part. /// /// The new item. - private void OnSelectedItemChanged(object newItem) + private void OnSelectedItemChanged(object? newItem) { - string text; + string? text; if (newItem == null) { @@ -2461,9 +2465,9 @@ namespace Avalonia.Controls /// /// The source object. /// The selection changed event data. - private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e) + private void OnAdapterSelectionChanged(object? sender, SelectionChangedEventArgs e) { - SelectedItem = _adapter.SelectedItem; + SelectedItem = _adapter!.SelectedItem; } //TODO Check UpdateTextCompletion @@ -2472,7 +2476,7 @@ namespace Avalonia.Controls /// /// The source object. /// The event data. - private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e) + private void OnAdapterSelectionComplete(object? sender, RoutedEventArgs e) { IsDropDownOpen = false; @@ -2482,7 +2486,7 @@ namespace Avalonia.Controls // Text should not be selected ClearTextBoxSelection(); - TextBox.Focus(); + TextBox!.Focus(); } /// @@ -2490,7 +2494,7 @@ namespace Avalonia.Controls /// /// The source object. /// The event data. - private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e) + private void OnAdapterSelectionCanceled(object? sender, RoutedEventArgs e) { UpdateTextValue(SearchText); @@ -2510,7 +2514,7 @@ namespace Avalonia.Controls /// /// The built-in search mode. /// Returns the string-based comparison function. - public static AutoCompleteFilterPredicate GetFilter(AutoCompleteFilterMode FilterMode) + public static AutoCompleteFilterPredicate? GetFilter(AutoCompleteFilterMode FilterMode) { switch (FilterMode) { @@ -2566,9 +2570,11 @@ namespace Avalonia.Controls /// The string value to search for. /// The string comparison type. /// Returns true when the substring is found. - private static bool Contains(string s, string value, StringComparison comparison) + private static bool Contains(string? s, string? value, StringComparison comparison) { - return s.IndexOf(value, comparison) >= 0; + if (s is not null && value is not null) + return s.IndexOf(value, comparison) >= 0; + return false; } /// @@ -2577,9 +2583,11 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool StartsWith(string text, string value) + public static bool StartsWith(string? text, string? value) { - return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase); + if (value is not null && text is not null) + return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase); + return false; } /// @@ -2588,9 +2596,11 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool StartsWithCaseSensitive(string text, string value) + public static bool StartsWithCaseSensitive(string? text, string? value) { - return value.StartsWith(text, StringComparison.CurrentCulture); + if (value is not null && text is not null) + return value.StartsWith(text, StringComparison.CurrentCulture); + return false; } /// @@ -2599,9 +2609,11 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool StartsWithOrdinal(string text, string value) + public static bool StartsWithOrdinal(string? text, string? value) { - return value.StartsWith(text, StringComparison.OrdinalIgnoreCase); + if (value is not null && text is not null) + return value.StartsWith(text, StringComparison.OrdinalIgnoreCase); + return false; } /// @@ -2610,9 +2622,11 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool StartsWithOrdinalCaseSensitive(string text, string value) + public static bool StartsWithOrdinalCaseSensitive(string? text, string? value) { - return value.StartsWith(text, StringComparison.Ordinal); + if (value is not null && text is not null) + return value.StartsWith(text, StringComparison.Ordinal); + return false; } /// @@ -2622,7 +2636,7 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool Contains(string text, string value) + public static bool Contains(string? text, string? value) { return Contains(value, text, StringComparison.CurrentCultureIgnoreCase); } @@ -2633,7 +2647,7 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool ContainsCaseSensitive(string text, string value) + public static bool ContainsCaseSensitive(string? text, string? value) { return Contains(value, text, StringComparison.CurrentCulture); } @@ -2644,7 +2658,7 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool ContainsOrdinal(string text, string value) + public static bool ContainsOrdinal(string? text, string? value) { return Contains(value, text, StringComparison.OrdinalIgnoreCase); } @@ -2655,7 +2669,7 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool ContainsOrdinalCaseSensitive(string text, string value) + public static bool ContainsOrdinalCaseSensitive(string? text, string? value) { return Contains(value, text, StringComparison.Ordinal); } @@ -2666,9 +2680,9 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool Equals(string text, string value) + public static bool Equals(string? text, string? value) { - return value.Equals(text, StringComparison.CurrentCultureIgnoreCase); + return string.Equals(value, text, StringComparison.CurrentCultureIgnoreCase); } /// @@ -2677,9 +2691,9 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool EqualsCaseSensitive(string text, string value) + public static bool EqualsCaseSensitive(string? text, string? value) { - return value.Equals(text, StringComparison.CurrentCulture); + return string.Equals(value, text, StringComparison.CurrentCulture); } /// @@ -2688,9 +2702,9 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool EqualsOrdinal(string text, string value) + public static bool EqualsOrdinal(string? text, string? value) { - return value.Equals(text, StringComparison.OrdinalIgnoreCase); + return string.Equals(value, text, StringComparison.OrdinalIgnoreCase); } /// @@ -2699,9 +2713,9 @@ namespace Avalonia.Controls /// The AutoCompleteBox prefix text. /// The item's string value. /// Returns true if the condition is met. - public static bool EqualsOrdinalCaseSensitive(string text, string value) + public static bool EqualsOrdinalCaseSensitive(string? text, string? value) { - return value.Equals(text, StringComparison.Ordinal); + return string.Equals(value, text, StringComparison.Ordinal); } } @@ -2715,7 +2729,7 @@ namespace Avalonia.Controls /// /// Gets or sets the string value binding used by the control. /// - private IBinding _binding; + private IBinding? _binding; #region public T Value @@ -2739,13 +2753,14 @@ namespace Avalonia.Controls /// /// Gets or sets the value binding. /// - public IBinding ValueBinding + public IBinding? ValueBinding { get { return _binding; } set { _binding = value; - AvaloniaObjectExtensions.Bind(this, ValueProperty, value); + if (value is not null) + AvaloniaObjectExtensions.Bind(this, ValueProperty, value); } } @@ -2760,7 +2775,7 @@ namespace Avalonia.Controls /// setting the initial binding to the provided parameter. /// /// The initial string value binding. - public BindingEvaluator(IBinding binding) + public BindingEvaluator(IBinding? binding) : this() { ValueBinding = binding; @@ -2802,7 +2817,7 @@ namespace Avalonia.Controls /// The object to use as the data context. /// Returns the evaluated T value of the bound dependency /// property. - public T GetDynamicValue(object o) + public T GetDynamicValue(object? o) { DataContext = o; return Value; diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index e2c6a714aa..543a513d57 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -18,4 +18,5 @@ + diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index ee67f303f3..ee3be1d5b3 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -17,14 +17,14 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty BackgroundProperty = - AvaloniaProperty.Register(nameof(Background)); + public static readonly StyledProperty BackgroundProperty = + AvaloniaProperty.Register(nameof(Background)); /// /// Defines the property. /// - public static readonly StyledProperty BorderBrushProperty = - AvaloniaProperty.Register(nameof(BorderBrush)); + public static readonly StyledProperty BorderBrushProperty = + AvaloniaProperty.Register(nameof(BorderBrush)); /// /// Defines the property. @@ -91,7 +91,7 @@ namespace Avalonia.Controls /// /// Gets or sets a brush with which to paint the background. /// - public IBrush Background + public IBrush? Background { get { return GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } @@ -100,7 +100,7 @@ namespace Avalonia.Controls /// /// Gets or sets a brush with which to paint the border. /// - public IBrush BorderBrush + public IBrush? BorderBrush { get { return GetValue(BorderBrushProperty); } set { SetValue(BorderBrushProperty, value); } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 8537c9acbc..a2efc7fba0 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -42,30 +42,30 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly DirectProperty CommandProperty = - AvaloniaProperty.RegisterDirect(nameof(Command), + public static readonly DirectProperty CommandProperty = + AvaloniaProperty.RegisterDirect(nameof(Command), button => button.Command, (button, command) => button.Command = command, enableDataValidation: true); /// /// Defines the property. /// - public static readonly StyledProperty HotKeyProperty = + public static readonly StyledProperty HotKeyProperty = HotKeyManager.HotKeyProperty.AddOwner