Browse Source

Merge branch 'master' into undecorated-window

pull/3221/head
mstr2 7 years ago
committed by GitHub
parent
commit
4fe923eea4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      native/Avalonia.Native/src/OSX/window.mm
  2. 12
      samples/ControlCatalog/App.xaml
  3. 11
      samples/ControlCatalog/App.xaml.cs
  4. 24
      samples/ControlCatalog/MainWindow.xaml
  5. 14
      samples/ControlCatalog/MainWindow.xaml.cs
  6. 20
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  7. 11
      src/Avalonia.Base/AvaloniaObject.cs
  8. 63
      src/Avalonia.Base/AvaloniaProperty.cs
  9. 8
      src/Avalonia.Controls/Application.cs
  10. 2
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  11. 867
      src/Avalonia.Controls/GridSplitter.cs
  12. 22
      src/Avalonia.Controls/ListBox.cs
  13. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  14. 10
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  15. 10
      src/Avalonia.Controls/Primitives/TabStrip.cs
  16. 2
      src/Avalonia.Controls/Primitives/Thumb.cs
  17. 57
      src/Avalonia.Controls/TabControl.cs
  18. 21
      src/Avalonia.Controls/TabItem.cs
  19. 8
      src/Avalonia.Controls/TextBox.cs
  20. 19
      src/Avalonia.Controls/TreeView.cs
  21. 4
      src/Avalonia.Diagnostics/Views/TreePageView.xaml
  22. 105
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  23. 62
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  24. BIN
      src/Avalonia.Dialogs/Assets/Roboto-Light.ttf
  25. 1
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  26. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  27. 2
      src/Avalonia.Input/FocusManager.cs
  28. 7
      src/Avalonia.Layout/LayoutHelper.cs
  29. 8
      src/Avalonia.Layout/LayoutManager.cs
  30. 25
      src/Avalonia.Layout/LayoutQueue.cs
  31. 51
      src/Avalonia.Layout/Layoutable.cs
  32. 1
      src/Avalonia.Native/Avalonia.Native.csproj
  33. 34
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  34. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  35. 2
      src/Avalonia.Native/WindowImplBase.cs
  36. 58
      src/Avalonia.Themes.Default/GridSplitter.xaml
  37. 2
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  38. 4
      src/Windows/Avalonia.Win32/WindowImpl.cs
  39. 431
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  40. 20
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  41. 37
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  42. 1
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  43. 23
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs

11
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)
{

12
samples/ControlCatalog/App.xaml

@ -17,16 +17,4 @@
</Style>
<StyleInclude Source="/SideBar.xaml"/>
</Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenu.Menu>
</Application>

11
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()

24
samples/ControlCatalog/MainWindow.xaml

@ -37,12 +37,20 @@
</NativeMenu>
</NativeMenu.Menu>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>
</Window.DataTemplates>
<Panel>
<local:MainView/>
</Panel>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>
</Window.DataTemplates>
<DockPanel LastChildFill="True">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />
</DockPanel>
</Window>

14
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<Menu>("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

20
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<Unit, Unit> ShowManagedNotificationCommand { get; }
public ReactiveCommand<Unit, Unit> ShowNativeNotificationCommand { get; }
public ReactiveCommand<Unit, Unit> AboutCommand { get; }
public ReactiveCommand<Unit, Unit> ExitCommand { get; }
}
}

11
src/Avalonia.Base/AvaloniaObject.cs

@ -210,7 +210,11 @@ namespace Avalonia
/// <returns>The value.</returns>
public object GetValue(AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
if (property is null)
{
throw new ArgumentNullException(nameof(property));
}
VerifyAccess();
if (property.IsDirect)
@ -231,7 +235,10 @@ namespace Avalonia
/// <returns>The value.</returns>
public T GetValue<T>(AvaloniaProperty<T> property)
{
Contract.Requires<ArgumentNullException>(property != null);
if (property is null)
{
throw new ArgumentNullException(nameof(property));
}
return (T)GetValue((AvaloniaProperty)property);
}

63
src/Avalonia.Base/AvaloniaProperty.cs

@ -28,6 +28,8 @@ namespace Avalonia
private readonly Dictionary<Type, PropertyMetadata> _metadata;
private readonly Dictionary<Type, PropertyMetadata> _metadataCache = new Dictionary<Type, PropertyMetadata>();
private bool _hasMetadataOverrides;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// </summary>
@ -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<ArgumentNullException>(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);
}
/// <summary>
@ -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]

8
src/Avalonia.Controls/Application.cs

@ -48,6 +48,14 @@ namespace Avalonia
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Creates an instance of the <see cref="Application"/> class.
/// </summary>
public Application()
{
Name = "Avalonia Application";
}
/// <summary>
/// Gets the current instance of the <see cref="Application"/> class.
/// </summary>

2
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)

