diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 2a5acaea91..0e85332555 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -855,8 +855,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent if(type == Wheel) { - delta.X = [event scrollingDeltaX] / 50; - delta.Y = [event scrollingDeltaY] / 50; + auto speed = 5; + + if([event hasPreciseScrollingDeltas]) + { + speed = 50; + } + + delta.X = [event scrollingDeltaX] / speed; + delta.Y = [event scrollingDeltaY] / speed; if(delta.X == 0 && delta.Y == 0) { diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 335c460b40..e40509dfda 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -17,16 +17,4 @@ - - - - - - - - - - - - diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 4fc63ea054..958729e2e8 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -8,20 +8,9 @@ namespace ControlCatalog { public class App : Application { - private NativeMenu _recentMenu; - public override void Initialize() { AvaloniaXamlLoader.Load(this); - - Name = "Avalonia"; - - _recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu; - } - - public void OnOpenClicked(object sender, EventArgs args) - { - _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1))); } public override void OnFrameworkInitializationCompleted() diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 6088f2ec57..248f94082d 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -37,12 +37,20 @@ - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 7b0ee897c4..38cbde9d92 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -31,20 +31,28 @@ namespace ControlCatalog DataContext = new MainWindowViewModel(_notificationArea); _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu; + var mainMenu = this.FindControl("MainMenu"); + mainMenu.AttachedToVisualTree += MenuAttached; + } + + public void MenuAttached(object sender, VisualTreeAttachmentEventArgs e) + { + if (NativeMenu.GetIsNativeMenuExported(this) && sender is Menu mainMenu) + { + mainMenu.IsVisible = false; + } } public void OnOpenClicked(object sender, EventArgs args) { _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1))); } - + public void OnCloseClicked(object sender, EventArgs args) { Close(); } - - private void InitializeComponent() { // TODO: iOS does not support dynamically loading assemblies diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index adf0345a70..89e7653618 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -1,5 +1,7 @@ using System.Reactive; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; +using Avalonia.Dialogs; using ReactiveUI; namespace ControlCatalog.ViewModels @@ -26,6 +28,20 @@ namespace ControlCatalog.ViewModels { NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error)); }); + + AboutCommand = ReactiveCommand.CreateFromTask(async () => + { + var dialog = new AboutAvaloniaDialog(); + + var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; + + await dialog.ShowDialog(mainWindow); + }); + + ExitCommand = ReactiveCommand.Create(() => + { + (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); + }); } public IManagedNotificationManager NotificationManager @@ -39,5 +55,9 @@ namespace ControlCatalog.ViewModels public ReactiveCommand ShowManagedNotificationCommand { get; } public ReactiveCommand ShowNativeNotificationCommand { get; } + + public ReactiveCommand AboutCommand { get; } + + public ReactiveCommand ExitCommand { get; } } } diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 2450f1a3a1..0499907ab8 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -210,7 +210,11 @@ namespace Avalonia /// The value. public object GetValue(AvaloniaProperty property) { - Contract.Requires(property != null); + if (property is null) + { + throw new ArgumentNullException(nameof(property)); + } + VerifyAccess(); if (property.IsDirect) @@ -231,7 +235,10 @@ namespace Avalonia /// The value. public T GetValue(AvaloniaProperty property) { - Contract.Requires(property != null); + if (property is null) + { + throw new ArgumentNullException(nameof(property)); + } return (T)GetValue((AvaloniaProperty)property); } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 56ad241187..ac7d2c60af 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -28,6 +28,8 @@ namespace Avalonia private readonly Dictionary _metadata; private readonly Dictionary _metadataCache = new Dictionary(); + private bool _hasMetadataOverrides; + /// /// Initializes a new instance of the class. /// @@ -92,6 +94,9 @@ namespace Avalonia Id = source.Id; _defaultMetadata = source._defaultMetadata; + // Properties that have different owner can't use fast path for metadata. + _hasMetadataOverrides = true; + if (metadata != null) { _metadata.Add(ownerType, metadata); @@ -446,31 +451,12 @@ namespace Avalonia /// public PropertyMetadata GetMetadata(Type type) { - Contract.Requires(type != null); - - PropertyMetadata result; - Type currentType = type; - - if (_metadataCache.TryGetValue(type, out result)) + if (!_hasMetadataOverrides) { - return result; + return _defaultMetadata; } - while (currentType != null) - { - if (_metadata.TryGetValue(currentType, out result)) - { - _metadataCache[type] = result; - - return result; - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - _metadataCache[type] = _defaultMetadata; - - return _defaultMetadata; + return GetMetadataWithOverrides(type); } /// @@ -535,6 +521,39 @@ namespace Avalonia metadata.Merge(baseMetadata, this); _metadata.Add(type, metadata); _metadataCache.Clear(); + + _hasMetadataOverrides = true; + } + + private PropertyMetadata GetMetadataWithOverrides(Type type) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (_metadataCache.TryGetValue(type, out PropertyMetadata result)) + { + return result; + } + + Type currentType = type; + + while (currentType != null) + { + if (_metadata.TryGetValue(currentType, out result)) + { + _metadataCache[type] = result; + + return result; + } + + currentType = currentType.GetTypeInfo().BaseType; + } + + _metadataCache[type] = _defaultMetadata; + + return _defaultMetadata; } [DebuggerHidden] diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index ce60a0f0b9..59c6c47ed9 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -48,6 +48,14 @@ namespace Avalonia /// public event EventHandler ResourcesChanged; + /// + /// Creates an instance of the class. + /// + public Application() + { + Name = "Avalonia Application"; + } + /// /// Gets the current instance of the class. /// diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index d99648a158..c216037aba 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -19,8 +19,6 @@ namespace Avalonia.Controls.Generators { var tabItem = (TabItem)base.CreateContainer(item); - tabItem.ParentTabControl = Owner; - tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty]; if (tabItem.HeaderTemplate == null) diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs index 28b9b3a38f..a2fefa0548 100644 --- a/src/Avalonia.Controls/GridSplitter.cs +++ b/src/Avalonia.Controls/GridSplitter.cs @@ -1,210 +1,841 @@ -// 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. +// This source file is adapted from the Windows Presentation Foundation project. +// (https://github.com/dotnet/wpf/) +// +// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; +using Avalonia.Collections; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; -using Avalonia.VisualTree; +using Avalonia.Media; +using Avalonia.Utilities; namespace Avalonia.Controls { /// - /// Represents the control that redistributes space between columns or rows of a Grid control. + /// Represents the control that redistributes space between columns or rows of a control. /// - /// - /// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext. - /// public class GridSplitter : Thumb { - private List _definitions; + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty ResizeDirectionProperty = + AvaloniaProperty.Register(nameof(ResizeDirection)); - private Grid _grid; + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty ResizeBehaviorProperty = + AvaloniaProperty.Register(nameof(ResizeBehavior)); - private DefinitionBase _nextDefinition; + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty ShowsPreviewProperty = + AvaloniaProperty.Register(nameof(ShowsPreview)); - private Orientation _orientation; + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty KeyboardIncrementProperty = + AvaloniaProperty.Register(nameof(KeyboardIncrement), 10d); - private DefinitionBase _prevDefinition; + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty DragIncrementProperty = + AvaloniaProperty.Register(nameof(DragIncrement), 1d); - private void GetDeltaConstraints(out double min, out double max) + /// + /// Defines the property. + /// + public static readonly AvaloniaProperty> PreviewContentProperty = + AvaloniaProperty.Register>(nameof(PreviewContent)); + + private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast); + private static readonly Cursor s_rowSplitterCursor = new Cursor(StandardCursorType.SizeNorthSouth); + + private ResizeData _resizeData; + + /// + /// Indicates whether the Splitter resizes the Columns, Rows, or Both. + /// + public GridResizeDirection ResizeDirection { - var prevDefinitionLen = GetActualLength(_prevDefinition); - var prevDefinitionMin = GetMinLength(_prevDefinition); - var prevDefinitionMax = GetMaxLength(_prevDefinition); + get => GetValue(ResizeDirectionProperty); + set => SetValue(ResizeDirectionProperty, value); + } - var nextDefinitionLen = GetActualLength(_nextDefinition); - var nextDefinitionMin = GetMinLength(_nextDefinition); - var nextDefinitionMax = GetMaxLength(_nextDefinition); - // Determine the minimum and maximum the columns can be resized - min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); - max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); + /// + /// Indicates which Columns or Rows the Splitter resizes. + /// + public GridResizeBehavior ResizeBehavior + { + get => GetValue(ResizeBehaviorProperty); + set => SetValue(ResizeBehaviorProperty, value); } - protected override void OnDragDelta(VectorEventArgs e) + /// + /// Indicates whether to Preview the column resizing without updating layout. + /// + public bool ShowsPreview { - // WPF doesn't change anything when spliter is in the last row/column - // but resizes the splitter row/column when it's the first one. - // this is different, but more internally consistent. - if (_prevDefinition == null || _nextDefinition == null) - return; + get => GetValue(ShowsPreviewProperty); + set => SetValue(ShowsPreviewProperty, value); + } + + /// + /// The Distance to move the splitter when pressing the keyboard arrow keys. + /// + public double KeyboardIncrement + { + get => GetValue(KeyboardIncrementProperty); + set => SetValue(KeyboardIncrementProperty, value); + } + + /// + /// Restricts splitter to move a multiple of the specified units. + /// + public double DragIncrement + { + get => GetValue(DragIncrementProperty); + set => SetValue(DragIncrementProperty, value); + } + + /// + /// Gets or sets content that will be shown when is enabled and user starts resize operation. + /// + public ITemplate PreviewContent + { + get => GetValue(PreviewContentProperty); + set => SetValue(PreviewContentProperty, value); + } + + /// + /// Converts BasedOnAlignment direction to Rows, Columns, or Both depending on its width/height. + /// + internal GridResizeDirection GetEffectiveResizeDirection() + { + GridResizeDirection direction = ResizeDirection; + + if (direction != GridResizeDirection.Auto) + { + return direction; + } + + // When HorizontalAlignment is Left, Right or Center, resize Columns. + if (HorizontalAlignment != HorizontalAlignment.Stretch) + { + direction = GridResizeDirection.Columns; + } + else if (VerticalAlignment != VerticalAlignment.Stretch) + { + direction = GridResizeDirection.Rows; + } + else if (Bounds.Width <= Bounds.Height) // Fall back to Width vs Height. + { + direction = GridResizeDirection.Columns; + } + else + { + direction = GridResizeDirection.Rows; + } - var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; - double max; - double min; - GetDeltaConstraints(out min, out max); - delta = Math.Min(Math.Max(delta, min), max); + return direction; + } - var prevIsStar = IsStar(_prevDefinition); - var nextIsStar = IsStar(_nextDefinition); + /// + /// Convert BasedOnAlignment to Next/Prev/Both depending on alignment and Direction. + /// + private GridResizeBehavior GetEffectiveResizeBehavior(GridResizeDirection direction) + { + GridResizeBehavior resizeBehavior = ResizeBehavior; - if (prevIsStar && nextIsStar) + if (resizeBehavior == GridResizeBehavior.BasedOnAlignment) { - foreach (var definition in _definitions) + if (direction == GridResizeDirection.Columns) { - if (definition == _prevDefinition) + switch (HorizontalAlignment) { - SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta); + case HorizontalAlignment.Left: + resizeBehavior = GridResizeBehavior.PreviousAndCurrent; + break; + case HorizontalAlignment.Right: + resizeBehavior = GridResizeBehavior.CurrentAndNext; + break; + default: + resizeBehavior = GridResizeBehavior.PreviousAndNext; + break; } - else if (definition == _nextDefinition) + } + else + { + switch (VerticalAlignment) { - SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta); + case VerticalAlignment.Top: + resizeBehavior = GridResizeBehavior.PreviousAndCurrent; + break; + case VerticalAlignment.Bottom: + resizeBehavior = GridResizeBehavior.CurrentAndNext; + break; + default: + resizeBehavior = GridResizeBehavior.PreviousAndNext; + break; } - else if (IsStar(definition)) + } + } + + return resizeBehavior; + } + + /// + /// Removes preview adorner from the grid. + /// + private void RemovePreviewAdorner() + { + if (_resizeData.Adorner != null) + { + AdornerLayer layer = AdornerLayer.GetAdornerLayer(this); + layer.Children.Remove(_resizeData.Adorner); + } + } + + /// + /// Initialize the data needed for resizing. + /// + private void InitializeData(bool showsPreview) + { + // If not in a grid or can't resize, do nothing. + if (Parent is Grid grid) + { + GridResizeDirection resizeDirection = GetEffectiveResizeDirection(); + + // Setup data used for resizing. + _resizeData = new ResizeData + { + Grid = grid, + ShowsPreview = showsPreview, + ResizeDirection = resizeDirection, + SplitterLength = Math.Min(Bounds.Width, Bounds.Height), + ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection) + }; + + // Store the rows and columns to resize on drag events. + if (!SetupDefinitionsToResize()) + { + // Unable to resize, clear data. + _resizeData = null; + return; + } + + // Setup the preview in the adorner if ShowsPreview is true. + SetupPreviewAdorner(); + } + } + + /// + /// Returns true if GridSplitter can resize rows/columns. + /// + private bool SetupDefinitionsToResize() + { + int gridSpan = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ? + Grid.ColumnSpanProperty : + Grid.RowSpanProperty); + + if (gridSpan == 1) + { + var splitterIndex = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ? + Grid.ColumnProperty : + Grid.RowProperty); + + // Select the columns based on behavior. + int index1, index2; + + switch (_resizeData.ResizeBehavior) + { + case GridResizeBehavior.PreviousAndCurrent: + // Get current and previous. + index1 = splitterIndex - 1; + index2 = splitterIndex; + break; + case GridResizeBehavior.CurrentAndNext: + // Get current and next. + index1 = splitterIndex; + index2 = splitterIndex + 1; + break; + default: // GridResizeBehavior.PreviousAndNext. + // Get previous and next. + index1 = splitterIndex - 1; + index2 = splitterIndex + 1; + break; + } + + // Get count of rows/columns in the resize direction. + int count = _resizeData.ResizeDirection == GridResizeDirection.Columns ? + _resizeData.Grid.ColumnDefinitions.Count : + _resizeData.Grid.RowDefinitions.Count; + + if (index1 >= 0 && index2 < count) + { + _resizeData.SplitterIndex = splitterIndex; + + _resizeData.Definition1Index = index1; + _resizeData.Definition1 = GetGridDefinition(_resizeData.Grid, index1, _resizeData.ResizeDirection); + _resizeData.OriginalDefinition1Length = + _resizeData.Definition1.UserSizeValueCache; // Save Size if user cancels. + _resizeData.OriginalDefinition1ActualLength = GetActualLength(_resizeData.Definition1); + + _resizeData.Definition2Index = index2; + _resizeData.Definition2 = GetGridDefinition(_resizeData.Grid, index2, _resizeData.ResizeDirection); + _resizeData.OriginalDefinition2Length = + _resizeData.Definition2.UserSizeValueCache; // Save Size if user cancels. + _resizeData.OriginalDefinition2ActualLength = GetActualLength(_resizeData.Definition2); + + // Determine how to resize the columns. + bool isStar1 = IsStar(_resizeData.Definition1); + bool isStar2 = IsStar(_resizeData.Definition2); + + if (isStar1 && isStar2) { - SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars. + // If they are both stars, resize both. + _resizeData.SplitBehavior = SplitBehavior.Split; } + else + { + // One column is fixed width, resize the first one that is fixed. + _resizeData.SplitBehavior = !isStar1 ? SplitBehavior.Resize1 : SplitBehavior.Resize2; + } + + return true; } } - else if (prevIsStar) + + return false; + } + + /// + /// Create the preview adorner and add it to the adorner layer. + /// + private void SetupPreviewAdorner() + { + if (_resizeData.ShowsPreview) { - SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); + // Get the adorner layer and add an adorner to it. + var adornerLayer = AdornerLayer.GetAdornerLayer(_resizeData.Grid); + + var previewContent = PreviewContent; + + // Can't display preview. + if (adornerLayer == null) + { + return; + } + + IControl builtPreviewContent = previewContent?.Build(); + + _resizeData.Adorner = new PreviewAdorner(builtPreviewContent); + + AdornerLayer.SetAdornedElement(_resizeData.Adorner, this); + + adornerLayer.Children.Add(_resizeData.Adorner); + + // Get constraints on preview's translation. + GetDeltaConstraints(out _resizeData.MinChange, out _resizeData.MaxChange); } - else if (nextIsStar) + } + + protected override void OnPointerEnter(PointerEventArgs e) + { + base.OnPointerEnter(e); + + GridResizeDirection direction = GetEffectiveResizeDirection(); + + switch (direction) { - SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); + case GridResizeDirection.Columns: + Cursor = s_columnSplitterCursor; + break; + case GridResizeDirection.Rows: + Cursor = s_rowSplitterCursor; + break; } - else + } + + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + + if (_resizeData != null) { - SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta); - SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta); + CancelResize(); } } - private double GetActualLength(DefinitionBase definition) + protected override void OnDragStarted(VectorEventArgs e) { - if (definition == null) - return 0; - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; + base.OnDragStarted(e); + + // TODO: Looks like that sometimes thumb will raise multiple drag started events. + // Debug.Assert(_resizeData == null, "_resizeData is not null, DragCompleted was not called"); + + if (_resizeData != null) + { + return; + } + + InitializeData(ShowsPreview); } - private double GetMinLength(DefinitionBase definition) + protected override void OnDragDelta(VectorEventArgs e) { - if (definition == null) - return 0; - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; + base.OnDragDelta(e); + + if (_resizeData != null) + { + double horizontalChange = e.Vector.X; + double verticalChange = e.Vector.Y; + + // Round change to nearest multiple of DragIncrement. + double dragIncrement = DragIncrement; + horizontalChange = Math.Round(horizontalChange / dragIncrement) * dragIncrement; + verticalChange = Math.Round(verticalChange / dragIncrement) * dragIncrement; + + if (_resizeData.ShowsPreview) + { + // Set the Translation of the Adorner to the distance from the thumb. + if (_resizeData.ResizeDirection == GridResizeDirection.Columns) + { + _resizeData.Adorner.OffsetX = Math.Min( + Math.Max(horizontalChange, _resizeData.MinChange), + _resizeData.MaxChange); + } + else + { + _resizeData.Adorner.OffsetY = Math.Min( + Math.Max(verticalChange, _resizeData.MinChange), + _resizeData.MaxChange); + } + } + else + { + // Directly update the grid. + MoveSplitter(horizontalChange, verticalChange); + } + } } - private double GetMaxLength(DefinitionBase definition) + protected override void OnDragCompleted(VectorEventArgs e) { - if (definition == null) - return 0; - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; + base.OnDragCompleted(e); + + if (_resizeData != null) + { + if (_resizeData.ShowsPreview) + { + // Update the grid. + MoveSplitter(_resizeData.Adorner.OffsetX, _resizeData.Adorner.OffsetY); + RemovePreviewAdorner(); + } + + _resizeData = null; + } } - private bool IsStar(DefinitionBase definition) + protected override void OnKeyDown(KeyEventArgs e) { - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; + Key key = e.Key; + + switch (key) + { + case Key.Escape: + if (_resizeData != null) + { + CancelResize(); + e.Handled = true; + } + + break; + + case Key.Left: + e.Handled = KeyboardMoveSplitter(-KeyboardIncrement, 0); + break; + case Key.Right: + e.Handled = KeyboardMoveSplitter(KeyboardIncrement, 0); + break; + case Key.Up: + e.Handled = KeyboardMoveSplitter(0, -KeyboardIncrement); + break; + case Key.Down: + e.Handled = KeyboardMoveSplitter(0, KeyboardIncrement); + break; + } } - private void SetLengthInStars(DefinitionBase definition, double value) + /// + /// Cancels the resize operation. + /// + private void CancelResize() { - var columnDefinition = definition as ColumnDefinition; - if (columnDefinition != null) + // Restore original column/row lengths. + if (_resizeData.ShowsPreview) { - columnDefinition.Width = new GridLength(value, GridUnitType.Star); + RemovePreviewAdorner(); } - else + else // Reset the columns/rows lengths to the saved values. { - ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); + SetDefinitionLength(_resizeData.Definition1, _resizeData.OriginalDefinition1Length); + SetDefinitionLength(_resizeData.Definition2, _resizeData.OriginalDefinition2Length); } + + _resizeData = null; + } + + /// + /// Returns true if the row/column has a star length. + /// + private static bool IsStar(DefinitionBase definition) + { + return definition.UserSizeValueCache.IsStar; } - private void SetLength(DefinitionBase definition, double value) + /// + /// Gets Column or Row definition at index from grid based on resize direction. + /// + private static DefinitionBase GetGridDefinition(Grid grid, int index, GridResizeDirection direction) { - var columnDefinition = definition as ColumnDefinition; - if (columnDefinition != null) + return direction == GridResizeDirection.Columns ? + (DefinitionBase)grid.ColumnDefinitions[index] : + (DefinitionBase)grid.RowDefinitions[index]; + } + + /// + /// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row. + /// + private double GetActualLength(DefinitionBase definition) + { + var column = definition as ColumnDefinition; + + return column?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; + } + + /// + /// Gets Column or Row definition at index from grid based on resize direction. + /// + private static void SetDefinitionLength(DefinitionBase definition, GridLength length) + { + definition.SetValue( + definition is ColumnDefinition ? ColumnDefinition.WidthProperty : RowDefinition.HeightProperty, length); + } + + /// + /// Get the minimum and maximum Delta can be given definition constraints (MinWidth/MaxWidth). + /// + private void GetDeltaConstraints(out double minDelta, out double maxDelta) + { + double definition1Len = GetActualLength(_resizeData.Definition1); + double definition1Min = _resizeData.Definition1.UserMinSizeValueCache; + double definition1Max = _resizeData.Definition1.UserMaxSizeValueCache; + + double definition2Len = GetActualLength(_resizeData.Definition2); + double definition2Min = _resizeData.Definition2.UserMinSizeValueCache; + double definition2Max = _resizeData.Definition2.UserMaxSizeValueCache; + + // Set MinWidths to be greater than width of splitter. + if (_resizeData.SplitterIndex == _resizeData.Definition1Index) { - columnDefinition.Width = new GridLength(value); + definition1Min = Math.Max(definition1Min, _resizeData.SplitterLength); } - else + else if (_resizeData.SplitterIndex == _resizeData.Definition2Index) { - ((RowDefinition)definition).Height = new GridLength(value); + definition2Min = Math.Max(definition2Min, _resizeData.SplitterLength); } + + // Determine the minimum and maximum the columns can be resized. + minDelta = -Math.Min(definition1Len - definition1Min, definition2Max - definition2Len); + maxDelta = Math.Min(definition1Max - definition1Len, definition2Len - definition2Min); } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + /// + /// Sets the length of definition1 and definition2. + /// + private void SetLengths(double definition1Pixels, double definition2Pixels) { - base.OnAttachedToVisualTree(e); - _grid = this.GetVisualParent(); + // For the case where both definition1 and 2 are stars, update all star values to match their current pixel values. + if (_resizeData.SplitBehavior == SplitBehavior.Split) + { + var definitions = _resizeData.ResizeDirection == GridResizeDirection.Columns ? + (IAvaloniaReadOnlyList)_resizeData.Grid.ColumnDefinitions : + (IAvaloniaReadOnlyList)_resizeData.Grid.RowDefinitions; - _orientation = DetectOrientation(); + var definitionsCount = definitions.Count; + + for (var i = 0; i < definitionsCount; i++) + { + DefinitionBase definition = definitions[i]; - int definitionIndex; //row or col - if (_orientation == Orientation.Vertical) + // For each definition, if it is a star, set is value to ActualLength in stars + // This makes 1 star == 1 pixel in length + if (i == _resizeData.Definition1Index) + { + SetDefinitionLength(definition, new GridLength(definition1Pixels, GridUnitType.Star)); + } + else if (i == _resizeData.Definition2Index) + { + SetDefinitionLength(definition, new GridLength(definition2Pixels, GridUnitType.Star)); + } + else if (IsStar(definition)) + { + SetDefinitionLength(definition, new GridLength(GetActualLength(definition), GridUnitType.Star)); + } + } + } + else if (_resizeData.SplitBehavior == SplitBehavior.Resize1) { - Cursor = new Cursor(StandardCursorType.SizeWestEast); - _definitions = _grid.ColumnDefinitions.Cast().ToList(); - definitionIndex = GetValue(Grid.ColumnProperty); - PseudoClasses.Add(":vertical"); + SetDefinitionLength(_resizeData.Definition1, new GridLength(definition1Pixels)); } else { - Cursor = new Cursor(StandardCursorType.SizeNorthSouth); - definitionIndex = GetValue(Grid.RowProperty); - _definitions = _grid.RowDefinitions.Cast().ToList(); - PseudoClasses.Add(":horizontal"); + SetDefinitionLength(_resizeData.Definition2, new GridLength(definition2Pixels)); + } + } + + /// + /// Move the splitter by the given Delta's in the horizontal and vertical directions. + /// + private void MoveSplitter(double horizontalChange, double verticalChange) + { + Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter"); + + // Calculate the offset to adjust the splitter. + var delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange; + + DefinitionBase definition1 = _resizeData.Definition1; + DefinitionBase definition2 = _resizeData.Definition2; + + if (definition1 != null && definition2 != null) + { + double actualLength1 = GetActualLength(definition1); + double actualLength2 = GetActualLength(definition2); + + // When splitting, Check to see if the total pixels spanned by the definitions + // is the same asbefore starting resize. If not cancel the drag + if (_resizeData.SplitBehavior == SplitBehavior.Split && + !MathUtilities.AreClose( + actualLength1 + actualLength2, + _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength)) + { + CancelResize(); + + return; + } + + GetDeltaConstraints(out var min, out var max); + + // Constrain Delta to Min/MaxWidth of columns + delta = Math.Min(Math.Max(delta, min), max); + + double definition1LengthNew = actualLength1 + delta; + double definition2LengthNew = actualLength1 + actualLength2 - definition1LengthNew; + + SetLengths(definition1LengthNew, definition2LengthNew); } + } - if (definitionIndex > 0) - _prevDefinition = _definitions[definitionIndex - 1]; + /// + /// Move the splitter using the Keyboard (Don't show preview). + /// + private bool KeyboardMoveSplitter(double horizontalChange, double verticalChange) + { + // If moving with the mouse, ignore keyboard motion. + if (_resizeData != null) + { + return false; // Don't handle the event. + } + + // Don't show preview. + InitializeData(false); + + // Check that we are actually able to resize. + if (_resizeData == null) + { + return false; // Don't handle the event. + } - if (definitionIndex < _definitions.Count - 1) - _nextDefinition = _definitions[definitionIndex + 1]; + MoveSplitter(horizontalChange, verticalChange); + + _resizeData = null; + + return true; } - private Orientation DetectOrientation() + /// + /// This adorner draws the preview for the . + /// It also positions the adorner. + /// + private sealed class PreviewAdorner : Decorator { - if (!_grid.ColumnDefinitions.Any()) - return Orientation.Horizontal; - if (!_grid.RowDefinitions.Any()) - return Orientation.Vertical; + private readonly TranslateTransform _translation; + private readonly Decorator _decorator; + + public PreviewAdorner(IControl previewControl) + { + // Add a decorator to perform translations. + _translation = new TranslateTransform(); + + _decorator = new Decorator + { + Child = previewControl, + RenderTransform = _translation + }; + + Child = _decorator; + } - var col = GetValue(Grid.ColumnProperty); - var row = GetValue(Grid.RowProperty); - var width = _grid.ColumnDefinitions[col].Width; - var height = _grid.RowDefinitions[row].Height; - if (width.IsAuto && !height.IsAuto) + /// + /// The Preview's Offset in the X direction from the GridSplitter. + /// + public double OffsetX { - return Orientation.Vertical; + get => _translation.X; + set => _translation.X = value; } - if (!width.IsAuto && height.IsAuto) + + /// + /// The Preview's Offset in the Y direction from the GridSplitter. + /// + public double OffsetY { - return Orientation.Horizontal; + get => _translation.Y; + set => _translation.Y = value; } - if (_grid.Children.OfType() // Decision based on other controls in the same column - .Where(c => Grid.GetColumn(c) == col) - .Any(c => c.GetType() != typeof(GridSplitter))) + + protected override Size ArrangeOverride(Size finalSize) { - return Orientation.Horizontal; + // Adorners always get clipped to the owner control. In this case we want + // to constrain size to the splitter size but draw on top of the parent grid. + Clip = null; + + return base.ArrangeOverride(finalSize); } - return Orientation.Vertical; } + + /// + /// has special Behavior when columns are fixed. + /// If the left column is fixed, splitter will only resize that column. + /// Else if the right column is fixed, splitter will only resize the right column. + /// + private enum SplitBehavior + { + /// + /// Both columns/rows are star lengths. + /// + Split, + + /// + /// Resize 1 only. + /// + Resize1, + + /// + /// Resize 2 only. + /// + Resize2 + } + + /// + /// Stores data during the resizing operation. + /// + private class ResizeData + { + public bool ShowsPreview; + public PreviewAdorner Adorner; + + // The constraints to keep the Preview within valid ranges. + public double MinChange; + public double MaxChange; + + // The grid to Resize. + public Grid Grid; + + // Cache of Resize Direction and Behavior. + public GridResizeDirection ResizeDirection; + public GridResizeBehavior ResizeBehavior; + + // The columns/rows to resize. + public DefinitionBase Definition1; + public DefinitionBase Definition2; + + // Are the columns/rows star lengths. + public SplitBehavior SplitBehavior; + + // The index of the splitter. + public int SplitterIndex; + + // The indices of the columns/rows. + public int Definition1Index; + public int Definition2Index; + + // The original lengths of Definition1 and Definition2 (to restore lengths if user cancels resize). + public GridLength OriginalDefinition1Length; + public GridLength OriginalDefinition2Length; + public double OriginalDefinition1ActualLength; + public double OriginalDefinition2ActualLength; + + // The minimum of Width/Height of Splitter. Used to ensure splitter + // isn't hidden by resizing a row/column smaller than the splitter. + public double SplitterLength; + } + } + + /// + /// Enum to indicate whether resizes Columns or Rows. + /// + public enum GridResizeDirection + { + /// + /// Determines whether to resize rows or columns based on its Alignment and + /// width compared to height. + /// + Auto, + + /// + /// Resize columns when dragging Splitter. + /// + Columns, + + /// + /// Resize rows when dragging Splitter. + /// + Rows + } + + /// + /// Enum to indicate what Columns or Rows the resizes. + /// + public enum GridResizeBehavior + { + /// + /// Determine which columns or rows to resize based on its Alignment. + /// + BasedOnAlignment, + + /// + /// Resize the current and next Columns or Rows. + /// + CurrentAndNext, + + /// + /// Resize the previous and current Columns or Rows. + /// + PreviousAndCurrent, + + /// + /// Resize the previous and next Columns or Rows. + /// + PreviousAndNext } } diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 449ca18465..4966e669ed 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -132,21 +133,26 @@ namespace Avalonia.Controls { base.OnPointerPressed(e); - if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right) + if (e.Source is IVisual source) { - e.Handled = UpdateSelectionFromEventSource( - e.Source, - true, - (e.InputModifiers & InputModifiers.Shift) != 0, - (e.InputModifiers & InputModifiers.Control) != 0, - e.MouseButton == MouseButton.Right); + var point = e.GetCurrentPoint(source); + + if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed) + { + e.Handled = UpdateSelectionFromEventSource( + e.Source, + true, + (e.KeyModifiers & KeyModifiers.Shift) != 0, + (e.KeyModifiers & KeyModifiers.Control) != 0, + point.Properties.IsRightButtonPressed); + } } } protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - base.OnTemplateApplied(e); Scroll = e.NameScope.Find("PART_ScrollViewer"); + base.OnTemplateApplied(e); } } } diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index f8ae5c9690..6d450a0155 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -804,9 +804,9 @@ namespace Avalonia.Controls private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e) { - if (e.Device.Captured != Spinner) + if (e.Pointer.Captured != Spinner) { - Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input); + Dispatcher.UIThread.InvokeAsync(() => { e.Pointer.Capture(Spinner); }, DispatcherPriority.Input); } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index b6ae567123..329b086a7c 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -367,17 +367,17 @@ namespace Avalonia.Controls.Primitives { if ((container.ContainerControl as ISelectable)?.IsSelected == true) { - if (SelectedIndex == -1) - { - SelectedIndex = container.Index; - } - else + if (SelectionMode.HasFlag(SelectionMode.Multiple)) { if (_selection.Add(container.Index)) { resetSelectedItems = true; } } + else + { + SelectedIndex = container.Index; + } MarkContainerSelected(container.ContainerControl, true); } diff --git a/src/Avalonia.Controls/Primitives/TabStrip.cs b/src/Avalonia.Controls/Primitives/TabStrip.cs index ec0dbd124c..e1a6cf79bb 100644 --- a/src/Avalonia.Controls/Primitives/TabStrip.cs +++ b/src/Avalonia.Controls/Primitives/TabStrip.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Generators; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { @@ -44,9 +45,14 @@ namespace Avalonia.Controls.Primitives { base.OnPointerPressed(e); - if (e.MouseButton == MouseButton.Left) + if (e.Source is IVisual source) { - e.Handled = UpdateSelectionFromEventSource(e.Source); + var point = e.GetCurrentPoint(source); + + if (point.Properties.IsLeftButtonPressed) + { + e.Handled = UpdateSelectionFromEventSource(e.Source); + } } } } diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index 9f5ddb666c..d88018cf32 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -73,7 +73,6 @@ namespace Avalonia.Controls.Primitives protected override void OnPointerPressed(PointerPressedEventArgs e) { - e.Device.Capture(this); e.Handled = true; _lastPoint = e.GetPosition(this); @@ -92,7 +91,6 @@ namespace Avalonia.Controls.Primitives { if (_lastPoint.HasValue) { - e.Device.Capture(null); e.Handled = true; _lastPoint = null; diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 61ac0822b0..8b1cd48379 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -4,7 +4,6 @@ using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Generators; -using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -70,6 +69,7 @@ namespace Avalonia.Controls SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); + SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.UpdateSelectedContent(e)); } /// @@ -145,6 +145,61 @@ namespace Avalonia.Controls return RegisterContentPresenter(presenter); } + protected override void OnContainersMaterialized(ItemContainerEventArgs e) + { + base.OnContainersMaterialized(e); + + if (SelectedContent != null || SelectedIndex == -1) + { + return; + } + + var container = (TabItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex); + + if (container == null) + { + return; + } + + UpdateSelectedContent(container); + } + + private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e) + { + var index = (int)e.NewValue; + + if (index == -1) + { + SelectedContentTemplate = null; + + SelectedContent = null; + + return; + } + + var container = (TabItem)ItemContainerGenerator.ContainerFromIndex(index); + + if (container == null) + { + return; + } + + UpdateSelectedContent(container); + } + + private void UpdateSelectedContent(IContentControl item) + { + if (SelectedContentTemplate != item.ContentTemplate) + { + SelectedContentTemplate = item.ContentTemplate; + } + + if (SelectedContent != item.Content) + { + SelectedContent = item.Content; + } + } + /// /// Called when an is registered with the control. /// diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index fca1e022aa..e27977bf3d 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -30,7 +30,6 @@ namespace Avalonia.Controls { SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); - IsSelectedProperty.Changed.AddClassHandler((x, e) => x.UpdateSelectedContent(e)); DataContextProperty.Changed.AddClassHandler((x, e) => x.UpdateHeader(e)); } @@ -54,8 +53,6 @@ namespace Avalonia.Controls set { SetValue(IsSelectedProperty, value); } } - internal TabControl ParentTabControl { get; set; } - private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) { if (Header == null) @@ -83,23 +80,5 @@ namespace Avalonia.Controls } } } - - private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e) - { - if (!IsSelected) - { - return; - } - - if (ParentTabControl.SelectedContentTemplate != ContentTemplate) - { - ParentTabControl.SelectedContentTemplate = ContentTemplate; - } - - if (ParentTabControl.SelectedContent != Content) - { - ParentTabControl.SelectedContent = Content; - } - } } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index fdc9d153e2..3d472fca18 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -677,13 +677,13 @@ namespace Avalonia.Controls } } - e.Device.Capture(_presenter); + e.Pointer.Capture(_presenter); e.Handled = true; } protected override void OnPointerMoved(PointerEventArgs e) { - if (_presenter != null && e.Device.Captured == _presenter) + if (_presenter != null && e.Pointer.Captured == _presenter) { var point = e.GetPosition(_presenter); @@ -694,9 +694,9 @@ namespace Avalonia.Controls protected override void OnPointerReleased(PointerReleasedEventArgs e) { - if (_presenter != null && e.Device.Captured == _presenter) + if (_presenter != null && e.Pointer.Captured == _presenter) { - e.Device.Capture(null); + e.Pointer.Capture(null); } } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 8907137ecb..59844be8a6 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -507,14 +507,19 @@ namespace Avalonia.Controls { base.OnPointerPressed(e); - if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right) + if (e.Source is IVisual source) { - e.Handled = UpdateSelectionFromEventSource( - e.Source, - true, - (e.InputModifiers & InputModifiers.Shift) != 0, - (e.InputModifiers & InputModifiers.Control) != 0, - e.MouseButton == MouseButton.Right); + var point = e.GetCurrentPoint(source); + + if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed) + { + e.Handled = UpdateSelectionFromEventSource( + e.Source, + true, + (e.KeyModifiers & KeyModifiers.Shift) != 0, + (e.KeyModifiers & KeyModifiers.Control) != 0, + point.Properties.IsRightButtonPressed); + } } } diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Views/TreePageView.xaml index ca7314264a..2619fd744a 100644 --- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Views/TreePageView.xaml @@ -2,7 +2,7 @@ xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Avalonia.Diagnostics.Views.TreePageView"> - + - + diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml new file mode 100644 index 0000000000..e227fc2812 --- /dev/null +++ b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +