Browse Source

Merge branch 'master' into datagrid-grouping-bug

pull/5610/head
Jumar Macato 6 years ago
committed by GitHub
parent
commit
5dc2d2f1fa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Avalonia.Base/Data/Converters/BoolConverters.cs
  2. 70
      src/Avalonia.Controls/ComboBox.cs
  3. 67
      src/Avalonia.Controls/Control.cs
  4. 45
      src/Avalonia.Controls/Window.cs
  5. 21
      src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs
  6. 226
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  7. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  8. 51
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PseudoClassViewModel.cs
  9. 27
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  10. 59
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs
  11. 43
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs
  12. 7
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  13. 124
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  14. 10
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  15. 2
      src/Avalonia.Layout/ElementManager.cs
  16. 10
      src/Avalonia.Layout/UniformGridLayoutState.cs
  17. 4
      src/Avalonia.Styling/ApiCompatBaseline.txt
  18. 21
      src/Avalonia.Styling/Diagnostics/StyleDiagnostics.cs
  19. 17
      src/Avalonia.Styling/Diagnostics/StyledElementExtensions.cs
  20. 12
      src/Avalonia.Styling/StyledElement.cs
  21. 5
      src/Avalonia.Styling/Styling/IStyleInstance.cs
  22. 9
      src/Avalonia.Styling/Styling/StyleInstance.cs
  23. 14
      src/Avalonia.Visuals/Visual.cs
  24. 36
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  25. 124
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  26. 6
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs

6
src/Avalonia.Base/Data/Converters/BoolConverters.cs

@ -12,5 +12,11 @@ namespace Avalonia.Data.Converters
/// </summary> /// </summary>
public static readonly IMultiValueConverter And = public static readonly IMultiValueConverter And =
new FuncMultiValueConverter<bool, bool>(x => x.All(y => y)); new FuncMultiValueConverter<bool, bool>(x => x.All(y => y));
/// <summary>
/// A multi-value converter that returns true if any of the inputs is true.
/// </summary>
public static readonly IMultiValueConverter Or =
new FuncMultiValueConverter<bool, bool>(x => x.Any(y => y));
} }
} }

70
src/Avalonia.Controls/ComboBox.cs

@ -10,6 +10,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -76,6 +77,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty = public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>(); ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
/// <summary>
/// Defines the <see cref="IsTextSearchEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<ComboBox, bool>(nameof(IsTextSearchEnabled), true);
private string _textSearchTerm = string.Empty;
private DispatcherTimer _textSearchTimer;
private bool _isDropDownOpen; private bool _isDropDownOpen;
private Popup _popup; private Popup _popup;
private object _selectionBoxItem; private object _selectionBoxItem;
@ -164,6 +173,15 @@ namespace Avalonia.Controls
set { SetValue(VerticalContentAlignmentProperty, value); } set { SetValue(VerticalContentAlignmentProperty, value); }
} }
/// <summary>
/// Gets or sets a value that specifies whether a user can jump to a value by typing.
/// </summary>
public bool IsTextSearchEnabled
{
get { return GetValue(IsTextSearchEnabledProperty); }
set { SetValue(IsTextSearchEnabledProperty, value); }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {
@ -229,6 +247,32 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override void OnTextInput(TextInputEventArgs e)
{
if (!IsTextSearchEnabled || e.Handled)
return;
StopTextSearchTimer();
_textSearchTerm += e.Text;
bool match(ItemContainerInfo info) =>
info.ContainerControl is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
var info = ItemContainerGenerator.Containers.FirstOrDefault(match);
if (info != null)
{
SelectedIndex = info.Index;
}
StartTextSearchTimer();
e.Handled = true;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e) protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{ {
@ -426,5 +470,31 @@ namespace Avalonia.Controls
SelectedIndex = prev; SelectedIndex = prev;
} }
private void StartTextSearchTimer()
{
_textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_textSearchTimer.Tick += TextSearchTimer_Tick;
_textSearchTimer.Start();
}
private void StopTextSearchTimer()
{
if (_textSearchTimer == null)
{
return;
}
_textSearchTimer.Stop();
_textSearchTimer.Tick -= TextSearchTimer_Tick;
_textSearchTimer = null;
}
private void TextSearchTimer_Tick(object sender, EventArgs e)
{
_textSearchTerm = string.Empty;
StopTextSearchTimer();
}
} }
} }

67
src/Avalonia.Controls/Control.cs