867
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
{
/// <summary>
/// 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 <see cref="Grid"/> control.
/// </summary>
/// <remarks>
/// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
/// </remarks>
public class GridSplitter : Thumb
{
private List<DefinitionBase> _definitions;
/// <summary>
/// Defines the <see cref="ResizeDirection"/> property.
/// </summary>
public static readonly AvaloniaProperty<GridResizeDirection> ResizeDirectionProperty =
AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection));
private Grid _grid;
/// <summary>
/// Defines the <see cref="ResizeBehavior"/> property.
/// </summary>
public static readonly AvaloniaProperty<GridResizeBehavior> ResizeBehaviorProperty =
AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior));
private DefinitionBase _nextDefinition;
/// <summary>
/// Defines the <see cref="ShowsPreview"/> property.
/// </summary>
public static readonly AvaloniaProperty<bool> ShowsPreviewProperty =
AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview));
private Orientation _orientation;
/// <summary>
/// Defines the <see cref="KeyboardIncrement"/> property.
/// </summary>
public static readonly AvaloniaProperty<double> KeyboardIncrementProperty =
AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d);
private DefinitionBase _prevDefinition;
/// <summary>
/// Defines the <see cref="DragIncrement"/> property.
/// </summary>
public static readonly AvaloniaProperty<double> DragIncrementProperty =
AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d);
private void GetDeltaConstraints(out double min, out double max)
/// <summary>
/// Defines the <see cref="PreviewContent"/> property.
/// </summary>
public static readonly AvaloniaProperty<ITemplate<IControl>> PreviewContentProperty =
AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(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;
/// <summary>
/// Indicates whether the Splitter resizes the Columns, Rows, or Both.
/// </summary>
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);
/// <summary>
/// Indicates which Columns or Rows the Splitter resizes.
/// </summary>
public GridResizeBehavior ResizeBehavior
{
get => GetValue(ResizeBehaviorProperty);
set => SetValue(ResizeBehaviorProperty, value);
}
protected override void OnDragDelta(VectorEventArgs e)
/// <summary>
/// Indicates whether to Preview the column resizing without updating layout.
/// </summary>
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);
}
/// <summary>
/// The Distance to move the splitter when pressing the keyboard arrow keys.
/// </summary>
public double KeyboardIncrement
{
get => GetValue(KeyboardIncrementProperty);
set => SetValue(KeyboardIncrementProperty, value);
}
/// <summary>
/// Restricts splitter to move a multiple of the specified units.
/// </summary>
public double DragIncrement
{
get => GetValue(DragIncrementProperty);
set => SetValue(DragIncrementProperty, value);
}
/// <summary>
/// Gets or sets content that will be shown when <see cref="ShowsPreview"/> is enabled and user starts resize operation.
/// </summary>
public ITemplate<IControl> PreviewContent
{
get => GetValue(PreviewContentProperty);
set => SetValue(PreviewContentProperty, value);
}
/// <summary>
/// Converts BasedOnAlignment direction to Rows, Columns, or Both depending on its width/height.
/// </summary>
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);
/// <summary>
/// Convert BasedOnAlignment to Next/Prev/Both depending on alignment and Direction.
/// </summary>
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;
}
/// <summary>
/// Removes preview adorner from the grid.
/// </summary>
private void RemovePreviewAdorner()
{
if (_resizeData.Adorner != null)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
layer.Children.Remove(_resizeData.Adorner);
}
}
/// <summary>
/// Initialize the data needed for resizing.
/// </summary>
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();
}
}
/// <summary>
/// Returns true if GridSplitter can resize rows/columns.
/// </summary>
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;
}
/// <summary>
/// Create the preview adorner and add it to the adorner layer.
/// </summary>
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)
/// <summary>
/// Cancels the resize operation.
/// </summary>
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;
}
/// <summary>
/// Returns true if the row/column has a star length.
/// </summary>
private static bool IsStar(DefinitionBase definition)
{
return definition.UserSizeValueCache.IsStar;
}
private void SetLength(DefinitionBase definition, double value)
/// <summary>
/// Gets Column or Row definition at index from grid based on resize direction.
/// </summary>
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];
}
/// <summary>
/// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row.
/// </summary>
private double GetActualLength(DefinitionBase definition)
{
var column = definition as ColumnDefinition;
return column?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
}
/// <summary>
/// Gets Column or Row definition at index from grid based on resize direction.
/// </summary>
private static void SetDefinitionLength(DefinitionBase definition, GridLength length)
{
definition.SetValue(
definition is ColumnDefinition ? ColumnDefinition.WidthProperty : RowDefinition.HeightProperty, length);
}
/// <summary>
/// Get the minimum and maximum Delta can be given definition constraints (MinWidth/MaxWidth).
/// </summary>
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)
/// <summary>
/// Sets the length of definition1 and definition2.
/// </summary>
private void SetLengths(double definition1Pixels, double definition2Pixels)
{
base.OnAttachedToVisualTree(e);
_grid = this.GetVisualParent<Grid>();
// 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<DefinitionBase>)_resizeData.Grid.ColumnDefinitions :
(IAvaloniaReadOnlyList<DefinitionBase>)_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<DefinitionBase>().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<DefinitionBase>().ToList();
PseudoClasses.Add(":horizontal");
SetDefinitionLength(_resizeData.Definition2, new GridLength(definition2Pixels));
}
}
/// <summary>
/// Move the splitter by the given Delta's in the horizontal and vertical directions.
/// </summary>
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];
/// <summary>
/// Move the splitter using the Keyboard (Don't show preview).
/// </summary>
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()
/// <summary>
/// This adorner draws the preview for the <see cref="GridSplitter"/>.
/// It also positions the adorner.
/// </summary>
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)
/// <summary>
/// The Preview's Offset in the X direction from the GridSplitter.
/// </summary>
public double OffsetX
{
return Orientation.Vertical;
get => _translation.X;
set => _translation.X = value;
}
if (!width.IsAuto && height.IsAuto)
/// <summary>
/// The Preview's Offset in the Y direction from the GridSplitter.
/// </summary>
public double OffsetY
{
return Orientation.Horizontal;
get => _translation.Y;
set => _translation.Y = value;
}
if (_grid.Children.OfType<Control>() // 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;
}
/// <summary>
/// <see cref="GridSplitter"/> 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.
/// </summary>
private enum SplitBehavior
{
/// <summary>
/// Both columns/rows are star lengths.
/// </summary>
Split,
/// <summary>
/// Resize 1 only.
/// </summary>
Resize1,
/// <summary>
/// Resize 2 only.
/// </summary>
Resize2
}
/// <summary>
/// Stores data during the resizing operation.
/// </summary>
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;
}
}
/// <summary>
/// Enum to indicate whether <see cref="GridSplitter"/> resizes Columns or Rows.
/// </summary>
public enum GridResizeDirection
{
/// <summary>
/// Determines whether to resize rows or columns based on its Alignment and
/// width compared to height.
/// </summary>
Auto,
/// <summary>
/// Resize columns when dragging Splitter.
/// </summary>
Columns,
/// <summary>
/// Resize rows when dragging Splitter.
/// </summary>
Rows
}
/// <summary>
/// Enum to indicate what Columns or Rows the <see cref="GridSplitter"/> resizes.
/// </summary>
public enum GridResizeBehavior
{
/// <summary>
/// Determine which columns or rows to resize based on its Alignment.
/// </summary>
BasedOnAlignment,
/// <summary>
/// Resize the current and next Columns or Rows.
/// </summary>
CurrentAndNext,
/// <summary>
/// Resize the previous and current Columns or Rows.
/// </summary>
PreviousAndCurrent,
/// <summary>
/// Resize the previous and next Columns or Rows.
/// </summary>
PreviousAndNext
}
}

