diff --git a/Avalonia.sln b/Avalonia.sln index 74a2dbb94b..75f1dd8407 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -230,6 +230,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroComGenerator", "src\to EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2116,6 +2118,30 @@ Global {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhone.Build.0 = Release|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhone.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhone.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|Any CPU.Build.0 = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhone.ActiveCfg = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhone.Build.0 = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2176,6 +2202,7 @@ Global {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/build/EmbedXaml.props b/build/EmbedXaml.props index 7ce0366dea..0bb8da4f47 100644 --- a/build/EmbedXaml.props +++ b/build/EmbedXaml.props @@ -4,8 +4,9 @@ %(Filename) + Designer - \ No newline at end of file + diff --git a/build/SharedVersion.props b/build/SharedVersion.props index a5c0aa1bea..43ec995ed9 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -16,7 +16,7 @@ https://github.com/AvaloniaUI/Avalonia/releases git $(MSBuildThisFileDirectory)\avalonia.snk - false + true $(DefineConstants);SIGNED_BUILD diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index eb2da03a7e..8a5364c70b 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; namespace BindingDemo { @@ -25,7 +24,6 @@ namespace BindingDemo public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .UseReactiveUI() .LogToTrace(); } } diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index 817023fd71..d898b737a9 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -6,12 +6,11 @@ - + - diff --git a/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs b/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs index 0fe12a8ef7..7de083351e 100644 --- a/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs +++ b/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs @@ -1,9 +1,9 @@ -using ReactiveUI; +using MiniMvvm; using System; namespace BindingDemo.ViewModels { - public class ExceptionErrorViewModel : ReactiveObject + public class ExceptionErrorViewModel : ViewModelBase { private int _lessThan10; diff --git a/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs b/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs index caf75c846c..9ae8d9558f 100644 --- a/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs +++ b/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs @@ -1,11 +1,11 @@ -using ReactiveUI; +using MiniMvvm; using System; using System.ComponentModel; using System.Collections; namespace BindingDemo.ViewModels { - public class IndeiErrorViewModel : ReactiveObject, INotifyDataErrorInfo + public class IndeiErrorViewModel : ViewModelBase, INotifyDataErrorInfo { private int _maximum = 10; private int _value; diff --git a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs index f0241cad48..18a7a01a69 100644 --- a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs @@ -5,14 +5,14 @@ using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using System.Threading; -using ReactiveUI; +using MiniMvvm; using Avalonia.Controls; using Avalonia.Metadata; using Avalonia.Controls.Selection; namespace BindingDemo.ViewModels { - public class MainWindowViewModel : ReactiveObject + public class MainWindowViewModel : ViewModelBase { private string _booleanString = "True"; private double _doubleValue = 5.0; @@ -32,13 +32,13 @@ namespace BindingDemo.ViewModels Selection = new SelectionModel { SingleSelect = false }; - ShuffleItems = ReactiveCommand.Create(() => + ShuffleItems = MiniCommand.Create(() => { var r = new Random(); Items.Move(r.Next(Items.Count), 1); }); - StringValueCommand = ReactiveCommand.Create(param => + StringValueCommand = MiniCommand.Create(param => { BooleanFlag = !BooleanFlag; StringValue = param.ToString(); @@ -60,7 +60,7 @@ namespace BindingDemo.ViewModels public ObservableCollection Items { get; } public SelectionModel Selection { get; } - public ReactiveCommand ShuffleItems { get; } + public MiniCommand ShuffleItems { get; } public string BooleanString { @@ -93,7 +93,7 @@ namespace BindingDemo.ViewModels } public IObservable CurrentTimeObservable { get; } - public ReactiveCommand StringValueCommand { get; } + public MiniCommand StringValueCommand { get; } public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel(); diff --git a/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs b/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs index 0e9139ab98..1c2222b0b0 100644 --- a/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs +++ b/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs @@ -1,18 +1,18 @@ -using ReactiveUI; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; +using MiniMvvm; namespace BindingDemo.ViewModels { - public class NestedCommandViewModel : ReactiveObject + public class NestedCommandViewModel : ViewModelBase { public NestedCommandViewModel() { - Command = ReactiveCommand.Create(() => { }); + Command = MiniCommand.Create(() => { }); } public ICommand Command { get; } diff --git a/samples/BindingDemo/ViewModels/TestItem.cs b/samples/BindingDemo/ViewModels/TestItem.cs index 5a9f192f58..2f49a3c99f 100644 --- a/samples/BindingDemo/ViewModels/TestItem.cs +++ b/samples/BindingDemo/ViewModels/TestItem.cs @@ -1,8 +1,8 @@ -using ReactiveUI; +using MiniMvvm; namespace BindingDemo.ViewModels { - public class TestItem : ReactiveObject + public class TestItem : ViewModelBase { private string _stringValue = "String Value"; private string _detail; diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 5af646b180..7b8b27fff7 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -3,7 +3,6 @@ using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Platform; -using Avalonia.ReactiveUI; namespace ControlCatalog { @@ -19,8 +18,7 @@ namespace ControlCatalog public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .LogToTrace() - .UsePlatformDetect() - .UseReactiveUI(); + .UsePlatformDetect(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 675ea2e10f..1dc8c09c0e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -10,7 +10,6 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Dialogs; using Avalonia.Headless; using Avalonia.LogicalTree; -using Avalonia.ReactiveUI; using Avalonia.Threading; namespace ControlCatalog.NetCore @@ -118,7 +117,6 @@ namespace ControlCatalog.NetCore AllowEglInitialization = true }) .UseSkia() - .UseReactiveUI() .UseManagedSystemDialogs() .LogToTrace(); diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 22f4e9be1f..020fb2fff3 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -23,7 +23,7 @@ namespace ControlCatalog { new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentDark.xaml") + Source = new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml") }, DataGridFluent }; @@ -32,7 +32,7 @@ namespace ControlCatalog { new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentLight.xaml") + Source = new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml") }, DataGridFluent }; diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 8a88b89b48..1aa926a2a6 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -24,8 +24,8 @@ - + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index a316321cd1..723351ae57 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -67,7 +67,7 @@ namespace ControlCatalog if (Application.Current.Styles.Contains(App.FluentDark) || Application.Current.Styles.Contains(App.FluentLight)) { - var theme = new Avalonia.Themes.Fluent.FluentTheme(); + var theme = new Avalonia.Themes.Fluent.Controls.FluentControls(); theme.TryGetResource("Button", out _); } else diff --git a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs index b8503d6ae6..a14978fd2c 100644 --- a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs +++ b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs @@ -2,7 +2,6 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; using ControlCatalog.Models; -using ReactiveUI; namespace ControlCatalog.Pages { diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 3521ad71a9..f515db84d4 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -20,6 +20,6 @@ + SelectionMode="{Binding SelectionMode^}"/> diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 46dbe3dcad..5999510b6c 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -6,7 +6,6 @@ using System.Windows.Input; using Avalonia.Controls; using Avalonia.Markup.Xaml; using ControlCatalog.ViewModels; -using ReactiveUI; namespace ControlCatalog.Pages { diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs index 92da64d87e..31749edf08 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs @@ -6,7 +6,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Markup.Xaml; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.Pages { @@ -26,7 +26,7 @@ namespace ControlCatalog.Pages } - public class NumbersPageViewModel : ReactiveObject + public class NumbersPageViewModel : ViewModelBase { private IList _formats; private FormatObject _selectedFormat; diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs index 36d3768b13..dcd7a88a56 100644 --- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Markup.Xaml; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.Pages { - public class ScrollViewerPageViewModel : ReactiveObject + public class ScrollViewerPageViewModel : ViewModelBase { private bool _allowAutoHide; private ScrollBarVisibility _horizontalScrollVisibility; diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index a38a3ab4cb..f49b13091b 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -6,7 +6,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.Pages { @@ -56,7 +56,7 @@ namespace ControlCatalog.Pages return new Bitmap(assets.Open(new Uri(uri))); } - private class PageViewModel : ReactiveObject + private class PageViewModel : ViewModelBase { private Dock _tabPlacement; diff --git a/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs index 5c2f74d2d5..3f5d0cd93c 100644 --- a/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs @@ -3,7 +3,7 @@ using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { @@ -12,9 +12,9 @@ namespace ControlCatalog.ViewModels public Control View { get; set; } public ContextMenuPageViewModel() { - OpenCommand = ReactiveCommand.CreateFromTask(Open); - SaveCommand = ReactiveCommand.Create(Save); - OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + OpenCommand = MiniCommand.CreateFromTask(Open); + SaveCommand = MiniCommand.Create(Save); + OpenRecentCommand = MiniCommand.Create(OpenRecent); MenuItems = new[] { @@ -44,9 +44,9 @@ namespace ControlCatalog.ViewModels } public IReadOnlyList MenuItems { get; set; } - public ReactiveCommand OpenCommand { get; } - public ReactiveCommand SaveCommand { get; } - public ReactiveCommand OpenRecentCommand { get; } + public MiniCommand OpenCommand { get; } + public MiniCommand SaveCommand { get; } + public MiniCommand OpenRecentCommand { get; } public async Task Open() { diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index f893a6e28e..ee1fa6ae77 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -2,11 +2,11 @@ using System.Collections.ObjectModel; using System.Linq; using Avalonia.Media; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class ItemsRepeaterPageViewModel : ReactiveObject + public class ItemsRepeaterPageViewModel : ViewModelBase { private int _newItemIndex = 1; private int _newGenerationIndex = 0; @@ -59,7 +59,7 @@ namespace ControlCatalog.ViewModels })); } - public class Item : ReactiveObject + public class Item : ViewModelBase { private double _height = double.NaN; diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index f75bc32105..7f2d6e9572 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -4,18 +4,18 @@ using System.Linq; using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class ListBoxPageViewModel : ReactiveObject + public class ListBoxPageViewModel : ViewModelBase { private bool _multiple; private bool _toggle; private bool _alwaysSelected; private bool _autoScrollToSelectedItem = true; private int _counter; - private ObservableAsPropertyHelper _selectionMode; + private IObservable _selectionMode; public ListBoxPageViewModel() { @@ -29,14 +29,13 @@ namespace ControlCatalog.ViewModels x => x.Toggle, x => x.AlwaysSelected, (m, t, a) => - (m ? SelectionMode.Multiple : 0) | - (t ? SelectionMode.Toggle : 0) | - (a ? SelectionMode.AlwaysSelected : 0)) - .ToProperty(this, x => x.SelectionMode); + (m ? Avalonia.Controls.SelectionMode.Multiple : 0) | + (t ? Avalonia.Controls.SelectionMode.Toggle : 0) | + (a ? Avalonia.Controls.SelectionMode.AlwaysSelected : 0)); - AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem())); + AddItemCommand = MiniCommand.Create(() => Items.Add(GenerateItem())); - RemoveItemCommand = ReactiveCommand.Create(() => + RemoveItemCommand = MiniCommand.Create(() => { var items = Selection.SelectedItems.ToList(); @@ -46,7 +45,7 @@ namespace ControlCatalog.ViewModels } }); - SelectRandomItemCommand = ReactiveCommand.Create(() => + SelectRandomItemCommand = MiniCommand.Create(() => { var random = new Random(); @@ -60,7 +59,7 @@ namespace ControlCatalog.ViewModels public ObservableCollection Items { get; } public SelectionModel Selection { get; } - public SelectionMode SelectionMode => _selectionMode.Value; + public IObservable SelectionMode => _selectionMode; public bool Multiple { @@ -86,9 +85,9 @@ namespace ControlCatalog.ViewModels set => this.RaiseAndSetIfChanged(ref _autoScrollToSelectedItem, value); } - public ReactiveCommand AddItemCommand { get; } - public ReactiveCommand RemoveItemCommand { get; } - public ReactiveCommand SelectRandomItemCommand { get; } + public MiniCommand AddItemCommand { get; } + public MiniCommand RemoveItemCommand { get; } + public MiniCommand SelectRandomItemCommand { get; } private string GenerateItem() => $"Item {_counter++.ToString()}"; } diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 4356a032fa..4b3cfa9c9d 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -5,11 +5,11 @@ using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; using System; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - class MainWindowViewModel : ReactiveObject + class MainWindowViewModel : ViewModelBase { private IManagedNotificationManager _notificationManager; @@ -27,22 +27,22 @@ namespace ControlCatalog.ViewModels { _notificationManager = notificationManager; - ShowCustomManagedNotificationCommand = ReactiveCommand.Create(() => + ShowCustomManagedNotificationCommand = MiniCommand.Create(() => { NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" }); }); - ShowManagedNotificationCommand = ReactiveCommand.Create(() => + ShowManagedNotificationCommand = MiniCommand.Create(() => { NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information)); }); - ShowNativeNotificationCommand = ReactiveCommand.Create(() => + ShowNativeNotificationCommand = MiniCommand.Create(() => { NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error)); }); - AboutCommand = ReactiveCommand.CreateFromTask(async () => + AboutCommand = MiniCommand.CreateFromTask(async () => { var dialog = new AboutAvaloniaDialog(); @@ -51,12 +51,12 @@ namespace ControlCatalog.ViewModels await dialog.ShowDialog(mainWindow); }); - ExitCommand = ReactiveCommand.Create(() => + ExitCommand = MiniCommand.Create(() => { (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); }); - ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() => + ToggleMenuItemCheckedCommand = MiniCommand.Create(() => { IsMenuItemChecked = !IsMenuItemChecked; }); @@ -153,16 +153,16 @@ namespace ControlCatalog.ViewModels set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); } } - public ReactiveCommand ShowCustomManagedNotificationCommand { get; } + public MiniCommand ShowCustomManagedNotificationCommand { get; } - public ReactiveCommand ShowManagedNotificationCommand { get; } + public MiniCommand ShowManagedNotificationCommand { get; } - public ReactiveCommand ShowNativeNotificationCommand { get; } + public MiniCommand ShowNativeNotificationCommand { get; } - public ReactiveCommand AboutCommand { get; } + public MiniCommand AboutCommand { get; } - public ReactiveCommand ExitCommand { get; } + public MiniCommand ExitCommand { get; } - public ReactiveCommand ToggleMenuItemCheckedCommand { get; } + public MiniCommand ToggleMenuItemCheckedCommand { get; } } } diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index 9e7ae8b716..ecbd59c5d7 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -4,7 +4,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { @@ -13,9 +13,9 @@ namespace ControlCatalog.ViewModels public Control View { get; set; } public MenuPageViewModel() { - OpenCommand = ReactiveCommand.CreateFromTask(Open); - SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false)); - OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + OpenCommand = MiniCommand.CreateFromTask(Open); + SaveCommand = MiniCommand.Create(Save); + OpenRecentCommand = MiniCommand.Create(OpenRecent); var recentItems = new[] { @@ -65,9 +65,9 @@ namespace ControlCatalog.ViewModels public IReadOnlyList MenuItems { get; set; } public IReadOnlyList RecentItems { get; set; } - public ReactiveCommand OpenCommand { get; } - public ReactiveCommand SaveCommand { get; } - public ReactiveCommand OpenRecentCommand { get; } + public MiniCommand OpenCommand { get; } + public MiniCommand SaveCommand { get; } + public MiniCommand OpenRecentCommand { get; } public async Task Open() { diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs index 8724ba344b..2052481015 100644 --- a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs +++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs @@ -1,6 +1,6 @@ using System.Reactive; using Avalonia.Controls.Notifications; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { @@ -8,12 +8,12 @@ namespace ControlCatalog.ViewModels { public NotificationViewModel(INotificationManager manager) { - YesCommand = ReactiveCommand.Create(() => + YesCommand = MiniCommand.Create(() => { manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today.")); }); - NoCommand = ReactiveCommand.Create(() => + NoCommand = MiniCommand.Create(() => { manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit...")); }); @@ -22,9 +22,9 @@ namespace ControlCatalog.ViewModels public string Title { get; set; } public string Message { get; set; } - public ReactiveCommand YesCommand { get; } + public MiniCommand YesCommand { get; } - public ReactiveCommand NoCommand { get; } + public MiniCommand NoCommand { get; } } } diff --git a/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs index f27f605a8b..9e6932bb76 100644 --- a/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs @@ -1,10 +1,10 @@ using System; using Avalonia.Controls; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class SplitViewPageViewModel : ReactiveObject + public class SplitViewPageViewModel : ViewModelBase { private bool _isLeft = true; private int _displayMode = 3; //CompactOverlay diff --git a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs index 210e281ed6..c03379330f 100644 --- a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs @@ -3,11 +3,11 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reactive; using Avalonia.Controls; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class TreeViewPageViewModel : ReactiveObject + public class TreeViewPageViewModel : ViewModelBase { private readonly Node _root; private SelectionMode _selectionMode; @@ -19,16 +19,16 @@ namespace ControlCatalog.ViewModels Items = _root.Children; SelectedItems = new ObservableCollection(); - AddItemCommand = ReactiveCommand.Create(AddItem); - RemoveItemCommand = ReactiveCommand.Create(RemoveItem); - SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem); + AddItemCommand = MiniCommand.Create(AddItem); + RemoveItemCommand = MiniCommand.Create(RemoveItem); + SelectRandomItemCommand = MiniCommand.Create(SelectRandomItem); } public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } - public ReactiveCommand AddItemCommand { get; } - public ReactiveCommand RemoveItemCommand { get; } - public ReactiveCommand SelectRandomItemCommand { get; } + public MiniCommand AddItemCommand { get; } + public MiniCommand RemoveItemCommand { get; } + public MiniCommand SelectRandomItemCommand { get; } public SelectionMode SelectionMode { diff --git a/samples/MiniMvvm/MiniCommand.cs b/samples/MiniMvvm/MiniCommand.cs new file mode 100644 index 0000000000..c6a9273c20 --- /dev/null +++ b/samples/MiniMvvm/MiniCommand.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MiniMvvm +{ + public sealed class MiniCommand : MiniCommand, ICommand + { + private readonly Action _cb; + private bool _busy; + private Func _acb; + + public MiniCommand(Action cb) + { + _cb = cb; + } + + public MiniCommand(Func cb) + { + _acb = cb; + } + + private bool Busy + { + get => _busy; + set + { + _busy = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + + public override event EventHandler CanExecuteChanged; + public override bool CanExecute(object parameter) => !_busy; + + public override async void Execute(object parameter) + { + if(Busy) + return; + try + { + Busy = true; + if (_cb != null) + _cb((T)parameter); + else + await _acb((T)parameter); + } + finally + { + Busy = false; + } + } + } + + public abstract class MiniCommand : ICommand + { + public static MiniCommand Create(Action cb) => new MiniCommand(_ => cb()); + public static MiniCommand Create(Action cb) => new MiniCommand(cb); + public static MiniCommand CreateFromTask(Func cb) => new MiniCommand(_ => cb()); + + public abstract bool CanExecute(object parameter); + public abstract void Execute(object parameter); + public abstract event EventHandler CanExecuteChanged; + } +} diff --git a/samples/MiniMvvm/MiniMvvm.csproj b/samples/MiniMvvm/MiniMvvm.csproj new file mode 100644 index 0000000000..6535b2bdbd --- /dev/null +++ b/samples/MiniMvvm/MiniMvvm.csproj @@ -0,0 +1,6 @@ + + + netstandard2.0 + + + diff --git a/samples/MiniMvvm/PropertyChangedExtensions.cs b/samples/MiniMvvm/PropertyChangedExtensions.cs new file mode 100644 index 0000000000..f1065c7530 --- /dev/null +++ b/samples/MiniMvvm/PropertyChangedExtensions.cs @@ -0,0 +1,108 @@ +using System; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Reactive.Linq; +using System.Reflection; + +namespace MiniMvvm +{ + public static class PropertyChangedExtensions + { + class PropertyObservable : IObservable + { + private readonly INotifyPropertyChanged _target; + private readonly PropertyInfo _info; + + public PropertyObservable(INotifyPropertyChanged target, PropertyInfo info) + { + _target = target; + _info = info; + } + + class Subscription : IDisposable + { + private readonly INotifyPropertyChanged _target; + private readonly PropertyInfo _info; + private readonly IObserver _observer; + + public Subscription(INotifyPropertyChanged target, PropertyInfo info, IObserver observer) + { + _target = target; + _info = info; + _observer = observer; + _target.PropertyChanged += OnPropertyChanged; + _observer.OnNext((T)_info.GetValue(_target)); + } + + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == _info.Name) + _observer.OnNext((T)_info.GetValue(_target)); + } + + public void Dispose() + { + _target.PropertyChanged -= OnPropertyChanged; + _observer.OnCompleted(); + } + } + + public IDisposable Subscribe(IObserver observer) + { + return new Subscription(_target, _info, observer); + } + } + + public static IObservable WhenAnyValue(this TModel model, + Expression> expr) where TModel : INotifyPropertyChanged + { + var l = (LambdaExpression)expr; + var ma = (MemberExpression)l.Body; + var prop = (PropertyInfo)ma.Member; + return new PropertyObservable(model, prop); + } + + public static IObservable WhenAnyValue(this TModel model, + Expression> v1, + Func cb + ) where TModel : INotifyPropertyChanged + { + return model.WhenAnyValue(v1).Select(cb); + } + + public static IObservable WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2, + Func cb + ) where TModel : INotifyPropertyChanged => + Observable.CombineLatest( + model.WhenAnyValue(v1), + model.WhenAnyValue(v2), + cb); + + public static IObservable> WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2 + ) where TModel : INotifyPropertyChanged => + model.WhenAnyValue(v1, v2, (a1, a2) => (a1, a2)); + + public static IObservable WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2, + Expression> v3, + Func cb + ) where TModel : INotifyPropertyChanged => + Observable.CombineLatest( + model.WhenAnyValue(v1), + model.WhenAnyValue(v2), + model.WhenAnyValue(v3), + cb); + + public static IObservable> WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2, + Expression> v3 + ) where TModel : INotifyPropertyChanged => + model.WhenAnyValue(v1, v2, v3, (a1, a2, a3) => (a1, a2, a3)); + } +} diff --git a/samples/MiniMvvm/ViewModelBase.cs b/samples/MiniMvvm/ViewModelBase.cs new file mode 100644 index 0000000000..7256b05cef --- /dev/null +++ b/samples/MiniMvvm/ViewModelBase.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Reactive.Joins; +using System.Runtime.CompilerServices; + +namespace MiniMvvm +{ + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (!EqualityComparer.Default.Equals(field, value)) + { + field = value; + RaisePropertyChanged(propertyName); + return true; + } + return false; + } + + + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index cd3daf61e1..cfedb7ad9e 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -8,7 +8,6 @@ %(Filename) - diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index 61e4d2385b..7cdcea2e1d 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="RenderDemo.App"> - + diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index e6ec963d75..8054b06964 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; namespace RenderDemo { @@ -32,7 +31,6 @@ namespace RenderDemo OverlayPopups = true, }) .UsePlatformDetect() - .UseReactiveUI() .LogToTrace(); } } diff --git a/samples/RenderDemo/MainWindow.xaml.cs b/samples/RenderDemo/MainWindow.xaml.cs index b45a605e04..877eb8016a 100644 --- a/samples/RenderDemo/MainWindow.xaml.cs +++ b/samples/RenderDemo/MainWindow.xaml.cs @@ -3,7 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using RenderDemo.ViewModels; -using ReactiveUI; +using MiniMvvm; namespace RenderDemo { diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj index d1654f4b54..0d33b4c111 100644 --- a/samples/RenderDemo/RenderDemo.csproj +++ b/samples/RenderDemo/RenderDemo.csproj @@ -9,12 +9,11 @@ - + - diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index 7b89b7944c..f8ba01f3d1 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -1,10 +1,10 @@ using System; -using ReactiveUI; +using MiniMvvm; using Avalonia.Animation; namespace RenderDemo.ViewModels { - public class AnimationsPageViewModel : ReactiveObject + public class AnimationsPageViewModel : ViewModelBase { private bool _isPlaying = true; diff --git a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs index eda5e80530..19917c20df 100644 --- a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs @@ -1,11 +1,10 @@ using System.Reactive; using System.Threading.Tasks; - -using ReactiveUI; +using MiniMvvm; namespace RenderDemo.ViewModels { - public class MainWindowViewModel : ReactiveObject + public class MainWindowViewModel : ViewModelBase { private bool drawDirtyRects = false; private bool drawFps = true; @@ -14,9 +13,9 @@ namespace RenderDemo.ViewModels public MainWindowViewModel() { - ToggleDrawDirtyRects = ReactiveCommand.Create(() => DrawDirtyRects = !DrawDirtyRects); - ToggleDrawFps = ReactiveCommand.Create(() => DrawFps = !DrawFps); - ResizeWindow = ReactiveCommand.CreateFromTask(ResizeWindowAsync); + ToggleDrawDirtyRects = MiniCommand.Create(() => DrawDirtyRects = !DrawDirtyRects); + ToggleDrawFps = MiniCommand.Create(() => DrawFps = !DrawFps); + ResizeWindow = MiniCommand.CreateFromTask(ResizeWindowAsync); } public bool DrawDirtyRects @@ -43,9 +42,9 @@ namespace RenderDemo.ViewModels set => this.RaiseAndSetIfChanged(ref height, value); } - public ReactiveCommand ToggleDrawDirtyRects { get; } - public ReactiveCommand ToggleDrawFps { get; } - public ReactiveCommand ResizeWindow { get; } + public MiniCommand ToggleDrawDirtyRects { get; } + public MiniCommand ToggleDrawFps { get; } + public MiniCommand ResizeWindow { get; } private async Task ResizeWindowAsync() { diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml index 699781eb94..f601f9f78f 100644 --- a/samples/Sandbox/App.axaml +++ b/samples/Sandbox/App.axaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sandbox.App"> - + diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs index 7d41a8b8c0..1e74105196 100644 --- a/samples/Sandbox/Program.cs +++ b/samples/Sandbox/Program.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.ReactiveUI; namespace Sandbox { @@ -9,7 +8,6 @@ namespace Sandbox { AppBuilder.Configure() .UsePlatformDetect() - .UseReactiveUI() .LogToTrace() .StartWithClassicDesktopLifetime(args); } diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index 1a0a8a7ce5..0c19440a1e 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -8,7 +8,6 @@ - diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs index 46c05f74b2..febda46450 100644 --- a/samples/VirtualizationDemo/Program.cs +++ b/samples/VirtualizationDemo/Program.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.ReactiveUI; namespace VirtualizationDemo { @@ -8,7 +7,6 @@ namespace VirtualizationDemo public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .UseReactiveUI() .LogToTrace(); public static int Main(string[] args) diff --git a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs index cf34980b40..9ba505ffe5 100644 --- a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs @@ -1,9 +1,9 @@ using System; -using ReactiveUI; +using MiniMvvm; namespace VirtualizationDemo.ViewModels { - internal class ItemViewModel : ReactiveObject + internal class ItemViewModel : ViewModelBase { private string _prefix; private int _index; diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index 852c01399f..514df691ae 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -5,13 +5,13 @@ using System.Reactive; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; -using ReactiveUI; using Avalonia.Layout; using Avalonia.Controls.Selection; +using MiniMvvm; namespace VirtualizationDemo.ViewModels { - internal class MainWindowViewModel : ReactiveObject + internal class MainWindowViewModel : ViewModelBase { private int _itemCount = 200; private string _newItemString = "New Item"; @@ -26,15 +26,15 @@ namespace VirtualizationDemo.ViewModels public MainWindowViewModel() { this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); - RecreateCommand = ReactiveCommand.Create(() => Recreate()); + RecreateCommand = MiniCommand.Create(() => Recreate()); - AddItemCommand = ReactiveCommand.Create(() => AddItem()); + AddItemCommand = MiniCommand.Create(() => AddItem()); - RemoveItemCommand = ReactiveCommand.Create(() => Remove()); + RemoveItemCommand = MiniCommand.Create(() => Remove()); - SelectFirstCommand = ReactiveCommand.Create(() => SelectItem(0)); + SelectFirstCommand = MiniCommand.Create(() => SelectItem(0)); - SelectLastCommand = ReactiveCommand.Create(() => SelectItem(Items.Count - 1)); + SelectLastCommand = MiniCommand.Create(() => SelectItem(Items.Count - 1)); } public string NewItemString @@ -90,11 +90,11 @@ namespace VirtualizationDemo.ViewModels public IEnumerable VirtualizationModes => Enum.GetValues(typeof(ItemVirtualizationMode)).Cast(); - public ReactiveCommand AddItemCommand { get; private set; } - public ReactiveCommand RecreateCommand { get; private set; } - public ReactiveCommand RemoveItemCommand { get; private set; } - public ReactiveCommand SelectFirstCommand { get; private set; } - public ReactiveCommand SelectLastCommand { get; private set; } + public MiniCommand AddItemCommand { get; private set; } + public MiniCommand RecreateCommand { get; private set; } + public MiniCommand RemoveItemCommand { get; private set; } + public MiniCommand SelectFirstCommand { get; private set; } + public MiniCommand SelectLastCommand { get; private set; } public void RandomizeSize() { diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index 817023fd71..d898b737a9 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -6,12 +6,11 @@ - + - diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index bd6b6f170f..cd9963a2e5 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -22,9 +22,9 @@ - + diff --git a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs index d39a21cd07..21679a99c5 100644 --- a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs +++ b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using ReactiveUI; +using MiniMvvm; namespace Direct3DInteropSample { - public class MainWindowViewModel : ReactiveObject + public class MainWindowViewModel : ViewModelBase { private double _rotationX; diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index c067d38595..8394d7cb13 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -136,10 +136,6 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Avalonia.Layout - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - {eb582467-6abb-43a1-b052-e981ba910e3a} Avalonia.Visuals @@ -190,4 +186,4 @@ - \ No newline at end of file + diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index b8697e0ca2..f880e48282 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -127,10 +127,6 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Avalonia.Layout - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - {eb582467-6abb-43a1-b052-e981ba910e3a} Avalonia.Visuals diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index d43b4e04bb..5681214222 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -63,6 +63,15 @@ namespace Avalonia.Collections _inner = new List(); } + /// + /// Initializes a new instance of the . + /// + /// Initial list capacity. + public AvaloniaList(int capacity) + { + _inner = new List(capacity); + } + /// /// Initializes a new instance of the class. /// @@ -175,6 +184,15 @@ namespace Avalonia.Collections set { this[index] = (T)value; } } + /// + /// Gets or sets the total number of elements the internal data structure can hold without resizing. + /// + public int Capacity + { + get => _inner.Capacity; + set => _inner.Capacity = value; + } + /// /// Adds an item to the collection. /// diff --git a/src/Avalonia.Controls/DefinitionList.cs b/src/Avalonia.Controls/DefinitionList.cs index bb815171c0..ad0e2513c4 100644 --- a/src/Avalonia.Controls/DefinitionList.cs +++ b/src/Avalonia.Controls/DefinitionList.cs @@ -1,8 +1,9 @@ -using System; +using System.Collections; using System.Collections.Specialized; -using System.Linq; using Avalonia.Collections; +#nullable enable + namespace Avalonia.Controls { public abstract class DefinitionList : AvaloniaList where T : DefinitionBase @@ -14,44 +15,65 @@ namespace Avalonia.Controls } internal bool IsDirty = true; - private Grid _parent; + private Grid? _parent; - internal Grid Parent + internal Grid? Parent { get => _parent; set => SetParent(value); } - private void SetParent(Grid value) + private void SetParent(Grid? value) { _parent = value; - foreach (var pair in this.Select((definitions, index) => (definitions, index))) + var idx = 0; + + foreach (T definition in this) { - pair.definitions.Parent = value; - pair.definitions.Index = pair.index; + definition.Parent = value; + definition.Index = idx++; } } internal void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - foreach (var nI in this.Select((d, i) => (d, i))) - nI.d._parentIndex = nI.i; + var idx = 0; - foreach (var nD in e.NewItems?.Cast() - ?? Enumerable.Empty()) + foreach (T definition in this) { - nD.Parent = this.Parent; - nD.OnEnterParentTree(); + definition.Index = idx++; } + + UpdateDefinitionParent(e.NewItems, false); + UpdateDefinitionParent(e.OldItems, true); + + IsDirty = true; + } - foreach (var oD in e.OldItems?.Cast() - ?? Enumerable.Empty()) + private void UpdateDefinitionParent(IList? items, bool wasRemoved) + { + if (items is null) { - oD.OnExitParentTree(); + return; } + + var count = items.Count; - IsDirty = true; + for (var i = 0; i < count; i++) + { + var definition = (DefinitionBase) items[i]; + + if (wasRemoved) + { + definition.OnExitParentTree(); + } + else + { + definition.Parent = Parent; + definition.OnEnterParentTree(); + } + } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs index 023b108061..31d9a5c02e 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerInfo.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerInfo.cs @@ -37,6 +37,6 @@ namespace Avalonia.Controls.Generators /// /// Gets the index of the item in the collection. /// - public int Index { get; internal set; } + public int Index { get; set; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Layout/LayoutContext.cs b/src/Avalonia.Layout/LayoutContext.cs index 45a8048ea2..dadce58c0c 100644 --- a/src/Avalonia.Layout/LayoutContext.cs +++ b/src/Avalonia.Layout/LayoutContext.cs @@ -14,7 +14,11 @@ namespace Avalonia.Layout /// /// Gets or sets an object that represents the state of a layout. /// - public object LayoutState { get; set; } + public object LayoutState + { + get => LayoutStateCore; + set => LayoutStateCore = value; + } /// /// Implements the behavior of in a derived or custom LayoutContext. diff --git a/src/Avalonia.Native/DoubleClickHelper.cs b/src/Avalonia.Native/DoubleClickHelper.cs new file mode 100644 index 0000000000..7618d6976a --- /dev/null +++ b/src/Avalonia.Native/DoubleClickHelper.cs @@ -0,0 +1,31 @@ +using Avalonia.Platform; + +namespace Avalonia.Native +{ + internal class DoubleClickHelper + { + private int _clickCount; + private Rect _lastClickRect; + private ulong _lastClickTime; + + public bool IsDoubleClick( + ulong timestamp, + Point p) + { + var settings = AvaloniaLocator.Current.GetService(); + var doubleClickTime = settings.DoubleClickTime.TotalMilliseconds; + + if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime) + { + _clickCount = 0; + } + + ++_clickCount; + _lastClickTime = timestamp; + _lastClickRect = new Rect(p, new Size()) + .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2)); + + return _clickCount == 2; + } + } +} diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index f60e83efe3..f3b60f07be 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -1,4 +1,5 @@ using System; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -17,6 +18,8 @@ namespace Avalonia.Native private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; IAvnWindow _native; private double _extendTitleBarHeight = -1; + private DoubleClickHelper _doubleClickHelper; + internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) : base(opts, glFeature) @@ -24,6 +27,8 @@ namespace Avalonia.Native _factory = factory; _opts = opts; _glFeature = glFeature; + _doubleClickHelper = new DoubleClickHelper(); + using (var e = new WindowEvents(this)) { var context = _opts.UseGpu ? glFeature?.MainContext : null; @@ -118,7 +123,22 @@ namespace Avalonia.Native if(visual == null) { - _native.BeginMoveDrag(); + if (_doubleClickHelper.IsDoubleClick(e.Timestamp, e.Position)) + { + // TOGGLE WINDOW STATE. + if (WindowState == WindowState.Maximized || WindowState == WindowState.FullScreen) + { + WindowState = WindowState.Normal; + } + else + { + WindowState = WindowState.Maximized; + } + } + else + { + _native.BeginMoveDrag(); + } } } } diff --git a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt new file mode 100644 index 0000000000..44e250b889 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt @@ -0,0 +1,4 @@ +Compat issues with assembly Avalonia.Themes.Fluent: +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Themes.Fluent.FluentTheme' does not inherit from base type 'Avalonia.Styling.Styles' in the implementation but it does in the contract. +MembersMustExist : Member 'public void Avalonia.Themes.Fluent.FluentTheme..ctor()' does not exist in the implementation but it does exist in the contract. +Total Issues: 2 diff --git a/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml rename to src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml diff --git a/src/Avalonia.Themes.Fluent/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Button.xaml rename to src/Avalonia.Themes.Fluent/Controls/Button.xaml diff --git a/src/Avalonia.Themes.Fluent/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ButtonSpinner.xaml rename to src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml diff --git a/src/Avalonia.Themes.Fluent/Calendar.xaml b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Calendar.xaml rename to src/Avalonia.Themes.Fluent/Controls/Calendar.xaml diff --git a/src/Avalonia.Themes.Fluent/CalendarButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/CalendarButton.xaml rename to src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml diff --git a/src/Avalonia.Themes.Fluent/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/CalendarDatePicker.xaml rename to src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml diff --git a/src/Avalonia.Themes.Fluent/CalendarDayButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/CalendarDayButton.xaml rename to src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml diff --git a/src/Avalonia.Themes.Fluent/CalendarItem.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/CalendarItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml diff --git a/src/Avalonia.Themes.Fluent/CaptionButtons.xaml b/src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/CaptionButtons.xaml rename to src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml diff --git a/src/Avalonia.Themes.Fluent/Carousel.xaml b/src/Avalonia.Themes.Fluent/Controls/Carousel.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Carousel.xaml rename to src/Avalonia.Themes.Fluent/Controls/Carousel.xaml diff --git a/src/Avalonia.Themes.Fluent/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/CheckBox.xaml rename to src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml diff --git a/src/Avalonia.Themes.Fluent/ComboBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ComboBox.xaml rename to src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml diff --git a/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml b/src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ComboBoxItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/ComboBoxItem.xaml diff --git a/src/Avalonia.Themes.Fluent/Common.xaml b/src/Avalonia.Themes.Fluent/Controls/Common.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Common.xaml rename to src/Avalonia.Themes.Fluent/Controls/Common.xaml diff --git a/src/Avalonia.Themes.Fluent/ContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ContentControl.xaml rename to src/Avalonia.Themes.Fluent/Controls/ContentControl.xaml diff --git a/src/Avalonia.Themes.Fluent/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ContextMenu.xaml rename to src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml diff --git a/src/Avalonia.Themes.Fluent/DataValidationErrors.xaml b/src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/DataValidationErrors.xaml rename to src/Avalonia.Themes.Fluent/Controls/DataValidationErrors.xaml diff --git a/src/Avalonia.Themes.Fluent/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/DatePicker.xaml rename to src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml diff --git a/src/Avalonia.Themes.Fluent/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/EmbeddableControlRoot.xaml rename to src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml diff --git a/src/Avalonia.Themes.Fluent/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Expander.xaml rename to src/Avalonia.Themes.Fluent/Controls/Expander.xaml diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml new file mode 100644 index 0000000000..0acb8f4f6e --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs similarity index 63% rename from src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs rename to src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs index 9c65a7227c..37822f5c8c 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml.cs @@ -1,12 +1,12 @@ using Avalonia.Markup.Xaml; using Avalonia.Styling; -namespace Avalonia.Themes.Fluent +namespace Avalonia.Themes.Fluent.Controls { /// /// The default Avalonia theme. /// - public class FluentTheme : Styles + public class FluentControls : Styles { } } diff --git a/src/Avalonia.Themes.Fluent/FocusAdorner.xaml b/src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/FocusAdorner.xaml rename to src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml diff --git a/src/Avalonia.Themes.Fluent/GridSplitter.xaml b/src/Avalonia.Themes.Fluent/Controls/GridSplitter.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/GridSplitter.xaml rename to src/Avalonia.Themes.Fluent/Controls/GridSplitter.xaml diff --git a/src/Avalonia.Themes.Fluent/ItemsControl.xaml b/src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ItemsControl.xaml rename to src/Avalonia.Themes.Fluent/Controls/ItemsControl.xaml diff --git a/src/Avalonia.Themes.Fluent/Label.xaml b/src/Avalonia.Themes.Fluent/Controls/Label.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Label.xaml rename to src/Avalonia.Themes.Fluent/Controls/Label.xaml diff --git a/src/Avalonia.Themes.Fluent/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ListBox.xaml rename to src/Avalonia.Themes.Fluent/Controls/ListBox.xaml diff --git a/src/Avalonia.Themes.Fluent/ListBoxItem.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ListBoxItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml diff --git a/src/Avalonia.Themes.Fluent/Menu.xaml b/src/Avalonia.Themes.Fluent/Controls/Menu.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Menu.xaml rename to src/Avalonia.Themes.Fluent/Controls/Menu.xaml diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/MenuItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml diff --git a/src/Avalonia.Themes.Fluent/NativeMenuBar.xaml b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/NativeMenuBar.xaml rename to src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml diff --git a/src/Avalonia.Themes.Fluent/NotificationCard.xaml b/src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/NotificationCard.xaml rename to src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml diff --git a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/NumericUpDown.xaml rename to src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml diff --git a/src/Avalonia.Themes.Fluent/OverlayPopupHost.xaml b/src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/OverlayPopupHost.xaml rename to src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml diff --git a/src/Avalonia.Themes.Fluent/PathIcon.xaml b/src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/PathIcon.xaml rename to src/Avalonia.Themes.Fluent/Controls/PathIcon.xaml diff --git a/src/Avalonia.Themes.Fluent/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/PopupRoot.xaml rename to src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml diff --git a/src/Avalonia.Themes.Fluent/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ProgressBar.xaml rename to src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml diff --git a/src/Avalonia.Themes.Fluent/RadioButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/RadioButton.xaml rename to src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml diff --git a/src/Avalonia.Themes.Fluent/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/RepeatButton.xaml rename to src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml diff --git a/src/Avalonia.Themes.Fluent/ScrollBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ScrollBar.xaml rename to src/Avalonia.Themes.Fluent/Controls/ScrollBar.xaml diff --git a/src/Avalonia.Themes.Fluent/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ScrollViewer.xaml rename to src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml diff --git a/src/Avalonia.Themes.Fluent/Separator.xaml b/src/Avalonia.Themes.Fluent/Controls/Separator.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Separator.xaml rename to src/Avalonia.Themes.Fluent/Controls/Separator.xaml diff --git a/src/Avalonia.Themes.Fluent/Slider.xaml b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Slider.xaml rename to src/Avalonia.Themes.Fluent/Controls/Slider.xaml diff --git a/src/Avalonia.Themes.Fluent/SplitView.xaml b/src/Avalonia.Themes.Fluent/Controls/SplitView.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/SplitView.xaml rename to src/Avalonia.Themes.Fluent/Controls/SplitView.xaml diff --git a/src/Avalonia.Themes.Fluent/TabControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TabControl.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TabControl.xaml rename to src/Avalonia.Themes.Fluent/Controls/TabControl.xaml diff --git a/src/Avalonia.Themes.Fluent/TabItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TabItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/TabItem.xaml diff --git a/src/Avalonia.Themes.Fluent/TabStrip.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TabStrip.xaml rename to src/Avalonia.Themes.Fluent/Controls/TabStrip.xaml diff --git a/src/Avalonia.Themes.Fluent/TabStripItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TabStripItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TextBox.xaml rename to src/Avalonia.Themes.Fluent/Controls/TextBox.xaml diff --git a/src/Avalonia.Themes.Fluent/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TimePicker.xaml rename to src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TitleBar.xaml rename to src/Avalonia.Themes.Fluent/Controls/TitleBar.xaml diff --git a/src/Avalonia.Themes.Fluent/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ToggleButton.xaml rename to src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml diff --git a/src/Avalonia.Themes.Fluent/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ToggleSwitch.xaml rename to src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml diff --git a/src/Avalonia.Themes.Fluent/ToolTip.xaml b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/ToolTip.xaml rename to src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml diff --git a/src/Avalonia.Themes.Fluent/TreeView.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeView.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TreeView.xaml rename to src/Avalonia.Themes.Fluent/Controls/TreeView.xaml diff --git a/src/Avalonia.Themes.Fluent/TreeViewItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/TreeViewItem.xaml rename to src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml diff --git a/src/Avalonia.Themes.Fluent/UserControl.xaml b/src/Avalonia.Themes.Fluent/Controls/UserControl.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/UserControl.xaml rename to src/Avalonia.Themes.Fluent/Controls/UserControl.xaml diff --git a/src/Avalonia.Themes.Fluent/Window.xaml b/src/Avalonia.Themes.Fluent/Controls/Window.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/Window.xaml rename to src/Avalonia.Themes.Fluent/Controls/Window.xaml diff --git a/src/Avalonia.Themes.Fluent/WindowNotificationManager.xaml b/src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml similarity index 100% rename from src/Avalonia.Themes.Fluent/WindowNotificationManager.xaml rename to src/Avalonia.Themes.Fluent/Controls/WindowNotificationManager.xaml diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml b/src/Avalonia.Themes.Fluent/FluentDark.xaml similarity index 85% rename from src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml rename to src/Avalonia.Themes.Fluent/FluentDark.xaml index 9ef92a44d5..74b583a240 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml +++ b/src/Avalonia.Themes.Fluent/FluentDark.xaml @@ -5,5 +5,5 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml b/src/Avalonia.Themes.Fluent/FluentLight.xaml similarity index 85% rename from src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml rename to src/Avalonia.Themes.Fluent/FluentLight.xaml index 8c92040122..1bc51f655e 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml +++ b/src/Avalonia.Themes.Fluent/FluentLight.xaml @@ -5,5 +5,5 @@ - + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs new file mode 100644 index 0000000000..43b71567fa --- /dev/null +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +#nullable enable + +namespace Avalonia.Themes.Fluent +{ + public enum FluentThemeMode + { + Light, + Dark, + } + + /// + /// Includes the fluent theme in an application. + /// + public class FluentTheme : IStyle, IResourceProvider + { + private readonly Uri _baseUri; + private IStyle[]? _loaded; + private bool _isLoading; + + /// + /// Initializes a new instance of the class. + /// + /// The base URL for the XAML context. + public FluentTheme(Uri baseUri) + { + _baseUri = baseUri; + } + + /// + /// Initializes a new instance of the class. + /// + /// The XAML service provider. + public FluentTheme(IServiceProvider serviceProvider) + { + _baseUri = ((IUriContext)serviceProvider.GetService(typeof(IUriContext))).BaseUri; + } + + /// + /// Gets or sets the mode of the fluent theme (light, dark). + /// + public FluentThemeMode Mode { get; set; } + + public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner; + + /// + /// Gets the loaded style. + /// + public IStyle Loaded + { + get + { + if (_loaded == null) + { + _isLoading = true; + var loaded = (IStyle)AvaloniaXamlLoader.Load(GetUri(), _baseUri); + _loaded = new[] { loaded }; + _isLoading = false; + } + + return _loaded?[0]!; + } + } + + bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false; + + IReadOnlyList IStyle.Children => _loaded ?? Array.Empty(); + + public event EventHandler OwnerChanged + { + add + { + if (Loaded is IResourceProvider rp) + { + rp.OwnerChanged += value; + } + } + remove + { + if (Loaded is IResourceProvider rp) + { + rp.OwnerChanged -= value; + } + } + } + + public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host); + + public bool TryGetResource(object key, out object? value) + { + if (!_isLoading && Loaded is IResourceProvider p) + { + return p.TryGetResource(key, out value); + } + + value = null; + return false; + } + + void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); + void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); + + private Uri GetUri() => Mode switch + { + FluentThemeMode.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute), + _ => new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml", UriKind.Absolute), + }; + } +} diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml deleted file mode 100644 index 4e0e4fe862..0000000000 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs b/src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0bad226ba7 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Avalonia.Metadata; + +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Themes.Fluent")] diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 2ccfd43f03..8136f843df 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -282,25 +282,44 @@ namespace Avalonia } /// - /// Inverts the Matrix. + /// Attempts to invert the Matrix. /// - /// The inverted matrix. - public Matrix Invert() + /// The inverted matrix or when matrix is not invertible. + public bool TryInvert(out Matrix inverted) { double d = GetDeterminant(); if (MathUtilities.IsZero(d)) { - throw new InvalidOperationException("Transform is not invertible."); + inverted = default; + + return false; } - return new Matrix( + inverted = new Matrix( _m22 / d, -_m12 / d, -_m21 / d, _m11 / d, ((_m21 * _m32) - (_m22 * _m31)) / d, ((_m12 * _m31) - (_m11 * _m32)) / d); + + return true; + } + + /// + /// Inverts the Matrix. + /// + /// Matrix is not invertible. + /// The inverted matrix. + public Matrix Invert() + { + if (!TryInvert(out Matrix inverted)) + { + throw new InvalidOperationException("Transform is not invertible."); + } + + return inverted; } /// diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index 6079e5941f..e6523a1469 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -50,7 +50,13 @@ namespace Avalonia { var thisOffset = GetOffsetFrom(common, from); var thatOffset = GetOffsetFrom(common, to); - return -thatOffset * thisOffset; + + if (!thatOffset.TryInvert(out var thatOffsetInverted)) + { + return null; + } + + return thatOffsetInverted * thisOffset; } return null; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlAvaloniaListConstantAstNode.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlAvaloniaListConstantAstNode.cs new file mode 100644 index 0000000000..0f4efc9f65 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AstNodes/AvaloniaXamlIlAvaloniaListConstantAstNode.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.AstNodes +{ + class AvaloniaXamlIlAvaloniaListConstantAstNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode + { + private readonly IXamlType _elementType; + private readonly IReadOnlyList _values; + private readonly IXamlConstructor _constructor; + private readonly IXamlMethod _listAddMethod; + private readonly IXamlMethod _listSetCapacityMethod; + + public AvaloniaXamlIlAvaloniaListConstantAstNode(IXamlLineInfo lineInfo, AvaloniaXamlIlWellKnownTypes types, IXamlType listType, IXamlType elementType, IReadOnlyList values) : base(lineInfo) + { + _constructor = listType.GetConstructor(); + _listAddMethod = listType.GetMethod(new FindMethodMethodSignature("Add", types.XamlIlTypes.Void, elementType)); + _listSetCapacityMethod = listType.GetMethod(new FindMethodMethodSignature("set_Capacity", types.XamlIlTypes.Void, types.Int)); + + _elementType = elementType; + _values = values; + + Type = new XamlAstClrTypeReference(lineInfo, listType, false); + } + + public IXamlAstTypeReference Type { get; } + + public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Newobj(_constructor); + + codeGen + .Dup() + .Ldc_I4(_values.Count) + .EmitCall(_listSetCapacityMethod); + + foreach (var value in _values) + { + codeGen.Dup(); + + context.Emit(value, codeGen, _elementType); + + codeGen.EmitCall(_listAddMethod); + } + + 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 40cfd21a76..a82f5b9e60 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs @@ -177,182 +177,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } var text = textNode.Text; - var types = context.GetAvaloniaTypes(); - if (type.FullName == "System.TimeSpan") + if (AvaloniaXamlIlLanguageParseIntrinsics.TryConvert(context, node, text, type, types, out result)) { - var tsText = text.Trim(); - - if (!TimeSpan.TryParse(tsText, CultureInfo.InvariantCulture, out var timeSpan)) - { - // // shorthand seconds format (ie. "0.25") - if (!tsText.Contains(":") && double.TryParse(tsText, - NumberStyles.Float | NumberStyles.AllowThousands, - CultureInfo.InvariantCulture, out var seconds)) - timeSpan = TimeSpan.FromSeconds(seconds); - else - throw new XamlX.XamlLoadException($"Unable to parse {text} as a time span", node); - } - - - result = new XamlStaticOrTargetedReturnMethodCallNode(node, - type.FindMethod("FromTicks", type, false, types.Long), - new[] { new XamlConstantNode(node, types.Long, timeSpan.Ticks) }); return true; } - - if (type.Equals(types.FontFamily)) - { - result = new AvaloniaXamlIlFontFamilyAstNode(types, text, node); - return true; - } - - if (type.Equals(types.Thickness)) - { - 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; - } - catch - { - throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node); - } - } - - if (type.Equals(types.Point)) - { - try - { - var point = Point.Parse(text); - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor, - new[] { point.X, point.Y }); - - return true; - } - catch - { - throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node); - } - } - - if (type.Equals(types.Vector)) - { - try - { - var vector = Vector.Parse(text); - - result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor, - new[] { vector.X, vector.Y }); - - return true; - } - catch - { - throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node); - } - } - - if (type.Equals(types.Size)) - { - try - { - var size = Size.Parse(text); - - 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)) - { - 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; - } - catch - { - throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node); - } - } - - if (type.Equals(types.CornerRadius)) - { - 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/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs new file mode 100644 index 0000000000..7c4cc8d28b --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -0,0 +1,243 @@ +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.Ast; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions +{ + class AvaloniaXamlIlLanguageParseIntrinsics + { + public static bool TryConvert(AstTransformationContext context, IXamlAstValueNode node, string text, IXamlType type, AvaloniaXamlIlWellKnownTypes types, out IXamlAstValueNode result) + { + if (type.FullName == "System.TimeSpan") + { + var tsText = text.Trim(); + + if (!TimeSpan.TryParse(tsText, CultureInfo.InvariantCulture, out var timeSpan)) + { + // // shorthand seconds format (ie. "0.25") + if (!tsText.Contains(":") && double.TryParse(tsText, + NumberStyles.Float | NumberStyles.AllowThousands, + CultureInfo.InvariantCulture, out var seconds)) + timeSpan = TimeSpan.FromSeconds(seconds); + else + throw new XamlX.XamlLoadException($"Unable to parse {text} as a time span", node); + } + + result = new XamlStaticOrTargetedReturnMethodCallNode(node, + type.FindMethod("FromTicks", type, false, types.Long), + new[] { new XamlConstantNode(node, types.Long, timeSpan.Ticks) }); + return true; + } + + if (type.Equals(types.FontFamily)) + { + result = new AvaloniaXamlIlFontFamilyAstNode(types, text, node); + return true; + } + + if (type.Equals(types.Thickness)) + { + 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; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a thickness", node); + } + } + + if (type.Equals(types.Point)) + { + try + { + var point = Point.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor, + new[] { point.X, point.Y }); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node); + } + } + + if (type.Equals(types.Vector)) + { + try + { + var vector = Vector.Parse(text); + + result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor, + new[] { vector.X, vector.Y }); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node); + } + } + + if (type.Equals(types.Size)) + { + try + { + var size = Size.Parse(text); + + 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)) + { + 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; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node); + } + } + + if (type.Equals(types.CornerRadius)) + { + 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.Equals(types.ColumnDefinitions)) + { + return ConvertDefinitionList(node, text, types, types.ColumnDefinitions, types.ColumnDefinition, "column definitions", out result); + } + + if (type.Equals(types.RowDefinitions)) + { + return ConvertDefinitionList(node, text, types, types.RowDefinitions, types.RowDefinition, "row definitions", out result); + } + + result = null; + return false; + } + + private static bool ConvertDefinitionList( + IXamlAstValueNode node, + string text, + AvaloniaXamlIlWellKnownTypes types, + IXamlType listType, + IXamlType elementType, + string errorDisplayName, + out IXamlAstValueNode result) + { + try + { + var lengths = GridLength.ParseLengths(text); + + var definitionTypeRef = new XamlAstClrTypeReference(node, elementType, false); + + var definitionConstructorGridLength = elementType.GetConstructor(new List {types.GridLength}); + + IXamlAstValueNode CreateDefinitionNode(GridLength length) + { + var lengthNode = new AvaloniaXamlIlGridLengthAstNode(node, types, length); + + return new XamlAstNewClrObjectNode(node, definitionTypeRef, + definitionConstructorGridLength, new List {lengthNode}); + } + + var definitionNodes = + new List(lengths.Select(CreateDefinitionNode)); + + result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, listType, elementType, definitionNodes); + + return true; + } + catch + { + throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a {errorDisplayName}", node); + } + } + } +} 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 f046778429..125701ca9e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -50,6 +50,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType RelativeSource { get; } public IXamlType UInt { get; } + public IXamlType Int { get; } public IXamlType Long { get; } public IXamlType Uri { get; } public IXamlType FontFamily { get; } @@ -72,6 +73,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType StandardCursorType { get; } public IXamlType Cursor { get; } public IXamlConstructor CursorTypeConstructor { get; } + public IXamlType RowDefinition { get; } + public IXamlType RowDefinitions { get; } + public IXamlType ColumnDefinition { get; } + public IXamlType ColumnDefinitions { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -130,6 +135,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension"); RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource"); UInt = cfg.TypeSystem.GetType("System.UInt32"); + Int = cfg.TypeSystem.GetType("System.Int32"); Long = cfg.TypeSystem.GetType("System.Int64"); Uri = cfg.TypeSystem.GetType("System.Uri"); FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); @@ -156,6 +162,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers StandardCursorType = cfg.TypeSystem.GetType("Avalonia.Input.StandardCursorType"); Cursor = cfg.TypeSystem.GetType("Avalonia.Input.Cursor"); CursorTypeConstructor = Cursor.GetConstructor(new List { StandardCursorType }); + ColumnDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.ColumnDefinition"); + ColumnDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.ColumnDefinitions"); + RowDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinition"); + RowDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinitions"); } } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 72700fb8fd..6d0be9f64d 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -3,6 +3,8 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; + using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; using Avalonia.OpenGL; @@ -166,12 +168,13 @@ namespace Avalonia.Skia LinearMetrics = true }; - private static readonly SKTextBlobBuilder s_textBlobBuilder = new SKTextBlobBuilder(); + private static readonly ThreadLocal s_textBlobBuilderThreadLocal = new ThreadLocal(() => new SKTextBlobBuilder()); /// public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width) { var count = glyphRun.GlyphIndices.Length; + var textBlobBuilder = s_textBlobBuilderThreadLocal.Value; var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl; @@ -191,15 +194,15 @@ namespace Avalonia.Skia { if (glyphTypeface.IsFixedPitch) { - s_textBlobBuilder.AddRun(glyphRun.GlyphIndices.Buffer.Span, s_font); + textBlobBuilder.AddRun(glyphRun.GlyphIndices.Buffer.Span, s_font); - textBlob = s_textBlobBuilder.Build(); + textBlob = textBlobBuilder.Build(); width = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[0]) * scale * glyphRun.GlyphIndices.Length; } else { - var buffer = s_textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); + var buffer = textBlobBuilder.AllocateHorizontalRun(s_font, count, 0); var positions = buffer.GetPositionSpan(); @@ -219,12 +222,12 @@ namespace Avalonia.Skia buffer.SetGlyphs(glyphRun.GlyphIndices.Buffer.Span); - textBlob = s_textBlobBuilder.Build(); + textBlob = textBlobBuilder.Build(); } } else { - var buffer = s_textBlobBuilder.AllocatePositionedRun(s_font, count); + var buffer = textBlobBuilder.AllocatePositionedRun(s_font, count); var glyphPositions = buffer.GetPositionSpan(); @@ -250,7 +253,7 @@ namespace Avalonia.Skia width = currentX; - textBlob = s_textBlobBuilder.Build(); + textBlob = textBlobBuilder.Build(); } return new GlyphRunImpl(textBlob); diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 7d9e0a8bd2..047b7c361f 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -12,10 +12,17 @@ namespace Avalonia.Win32 { internal class ClipboardImpl : IClipboard { + private const int OleRetryCount = 10; + private const int OleRetryDelay = 100; + private async Task OpenClipboard() { + var i = OleRetryCount; + while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) { + if (--i == 0) + throw new TimeoutException("Timeout opening clipboard."); await Task.Delay(100); } @@ -72,20 +79,32 @@ namespace Avalonia.Win32 { Dispatcher.UIThread.VerifyAccess(); var wrapper = new DataObject(data); + var i = OleRetryCount; + while (true) { - if (UnmanagedMethods.OleSetClipboard(wrapper) == 0) + var hr = UnmanagedMethods.OleSetClipboard(wrapper); + + if (hr == 0) break; - await Task.Delay(100); + + if (--i == 0) + Marshal.ThrowExceptionForHR(hr); + + await Task.Delay(OleRetryDelay); } } public async Task GetFormatsAsync() { Dispatcher.UIThread.VerifyAccess(); + var i = OleRetryCount; + while (true) { - if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0) + var hr = UnmanagedMethods.OleGetClipboard(out var dataObject); + + if (hr == 0) { var wrapper = new OleDataObject(dataObject); var formats = wrapper.GetDataFormats().ToArray(); @@ -93,16 +112,23 @@ namespace Avalonia.Win32 return formats; } - await Task.Delay(100); + if (--i == 0) + Marshal.ThrowExceptionForHR(hr); + + await Task.Delay(OleRetryDelay); } } public async Task GetDataAsync(string format) { Dispatcher.UIThread.VerifyAccess(); + var i = OleRetryCount; + while (true) { - if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0) + var hr = UnmanagedMethods.OleGetClipboard(out var dataObject); + + if (hr == 0) { var wrapper = new OleDataObject(dataObject); var rv = wrapper.Get(format); @@ -110,7 +136,10 @@ namespace Avalonia.Win32 return rv; } - await Task.Delay(100); + if (--i == 0) + Marshal.ThrowExceptionForHR(hr); + + await Task.Delay(OleRetryDelay); } } } diff --git a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj index dd50eff2b6..5b686dea4c 100644 --- a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj +++ b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 0d87f0eb03..badfd09430 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -10,6 +10,7 @@ + @@ -17,4 +18,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj index 396dfee691..c0c9303767 100644 --- a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 7a6d77ef46..6b17427eda 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index 1cfc1a6ab8..c59e59be63 100644 --- a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -27,4 +27,5 @@ + diff --git a/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj b/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj index e346ce944d..42229ba456 100644 --- a/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj +++ b/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj @@ -7,6 +7,7 @@ + @@ -22,4 +23,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj b/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj index 463bc50008..74cc6e292b 100644 --- a/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj +++ b/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj @@ -8,6 +8,7 @@ + diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index 27f3223c6c..d49a859b89 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -7,6 +7,7 @@ + @@ -25,4 +26,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj index ec5b2f0ed1..7d1285c025 100644 --- a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj +++ b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index ad3592294d..44b604197d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj index 65df5e7cb0..14d0f4debf 100644 --- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj +++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj @@ -29,4 +29,5 @@ + diff --git a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj index 584eaf6fa9..ef69865e32 100644 --- a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj +++ b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj @@ -8,6 +8,7 @@ + @@ -23,4 +24,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj index 2b7f6263a7..a07ec050a6 100644 --- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj +++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 6b5c7a66ff..d4abf9416a 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -28,4 +28,5 @@ + diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj index aee54e87a9..13a04be5db 100644 --- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj +++ b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj @@ -13,6 +13,7 @@ + diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs index 447a68aa69..38131fbfca 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs @@ -235,6 +235,25 @@ namespace Avalonia.Visuals.UnitTests Assert.Equal(new Point(100, 100), point); } + [Fact] + public void TransformToVisual_With_NonInvertible_RenderTransform_Should_Work() + { + var child = new Decorator + { + Width = 100, + Height = 100, + RenderTransform = new ScaleTransform() { ScaleX = 0, ScaleY = 0 } + }; + var root = new TestRoot() { Child = child, Width = 400, Height = 400 }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(new Point(), root.DesiredSize)); + + var tr = root.TransformToVisual(child); + + Assert.Null(tr); + } + [Fact] public void Should_Not_Log_Binding_Error_When_Not_Attached_To_Logical_Tree() {