@ -8,6 +8,8 @@ using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
@ -23,20 +25,20 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="FocusAdorner"/> property. /// Defines the <see cref="FocusAdorner"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ITemplate<IControl>> FocusAdornerProperty = public static readonly StyledProperty<ITemplate<IControl>?> FocusAdornerProperty =
AvaloniaProperty.Register<Control, ITemplate<IControl>>(nameof(FocusAdorner)); AvaloniaProperty.Register<Control, ITemplate<IControl>?>(nameof(FocusAdorner));
/// <summary> /// <summary>
/// Defines the <see cref="Tag"/> property. /// Defines the <see cref="Tag"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<object> TagProperty = public static readonly StyledProperty<object?> TagProperty =
AvaloniaProperty.Register<Control, object>(nameof(Tag)); AvaloniaProperty.Register<Control, object?>(nameof(Tag));
/// <summary> /// <summary>
/// Defines the <see cref="ContextMenu"/> property. /// Defines the <see cref="ContextMenu"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ContextMenu> ContextMenuProperty = public static readonly StyledProperty<ContextMenu?> ContextMenuProperty =
AvaloniaProperty.Register<Control, ContextMenu>(nameof(ContextMenu)); AvaloniaProperty.Register<Control, ContextMenu?>(nameof(ContextMenu));
/// <summary> /// <summary>
/// Event raised when an element wishes to be scrolled into view. /// Event raised when an element wishes to be scrolled into view.
@ -44,16 +46,16 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent = public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble); RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private DataTemplates _dataTemplates; private DataTemplates? _dataTemplates;
private IControl _focusAdorner; private IControl? _focusAdorner;
/// <summary> /// <summary>
/// Gets or sets the control's focus adorner. /// Gets or sets the control's focus adorner.
/// </summary> /// </summary>
public ITemplate<IControl> FocusAdorner public ITemplate<IControl>? FocusAdorner
{ {
get { return GetValue(FocusAdornerProperty); } get => GetValue(FocusAdornerProperty);
set { SetValue(FocusAdornerProperty, value); } set => SetValue(FocusAdornerProperty, value);
} }
/// <summary> /// <summary>
@ -63,27 +65,27 @@ namespace Avalonia.Controls
/// Each control may define data templates which are applied to the control itself and its /// Each control may define data templates which are applied to the control itself and its
/// children. /// children.
/// </remarks> /// </remarks>
public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); public DataTemplates DataTemplates => _dataTemplates ??= new DataTemplates();
/// <summary> /// <summary>
/// Gets or sets a context menu to the control. /// Gets or sets a context menu to the control.
/// </summary> /// </summary>
public ContextMenu ContextMenu public ContextMenu? ContextMenu
{ {
get { return GetValue(ContextMenuProperty); } get => GetValue(ContextMenuProperty);
set { SetValue(ContextMenuProperty, value); } set => SetValue(ContextMenuProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets a user-defined object attached to the control. /// Gets or sets a user-defined object attached to the control.
/// </summary> /// </summary>
public object Tag public object? Tag
{ {
get { return GetValue(TagProperty); } get => GetValue(TagProperty);
set { SetValue(TagProperty, value); } set => SetValue(TagProperty, value);
} }
public new IControl Parent => (IControl)base.Parent; public new IControl? Parent => (IControl?)base.Parent;
/// <inheritdoc/> /// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
@ -106,15 +108,10 @@ namespace Avalonia.Controls
{ {
var c = i as IControl; var c = i as IControl;
if (c?.IsInitialized == false) if (c?.IsInitialized == false && c is ISupportInitialize init)
{ {
var init = c as ISupportInitialize; init.BeginInit();
init.EndInit();
if (init != null)
{
init.BeginInit();
init.EndInit();
}
} }
} }
} }
@ -131,10 +128,7 @@ namespace Avalonia.Controls
/// Gets the element that receives the focus adorner. /// Gets the element that receives the focus adorner.
/// </summary> /// </summary>
/// <returns>The control that receives the focus adorner.</returns> /// <returns>The control that receives the focus adorner.</returns>
protected virtual IControl GetTemplateFocusTarget() protected virtual IControl? GetTemplateFocusTarget() => this;
{
return this;
}
/// <inheritdoc/> /// <inheritdoc/>
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -173,15 +167,10 @@ namespace Avalonia.Controls
} }
} }
if (_focusAdorner != null) if (_focusAdorner != null && GetTemplateFocusTarget() is Visual target)
{ {
var target = (Visual)GetTemplateFocusTarget(); AdornerLayer.SetAdornedElement((Visual)_focusAdorner, target);
adornerLayer.Children.Add(_focusAdorner);
if (target != null)
{
AdornerLayer.SetAdornedElement((Visual)_focusAdorner, target);
adornerLayer.Children.Add(_focusAdorner);
}
} }
} }
} }

45
src/Avalonia.Controls/Window.cs