22
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<IScrollable>("PART_ScrollViewer");
base.OnTemplateApplied(e);
}
}
}

4
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);
}
}

10
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);
}

10
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);
}
}
}
}

2
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;

57
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<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
SelectedIndexProperty.Changed.AddClassHandler<TabControl>((x, e) => x.UpdateSelectedContent(e));
}
/// <summary>
@ -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;
}
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>

21
src/Avalonia.Controls/TabItem.cs

@ -30,7 +30,6 @@ namespace Avalonia.Controls
{
SelectableMixin.Attach<TabItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
IsSelectedProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateSelectedContent(e));
DataContextProperty.Changed.AddClassHandler<TabItem>((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;
}
}
}
}

8
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);
}
}

19
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);
}
}
}

4
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">
<Grid ColumnDefinitions="*,4,3*">
<Grid ColumnDefinitions="*,Auto,3*">
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TreeNode"
@ -20,7 +20,7 @@
</TreeView.Styles>
</TreeView>
<GridSplitter Width="4" Grid.Column="1" />
<GridSplitter Grid.Column="1" />
<ContentControl Content="{Binding Details}" Grid.Column="2" />
</Grid>
</UserControl>

105
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -0,0 +1,105 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MaxWidth="400"
MaxHeight="475"
MinWidth="430"
MinHeight="475"
Title="About Avalonia"
Background="Purple"
FontFamily="/Assets/Roboto-Light.ttf#Roboto"
x:Class="Avalonia.Dialogs.AboutAvaloniaDialog">
<Window.Styles>
<Style>
<Style.Resources>
<DrawingGroup x:Key="AvaloniaLogo">
<GeometryDrawing Geometry="m 150.66581 0.66454769 c -54.77764 0 -101.0652 38.86360031 -112.62109 90.33008031 a 26.1 26.1 0 0 1 18.92187 25.070312 26.1 26.1 0 0 1 -18.91992 25.08202 c 11.56024 51.46073 57.8456 90.31837 112.61914 90.31837 63.37832 0 115.40039 -52.02207 115.40039 -115.40039 0 -63.378322 -52.02207 -115.40039231 -115.40039 -115.40039231 z m 0 60.00000031 c 30.95192 0 55.40039 24.44847 55.40039 55.400392 0 30.9519 -24.44847 55.40039 -55.40039 55.40039 -30.95191 0 -55.40039 -24.44848 -55.40039 -55.40039 0 -30.951922 24.44848 -55.400392 55.40039 -55.400392 z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="272,411" EndPoint="435,248">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#B0B0B0" Offset="0" />
<GradientStop Color="#FFFFFF" Offset="0.6784" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#B0B0B0">
<GeometryDrawing.Geometry>
<EllipseGeometry Rect="9.6,95.8,40.6,40.6" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="206.06355, 114.56503,60,116.2" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</Style.Resources>
</Style>
<Style Selector="Rectangle.Abstract">
<Setter Property="Fill" Value="White" />
<Setter Property="Width" Value="750" />
<Setter Property="Height" Value="700" />
</Style>
<Style Selector="Button.Hyperlink">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="-5"/>
<Setter Property="Foreground" Value="#419df2" />
<Setter Property="Command" Value="{Binding OpenBrowser}" />
<Setter Property="Content" Value="{Binding $self.CommandParameter}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
</Style>
</Window.Styles>
<Grid Background="#4A255D">
<Canvas>
<Rectangle Classes="Abstract" Canvas.Top="90" Opacity="0.132">
<Rectangle.RenderTransform>
<RotateTransform Angle="-2" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="95" Opacity="0.3">
<Rectangle.RenderTransform>
<RotateTransform Angle="-4" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="100" Opacity="0.3">
<Rectangle.RenderTransform>
<RotateTransform Angle="-8" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="105" Opacity="0.7">
<Rectangle.RenderTransform>
<RotateTransform Angle="-12" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="18">
<Border Height="70" Width="70">
<DrawingPresenter Drawing="{DynamicResource AvaloniaLogo}" />
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0">
<TextBlock Text="Avalonia 0.9" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
</StackPanel>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0">
<TextBlock Text="This product is built with the Avalonia cross-platform UI Framework. &#x0A;&#x0A;Avalonia is made possible by the generous support of it's contributors and community." TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Main source repository | " />
<Button Classes="Hyperlink" CommandParameter="https://github.com/AvaloniaUI/Avalonia/" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Documentation and Information | " />
<Button Classes="Hyperlink" CommandParameter="https://avaloniaui.net/" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Chat Room | " />
<Button Classes="Hyperlink" CommandParameter="https://gitter.im/AvaloniaUI/Avalonia/" />
</StackPanel>
</StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10">
<TextBlock Text="© 2019 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
</StackPanel>
</Grid>
</Window>

