diff --git a/.editorconfig b/.editorconfig index b7a03207a4..5f08d1e940 100644 --- a/.editorconfig +++ b/.editorconfig @@ -132,6 +132,9 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false +# Wrapping preferences +csharp_wrap_before_ternary_opsigns = false + # Xaml files [*.xaml] indent_size = 4 diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml index f8f3cd5848..c03edb8b03 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml @@ -9,7 +9,7 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - + diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index c5dd072d8a..1f6870d60d 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -210,7 +210,7 @@ namespace Avalonia.Controls var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform"; var platformClassFullName = assemblyName + "." + platformClassName; var platformClass = assembly.GetType(platformClassFullName); - var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]); + var init = platformClass.GetRuntimeMethod("Initialize", Type.EmptyTypes); init.Invoke(null, null); }; @@ -245,7 +245,7 @@ namespace Avalonia.Controls select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors where constructor.GetParameters().Length == 0 && !constructor.IsStatic select constructor).Single() into constructor - select (Action)(() => constructor.Invoke(new object[0])); + select (Action)(() => constructor.Invoke(Array.Empty())); Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke(); } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index f572c67284..5ed0abf25d 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -32,6 +32,8 @@ namespace Avalonia.Controls /// public class Button : ContentControl { + private ICommand _command; + /// /// Defines the property. /// @@ -69,8 +71,6 @@ namespace Avalonia.Controls public static readonly RoutedEvent ClickEvent = RoutedEvent.Register(nameof(Click), RoutingStrategies.Bubble); - private ICommand _command; - public static readonly StyledProperty IsPressedProperty = AvaloniaProperty.Register(nameof(IsPressed)); diff --git a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs index 24b3fc1f32..0da84008f6 100644 --- a/src/Avalonia.Controls/Generators/TreeContainerIndex.cs +++ b/src/Avalonia.Controls/Generators/TreeContainerIndex.cs @@ -34,7 +34,12 @@ namespace Avalonia.Controls.Generators /// /// Gets the currently materialized containers. /// - public IEnumerable Items => _containerToItem.Keys; + public IEnumerable Containers => _containerToItem.Keys; + + /// + /// Gets the items of currently materialized containers. + /// + public IEnumerable Items => _containerToItem.Values; /// /// Adds an entry to the index. diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index c2227b9dfc..64daa133a3 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -20,11 +20,16 @@ namespace Avalonia.Controls /// public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable { + private ICommand _command; + /// /// Defines the property. /// - public static readonly StyledProperty CommandProperty = - AvaloniaProperty.Register(nameof(Command)); + public static readonly DirectProperty CommandProperty = + Button.CommandProperty.AddOwner( + menuItem => menuItem.Command, + (menuItem, command) => menuItem.Command = command, + enableDataValidation: true); /// /// Defines the property. @@ -159,8 +164,8 @@ namespace Avalonia.Controls /// public ICommand Command { - get { return GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } + get { return _command; } + set { SetAndRaise(CommandProperty, ref _command, value); } } /// diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index c40ddc37ad..a64dbe0546 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -10,6 +10,7 @@ using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Styling; using Avalonia.VisualTree; @@ -99,7 +100,7 @@ namespace Avalonia.Controls.Primitives "SelectionChanged", RoutingStrategies.Bubble); - private static readonly IList Empty = new object[0]; + private static readonly IList Empty = Array.Empty(); private int _selectedIndex = -1; private object _selectedItem; @@ -459,6 +460,23 @@ namespace Avalonia.Controls.Primitives } } + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (!e.Handled) + { + var keymap = AvaloniaLocator.Current.GetService(); + bool Match(List gestures) => gestures.Any(g => g.Matches(e)); + + if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll)) + { + SynchronizeItems(SelectedItems, Items?.Cast()); + e.Handled = true; + } + } + } + /// /// Moves the selection in the specified direction relative to the current selection. /// @@ -614,32 +632,12 @@ namespace Avalonia.Controls.Primitives return false; } - /// - /// Gets a range of items from an IEnumerable. - /// - /// The items. - /// The index of the first item. - /// The index of the last item. - /// The items. - private static IEnumerable GetRange(IEnumerable items, int first, int last) - { - var list = (items as IList) ?? items.Cast().ToList(); - int step = first > last ? -1 : 1; - - for (int i = first; i != last; i += step) - { - yield return list[i]; - } - - yield return list[last]; - } - /// /// Makes a list of objects equal another. /// /// The items collection. /// The desired items. - private static void SynchronizeItems(IList items, IEnumerable desired) + internal static void SynchronizeItems(IList items, IEnumerable desired) { int index = 0; @@ -666,6 +664,26 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Gets a range of items from an IEnumerable. + /// + /// The items. + /// The index of the first item. + /// The index of the last item. + /// The items. + private static IEnumerable GetRange(IEnumerable items, int first, int last) + { + var list = (items as IList) ?? items.Cast().ToList(); + int step = first > last ? -1 : 1; + + for (int i = first; i != last; i += step) + { + yield return list[i]; + } + + yield return list[last]; + } + /// /// Called when a container raises the . /// diff --git a/src/Avalonia.Controls/SystemDialog.cs b/src/Avalonia.Controls/SystemDialog.cs index e7cb4763ed..f321625bcc 100644 --- a/src/Avalonia.Controls/SystemDialog.cs +++ b/src/Avalonia.Controls/SystemDialog.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls throw new ArgumentNullException(nameof(parent)); return ((await AvaloniaLocator.Current.GetService() .ShowFileDialogAsync(this, parent?.PlatformImpl)) ?? - new string[0]).FirstOrDefault(); + Array.Empty()).FirstOrDefault(); } } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index c574799724..94989254dc 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -2,13 +2,16 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; -using Avalonia.Styling; using Avalonia.Threading; using Avalonia.VisualTree; @@ -34,14 +37,24 @@ namespace Avalonia.Controls (o, v) => o.SelectedItem = v); /// - /// Defines the event. + /// Defines the property. /// - public static readonly RoutedEvent SelectedItemChangedEvent = - RoutedEvent.Register( - "SelectedItemChanged", - RoutingStrategies.Bubble); + public static readonly DirectProperty SelectedItemsProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedItems), + o => o.SelectedItems, + (o, v) => o.SelectedItems = v); + /// + /// Defines the property. + /// + protected static readonly StyledProperty SelectionModeProperty = + AvaloniaProperty.Register( + nameof(SelectionMode)); + + private static readonly IList Empty = new object[0]; private object _selectedItem; + private IList _selectedItems; /// /// Initializes static members of the class. @@ -54,16 +67,16 @@ namespace Avalonia.Controls /// /// Occurs when the control's selection changes. /// - public event EventHandler SelectedItemChanged + public event EventHandler SelectionChanged { - add { AddHandler(SelectedItemChangedEvent, value); } - remove { RemoveHandler(SelectedItemChangedEvent, value); } + add => AddHandler(SelectingItemsControl.SelectionChangedEvent, value); + remove => RemoveHandler(SelectingItemsControl.SelectionChangedEvent, value); } /// /// Gets the for the tree view. /// - public new ITreeItemContainerGenerator ItemContainerGenerator => + public new ITreeItemContainerGenerator ItemContainerGenerator => (ITreeItemContainerGenerator)base.ItemContainerGenerator; /// @@ -71,67 +84,258 @@ namespace Avalonia.Controls /// public bool AutoScrollToSelectedItem { - get { return GetValue(AutoScrollToSelectedItemProperty); } - set { SetValue(AutoScrollToSelectedItemProperty, value); } + get => GetValue(AutoScrollToSelectedItemProperty); + set => SetValue(AutoScrollToSelectedItemProperty, value); + } + + private bool _syncingSelectedItems; + + /// + /// Gets or sets the selection mode. + /// + public SelectionMode SelectionMode + { + get => GetValue(SelectionModeProperty); + set => SetValue(SelectionModeProperty, value); } /// /// Gets or sets the selected item. /// public object SelectedItem + { + get => _selectedItem; + set + { + SetAndRaise(SelectedItemProperty, ref _selectedItem, + (object val, ref object backing, Action notifyWrapper) => + { + var old = backing; + backing = val; + + notifyWrapper(() => + RaisePropertyChanged( + SelectedItemProperty, + old, + val)); + + if (val != null) + { + if (SelectedItems.Count != 1 || SelectedItems[0] != val) + { + _syncingSelectedItems = true; + SelectSingleItem(val); + _syncingSelectedItems = false; + } + } + else if (SelectedItems.Count > 0) + { + SelectedItems.Clear(); + } + }, value); + } + } + + /// + /// Gets the selected items. + /// + public IList SelectedItems { get { - return _selectedItem; + if (_selectedItems == null) + { + _selectedItems = new AvaloniaList(); + SubscribeToSelectedItems(); + } + + return _selectedItems; } set { - if (_selectedItem != null) + if (value?.IsFixedSize == true || value?.IsReadOnly == true) { - var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); - MarkContainerSelected(container, false); + throw new NotSupportedException( + "Cannot use a fixed size or read-only collection as SelectedItems."); } - var oldItem = _selectedItem; - SetAndRaise(SelectedItemProperty, ref _selectedItem, value); + UnsubscribeFromSelectedItems(); + _selectedItems = value ?? new AvaloniaList(); + SubscribeToSelectedItems(); + } + } - if (_selectedItem != null) - { - var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem); - MarkContainerSelected(container, true); + /// + /// Subscribes to the CollectionChanged event, if any. + /// + private void SubscribeToSelectedItems() + { + if (_selectedItems is INotifyCollectionChanged incc) + { + incc.CollectionChanged += SelectedItemsCollectionChanged; + } + + SelectedItemsCollectionChanged( + _selectedItems, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + private void SelectSingleItem(object item) + { + SelectedItems.Clear(); + SelectedItems.Add(item); + } + + /// + /// Called when the CollectionChanged event is raised. + /// + /// The event sender. + /// The event args. + private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + IList added = null; + IList removed = null; - if (AutoScrollToSelectedItem && container != null) + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + + SelectedItemsAdded(e.NewItems.Cast().ToArray()); + + if (AutoScrollToSelectedItem) { - container.BringIntoView(); + var container = (TreeViewItem)ItemContainerGenerator.Index.ContainerFromItem(e.NewItems[0]); + + container?.BringIntoView(); } - } - if (oldItem != _selectedItem) - { - // Fire the SelectionChanged event - List removed = new List(); - if (oldItem != null) + added = e.NewItems; + + break; + case NotifyCollectionChangedAction.Remove: + + if (!_syncingSelectedItems) { - removed.Add(oldItem); + if (SelectedItems.Count == 0) + { + SelectedItem = null; + } + else + { + var selectedIndex = SelectedItems.IndexOf(_selectedItem); + + if (selectedIndex == -1) + { + var old = _selectedItem; + _selectedItem = SelectedItems[0]; + + RaisePropertyChanged(SelectedItemProperty, old, _selectedItem); + } + } } - List added = new List(); - if (_selectedItem != null) + foreach (var item in e.OldItems) { - added.Add(_selectedItem); + MarkItemSelected(item, false); } - var changed = new SelectionChangedEventArgs( - SelectedItemChangedEvent, - added, - removed); - RaiseEvent(changed); - } + removed = e.OldItems; + + break; + case NotifyCollectionChangedAction.Reset: + + foreach (IControl container in ItemContainerGenerator.Index.Containers) + { + MarkContainerSelected(container, false); + } + + if (SelectedItems.Count > 0) + { + SelectedItemsAdded(SelectedItems); + + added = SelectedItems; + } + else if (!_syncingSelectedItems) + { + SelectedItem = null; + } + + break; + case NotifyCollectionChangedAction.Replace: + + foreach (var item in e.OldItems) + { + MarkItemSelected(item, false); + } + + foreach (var item in e.NewItems) + { + MarkItemSelected(item, true); + } + + if (SelectedItem != SelectedItems[0] && !_syncingSelectedItems) + { + var oldItem = SelectedItem; + var item = SelectedItems[0]; + _selectedItem = item; + RaisePropertyChanged(SelectedItemProperty, oldItem, item); + } + + added = e.NewItems; + removed = e.OldItems; + + break; + } + + if (added?.Count > 0 || removed?.Count > 0) + { + var changed = new SelectionChangedEventArgs( + SelectingItemsControl.SelectionChangedEvent, + added ?? Empty, + removed ?? Empty); + RaiseEvent(changed); + } + } + + private void MarkItemSelected(object item, bool selected) + { + var container = ItemContainerGenerator.Index.ContainerFromItem(item); + + MarkContainerSelected(container, selected); + } + + private void SelectedItemsAdded(IList items) + { + if (items.Count == 0) + { + return; + } + + foreach (object item in items) + { + MarkItemSelected(item, true); + } + + if (SelectedItem == null && !_syncingSelectedItems) + { + SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]); + } + } + + /// + /// Unsubscribes from the CollectionChanged event, if any. + /// + private void UnsubscribeFromSelectedItems() + { + if (_selectedItems is INotifyCollectionChanged incc) + { + incc.CollectionChanged -= SelectedItemsCollectionChanged; } } - (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction) + (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, + NavigationDirection direction) { if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) { @@ -142,10 +346,8 @@ namespace Avalonia.Controls ItemContainerGenerator.ContainerFromIndex(0); return (true, result); } - else - { - return (true, null); - } + + return (true, null); } return (false, null); @@ -186,7 +388,7 @@ namespace Avalonia.Controls if (SelectedItem != null) { var next = GetContainerInDirection( - GetContainerFromEventSource(e.Source) as TreeViewItem, + GetContainerFromEventSource(e.Source), direction.Value, true); @@ -201,6 +403,18 @@ namespace Avalonia.Controls SelectedItem = ElementAt(Items, 0); } } + + if (!e.Handled) + { + var keymap = AvaloniaLocator.Current.GetService(); + bool Match(List gestures) => gestures.Any(g => g.Matches(e)); + + if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll)) + { + SelectingItemsControl.SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Items); + e.Handled = true; + } + } } private TreeViewItem GetContainerInDirection( @@ -208,17 +422,9 @@ namespace Avalonia.Controls NavigationDirection direction, bool intoChildren) { - IItemContainerGenerator parentGenerator; + IItemContainerGenerator parentGenerator = GetParentContainerGenerator(from); - if (from?.Parent is TreeView treeView) - { - parentGenerator = treeView.ItemContainerGenerator; - } - else if (from?.Parent is TreeViewItem item) - { - parentGenerator = item.ItemContainerGenerator; - } - else + if (parentGenerator == null) { return null; } @@ -257,6 +463,7 @@ namespace Avalonia.Controls { return GetContainerInDirection(parentItem, direction, false); } + break; } @@ -293,18 +500,182 @@ namespace Avalonia.Controls { var item = ItemContainerGenerator.Index.ItemFromContainer(container); - if (item != null) + if (item == null) { - if (SelectedItem != null) + return; + } + + IControl selectedContainer = null; + + if (SelectedItem != null) + { + selectedContainer = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem); + } + + var mode = SelectionMode; + var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0; + var multi = (mode & SelectionMode.Multiple) != 0; + var range = multi && selectedContainer != null && rangeModifier; + + if (!toggle && !range) + { + SelectSingleItem(item); + } + else if (multi && range) + { + SelectingItemsControl.SynchronizeItems( + SelectedItems, + GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem)); + } + else + { + var i = SelectedItems.IndexOf(item); + + if (i != -1) + { + SelectedItems.Remove(item); + } + else + { + if (multi) + { + SelectedItems.Add(item); + } + else + { + SelectedItem = item; + } + } + } + } + + private static IItemContainerGenerator GetParentContainerGenerator(TreeViewItem item) + { + if (item == null) + { + return null; + } + + switch (item.Parent) + { + case TreeView treeView: + return treeView.ItemContainerGenerator; + case TreeViewItem treeViewItem: + return treeViewItem.ItemContainerGenerator; + default: + return null; + } + } + + /// + /// Find which node is first in hierarchy. + /// + /// Search root. + /// Nodes to find. + /// Node to find. + /// Found first node. + private static TreeViewItem FindFirstNode(TreeView treeView, TreeViewItem nodeA, TreeViewItem nodeB) + { + return FindInContainers(treeView.ItemContainerGenerator, nodeA, nodeB); + } + + private static TreeViewItem FindInContainers(ITreeItemContainerGenerator containerGenerator, + TreeViewItem nodeA, + TreeViewItem nodeB) + { + IEnumerable containers = containerGenerator.Containers; + + foreach (ItemContainerInfo container in containers) + { + TreeViewItem node = FindFirstNode(container.ContainerControl as TreeViewItem, nodeA, nodeB); + + if (node != null) + { + return node; + } + } + + return null; + } + + private static TreeViewItem FindFirstNode(TreeViewItem node, TreeViewItem nodeA, TreeViewItem nodeB) + { + if (node == null) + { + return null; + } + + TreeViewItem match = node == nodeA ? nodeA : node == nodeB ? nodeB : null; + + if (match != null) + { + return match; + } + + return FindInContainers(node.ItemContainerGenerator, nodeA, nodeB); + } + + /// + /// Returns all items that belong to containers between and . + /// The range is inclusive. + /// + /// From container. + /// To container. + private List GetItemsInRange(TreeViewItem from, TreeViewItem to) + { + var items = new List(); + + if (from == null || to == null) + { + return items; + } + + TreeViewItem firstItem = FindFirstNode(this, from, to); + + if (firstItem == null) + { + return items; + } + + bool wasReversed = false; + + if (firstItem == to) + { + var temp = from; + + from = to; + to = temp; + + wasReversed = true; + } + + TreeViewItem node = from; + + while (node != to) + { + var item = ItemContainerGenerator.Index.ItemFromContainer(node); + + if (item != null) { - var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem); - MarkContainerSelected(old, false); + items.Add(item); } - SelectedItem = item; + node = GetContainerInDirection(node, NavigationDirection.Down, true); + } + + var toItem = ItemContainerGenerator.Index.ItemFromContainer(to); - MarkContainerSelected(container, true); + if (toItem != null) + { + items.Add(toItem); + } + + if (wasReversed) + { + items.Reverse(); } + + return items; } /// @@ -341,7 +712,7 @@ namespace Avalonia.Controls /// /// The control that raised the event. /// The container or null if the event did not originate in a container. - protected IControl GetContainerFromEventSource(IInteractive eventSource) + protected TreeViewItem GetContainerFromEventSource(IInteractive eventSource) { var item = ((IVisual)eventSource).GetSelfAndVisualAncestors() .OfType() @@ -349,7 +720,7 @@ namespace Avalonia.Controls if (item != null) { - if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index) + if (item.ItemContainerGenerator.Index == ItemContainerGenerator.Index) { return item; } @@ -367,21 +738,23 @@ namespace Avalonia.Controls { var selectedItem = SelectedItem; - if (selectedItem != null) + if (selectedItem == null) { - foreach (var container in e.Containers) - { - if (container.Item == selectedItem) - { - ((TreeViewItem)container.ContainerControl).IsSelected = true; + return; + } - if (AutoScrollToSelectedItem) - { - Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView); - } + foreach (var container in e.Containers) + { + if (container.Item == selectedItem) + { + ((TreeViewItem)container.ContainerControl).IsSelected = true; - break; + if (AutoScrollToSelectedItem) + { + Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView); } + + break; } } } @@ -393,18 +766,18 @@ namespace Avalonia.Controls /// Whether the control is selected private void MarkContainerSelected(IControl container, bool selected) { - if (container != null) + if (container == null) { - var selectable = container as ISelectable; + return; + } - if (selectable != null) - { - selectable.IsSelected = selected; - } - else - { - ((IPseudoClasses)container.Classes).Set(":selected", selected); - } + if (container is ISelectable selectable) + { + selectable.IsSelected = selected; + } + else + { + container.Classes.Set(":selected", selected); } } } diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 726e086d9c..e364d5de0b 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -163,6 +163,8 @@ namespace Avalonia this.Log().Info($"Ready to show {view} with autowired {viewModel}."); view.ViewModel = viewModel; + if (view is IStyledElement styled) + styled.DataContext = viewModel; UpdateContent(view); } diff --git a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs index fb909dd58d..8e17dbccfd 100644 --- a/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs +++ b/src/Avalonia.Remote.Protocol/DefaultMessageTypeResolver.cs @@ -12,7 +12,7 @@ namespace Avalonia.Remote.Protocol public DefaultMessageTypeResolver(params Assembly[] assemblies) { foreach (var asm in - (assemblies ?? new Assembly[0]).Concat(new[] + (assemblies ?? Array.Empty()).Concat(new[] {typeof(AvaloniaRemoteMessageGuidAttribute).GetTypeInfo().Assembly})) { foreach (var t in asm.ExportedTypes) diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 5593112bd6..524ec09ca9 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -806,7 +806,7 @@ namespace Metsys.Bson { return Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType)); } - if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null) != null) + if (type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null) != null) { return Activator.CreateInstance(type); } @@ -853,7 +853,7 @@ namespace Metsys.Bson return (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(keyType, valueType)); } - if (dictionaryType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null) != null) + if (dictionaryType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null) != null) { return (IDictionary)Activator.CreateInstance(dictionaryType); } diff --git a/src/Avalonia.Styling/Styling/OrSelector.cs b/src/Avalonia.Styling/Styling/OrSelector.cs new file mode 100644 index 0000000000..58c5c778fb --- /dev/null +++ b/src/Avalonia.Styling/Styling/OrSelector.cs @@ -0,0 +1,131 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; + +namespace Avalonia.Styling +{ + /// + /// The OR style selector. + /// + internal class OrSelector : Selector + { + private readonly IReadOnlyList _selectors; + private string _selectorString; + private Type _targetType; + + /// + /// Initializes a new instance of the class. + /// + /// The selectors to OR. + public OrSelector(IReadOnlyList selectors) + { + Contract.Requires(selectors != null); + Contract.Requires(selectors.Count > 1); + + _selectors = selectors; + } + + /// + public override bool InTemplate => false; + + /// + public override bool IsCombinator => false; + + /// + public override Type TargetType + { + get + { + if (_targetType == null) + { + _targetType = EvaluateTargetType(); + } + + return _targetType; + } + } + + /// + public override string ToString() + { + if (_selectorString == null) + { + _selectorString = string.Join(", ", _selectors); + } + + return _selectorString; + } + + protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + { + var activators = new List>(); + var neverThisInstance = false; + + foreach (var selector in _selectors) + { + var match = selector.Match(control, subscribe); + + switch (match.Result) + { + case SelectorMatchResult.AlwaysThisType: + case SelectorMatchResult.AlwaysThisInstance: + return match; + case SelectorMatchResult.NeverThisInstance: + neverThisInstance = true; + break; + case SelectorMatchResult.Sometimes: + activators.Add(match.Activator); + break; + } + } + + if (activators.Count > 1) + { + return new SelectorMatch(StyleActivator.Or(activators)); + } + else if (activators.Count == 1) + { + return new SelectorMatch(activators[0]); + } + else if (neverThisInstance) + { + return SelectorMatch.NeverThisInstance; + } + else + { + return SelectorMatch.NeverThisType; + } + } + + protected override Selector MovePrevious() => null; + + private Type EvaluateTargetType() + { + var result = default(Type); + + foreach (var selector in _selectors) + { + if (selector.TargetType == null) + { + return null; + } + else if (result == null) + { + result = selector.TargetType; + } + else + { + while (!result.IsAssignableFrom(selector.TargetType)) + { + result = result.BaseType; + } + } + } + + return result; + } + } +} + diff --git a/src/Avalonia.Styling/Styling/Selectors.cs b/src/Avalonia.Styling/Styling/Selectors.cs index deb677e04c..3e7a30d389 100644 --- a/src/Avalonia.Styling/Styling/Selectors.cs +++ b/src/Avalonia.Styling/Styling/Selectors.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Linq; namespace Avalonia.Styling { @@ -137,6 +139,26 @@ namespace Avalonia.Styling return previous.OfType(typeof(T)); } + /// + /// Returns a selector which ORs selectors. + /// + /// The selectors to be OR'd. + /// The selector. + public static Selector Or(params Selector[] selectors) + { + return new OrSelector(selectors); + } + + /// + /// Returns a selector which ORs selectors. + /// + /// The selectors to be OR'd. + /// The selector. + public static Selector Or(IReadOnlyList selectors) + { + return new OrSelector(selectors); + } + /// /// Returns a selector which matches a control with the specified property value. /// diff --git a/src/Avalonia.Themes.Default/Separator.xaml b/src/Avalonia.Themes.Default/Separator.xaml index 6312a14df5..cf0db16ee6 100644 --- a/src/Avalonia.Themes.Default/Separator.xaml +++ b/src/Avalonia.Themes.Default/Separator.xaml @@ -11,13 +11,7 @@ - - -