@ -4,10 +4,7 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Chrome;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
@ -482,10 +479,9 @@ namespace Avalonia.Controls
try try
{ {
if (!ignoreCancel && HandleClosing()) if (!ignoreCancel && ShouldCancelClose())
{ {
close = false; close = false;
return;
} }
} }
finally finally
@ -497,11 +493,25 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Handles a closing notification from <see cref="IWindowImpl.Closing"/>.
/// <returns>true if closing is cancelled. Otherwise false.</returns>
/// </summary>
protected virtual bool HandleClosing()
{
if (!ShouldCancelClose())
{
CloseInternal();
return false;
}
return true;
}
private void CloseInternal() private void CloseInternal()
{ {
foreach (var (child, _) in _children.ToList()) foreach (var (child, _) in _children.ToList())
{ {
// if we HandleClosing() before then there will be no children.
child.CloseInternal(); child.CloseInternal();
} }
@ -515,20 +525,18 @@ namespace Avalonia.Controls
PlatformImpl?.Dispose(); PlatformImpl?.Dispose();
} }
/// <summary> private bool ShouldCancelClose(CancelEventArgs args = null)
/// Handles a closing notification from <see cref="IWindowImpl.Closing"/>.
/// </summary>
protected virtual bool HandleClosing()
{ {
if (args is null)
{
args = new CancelEventArgs();
}
bool canClose = true; bool canClose = true;
foreach (var (child, _) in _children.ToList()) foreach (var (child, _) in _children.ToList())
{ {
if (!child.HandleClosing()) if (child.ShouldCancelClose(args))
{
child.CloseInternal();
}
else
{ {
canClose = false; canClose = false;
} }
@ -536,15 +544,12 @@ namespace Avalonia.Controls
if (canClose) if (canClose)
{ {
var args = new CancelEventArgs();
OnClosing(args); OnClosing(args);
return args.Cancel; return args.Cancel;
} }
else
{ return true;
return !canClose;
}
} }
protected virtual void HandleWindowStateChanged(WindowState state) protected virtual void HandleWindowStateChanged(WindowState state)

21
src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs

@ -0,0 +1,21 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Avalonia.Diagnostics.Converters
{
internal class BoolToOpacityConverter : IValueConverter
{
public double Opacity { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1d : Opacity;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

226
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -1,8 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
@ -12,6 +19,10 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IVisual _control; private readonly IVisual _control;
private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex; private readonly IDictionary<object, List<PropertyViewModel>> _propertyIndex;
private AvaloniaPropertyViewModel _selectedProperty; private AvaloniaPropertyViewModel _selectedProperty;
private string _styleFilter;
private bool _snapshotStyles;
private bool _showInactiveStyles;
private string _styleStatus;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
{ {
@ -43,20 +54,160 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
ao.PropertyChanged += ControlPropertyChanged; ao.PropertyChanged += ControlPropertyChanged;
} }
AppliedStyles = new ObservableCollection<StyleViewModel>();
PseudoClasses = new ObservableCollection<PseudoClassViewModel>();
if (control is StyledElement styledElement)
{
styledElement.Classes.CollectionChanged += OnClassesChanged;
var pseudoClassAttributes = styledElement.GetType().GetCustomAttributes<PseudoClassesAttribute>(true);
foreach (var classAttribute in pseudoClassAttributes)
{
foreach (var className in classAttribute.PseudoClasses)
{
PseudoClasses.Add(new PseudoClassViewModel(className, styledElement));
}
}
var styleDiagnostics = styledElement.GetStyleDiagnostics();
foreach (var appliedStyle in styleDiagnostics.AppliedStyles)
{
var styleSource = appliedStyle.Source;
var setters = new List<SetterViewModel>();
if (styleSource is Style style)
{
foreach (var setter in style.Setters)
{
if (setter is Setter regularSetter)
{
var setterValue = regularSetter.Value;
var resourceInfo = GetResourceInfo(setterValue);
SetterViewModel setterVm;
if (resourceInfo.HasValue)
{
var resourceKey = resourceInfo.Value.resourceKey;
var resourceValue = styledElement.FindResource(resourceKey);
setterVm = new ResourceSetterViewModel(regularSetter.Property, resourceKey, resourceValue, resourceInfo.Value.isDynamic);
}
else
{
setterVm = new SetterViewModel(regularSetter.Property, setterValue);
}
setters.Add(setterVm);
}
}
AppliedStyles.Add(new StyleViewModel(appliedStyle, style.Selector?.ToString() ?? "No selector", setters));
}
}
UpdateStyles();
}
}
private (object resourceKey, bool isDynamic)? GetResourceInfo(object value)
{
if (value is StaticResourceExtension staticResource)
{
return (staticResource.ResourceKey, false);
}
else if (value is DynamicResourceExtension dynamicResource)
{
return (dynamicResource.ResourceKey, true);
}
return null;
} }
public TreePageViewModel TreePage { get; } public TreePageViewModel TreePage { get; }
public DataGridCollectionView PropertiesView { get; } public DataGridCollectionView PropertiesView { get; }
public ObservableCollection<StyleViewModel> AppliedStyles { get; }
public ObservableCollection<PseudoClassViewModel> PseudoClasses { get; }
public AvaloniaPropertyViewModel SelectedProperty public AvaloniaPropertyViewModel SelectedProperty
{ {
get => _selectedProperty; get => _selectedProperty;
set => RaiseAndSetIfChanged(ref _selectedProperty, value); set => RaiseAndSetIfChanged(ref _selectedProperty, value);
} }
public string StyleFilter
{
get => _styleFilter;
set => RaiseAndSetIfChanged(ref _styleFilter, value);
}
public bool SnapshotStyles
{
get => _snapshotStyles;
set => RaiseAndSetIfChanged(ref _snapshotStyles, value);
}
public bool ShowInactiveStyles
{
get => _showInactiveStyles;
set => RaiseAndSetIfChanged(ref _showInactiveStyles, value);
}
public string StyleStatus
{
get => _styleStatus;
set => RaiseAndSetIfChanged(ref _styleStatus, value);
}
public ControlLayoutViewModel Layout { get; } public ControlLayoutViewModel Layout { get; }
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(StyleFilter))
{
UpdateStyleFilters();
}
else if (e.PropertyName == nameof(SnapshotStyles))
{
if (!SnapshotStyles)
{
UpdateStyles();
}
}
}
private void UpdateStyleFilters()
{
var filter = StyleFilter;
bool hasFilter = !string.IsNullOrEmpty(filter);
foreach (var style in AppliedStyles)
{
var hasVisibleSetter = false;
foreach (var setter in style.Setters)
{
setter.IsVisible =
!hasFilter || setter.Name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0;
hasVisibleSetter |= setter.IsVisible;
}
style.IsVisible = hasVisibleSetter;
}
}
public void Dispose() public void Dispose()
{ {
if (_control is INotifyPropertyChanged inpc) if (_control is INotifyPropertyChanged inpc)
@ -68,6 +219,11 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
ao.PropertyChanged -= ControlPropertyChanged; ao.PropertyChanged -= ControlPropertyChanged;
} }
if (_control is StyledElement se)
{
se.Classes.CollectionChanged -= OnClassesChanged;
}
} }
private IEnumerable<PropertyViewModel> GetAvaloniaProperties(object o) private IEnumerable<PropertyViewModel> GetAvaloniaProperties(object o)
@ -129,6 +285,74 @@ namespace Avalonia.Diagnostics.ViewModels
property.Update(); property.Update();
} }
} }
if (!SnapshotStyles)
{
UpdateStyles();
}
}
private void OnClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!SnapshotStyles)
{
UpdateStyles();
}
}
private void UpdateStyles()
{
int activeCount = 0;
foreach (var style in AppliedStyles)
{
style.Update();
if (style.IsActive)
{
activeCount++;
}
}
var propertyBuckets = new Dictionary<AvaloniaProperty, List<SetterViewModel>>();
foreach (var style in AppliedStyles)
{
if (!style.IsActive)
{
continue;
}
foreach (var setter in style.Setters)
{
if (propertyBuckets.TryGetValue(setter.Property, out var setters))
{
foreach (var otherSetter in setters)
{
otherSetter.IsActive = false;
}
setter.IsActive = true;
setters.Add(setter);
}
else
{
setter.IsActive = true;
setters = new List<SetterViewModel> { setter };
propertyBuckets.Add(setter.Property, setters);
}
}
}
foreach (var pseudoClass in PseudoClasses)
{
pseudoClass.Update();
}
StyleStatus = $"Styles ({activeCount}/{AppliedStyles.Count} active)";
} }
private bool FilterProperty(object arg) private bool FilterProperty(object arg)

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -163,6 +163,14 @@ namespace Avalonia.Diagnostics.ViewModels
tree?.SelectControl(control); tree?.SelectControl(control);
} }
public void EnableSnapshotStyles(bool enable)
{
if (Content is TreePageViewModel treeVm && treeVm.Details != null)
{
treeVm.Details.SnapshotStyles = enable;
}
}
public void Dispose() public void Dispose()
{ {
KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged; KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged;

51
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PseudoClassViewModel.cs

@ -0,0 +1,51 @@
using Avalonia.Controls;
namespace Avalonia.Diagnostics.ViewModels
{
internal class PseudoClassViewModel : ViewModelBase
{
private readonly IPseudoClasses _pseudoClasses;
private readonly StyledElement _source;
private bool _isActive;
private bool _isUpdating;
public PseudoClassViewModel(string name, StyledElement source)
{
Name = name;
_source = source;
_pseudoClasses = _source.Classes;
Update();
}
public string Name { get; }
public bool IsActive
{
get => _isActive;
set
{
RaiseAndSetIfChanged(ref _isActive, value);
if (!_isUpdating)
{
_pseudoClasses.Set(Name, value);
}
}
}
public void Update()
{
try
{
_isUpdating = true;
IsActive = _source.Classes.Contains(Name);
}
finally
{
_isUpdating = false;
}
}
}
}

27
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs

@ -0,0 +1,27 @@
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels
{
internal class ResourceSetterViewModel : SetterViewModel
{
public object Key { get; }
public IBrush Tint { get; }
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object resourceValue, bool isDynamic) : base(property, resourceValue)
{
Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown;
}
public void CopyResourceKey()
{
if (Key is null)
{
return;
}
CopyToClipboard(Key.ToString());
}
}
}

59
src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs

@ -0,0 +1,59 @@
using Avalonia.Input.Platform;
namespace Avalonia.Diagnostics.ViewModels
{
internal class SetterViewModel : ViewModelBase
{
private bool _isActive;
private bool _isVisible;
public AvaloniaProperty Property { get; }
public string Name { get; }
public object Value { get; }
public bool IsActive
{
get => _isActive;
set => RaiseAndSetIfChanged(ref _isActive, value);
}
public bool IsVisible
{
get => _isVisible;
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public SetterViewModel(AvaloniaProperty property, object value)
{
Property = property;
Name = property.Name;
Value = value;
IsActive = true;
IsVisible = true;
}
public void CopyValue()
{
if (Value is null)
{
return;
}
CopyToClipboard(Value.ToString());
}
public void CopyPropertyName()
{
CopyToClipboard(Property.Name);
}
protected static void CopyToClipboard(string value)
{
var clipboard = AvaloniaLocator.Current.GetService<IClipboard>();
clipboard?.SetTextAsync(value);
}
}
}

43
src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs

@ -0,0 +1,43 @@
using System.Collections.Generic;
using Avalonia.Styling;
namespace Avalonia.Diagnostics.ViewModels
{
internal class StyleViewModel : ViewModelBase
{
private readonly IStyleInstance _styleInstance;
private bool _isActive;
private bool _isVisible;
public StyleViewModel(IStyleInstance styleInstance, string name, List<SetterViewModel> setters)
{
_styleInstance = styleInstance;
IsVisible = true;
Name = name;
Setters = setters;
Update();
}
public bool IsActive
{
get => _isActive;
set => RaiseAndSetIfChanged(ref _isActive, value);
}
public bool IsVisible
{
get => _isVisible;
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public string Name { get; }
public List<SetterViewModel> Setters { get; }
public void Update()
{
IsActive = _styleInstance.IsActive;
}
}
}

7
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -31,11 +31,18 @@ namespace Avalonia.Diagnostics.ViewModels
get => _selectedNode; get => _selectedNode;
private set private set
{ {
var oldDetails = Details;
if (RaiseAndSetIfChanged(ref _selectedNode, value)) if (RaiseAndSetIfChanged(ref _selectedNode, value))
{ {
Details = value != null ? Details = value != null ?
new ControlDetailsViewModel(this, value.Visual) : new ControlDetailsViewModel(this, value.Visual) :
null; null;
if (Details != null && oldDetails != null)
{
Details.StyleFilter = oldDetails.StyleFilter;
}
} }
} }
} }

124
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -2,7 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
xmlns:local="clr-namespace:Avalonia.Diagnostics.Views" xmlns:local="clr-namespace:Avalonia.Diagnostics.Views"
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"> xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"
x:Name="Main">
<UserControl.Resources> <UserControl.Resources>
<SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" /> <SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" />
@ -11,6 +13,7 @@
<SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" /> <SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" />
<SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" /> <SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" />
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" /> <SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
<conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/>
</UserControl.Resources> </UserControl.Resources>
<UserControl.Styles> <UserControl.Styles>
@ -105,7 +108,7 @@
<GridSplitter Grid.Column="1" /> <GridSplitter Grid.Column="1" />
<Grid Grid.Column="2" RowDefinitions="Auto,*" > <Grid Grid.Column="2" RowDefinitions="Auto,*, Auto,*,Auto" >
<TextBlock Grid.Row="0" Text="Layout Visualizer" Margin="4" /> <TextBlock Grid.Row="0" Text="Layout Visualizer" Margin="4" />
<Grid Grid.Row="1" x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto"> <Grid Grid.Row="1" x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto">
@ -148,7 +151,122 @@
<Rectangle x:Name="VerticalSizeEnd" /> <Rectangle x:Name="VerticalSizeEnd" />
</Canvas> </Canvas>
</Grid> </Grid>
<Grid Grid.Row="2" Margin="4" RowDefinitions="Auto,Auto">
<Grid Grid.Row="0" Margin="2" ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock FontWeight="Bold" Grid.Column="0" Text="{Binding StyleStatus}" VerticalAlignment="Center" />
<CheckBox Margin="2,0,0,0" Grid.Column="2" Content="Show inactive" IsChecked="{Binding ShowInactiveStyles}" ToolTip.Tip="Show styles that are currently inactive" />
<ToggleButton Margin="2,0,0,0" Grid.Column="3" ToolTip.Tip="Snapshot current styles (Alt+S/Alt+D to enable/disable within debugged window)" Content="Snapshot" IsChecked="{Binding SnapshotStyles}" />
</Grid>
<TextBox Grid.Row="1" Margin="2" Grid.Column="0" Watermark="Filter" Text="{Binding StyleFilter}" />
</Grid>
<ScrollViewer Grid.Row="3" HorizontalScrollBarVisibility="Disabled">
<ItemsControl Items="{Binding AppliedStyles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#6C6C6C" Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}">
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<MultiBinding Converter="{x:Static BoolConverters.Or}" >
<Binding Path="IsActive" />
<Binding Path="#Main.DataContext.ShowInactiveStyles" />
</MultiBinding>
<Binding Path="IsVisible" />
</MultiBinding>
</Border.IsVisible>
<Expander IsExpanded="True" Margin="0" Padding="8,0" ContentTransition="{x:Null}" >
<Expander.Header>
<TextBlock Grid.Row="0" Text="{Binding Name}" />
</Expander.Header>
<ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
<ItemsControl.DataTemplates>
<DataTemplate DataType="IBrush">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Background="{Binding}" Width="8" Height="8"/>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="Color">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Width="8" Height="8">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="vm:ResourceSetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy property name" Command="{Binding CopyPropertyName} "/>
<MenuItem Header="Copy value" Command="{Binding CopyValue} "/>
<MenuItem Header="Copy resource key" Command="{Binding CopyResourceKey}" />
</ContextMenu>
</Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/>
<TextBlock>(</TextBlock>
<Ellipse Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}"/>
<TextBlock FontStyle="Italic" Text="{Binding Key}" />
<TextBlock>)</TextBlock>
</StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" IsVisible="{Binding !IsActive}" />
</Panel>
</DataTemplate>
<DataTemplate DataType="vm:SetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy property name" Command="{Binding CopyPropertyName} "/>
<MenuItem Header="Copy value" Command="{Binding CopyValue} "/>
</ContextMenu>
</Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/>
</StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" VerticalAlignment="Center" IsVisible="{Binding !IsActive}" />
</Panel>
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Expander Header="Pseudo Classes" Grid.Row="4">
<ItemsControl Items="{Binding PseudoClasses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2" Content="{Binding Name}" IsChecked="{Binding IsActive, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</Grid> </Grid>
</Grid> </Grid>