62
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -0,0 +1,62 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Diagnostics;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs
{
public class AboutAvaloniaDialog : Window
{
public AboutAvaloniaDialog()
{
AvaloniaXamlLoader.Load(this);
DataContext = this;
}
public static void OpenBrowser(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// If no associated application/json MimeType is found xdg-open opens retrun error
// but it tries to open it anyway using the console editor (nano, vim, other..)
ShellExec($"xdg-open {url}", waitForExit: false);
}
else
{
using (Process process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"-e {url}" : "",
CreateNoWindow = true,
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
}));
}
}
private static void ShellExec(string cmd, bool waitForExit = true)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
using (var process = Process.Start(
new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
))
{
if (waitForExit)
{
process.WaitForExit();
}
}
}
}
}

BIN
src/Avalonia.Dialogs/Assets/Roboto-Light.ttf

Binary file not shown.

1
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -7,6 +7,7 @@
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\*" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -182,7 +182,7 @@ namespace Avalonia.Input
{
bool menuIsOpen = MainMenu?.IsOpen == true;
if ((e.Modifiers & InputModifiers.Alt) != 0 || menuIsOpen)
if ((e.KeyModifiers & KeyModifiers.Alt) != 0 || menuIsOpen)
{
// If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key.

2
src/Avalonia.Input/FocusManager.cs

@ -180,7 +180,7 @@ namespace Avalonia.Input
if (sender == e.Source && ev.MouseButton == MouseButton.Left)
{
var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
var element = (ev.Pointer?.Captured as IInputElement) ?? (e.Source as IInputElement);
if (element == null || !CanFocus(element))
{

7
src/Avalonia.Layout/LayoutHelper.cs

@ -21,8 +21,11 @@ namespace Avalonia.Layout
/// <returns>The control's size.</returns>
public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints)
{
double width = (control.Width > 0) ? control.Width : constraints.Width;
double height = (control.Height > 0) ? control.Height : constraints.Height;
var controlWidth = control.Width;
var controlHeight = control.Height;
double width = (controlWidth > 0) ? controlWidth : constraints.Width;
double height = (controlHeight > 0) ? controlHeight : constraints.Height;
width = Math.Min(width, control.MaxWidth);
width = Math.Max(width, control.MinWidth);
height = Math.Min(height, control.MaxHeight);

8
src/Avalonia.Layout/LayoutManager.cs

@ -15,9 +15,15 @@ namespace Avalonia.Layout
{
private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
private readonly Action _executeLayoutPass;
private bool _queued;
private bool _running;
public LayoutManager()
{
_executeLayoutPass = ExecuteLayoutPass;
}
/// <inheritdoc/>
public void InvalidateMeasure(ILayoutable control)
{
@ -215,7 +221,7 @@ namespace Avalonia.Layout
{
if (!_queued && !_running)
{
Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout);
Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout);
_queued = true;
}
}

25
src/Avalonia.Layout/LayoutQueue.cs

@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Layout
{
@ -18,9 +17,11 @@ namespace Avalonia.Layout
_shouldEnqueue = shouldEnqueue;
}
private Func<T, bool> _shouldEnqueue;
private Queue<T> _inner = new Queue<T>();
private Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
private readonly Func<T, bool> _shouldEnqueue;
private readonly Queue<T> _inner = new Queue<T>();
private readonly Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
private readonly List<KeyValuePair<T, Info>> _notFinalizedBuffer = new List<KeyValuePair<T, Info>>();
private int _maxEnqueueCountPerLoop = 1;
public int Count => _inner.Count;
@ -60,13 +61,19 @@ namespace Avalonia.Layout
public void EndLoop()
{
var notfinalized = _loopQueueInfo.Where(v => v.Value.Count >= _maxEnqueueCountPerLoop).ToArray();
foreach (KeyValuePair<T, Info> info in _loopQueueInfo)
{
if (info.Value.Count >= _maxEnqueueCountPerLoop)
{
_notFinalizedBuffer.Add(info);
}
}
_loopQueueInfo.Clear();
//prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
//one more time as a final attempt
foreach (var item in notfinalized)
// Prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
// one more time as a final attempt.
foreach (var item in _notFinalizedBuffer)
{
if (_shouldEnqueue(item.Key))
{
@ -74,6 +81,8 @@ namespace Avalonia.Layout
_inner.Enqueue(item.Key);
}
}
_notFinalizedBuffer.Clear();
}
}
}

51
src/Avalonia.Layout/Layoutable.cs

@ -518,17 +518,25 @@ namespace Avalonia.Layout
var width = measured.Width;
var height = measured.Height;
if (!double.IsNaN(Width))
{
width = Width;
double widthCache = Width;
if (!double.IsNaN(widthCache))
{
width = widthCache;
}
}
width = Math.Min(width, MaxWidth);
width = Math.Max(width, MinWidth);
if (!double.IsNaN(Height))
{
height = Height;
double heightCache = Height;
if (!double.IsNaN(heightCache))
{
height = heightCache;
}
}
height = Math.Min(height, MaxHeight);
@ -562,11 +570,19 @@ namespace Avalonia.Layout
double width = 0;
double height = 0;
foreach (ILayoutable child in this.GetVisualChildren())
var visualChildren = VisualChildren;
var visualCount = visualChildren.Count;
for (var i = 0; i < visualCount; i++)
{
child.Measure(availableSize);
width = Math.Max(width, child.DesiredSize.Width);
height = Math.Max(height, child.DesiredSize.Height);
IVisual visual = visualChildren[i];
if (visual is ILayoutable layoutable)
{
layoutable.Measure(availableSize);
width = Math.Max(width, layoutable.DesiredSize.Width);
height = Math.Max(height, layoutable.DesiredSize.Height);
}
}
return new Size(width, height);
@ -594,6 +610,7 @@ namespace Avalonia.Layout
var verticalAlignment = VerticalAlignment;
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var useLayoutRounding = UseLayoutRounding;
if (horizontalAlignment != HorizontalAlignment.Stretch)
{
@ -607,7 +624,7 @@ namespace Avalonia.Layout
size = LayoutHelper.ApplyLayoutConstraints(this, size);
if (UseLayoutRounding)
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
@ -641,7 +658,7 @@ namespace Avalonia.Layout
break;
}
if (UseLayoutRounding)
if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
@ -658,9 +675,19 @@ namespace Avalonia.Layout
/// <returns>The actual size used.</returns>
protected virtual Size ArrangeOverride(Size finalSize)
{
foreach (ILayoutable child in this.GetVisualChildren().OfType<ILayoutable>())
var arrangeRect = new Rect(finalSize);
var visualChildren = VisualChildren;
var visualCount = visualChildren.Count;
for (var i = 0; i < visualCount; i++)
{
child.Arrange(new Rect(finalSize));
IVisual visual = visualChildren[i];
if (visual is ILayoutable layoutable)
{
layoutable.Arrange(arrangeRect);
}
}
return finalSize;

1
src/Avalonia.Native/Avalonia.Native.csproj

@ -22,5 +22,6 @@
<PackageReference Include="SharpGenTools.Sdk" Version="1.1.2" PrivateAssets="all" />
<PackageReference Include="SharpGen.Runtime.COM" Version="1.1.0" />
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.Dialogs.csproj" />
</ItemGroup>
</Project>

34
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -3,12 +3,15 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Native.Interop;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using Avalonia.Dialogs;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Native
{
@ -211,6 +214,29 @@ namespace Avalonia.Native
DoLayoutReset();
}
private static NativeMenu CreateDefaultAppMenu()
{
var result = new NativeMenu();
var aboutItem = new NativeMenuItem
{
Header = "About Avalonia",
};
aboutItem.Clicked += async (sender, e) =>
{
var dialog = new AboutAvaloniaDialog();
var mainWindow = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
await dialog.ShowDialog(mainWindow);
};
result.Add(aboutItem);
return result;
}
private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
QueueReset();
@ -241,6 +267,10 @@ namespace Avalonia.Native
{
SetMenu(_menu);
}
else
{
SetMenu(CreateDefaultAppMenu());
}
}
else
{
@ -321,7 +351,7 @@ namespace Avalonia.Native
}), new MenuActionCallback(() => { item.RaiseClick(); }));
menu.AddItem(menuItem);
if (item.Menu?.Items?.Count > 0)
if (item.Menu?.Items?.Count >= 0)
{
var submenu = _factory.CreateMenu();
@ -362,7 +392,7 @@ namespace Avalonia.Native
return false;
}), new MenuActionCallback(() => { item.RaiseClick(); }));
if (item.Menu?.Items.Count > 0 || isMainMenu)
if (item.Menu?.Items.Count >= 0 || isMainMenu)
{
var subMenu = CreateSubmenu(item.Menu?.Items);

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -21,7 +21,6 @@ namespace Avalonia.Native
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
internal static readonly MouseDevice MouseDevice = new MouseDevice();
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
public Size DoubleClickSize => new Size(4, 4);

2
src/Avalonia.Native/WindowImplBase.cs

@ -96,7 +96,7 @@ namespace Avalonia.Native
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice => AvaloniaNativePlatform.MouseDevice;
public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup();

58
src/Avalonia.Themes.Default/GridSplitter.xaml

@ -1,51 +1,23 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="GridSplitter:vertical">
<Setter Property="Width" Value="6"/>
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Ellipse">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
<Setter Property="Fill" Value="{DynamicResource ThemeControlMidBrush}"/>
<Setter Property="Margin" Value="1"/>
</Style>
</StackPanel.Styles>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
</Border>
</ControlTemplate>
<Style Selector="GridSplitter">
<Setter Property="Focusable" Value="True" />
<Setter Property="MinWidth" Value="6" />
<Setter Property="MinHeight" Value="6" />
<Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
<Setter Property="PreviewContent">
<Template>
<Rectangle Fill="{DynamicResource HighlightBrush}" />
</Template>
</Setter>
</Style>
<Style Selector="GridSplitter:horizontal">
<Setter Property="Height" Value="6"/>
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Ellipse">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
<Setter Property="Fill" Value="{DynamicResource ThemeControlMidBrush}"/>
<Setter Property="Margin" Value="1"/>
</Style>
</StackPanel.Styles>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
</Border>
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter>
</Style>
</Styles>
</Styles>

2
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -31,7 +31,7 @@ namespace Avalonia.Skia
return _fontFamilies.TryGetValue(familyName, out var fontFamily) ?
fontFamily.GetOrAdd(key, GetFallback(fontFamily, key)) :
null;
new TypefaceCollectionEntry(Typeface.Default, SkiaSharp.SKTypeface.Default);
}
private static TypefaceCollectionEntry GetFallback(IDictionary<FontKey, TypefaceCollectionEntry> fontFamily, FontKey key)