10
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -90,6 +90,16 @@ namespace Avalonia.Diagnostics.Views
var vm = (MainViewModel)DataContext; var vm = (MainViewModel)DataContext;
vm.SelectControl((IControl)control); vm.SelectControl((IControl)control);
} }
}
else if (e.Modifiers == RawInputModifiers.Alt)
{
if (e.Key == Key.S || e.Key == Key.D)
{
var enable = e.Key == Key.S;
var vm = (MainViewModel)DataContext;
vm.EnableSnapshotStyles(enable);
}
} }
} }

2
src/Avalonia.Layout/ElementManager.cs

@ -207,7 +207,7 @@ namespace Avalonia.Layout
} }
} }
public bool IsIndexValidInData(int currentIndex) => currentIndex >= 0 && currentIndex < _context.ItemCount; public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context.ItemCount;
public ILayoutable GetRealizedElement(int dataIndex) public ILayoutable GetRealizedElement(int dataIndex)
{ {

10
src/Avalonia.Layout/UniformGridLayoutState.cs

@ -44,7 +44,7 @@ namespace Avalonia.Layout
Size availableSize, Size availableSize,
VirtualizingLayoutContext context, VirtualizingLayoutContext context,
double layoutItemWidth, double layoutItemWidth,
double LayoutItemHeight, double layoutItemHeight,
UniformGridLayoutItemsStretch stretch, UniformGridLayoutItemsStretch stretch,
Orientation orientation, Orientation orientation,
double minRowSpacing, double minRowSpacing,
@ -63,7 +63,7 @@ namespace Avalonia.Layout
if (realizedElement != null) if (realizedElement != null)
{ {
realizedElement.Measure(availableSize); realizedElement.Measure(availableSize);
SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); SetSize(realizedElement, layoutItemWidth, layoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
_cachedFirstElement = null; _cachedFirstElement = null;
} }
else else
@ -78,7 +78,7 @@ namespace Avalonia.Layout
_cachedFirstElement.Measure(availableSize); _cachedFirstElement.Measure(availableSize);
SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine); SetSize(_cachedFirstElement, layoutItemWidth, layoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
// See if we can move ownership to the flow algorithm. If we can, we do not need a local cache. // See if we can move ownership to the flow algorithm. If we can, we do not need a local cache.
bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement); bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement);
@ -93,7 +93,7 @@ namespace Avalonia.Layout
private void SetSize( private void SetSize(
ILayoutable element, ILayoutable element,
double layoutItemWidth, double layoutItemWidth,
double LayoutItemHeight, double layoutItemHeight,
Size availableSize, Size availableSize,
UniformGridLayoutItemsStretch stretch, UniformGridLayoutItemsStretch stretch,
Orientation orientation, Orientation orientation,
@ -107,7 +107,7 @@ namespace Avalonia.Layout
} }
EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth); EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth);
EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight); EffectiveItemHeight = (double.IsNaN(layoutItemHeight) ? element.DesiredSize.Height : layoutItemHeight);
var availableSizeMinor = orientation == Orientation.Horizontal ? availableSize.Width : availableSize.Height; var availableSizeMinor = orientation == Orientation.Horizontal ? availableSize.Width : availableSize.Height;
var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing; var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing;

4
src/Avalonia.Styling/ApiCompatBaseline.txt

@ -0,0 +1,4 @@
Compat issues with assembly Avalonia.Styling:
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Styling.IStyleInstance.IsActive' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Styling.IStyleInstance.IsActive.get()' is present in the implementation but not in the contract.
Total Issues: 2

21
src/Avalonia.Styling/Diagnostics/StyleDiagnostics.cs

@ -0,0 +1,21 @@
using System.Collections.Generic;
using Avalonia.Styling;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Contains information about style related diagnostics of a control.
/// </summary>
public class StyleDiagnostics
{
/// <summary>
/// Currently applied styles.
/// </summary>
public IReadOnlyList<IStyleInstance> AppliedStyles { get; }
public StyleDiagnostics(IReadOnlyList<IStyleInstance> appliedStyles)
{
AppliedStyles = appliedStyles;
}
}
}

17
src/Avalonia.Styling/Diagnostics/StyledElementExtensions.cs

@ -0,0 +1,17 @@
namespace Avalonia.Diagnostics
{
/// <summary>
/// Defines diagnostic extensions on <see cref="StyledElement"/>s.
/// </summary>
public static class StyledElementExtensions
{
/// <summary>
/// Gets a style diagnostics for a <see cref="StyledElement"/>.
/// </summary>
/// <param name="styledElement">The element.</param>
public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement)
{
return styledElement.GetStyleDiagnosticsInternal();
}
}
}