4
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -621,7 +621,7 @@ namespace Avalonia.Win32
timestamp,
_owner,
RawPointerEventType.LeaveWindow,
new Point(), WindowsKeyboardDevice.Instance.Modifiers);
new Point(-1,-1), WindowsKeyboardDevice.Instance.Modifiers);
break;
case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN:
@ -636,7 +636,7 @@ namespace Avalonia.Win32
: msg == (int)UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN
? RawPointerEventType.RightButtonDown
: RawPointerEventType.MiddleButtonDown,
new Point(0, 0), GetMouseModifiers(wParam));
PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam));
break;
case WindowsMessage.WM_TOUCH:
var touchInputCount = wParam.ToInt32();

431
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@ -2,9 +2,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -21,185 +19,366 @@ namespace Avalonia.Controls.UnitTests
public void Detects_Horizontal_Orientation()
{
GridSplitter splitter;
var grid = new Grid()
{
RowDefinitions = new RowDefinitions("*,Auto,*"),
ColumnDefinitions = new ColumnDefinitions("*,*"),
Children =
{
new Border { [Grid.RowProperty] = 0 },
(splitter = new GridSplitter { [Grid.RowProperty] = 1 }),
new Border { [Grid.RowProperty] = 2 }
}
};
var grid = new Grid
{
RowDefinitions = new RowDefinitions("*,Auto,*"),
ColumnDefinitions = new ColumnDefinitions("*,*"),
Children =
{
new Border { [Grid.RowProperty] = 0 },
(splitter = new GridSplitter { [Grid.RowProperty] = 1 }),
new Border { [Grid.RowProperty] = 2 }
}
};
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(splitter.Classes, ":horizontal".Equals);
Assert.Equal(GridResizeDirection.Rows, splitter.GetEffectiveResizeDirection());
}
[Fact]
public void Detects_Vertical_Orientation()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
RowDefinitions = new RowDefinitions("*,*"),
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 },
}
};
var grid = new Grid
{
ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
RowDefinitions = new RowDefinitions("*,*"),
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1 }),
new Border { [Grid.ColumnProperty] = 2 },
}
};
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(splitter.Classes, ":vertical".Equals);
Assert.Equal(GridResizeDirection.Columns, splitter.GetEffectiveResizeDirection());
}
[Fact]
public void Detects_With_Both_Auto()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
RowDefinitions = new RowDefinitions("Auto,Auto"),
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 },
}
};
var grid = new Grid
{
ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
RowDefinitions = new RowDefinitions("Auto,Auto"),
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1 }),
new Border { [Grid.ColumnProperty] = 2 },
}
};
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(splitter.Classes, ":vertical".Equals);
Assert.Equal(GridResizeDirection.Columns, splitter.GetEffectiveResizeDirection());
}
[Fact]
public void Horizontal_Stays_Within_Constraints()
public void In_First_Position_Doesnt_Throw_Exception()
{
GridSplitter splitter;
var grid = new Grid
{
ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
RowDefinitions = new RowDefinitions("*,*"),
Children =
{
(splitter = new GridSplitter { [Grid.ColumnProperty] = 0 }),
new Border { [Grid.ColumnProperty] = 1 },
new Border { [Grid.ColumnProperty] = 2 },
}
};
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
splitter.RaiseEvent(
new VectorEventArgs { RoutedEvent = Thumb.DragStartedEvent });
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent, Vector = new Vector(100, 1000)
});
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Horizontal_Stays_Within_Constraints(bool showsPreview)
{
var control1 = new Border { [Grid.RowProperty] = 0 };
var splitter = new GridSplitter
{
[Grid.RowProperty] = 1,
};
var splitter = new GridSplitter { [Grid.RowProperty] = 1, ShowsPreview = showsPreview};
var control2 = new Border { [Grid.RowProperty] = 2 };
var rowDefinitions = new RowDefinitions()
{
new RowDefinition(1, GridUnitType.Star) { MinHeight = 70, MaxHeight = 110 },
new RowDefinition(GridLength.Auto),
new RowDefinition(1, GridUnitType.Star) { MinHeight = 10, MaxHeight = 140 },
};
var grid = new Grid()
{
RowDefinitions = rowDefinitions,
Children =
{
control1, splitter, control2
}
};
var rowDefinitions = new RowDefinitions
{
new RowDefinition(1, GridUnitType.Star) { MinHeight = 70, MaxHeight = 110 },
new RowDefinition(GridLength.Auto),
new RowDefinition(1, GridUnitType.Star) { MinHeight = 10, MaxHeight = 140 },
};
var grid = new Grid { RowDefinitions = rowDefinitions, Children = { control1, splitter, control2 } };
var root = new TestRoot
{
Child = new VisualLayerManager
{
Child = grid
}
};
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 200));
root.Arrange(new Rect(0, 0, 100, 200));
splitter.RaiseEvent(
new VectorEventArgs { RoutedEvent = Thumb.DragStartedEvent });
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(0, -100)
});
Assert.Equal(rowDefinitions[0].Height, new GridLength(70, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(130, GridUnitType.Star));
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(0, -100)
});
if (showsPreview)
{
Assert.Equal(rowDefinitions[0].Height, new GridLength(1, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(1, GridUnitType.Star));
}
else
{
Assert.Equal(rowDefinitions[0].Height, new GridLength(70, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(130, GridUnitType.Star));
}
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(0, 100)
});
Assert.Equal(rowDefinitions[0].Height, new GridLength(110, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(90, GridUnitType.Star));
}
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(0, 100)
});
[Fact]
public void In_First_Position_Doesnt_Throw_Exception()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
RowDefinitions = new RowDefinitions("*,*"),
Children =
{
(splitter = new GridSplitter { [Grid.ColumnProperty] = 0} ),
new Border { [Grid.ColumnProperty] = 1 },
new Border { [Grid.ColumnProperty] = 2 },
}
};
if (showsPreview)
{
Assert.Equal(rowDefinitions[0].Height, new GridLength(1, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(1, GridUnitType.Star));
}
else
{
Assert.Equal(rowDefinitions[0].Height, new GridLength(110, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(90, GridUnitType.Star));
}
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(100, 1000)
});
{
RoutedEvent = Thumb.DragCompletedEvent
});
Assert.Equal(rowDefinitions[0].Height, new GridLength(110, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(90, GridUnitType.Star));
}
[Fact]
public void Vertical_Stays_Within_Constraints()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Vertical_Stays_Within_Constraints(bool showsPreview)
{
var control1 = new Border { [Grid.ColumnProperty] = 0 };
var splitter = new GridSplitter
{
[Grid.ColumnProperty] = 1,
};
var splitter = new GridSplitter { [Grid.ColumnProperty] = 1, ShowsPreview = showsPreview};
var control2 = new Border { [Grid.ColumnProperty] = 2 };
var columnDefinitions = new ColumnDefinitions()
{
new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 10, MaxWidth = 190 },
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 80, MaxWidth = 120 },
};
var grid = new Grid()
{
ColumnDefinitions = columnDefinitions,
Children =
{
control1, splitter, control2
}
};
var columnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 10, MaxWidth = 190 },
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 80, MaxWidth = 120 },
};
var root = new TestRoot { Child = grid };
var grid = new Grid { ColumnDefinitions = columnDefinitions, Children = { control1, splitter, control2 } };
var root = new TestRoot
{
Child = new VisualLayerManager
{
Child = grid
}
};
root.Measure(new Size(200, 100));
root.Arrange(new Rect(0, 0, 200, 100));
splitter.RaiseEvent(
new VectorEventArgs { RoutedEvent = Thumb.DragStartedEvent });
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(-100, 0)
});
if (showsPreview)
{
Assert.Equal(columnDefinitions[0].Width, new GridLength(1, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(1, GridUnitType.Star));
}
else
{
Assert.Equal(columnDefinitions[0].Width, new GridLength(80, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(120, GridUnitType.Star));
}
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(-100, 0)
});
Assert.Equal(columnDefinitions[0].Width, new GridLength(80, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(120, GridUnitType.Star));
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(100, 0)
});
if (showsPreview)
{
Assert.Equal(columnDefinitions[0].Width, new GridLength(1, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(1, GridUnitType.Star));
}
else
{
Assert.Equal(columnDefinitions[0].Width, new GridLength(120, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star));
}
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(100, 0)
});
{
RoutedEvent = Thumb.DragCompletedEvent
});
Assert.Equal(columnDefinitions[0].Width, new GridLength(120, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star));
}
[Theory]
[InlineData(Key.Up, 90, 110)]
[InlineData(Key.Down, 110, 90)]
public void Vertical_Keyboard_Input_Can_Move_Splitter(Key key, double expectedHeightFirst, double expectedHeightSecond)
{
var control1 = new Border { [Grid.RowProperty] = 0 };
var splitter = new GridSplitter { [Grid.RowProperty] = 1, KeyboardIncrement = 10d };
var control2 = new Border { [Grid.RowProperty] = 2 };
var rowDefinitions = new RowDefinitions
{
new RowDefinition(1, GridUnitType.Star),
new RowDefinition(GridLength.Auto),
new RowDefinition(1, GridUnitType.Star)
};
var grid = new Grid { RowDefinitions = rowDefinitions, Children = { control1, splitter, control2 } };
var root = new TestRoot
{
Child = grid
};
root.Measure(new Size(200, 200));
root.Arrange(new Rect(0, 0, 200, 200));
splitter.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key
});
Assert.Equal(rowDefinitions[0].Height, new GridLength(expectedHeightFirst, GridUnitType.Star));
Assert.Equal(rowDefinitions[2].Height, new GridLength(expectedHeightSecond, GridUnitType.Star));
}
[Theory]
[InlineData(Key.Left, 90, 110)]
[InlineData(Key.Right, 110, 90)]
public void Horizontal_Keyboard_Input_Can_Move_Splitter(Key key, double expectedWidthFirst, double expectedWidthSecond)
{
var control1 = new Border { [Grid.ColumnProperty] = 0 };
var splitter = new GridSplitter { [Grid.ColumnProperty] = 1, KeyboardIncrement = 10d };
var control2 = new Border { [Grid.ColumnProperty] = 2 };
var columnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(1, GridUnitType.Star)
};
var grid = new Grid { ColumnDefinitions = columnDefinitions, Children = { control1, splitter, control2 } };
var root = new TestRoot
{
Child = grid
};
root.Measure(new Size(200, 200));
root.Arrange(new Rect(0, 0, 200, 200));
splitter.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key
});
Assert.Equal(columnDefinitions[0].Width, new GridLength(expectedWidthFirst, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(expectedWidthSecond, GridUnitType.Star));
}
[Fact]
public void Pressing_Escape_Key_Cancels_Resizing()
{
var control1 = new Border { [Grid.ColumnProperty] = 0 };
var splitter = new GridSplitter { [Grid.ColumnProperty] = 1, KeyboardIncrement = 10d };
var control2 = new Border { [Grid.ColumnProperty] = 2 };
var columnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(1, GridUnitType.Star)
};
var grid = new Grid { ColumnDefinitions = columnDefinitions, Children = { control1, splitter, control2 } };
var root = new TestRoot
{
Child = grid
};
root.Measure(new Size(200, 200));
root.Arrange(new Rect(0, 0, 200, 200));
splitter.RaiseEvent(
new VectorEventArgs { RoutedEvent = Thumb.DragStartedEvent });
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
Vector = new Vector(-100, 0)
});
Assert.Equal(columnDefinitions[0].Width, new GridLength(0, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(200, GridUnitType.Star));
splitter.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = Key.Escape
});
Assert.Equal(columnDefinitions[0].Width, new GridLength(1, GridUnitType.Star));
Assert.Equal(columnDefinitions[2].Width, new GridLength(1, GridUnitType.Star));
}
}
}