12
src/Avalonia.Styling/StyledElement.cs

@ -356,6 +356,18 @@ namespace Avalonia
} }
} }
internal StyleDiagnostics GetStyleDiagnosticsInternal()
{
IReadOnlyList<IStyleInstance>? appliedStyles = _appliedStyles;
if (appliedStyles is null)
{
appliedStyles = Array.Empty<IStyleInstance>();
}
return new StyleDiagnostics(appliedStyles);
}
/// <inheritdoc/> /// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {

5
src/Avalonia.Styling/Styling/IStyleInstance.cs

@ -14,6 +14,11 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
IStyle Source { get; } IStyle Source { get; }
/// <summary>
/// Gets a value indicating whether this style is active.
/// </summary>
bool IsActive { get; }
/// <summary> /// <summary>
/// Instructs the style to start acting upon the control. /// Instructs the style to start acting upon the control.
/// </summary> /// </summary>

9
src/Avalonia.Styling/Styling/StyleInstance.cs

@ -17,7 +17,6 @@ namespace Avalonia.Styling
private readonly List<IDisposable>? _animations; private readonly List<IDisposable>? _animations;
private readonly IStyleActivator? _activator; private readonly IStyleActivator? _activator;
private readonly Subject<bool>? _animationTrigger; private readonly Subject<bool>? _animationTrigger;
private bool _active;
public StyleInstance( public StyleInstance(
IStyle source, IStyle source,
@ -29,6 +28,7 @@ namespace Avalonia.Styling
Source = source ?? throw new ArgumentNullException(nameof(source)); Source = source ?? throw new ArgumentNullException(nameof(source));
Target = target ?? throw new ArgumentNullException(nameof(target)); Target = target ?? throw new ArgumentNullException(nameof(target));
_activator = activator; _activator = activator;
IsActive = _activator is null;
if (setters is object) if (setters is object)
{ {
@ -56,6 +56,7 @@ namespace Avalonia.Styling
} }
} }
public bool IsActive { get; private set; }
public IStyle Source { get; } public IStyle Source { get; }
public IStyleable Target { get; } public IStyleable Target { get; }
@ -104,15 +105,15 @@ namespace Avalonia.Styling
private void ActivatorChanged(bool value) private void ActivatorChanged(bool value)
{ {
if (_active != value) if (IsActive != value)
{ {
_active = value; IsActive = value;
_animationTrigger?.OnNext(value); _animationTrigger?.OnNext(value);
if (_setters is object) if (_setters is object)
{ {
if (_active) if (IsActive)
{ {
foreach (var setter in _setters) foreach (var setter in _setters)
{ {

14
src/Avalonia.Visuals/Visual.cs

@ -150,7 +150,7 @@ namespace Avalonia
public TransformedBounds? TransformedBounds => _transformedBounds; public TransformedBounds? TransformedBounds => _transformedBounds;
/// <summary> /// <summary>
/// Gets a value indicating whether the control should be clipped to its bounds. /// Gets or sets a value indicating whether the control should be clipped to its bounds.
/// </summary> /// </summary>
public bool ClipToBounds public bool ClipToBounds
{ {
@ -191,7 +191,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets a value indicating whether this control is visible. /// Gets or sets a value indicating whether this control is visible.
/// </summary> /// </summary>
public bool IsVisible public bool IsVisible
{ {
@ -200,7 +200,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the opacity of the control. /// Gets or sets the opacity of the control.
/// </summary> /// </summary>
public double Opacity public double Opacity
{ {
@ -209,7 +209,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the opacity mask of the control. /// Gets or sets the opacity mask of the control.
/// </summary> /// </summary>
public IBrush OpacityMask public IBrush OpacityMask
{ {
@ -218,7 +218,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the render transform of the control. /// Gets or sets the render transform of the control.
/// </summary> /// </summary>
public ITransform RenderTransform public ITransform RenderTransform
{ {
@ -227,7 +227,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the transform origin of the control. /// Gets or sets the transform origin of the control.
/// </summary> /// </summary>
public RelativePoint RenderTransformOrigin public RelativePoint RenderTransformOrigin
{ {
@ -236,7 +236,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the Z index of the control. /// Gets or sets the Z index of the control.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Controls with a higher <see cref="ZIndex"/> will appear in front of controls with /// Controls with a higher <see cref="ZIndex"/> will appear in front of controls with

36
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -1,7 +1,9 @@
using System.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -137,5 +139,39 @@ namespace Avalonia.Controls.UnitTests
Assert.True(other.IsFocused); Assert.True(other.IsFocused);
} }
} }
[Theory]
[InlineData(-1, 2, "c", "A item", "B item", "C item")]
[InlineData(0, 1, "b", "A item", "B item", "C item")]
[InlineData(2, 2, "x", "A item", "B item", "C item")]
public void TextSearch_Should_Have_Expected_SelectedIndex(
int initialSelectedIndex,
int expectedSelectedIndex,
string searchTerm,
params string[] items)
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
{
var target = new ComboBox
{
Template = GetTemplate(),
Items = items.Select(x => new ComboBoxItem { Content = x })
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = initialSelectedIndex;
var args = new TextInputEventArgs
{
Text = searchTerm,
RoutedEvent = InputElement.TextInputEvent
};
target.RaiseEvent(args);
Assert.Equal(expectedSelectedIndex, target.SelectedIndex);
}
}
} }
} }

124
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Layout;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -137,6 +136,129 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, count); Assert.Equal(1, count);
} }
} }
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Child_windows_should_be_closed_before_parent(bool programaticClose)
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var child = new Window();
int count = 0;
int windowClosing = 0;
int childClosing = 0;
int windowClosed = 0;
int childClosed = 0;
window.Closing += (sender, e) =>
{
count++;
windowClosing = count;
};
child.Closing += (sender, e) =>
{
count++;
childClosing = count;
};
window.Closed += (sender, e) =>
{
count++;
windowClosed = count;
};
child.Closed += (sender, e) =>
{
count++;
childClosed = count;
};
window.Show();
child.Show(window);
if (programaticClose)
{
window.Close();
}
else
{
var cancel = window.PlatformImpl.Closing();
Assert.Equal(false, cancel);
}
Assert.Equal(2, windowClosing);
Assert.Equal(1, childClosing);
Assert.Equal(4, windowClosed);
Assert.Equal(3, childClosed);
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Child_windows_must_not_close_before_parent_has_chance_to_Cancel_OSCloseButton(bool programaticClose)
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var child = new Window();
int count = 0;
int windowClosing = 0;
int childClosing = 0;
int windowClosed = 0;
int childClosed = 0;
window.Closing += (sender, e) =>
{
count++;
windowClosing = count;
e.Cancel = true;
};
child.Closing += (sender, e) =>
{
count++;
childClosing = count;
};
window.Closed += (sender, e) =>
{
count++;
windowClosed = count;
};
child.Closed += (sender, e) =>
{
count++;
childClosed = count;
};
window.Show();
child.Show(window);
if (programaticClose)
{
window.Close();
}
else
{
var cancel = window.PlatformImpl.Closing();
Assert.Equal(true, cancel);
}
Assert.Equal(2, windowClosing);
Assert.Equal(1, childClosing);
Assert.Equal(0, windowClosed);
Assert.Equal(0, childClosed);
}
}
[Fact] [Fact]
public void Showing_Should_Start_Renderer() public void Showing_Should_Start_Renderer()

6
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs

@ -117,9 +117,11 @@ namespace Avalonia.Markup.UnitTests.Parsers
var result = run(); var result = run();
result.Item1.Subscribe(x => { }); result.Item1.Subscribe(x => { });
GC.Collect(); // Mono trickery
GC.Collect(2);
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
GC.Collect(); GC.WaitForPendingFinalizers();
GC.Collect(2);
Assert.Null(result.Item2.Target); Assert.Null(result.Item2.Target);
} }

Loading…
Cancel
Save