20
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -47,6 +47,26 @@ namespace Avalonia.Controls.UnitTests
Assert.IsType<ItemsPresenter>(target.Presenter);
}
[Fact]
public void ListBox_Should_Find_Scrollviewer_In_Template()
{
var target = new ListBox
{
Template = ListBoxTemplate(),
};
ScrollViewer viewer = null;
target.TemplateApplied += (sender, e) =>
{
viewer = target.Scroll as ScrollViewer;
};
Prepare(target);
Assert.NotNull(viewer);
}
[Fact]
public void ListBoxItem_Containers_Should_Be_Generated()
{

37
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -1185,6 +1185,33 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.MoveSelection(NavigationDirection.Next, true);
}
[Fact]
public void Pre_Selecting_Item_Should_Set_Selection_After_It_Was_Added_When_AlwaysSelected()
{
var target = new TestSelector(SelectionMode.AlwaysSelected)
{
Template = Template()
};
var second = new Item { IsSelected = true };
var items = new AvaloniaList<object>
{
new Item(),
second
};
target.Items = items;
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(second, target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
}
private FuncControlTemplate Template()
{
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
@ -1233,6 +1260,16 @@ namespace Avalonia.Controls.UnitTests.Primitives
private class TestSelector : SelectingItemsControl
{
public TestSelector()
{
}
public TestSelector(SelectionMode selectionMode)
{
SelectionMode = selectionMode;
}
public new bool MoveSelection(NavigationDirection direction, bool wrap)
{
return base.MoveSelection(direction, wrap);

1
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -1080,6 +1080,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var target = new TestSelector
{
Items = items,
SelectionMode = SelectionMode.Multiple,
Template = Template(),
};

23
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -45,6 +45,29 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(selected, target.SelectedItem);
}
[Fact]
public void Pre_Selecting_TabItem_Should_Set_SelectedContent_After_It_Was_Added()
{
var target = new TabControl
{
Template = TabControlTemplate(),
};
const string secondContent = "Second";
var items = new AvaloniaList<object>
{
new TabItem { Header = "First"},
new TabItem { Header = "Second", Content = secondContent, IsSelected = true }
};
target.Items = items;
ApplyTemplate(target);
Assert.Equal(secondContent, target.SelectedContent);
}
[Fact]
public void Logical_Children_Should_Be_TabItems()
{

Loading…
Cancel
Save