diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 908b9e1022c..3acc5e365b0 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -1283,6 +1283,9 @@ -(AvnWindow*) initWithParent: (WindowBaseImpl*) parent
_closed = false;
_lastScaling = [self backingScaleFactor];
+ [self setOpaque:NO];
+ [self setBackgroundColor: [NSColor clearColor]];
+ [self invalidateShadow];
return self;
}
diff --git a/readme.md b/readme.md
index 512b35a4549..97c65093620 100644
--- a/readme.md
+++ b/readme.md
@@ -22,7 +22,7 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it
For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
-Avalonia is delivered via NuGet package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
+Avalonia is delivered via NuGet package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
Use these commands in the Package Manager console to install Avalonia manually:
```
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 874560a2944..cbe2c628904 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -32,7 +32,11 @@
-
+
+
+
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml
index b44fac27cbf..9b8f8af765f 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml
@@ -1,45 +1,52 @@
-
- Image
- Displays an image
-
-
-
- No Stretch
-
-
-
-
- Fill
-
-
+
+
+ Image
+ Displays an image
+
-
- Uniform
-
-
+
+
+
+ Bitmap
+
+ None
+ Fill
+ Uniform
+ UniformToFill
+
+
+
-
- UniformToFill
-
-
-
-
- Window Icon as an Image
-
-
-
+
+ Drawing
+
+ None
+ Fill
+ Uniform
+ UniformToFill
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
index 792b25963ee..bbe89d1dfdf 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
@@ -1,40 +1,41 @@
-using System.IO;
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using Avalonia.Media.Imaging;
+using Avalonia.Media;
namespace ControlCatalog.Pages
{
public class ImagePage : UserControl
{
- private Image iconImage;
+ private readonly Image _bitmapImage;
+ private readonly Image _drawingImage;
+
public ImagePage()
{
- this.InitializeComponent();
+ InitializeComponent();
+ _bitmapImage = this.FindControl("bitmapImage");
+ _drawingImage = this.FindControl("drawingImage");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
- iconImage = this.Get("Icon");
}
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ public void BitmapStretchChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (_bitmapImage != null)
+ {
+ var comboxBox = (ComboBox)sender;
+ _bitmapImage.Stretch = (Stretch)comboxBox.SelectedIndex;
+ }
+ }
+
+ public void DrawingStretchChanged(object sender, SelectionChangedEventArgs e)
{
- base.OnAttachedToVisualTree(e);
- if (iconImage.Source == null)
+ if (_drawingImage != null)
{
- var windowRoot = e.Root as Window;
- if (windowRoot != null)
- {
- using (var stream = new MemoryStream())
- {
- windowRoot.Icon.Save(stream);
- stream.Seek(0, SeekOrigin.Begin);
- iconImage.Source = new Bitmap(stream);
- }
- }
+ var comboxBox = (ComboBox)sender;
+ _drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
}
diff --git a/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs b/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
index 3eb2276c489..f263786ab73 100644
--- a/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
+++ b/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
@@ -39,7 +39,7 @@ public override void Render(DrawingContext context)
ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100));
}
- context.DrawImage(_bitmap, 1,
+ context.DrawImage(_bitmap,
new Rect(0, 0, 200, 200),
new Rect(0, 0, 200, 200));
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
diff --git a/scripts/avalonia-rename.ps1 b/scripts/avalonia-rename.ps1
deleted file mode 100644
index c77dffb55da..00000000000
--- a/scripts/avalonia-rename.ps1
+++ /dev/null
@@ -1,64 +0,0 @@
-function Get-NewDirectoryName {
- param ([System.IO.DirectoryInfo]$item)
-
- $name = $item.Name.Replace("perspex", "avalonia")
- $name = $name.Replace("Perspex", "Avalonia")
- Join-Path $item.Parent.FullName $name
-}
-
-function Get-NewFileName {
- param ([System.IO.FileInfo]$item)
-
- $name = $item.Name.Replace("perspex", "avalonia")
- $name = $name.Replace("Perspex", "Avalonia")
- Join-Path $item.DirectoryName $name
-}
-
-function Rename-Contents {
- param ([System.IO.FileInfo] $file)
-
- $extensions = @(".cs",".xaml",".csproj",".sln",".md",".json",".yml",".partial",".ps1",".nuspec",".htm",".html",".gitmodules".".xml",".plist",".targets",".projitems",".shproj",".xib")
-
- if ($extensions.Contains($file.Extension)) {
- $text = [IO.File]::ReadAllText($file.FullName)
- $text = $text.Replace("github.com/perspex", "github.com/avaloniaui")
- $text = $text.Replace("github.com/Perspex", "github.com/AvaloniaUI")
- $text = $text.Replace("perspex", "avalonia")
- $text = $text.Replace("Perspex", "Avalonia")
- $text = $text.Replace("PERSPEX", "AVALONIA")
- [IO.File]::WriteAllText($file.FullName, $text)
- }
-}
-
-function Process-Files {
- param ([System.IO.DirectoryInfo] $item)
-
- $dirs = Get-ChildItem -Path $item.FullName -Directory
- $files = Get-ChildItem -Path $item.FullName -File
-
- foreach ($dir in $dirs) {
- Process-Files $dir.FullName
- }
-
- foreach ($file in $files) {
- Rename-Contents $file
-
- $renamed = Get-NewFileName $file
-
- if ($file.FullName -ne $renamed) {
- Write-Host git mv $file.FullName $renamed
- & git mv $file.FullName $renamed
- }
- }
-
- $renamed = Get-NewDirectoryName $item
-
- if ($item.FullName -ne $renamed) {
- Write-Host git mv $item.FullName $renamed
- & git mv $item.FullName $renamed
- }
-}
-
-& git submodule deinit .
-& git clean -xdf
-Process-Files .
diff --git a/src/Avalonia.Animation/IterationCount.cs b/src/Avalonia.Animation/IterationCount.cs
index e9cd0686d89..9f57455639c 100644
--- a/src/Avalonia.Animation/IterationCount.cs
+++ b/src/Avalonia.Animation/IterationCount.cs
@@ -63,7 +63,7 @@ public IterationCount(ulong value, IterationType type)
public IterationType RepeatType => _type;
///
- /// Gets a value that indicates whether the is set to loop.
+ /// Gets a value that indicates whether the is set to Infinite.
///
public bool IsInfinite => _type == IterationType.Infinite;
diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs
index b30a8668fd5..ee27aa7ec16 100644
--- a/src/Avalonia.Controls/DrawingPresenter.cs
+++ b/src/Avalonia.Controls/DrawingPresenter.cs
@@ -1,9 +1,11 @@
-using Avalonia.Controls.Shapes;
+using System;
+using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
+ [Obsolete("Use Image control with DrawingImage source")]
public class DrawingPresenter : Control
{
static DrawingPresenter()
diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index ff6cd482df4..41b6e5449a1 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -14,8 +14,8 @@ public class Image : Control
///
/// Defines the property.
///
- public static readonly StyledProperty SourceProperty =
- AvaloniaProperty.Register(nameof(Source));
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
///
/// Defines the property.
@@ -23,6 +23,14 @@ public class Image : Control
public static readonly StyledProperty StretchProperty =
AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StretchDirectionProperty =
+ AvaloniaProperty.Register(
+ nameof(StretchDirection),
+ StretchDirection.Both);
+
static Image()
{
AffectsRender(SourceProperty, StretchProperty);
@@ -30,9 +38,9 @@ static Image()
}
///
- /// Gets or sets the bitmap image that will be displayed.
+ /// Gets or sets the image that will be displayed.
///
- public IBitmap Source
+ public IImage Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
@@ -43,10 +51,19 @@ public IBitmap Source
///
public Stretch Stretch
{
- get { return (Stretch)GetValue(StretchProperty); }
+ get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
+ ///
+ /// Gets or sets a value controlling in what direction the image will be stretched.
+ ///
+ public StretchDirection StretchDirection
+ {
+ get { return GetValue(StretchDirectionProperty); }
+ set { SetValue(StretchDirectionProperty, value); }
+ }
+
///
/// Renders the control.
///
@@ -58,8 +75,8 @@ public override void Render(DrawingContext context)
if (source != null)
{
Rect viewPort = new Rect(Bounds.Size);
- Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
- Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
+ Size sourceSize = source.Size;
+ Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.CenterRect(new Rect(scaledSize))
@@ -69,7 +86,7 @@ public override void Render(DrawingContext context)
var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this);
- context.DrawImage(source, 1, sourceRect, destRect, interpolationMode);
+ context.DrawImage(source, sourceRect, destRect, interpolationMode);
}
}
@@ -85,15 +102,7 @@ protected override Size MeasureOverride(Size availableSize)
if (source != null)
{
- Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
- if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
- {
- result = sourceSize;
- }
- else
- {
- result = Stretch.CalculateSize(availableSize, sourceSize);
- }
+ result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
}
return result;
@@ -106,7 +115,7 @@ protected override Size ArrangeOverride(Size finalSize)
if (source != null)
{
- var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
+ var sourceSize = source.Size;
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index 1a9347e3177..f27bb5fac61 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -7,6 +7,7 @@
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
+using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Notifications
@@ -14,7 +15,7 @@ namespace Avalonia.Controls.Notifications
///
/// An that displays notifications in a .
///
- public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
+ public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{
private IList _items;
@@ -153,5 +154,7 @@ private void Install(Window host)
adornerLayer?.Children.Add(this);
}
+
+ public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
}
}
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index ebe5e0a93ea..9a2f0310d72 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -138,10 +138,7 @@ private void UpdateAdornedElement(Visual adorner, Visual adorned)
}
}
- public bool HitTest(Point point)
- {
- return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
- }
+ public bool HitTest(Point point) => Children.HitTestCustom(point);
private class AdornedElementInfo
{
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 487a5e91e49..5150033a53e 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -21,11 +21,8 @@ public static OverlayLayer GetOverlayLayer(IVisual visual)
return null;
}
-
- public bool HitTest(Point point)
- {
- return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
- }
+
+ public bool HitTest(Point point) => Children.HitTestCustom(point);
protected override Size ArrangeOverride(Size finalSize)
{
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index a5bbcec1862..69da211aa4d 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@@ -240,17 +241,14 @@ protected SelectionMode SelectionMode
public override void BeginInit()
{
base.BeginInit();
- ++_updateCount;
- _updateSelectedIndex = int.MinValue;
+
+ InternalBeginInit();
}
///
public override void EndInit()
{
- if (--_updateCount == 0)
- {
- UpdateFinished();
- }
+ InternalEndInit();
base.EndInit();
}
@@ -437,7 +435,8 @@ protected override void OnContainersRecycled(ItemContainerEventArgs e)
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextBeginUpdate();
- ++_updateCount;
+
+ InternalBeginInit();
}
///
@@ -445,10 +444,7 @@ protected override void OnDataContextEndUpdate()
{
base.OnDataContextEndUpdate();
- if (--_updateCount == 0)
- {
- UpdateFinished();
- }
+ InternalEndInit();
}
protected override void OnKeyDown(KeyEventArgs e)
@@ -1118,6 +1114,26 @@ private void UpdateFinished()
}
}
+ private void InternalBeginInit()
+ {
+ if (_updateCount == 0)
+ {
+ _updateSelectedIndex = int.MinValue;
+ }
+
+ ++_updateCount;
+ }
+
+ private void InternalEndInit()
+ {
+ Debug.Assert(_updateCount > 0);
+
+ if (--_updateCount == 0)
+ {
+ UpdateFinished();
+ }
+ }
+
private class Selection : IEnumerable
{
private readonly List _list = new List();
diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs
index e7cd0697a08..4b3e8e21108 100644
--- a/src/Avalonia.Controls/Primitives/ToggleButton.cs
+++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs
@@ -7,8 +7,14 @@
namespace Avalonia.Controls.Primitives
{
+ ///
+ /// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
+ ///
public class ToggleButton : Button
{
+ ///
+ /// Defines the property.
+ ///
public static readonly DirectProperty IsCheckedProperty =
AvaloniaProperty.RegisterDirect(
nameof(IsChecked),
@@ -17,9 +23,30 @@ public class ToggleButton : Button
unsetValue: null,
defaultBindingMode: BindingMode.TwoWay);
+ ///
+ /// Defines the property.
+ ///
public static readonly StyledProperty IsThreeStateProperty =
AvaloniaProperty.Register(nameof(IsThreeState));
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent CheckedEvent =
+ RoutedEvent.Register(nameof(Checked), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent UncheckedEvent =
+ RoutedEvent.Register(nameof(Unchecked), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent IndeterminateEvent =
+ RoutedEvent.Register(nameof(Indeterminate), RoutingStrategies.Bubble);
+
private bool? _isChecked = false;
static ToggleButton()
@@ -27,14 +54,49 @@ static ToggleButton()
PseudoClass(IsCheckedProperty, c => c == true, ":checked");
PseudoClass(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass(IsCheckedProperty, c => c == null, ":indeterminate");
+
+ IsCheckedProperty.Changed.AddClassHandler((x, e) => x.OnIsCheckedChanged(e));
+ }
+
+ ///
+ /// Raised when a is checked.
+ ///
+ public event EventHandler Checked
+ {
+ add => AddHandler(CheckedEvent, value);
+ remove => RemoveHandler(CheckedEvent, value);
+ }
+
+ ///
+ /// Raised when a is unchecked.
+ ///
+ public event EventHandler Unchecked
+ {
+ add => AddHandler(UncheckedEvent, value);
+ remove => RemoveHandler(UncheckedEvent, value);
+ }
+
+ ///
+ /// Raised when a is neither checked nor unchecked.
+ ///
+ public event EventHandler Indeterminate
+ {
+ add => AddHandler(IndeterminateEvent, value);
+ remove => RemoveHandler(IndeterminateEvent, value);
}
+ ///
+ /// Gets or sets whether the is checked.
+ ///
public bool? IsChecked
{
- get { return _isChecked; }
- set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
+ get => _isChecked;
+ set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
}
+ ///
+ /// Gets or sets a value that indicates whether the control supports three states.
+ ///
public bool IsThreeState
{
get => GetValue(IsThreeStateProperty);
@@ -47,18 +109,78 @@ protected override void OnClick()
base.OnClick();
}
+ ///
+ /// Toggles the property.
+ ///
protected virtual void Toggle()
{
if (IsChecked.HasValue)
+ {
if (IsChecked.Value)
+ {
if (IsThreeState)
+ {
IsChecked = null;
+ }
else
+ {
IsChecked = false;
+ }
+ }
else
+ {
IsChecked = true;
+ }
+ }
else
+ {
IsChecked = false;
+ }
+ }
+
+ ///
+ /// Called when becomes true.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnChecked(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when becomes false.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnUnchecked(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when becomes null.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnIndeterminate(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var newValue = (bool?)e.NewValue;
+
+ switch (newValue)
+ {
+ case true:
+ OnChecked(new RoutedEventArgs(CheckedEvent));
+ break;
+ case false:
+ OnUnchecked(new RoutedEventArgs(UncheckedEvent));
+ break;
+ default:
+ OnIndeterminate(new RoutedEventArgs(IndeterminateEvent));
+ break;
+ }
}
}
}
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index 539fe1ec4b4..c7a1a24c250 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -83,7 +83,7 @@ public override void Render(DrawingContext context)
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
}
- context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
+ context.DrawImage(_bitmap, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
new Rect(Bounds.Size));
}
base.Render(context);
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index 0e2136a6f3b..cbac1d6c1bd 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -562,33 +562,35 @@ private void OnDataSourcePropertyChanged(ItemsSourceView oldValue, ItemsSourceVi
if (Layout != null)
{
- if (Layout is VirtualizingLayout virtualLayout)
- {
- var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+ var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+ try
+ {
_processingItemsSourceChange = args;
- try
+ if (Layout is VirtualizingLayout virtualLayout)
{
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
- finally
- {
- _processingItemsSourceChange = null;
- }
- }
- else if (Layout is NonVirtualizingLayout nonVirtualLayout)
- {
- // Walk through all the elements and make sure they are cleared for
- // non-virtualizing layouts.
- foreach (var element in Children)
+ else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
- if (GetVirtualizationInfo(element).IsRealized)
+ // Walk through all the elements and make sure they are cleared for
+ // non-virtualizing layouts.
+ foreach (var element in Children)
{
- ClearElementImpl(element);
+ if (GetVirtualizationInfo(element).IsRealized)
+ {
+ ClearElementImpl(element);
+ }
}
+
+ Children.Clear();
}
}
+ finally
+ {
+ _processingItemsSourceChange = null;
+ }
InvalidateMeasure();
}
diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs
index 51c14d47d66..7d005a30b47 100644
--- a/src/Avalonia.Controls/Repeater/ViewManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewManager.cs
@@ -109,11 +109,22 @@ public void ClearElement(IControl element, bool isClearedDueToCollectionChange)
public void ClearElementToElementFactory(IControl element)
{
- var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
- var clearedIndex = virtInfo.Index;
_owner.OnElementClearing(element);
- _owner.ItemTemplateShim.RecycleElement(_owner, element);
+ if (_owner.ItemTemplateShim != null)
+ {
+ _owner.ItemTemplateShim.RecycleElement(_owner, element);
+ }
+ else
+ {
+ // No ItemTemplate to recycle to, remove the element from the children collection.
+ if (!_owner.Children.Remove(element))
+ {
+ throw new InvalidOperationException("ItemsRepeater's child not found in its Children collection.");
+ }
+ }
+
+ var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
virtInfo.MoveOwnershipToElementFactory();
if (_lastFocusedElement == element)
@@ -121,9 +132,8 @@ public void ClearElementToElementFactory(IControl element)
// Focused element is going away. Remove the tracked last focused element
// and pick a reasonable next focus if we can find one within the layout
// realized elements.
- MoveFocusFromClearedIndex(clearedIndex);
+ MoveFocusFromClearedIndex(virtInfo.Index);
}
-
}
private void MoveFocusFromClearedIndex(int clearedIndex)
@@ -190,7 +200,8 @@ public int GetElementIndex(VirtualizationInfo virtInfo)
{
if (virtInfo == null)
{
- throw new ArgumentException("Element is not a child of this ItemsRepeater.");
+ //Element is not a child of this ItemsRepeater.
+ return -1;
}
return virtInfo.IsRealized || virtInfo.IsInUniqueIdResetPool ? virtInfo.Index : -1;
@@ -515,21 +526,52 @@ private IControl GetElementFromPinnedElements(int index)
return element;
}
+ // There are several cases handled here with respect to which element gets returned and when DataContext is modified.
+ //
+ // 1. If there is no ItemTemplate:
+ // 1.1 If data is an IControl -> the data is returned
+ // 1.2 If data is not an IControl -> a default DataTemplate is used to fetch element and DataContext is set to data
+ //
+ // 2. If there is an ItemTemplate:
+ // 2.1 If data is not an IControl -> Element is fetched from ElementFactory and DataContext is set to the data
+ // 2.2 If data is an IControl:
+ // 2.2.1 If Element returned by the ElementFactory is the same as the data -> Element (a.k.a. data) is returned as is
+ // 2.2.2 If Element returned by the ElementFactory is not the same as the data
+ // -> Element that is fetched from the ElementFactory is returned and
+ // DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself
private IControl GetElementFromElementFactory(int index)
{
// The view generator is the provider of last resort.
+ var data = _owner.ItemsSourceView.GetAt(index);
+ var providedElementFactory = _owner.ItemTemplateShim;
+
+ ItemTemplateWrapper GetElementFactory()
+ {
+ if (providedElementFactory == null)
+ {
+ var factory = FuncDataTemplate.Default;
+ _owner.ItemTemplate = factory;
+ return _owner.ItemTemplateShim;
+ }
- var itemTemplateFactory = _owner.ItemTemplateShim;
- if (itemTemplateFactory == null)
+ return providedElementFactory;
+ }
+
+ IControl GetElement()
{
- // If no ItemTemplate was provided, use a default
- var factory = FuncDataTemplate.Default;
- _owner.ItemTemplate = factory;
- itemTemplateFactory = _owner.ItemTemplateShim;
+ if (providedElementFactory == null)
+ {
+ if (data is IControl dataAsElement)
+ {
+ return dataAsElement;
+ }
+ }
+
+ var elementFactory = GetElementFactory();
+ return elementFactory.GetElement(_owner, data);
}
- var data = _owner.ItemsSourceView.GetAt(index);
- var element = itemTemplateFactory.GetElement(_owner, data);
+ var element = GetElement();
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
if (virtInfo == null)
@@ -537,8 +579,11 @@ private IControl GetElementFromElementFactory(int index)
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
}
- // Prepare the element
- element.DataContext = data;
+ if (data != element)
+ {
+ // Prepare the element
+ element.DataContext = data;
+ }
virtInfo.MoveOwnershipToLayoutFromElementFactory(
index,
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 3d472fca18c..fbac2e02ec1 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -390,8 +390,10 @@ private async void Paste()
{
return;
}
+
_undoRedoHelper.Snapshot();
HandleTextInput(text);
+ _undoRedoHelper.Snapshot();
}
protected override void OnKeyDown(KeyEventArgs e)
@@ -401,12 +403,12 @@ protected override void OnKeyDown(KeyEventArgs e)
bool movement = false;
bool selection = false;
bool handled = false;
- var modifiers = e.Modifiers;
+ var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetService();
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
- bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
+ bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
if (Match(keymap.SelectAll))
{
diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs
index aa009770f65..00e68d629bf 100644
--- a/src/Avalonia.Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Input/AccessKeyHandler.cs
@@ -140,7 +140,7 @@ public void Unregister(IInputElement element)
/// The event args.
protected virtual void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
- if (e.Key == Key.LeftAlt)
+ if (e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
{
_altIsDown = true;
@@ -218,6 +218,7 @@ protected virtual void OnPreviewKeyUp(object sender, KeyEventArgs e)
switch (e.Key)
{
case Key.LeftAlt:
+ case Key.RightAlt:
_altIsDown = false;
if (_ignoreAltUp)
diff --git a/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
index a758a328bed..053f8947553 100644
--- a/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
+++ b/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
@@ -4,14 +4,14 @@ namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
- public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
+ public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{
}
- public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
- InputModifiers selectionModifiers = InputModifiers.Shift,
- InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
+ public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
+ KeyModifiers selectionModifiers = KeyModifiers.Shift,
+ KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
@@ -75,9 +75,9 @@ public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
};
}
- public InputModifiers CommandModifiers { get; set; }
- public InputModifiers WholeWordTextActionModifiers { get; set; }
- public InputModifiers SelectionModifiers { get; set; }
+ public KeyModifiers CommandModifiers { get; set; }
+ public KeyModifiers WholeWordTextActionModifiers { get; set; }
+ public KeyModifiers SelectionModifiers { get; set; }
public List Copy { get; set; }
public List Cut { get; set; }
public List Paste { get; set; }
diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
index 615ce725bd9..7f44c80a649 100644
--- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
+++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
@@ -72,6 +72,7 @@ public Size Measure(
bool isWrapping,
double minItemSpacing,
double lineSpacing,
+ int maxItemsPerLine,
ScrollOrientation orientation,
string layoutId)
{
@@ -94,14 +95,14 @@ public Size Measure(
_elementManager.OnBeginMeasure(orientation);
int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId);
- Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
- Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
+ Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
+ Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
if (isWrapping && IsReflowRequired())
{
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
_orientation.SetMinorStart(ref firstElementBounds, 0);
_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
- Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, layoutId);
+ Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
}
RaiseLineArranged();
@@ -115,10 +116,11 @@ public Size Measure(
public Size Arrange(
Size finalSize,
VirtualizingLayoutContext context,
+ bool isWrapping,
LineAlignment lineAlignment,
string layoutId)
{
- ArrangeVirtualizingLayout(finalSize, lineAlignment, layoutId);
+ ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId);
return new Size(
Math.Max(finalSize.Width, _lastExtent.Width),
@@ -270,6 +272,7 @@ private void Generate(
Size availableSize,
double minItemSpacing,
double lineSpacing,
+ int maxItemsPerLine,
string layoutId)
{
if (anchorIndex != -1)
@@ -280,7 +283,7 @@ private void Generate(
var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex);
var lineOffset = _orientation.MajorStart(anchorBounds);
var lineMajorSize = _orientation.MajorSize(anchorBounds);
- int countInLine = 1;
+ var countInLine = 1;
int count = 0;
bool lineNeedsReposition = false;
@@ -301,7 +304,7 @@ private void Generate(
if (direction == GenerateDirection.Forward)
{
double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize));
- if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
+ if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// No more space in this row. wrap to next row.
_orientation.SetMinorStart(ref currentBounds, 0);
@@ -339,7 +342,7 @@ private void Generate(
{
// Backward
double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing);
- if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
+ if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// Does not fit, wrap to the previous row
var availableSizeMinor = _orientation.Minor(availableSize);
@@ -544,6 +547,7 @@ private void RaiseLineArranged()
private void ArrangeVirtualizingLayout(
Size finalSize,
LineAlignment lineAlignment,
+ bool isWrapping,
string layoutId)
{
// Walk through the realized elements one line at a time and
@@ -563,7 +567,7 @@ private void ArrangeVirtualizingLayout(
if (_orientation.MajorStart(currentBounds) != currentLineOffset)
{
spaceAtLineEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
- PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, layoutId);
+ PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
spaceAtLineStart = _orientation.MinorStart(currentBounds);
countInLine = 0;
currentLineOffset = _orientation.MajorStart(currentBounds);
@@ -580,7 +584,7 @@ private void ArrangeVirtualizingLayout(
if (countInLine > 0)
{
var spaceAtEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
- PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, layoutId);
+ PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
}
}
}
@@ -594,6 +598,8 @@ private void PerformLineAlignment(
double spaceAtLineEnd,
double lineSize,
LineAlignment lineAlignment,
+ bool isWrapping,
+ Size finalSize,
string layoutId)
{
for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex)
@@ -659,6 +665,14 @@ private void PerformLineAlignment(
}
bounds = bounds.Translate(-_lastExtent.Position);
+
+ if (!isWrapping)
+ {
+ _orientation.SetMinorSize(
+ ref bounds,
+ Math.Max(_orientation.MinorSize(bounds), _orientation.Minor(finalSize)));
+ }
+
var element = _elementManager.GetAt(rangeIndex);
element.Arrange(bounds);
}
diff --git a/src/Avalonia.Layout/NonVirtualizingLayout.cs b/src/Avalonia.Layout/NonVirtualizingLayout.cs
index fba91e66c70..5d27ba9199c 100644
--- a/src/Avalonia.Layout/NonVirtualizingLayout.cs
+++ b/src/Avalonia.Layout/NonVirtualizingLayout.cs
@@ -20,25 +20,25 @@ public abstract class NonVirtualizingLayout : AttachedLayout
///
public sealed override void InitializeForContext(LayoutContext context)
{
- InitializeForContextCore((VirtualizingLayoutContext)context);
+ InitializeForContextCore((NonVirtualizingLayoutContext)context);
}
///
public sealed override void UninitializeForContext(LayoutContext context)
{
- UninitializeForContextCore((VirtualizingLayoutContext)context);
+ UninitializeForContextCore((NonVirtualizingLayoutContext)context);
}
///
public sealed override Size Measure(LayoutContext context, Size availableSize)
{
- return MeasureOverride((VirtualizingLayoutContext)context, availableSize);
+ return MeasureOverride((NonVirtualizingLayoutContext)context, availableSize);
}
///
public sealed override Size Arrange(LayoutContext context, Size finalSize)
{
- return ArrangeOverride((VirtualizingLayoutContext)context, finalSize);
+ return ArrangeOverride((NonVirtualizingLayoutContext)context, finalSize);
}
///
@@ -49,7 +49,7 @@ public sealed override Size Arrange(LayoutContext context, Size finalSize)
/// The context object that facilitates communication between the layout and its host
/// container.
///
- protected virtual void InitializeForContextCore(VirtualizingLayoutContext context)
+ protected virtual void InitializeForContextCore(LayoutContext context)
{
}
@@ -61,7 +61,7 @@ protected virtual void InitializeForContextCore(VirtualizingLayoutContext contex
/// The context object that facilitates communication between the layout and its host
/// container.
///
- protected virtual void UninitializeForContextCore(VirtualizingLayoutContext context)
+ protected virtual void UninitializeForContextCore(LayoutContext context)
{
}
@@ -83,7 +83,7 @@ protected virtual void UninitializeForContextCore(VirtualizingLayoutContext cont
/// of the allocated sizes for child objects or based on other considerations such as a
/// fixed container size.
///
- protected abstract Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize);
+ protected abstract Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize);
///
/// When implemented in a derived class, provides the behavior for the "Arrange" pass of
@@ -98,6 +98,6 @@ protected virtual void UninitializeForContextCore(VirtualizingLayoutContext cont
/// its children.
///
/// The actual size that is used after the element is arranged in layout.
- protected virtual Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) => finalSize;
+ protected virtual Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize) => finalSize;
}
}
diff --git a/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs b/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
new file mode 100644
index 00000000000..d3dec83e9b3
--- /dev/null
+++ b/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
@@ -0,0 +1,14 @@
+// This source file is adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+//
+// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
+
+namespace Avalonia.Layout
+{
+ ///
+ /// Represents the base class for layout context types that do not support virtualization.
+ ///
+ public abstract class NonVirtualizingLayoutContext : LayoutContext
+ {
+ }
+}
diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs
index e9735b9b31c..3c3729272c0 100644
--- a/src/Avalonia.Layout/StackLayout.cs
+++ b/src/Avalonia.Layout/StackLayout.cs
@@ -267,6 +267,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size
false,
0,
Spacing,
+ int.MaxValue,
_orientation.ScrollOrientation,
LayoutId);
@@ -278,6 +279,7 @@ protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
+ false,
FlowLayoutAlgorithm.LineAlignment.Start,
LayoutId);
diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs
index edc20429226..11a521ed1e3 100644
--- a/src/Avalonia.Layout/UniformGridLayout.cs
+++ b/src/Avalonia.Layout/UniformGridLayout.cs
@@ -110,6 +110,12 @@ public class UniformGridLayout : VirtualizingLayout, IFlowLayoutAlgorithmDelegat
public static readonly StyledProperty MinRowSpacingProperty =
AvaloniaProperty.Register(nameof(MinRowSpacing));
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MaximumRowsOrColumnsProperty =
+ AvaloniaProperty.Register(nameof(MinItemWidth));
+
///
/// Defines the property.
///
@@ -123,6 +129,7 @@ public class UniformGridLayout : VirtualizingLayout, IFlowLayoutAlgorithmDelegat
private double _minColumnSpacing;
private UniformGridLayoutItemsJustification _itemsJustification;
private UniformGridLayoutItemsStretch _itemsStretch;
+ private int _maximumRowsOrColumns = int.MaxValue;
///
/// Initializes a new instance of the class.
@@ -219,6 +226,15 @@ public double MinRowSpacing
set => SetValue(MinRowSpacingProperty, value);
}
+ ///
+ /// Gets or sets the maximum row or column count.
+ ///
+ public int MaximumRowsOrColumns
+ {
+ get => GetValue(MaximumRowsOrColumnsProperty);
+ set => SetValue(MaximumRowsOrColumnsProperty, value);
+ }
+
///
/// Gets or sets the axis along which items are laid out.
///
@@ -269,15 +285,17 @@ FlowLayoutAnchorInfo IFlowLayoutAlgorithmDelegates.Algorithm_GetAnchorForRealiza
{
var gridState = (UniformGridLayoutState)context.LayoutState;
var lastExtent = gridState.FlowAlgorithm.LastExtent;
- int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
- double majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
- double realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
+ var itemsPerLine = Math.Min( // note use of unsigned ints
+ Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
+ Math.Max(1u, (uint)_maximumRowsOrColumns));
+ var majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
+ var realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
if ((realizationWindowStartWithinExtent + _orientation.MajorSize(realizationRect)) >= 0 && realizationWindowStartWithinExtent <= majorSize)
{
double offset = Math.Max(0.0, _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent));
int anchorRowIndex = (int)(offset / GetMajorSizeWithSpacing(context));
- anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
+ anchorIndex = (int)Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
bounds = GetLayoutRectForDataIndex(availableSize, anchorIndex, lastExtent, context);
}
}
@@ -299,7 +317,9 @@ FlowLayoutAnchorInfo IFlowLayoutAlgorithmDelegates.Algorithm_GetAnchorForTargetE
int count = context.ItemCount;
if (targetIndex >= 0 && targetIndex < count)
{
- int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
+ int itemsPerLine = (int)Math.Min( // note use of unsigned ints
+ Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
+ Math.Max(1u, _maximumRowsOrColumns));
int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine;
index = indexOfFirstInLine;
var state = context.LayoutState as UniformGridLayoutState;
@@ -329,17 +349,21 @@ Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
// Constants
int itemsCount = context.ItemCount;
double availableSizeMinor = _orientation.Minor(availableSize);
- int itemsPerLine = Math.Max(1, !double.IsInfinity(availableSizeMinor) ?
- (int)(availableSizeMinor / GetMinorSizeWithSpacing(context)) : itemsCount);
+ int itemsPerLine =
+ (int)Math.Min( // note use of unsigned ints
+ Math.Max(1u, !double.IsInfinity(availableSizeMinor)
+ ? (uint)(availableSizeMinor / GetMinorSizeWithSpacing(context))
+ : (uint)itemsCount),
+ Math.Max(1u, _maximumRowsOrColumns));
double lineSize = GetMajorSizeWithSpacing(context);
if (itemsCount > 0)
{
_orientation.SetMinorSize(
ref extent,
- !double.IsInfinity(availableSizeMinor) ?
+ !double.IsInfinity(availableSizeMinor) && _itemsStretch == UniformGridLayoutItemsStretch.Fill ?
availableSizeMinor :
- Math.Max(0.0, itemsCount * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
+ Math.Max(0.0, itemsPerLine * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
_orientation.SetMajorSize(
ref extent,
Math.Max(0.0, (itemsCount / itemsPerLine) * lineSize - (double)LineSpacing));
@@ -398,7 +422,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size
// Set the width and height on the grid state. If the user already set them then use the preset.
// If not, we have to measure the first element and get back a size which we're going to be using for the rest of the items.
var gridState = (UniformGridLayoutState)context.LayoutState;
- gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing);
+ gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing, _maximumRowsOrColumns);
var desiredSize = GetFlowAlgorithm(context).Measure(
availableSize,
@@ -406,6 +430,7 @@ protected override Size MeasureOverride(VirtualizingLayoutContext context, Size
true,
MinItemSpacing,
LineSpacing,
+ _maximumRowsOrColumns,
_orientation.ScrollOrientation,
LayoutId);
@@ -421,6 +446,7 @@ protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
+ true,
(FlowLayoutAlgorithm.LineAlignment)_itemsJustification,
LayoutId);
return new Size(value.Width, value.Height);
@@ -471,6 +497,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args)
{
_minItemHeight = (double)args.NewValue;
}
+ else if (args.Property == MaximumRowsOrColumnsProperty)
+ {
+ _maximumRowsOrColumns = (int)args.NewValue;
+ }
InvalidateLayout();
}
@@ -499,7 +529,9 @@ Rect GetLayoutRectForDataIndex(
Rect lastExtent,
VirtualizingLayoutContext context)
{
- int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
+ int itemsPerLine = (int)Math.Min( //note use of unsigned ints
+ Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
+ Math.Max(1u, _maximumRowsOrColumns));
int rowIndex = (int)(index / itemsPerLine);
int indexInRow = index - (rowIndex * itemsPerLine);
diff --git a/src/Avalonia.Layout/UniformGridLayoutState.cs b/src/Avalonia.Layout/UniformGridLayoutState.cs
index e6d75bcf359..62c5174775d 100644
--- a/src/Avalonia.Layout/UniformGridLayoutState.cs
+++ b/src/Avalonia.Layout/UniformGridLayoutState.cs
@@ -48,8 +48,14 @@ internal void EnsureElementSize(
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
- double minColumnSpacing)
+ double minColumnSpacing,
+ int maxItemsPerLine)
{
+ if (maxItemsPerLine == 0)
+ {
+ maxItemsPerLine = 1;
+ }
+
if (context.ItemCount > 0)
{
// If the first element is realized we don't need to cache it or to get it from the context
@@ -57,7 +63,7 @@ internal void EnsureElementSize(
if (realizedElement != null)
{
realizedElement.Measure(availableSize);
- SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
+ SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
_cachedFirstElement = null;
}
else
@@ -72,7 +78,7 @@ internal void EnsureElementSize(
_cachedFirstElement.Measure(availableSize);
- SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
+ SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
// See if we can move ownership to the flow algorithm. If we can, we do not need a local cache.
bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement);
@@ -92,8 +98,14 @@ private void SetSize(
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
- double minColumnSpacing)
+ double minColumnSpacing,
+ int maxItemsPerLine)
{
+ if (maxItemsPerLine == 0)
+ {
+ maxItemsPerLine = 1;
+ }
+
EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth);
EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight);
@@ -101,11 +113,17 @@ private void SetSize(
var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing;
var itemSizeMinor = orientation == Orientation.Horizontal ? EffectiveItemWidth : EffectiveItemHeight;
- itemSizeMinor += minorItemSpacing;
- var numItemsPerColumn = (int)(Math.Max(1.0, availableSizeMinor / itemSizeMinor));
- var remainingSpace = ((int)availableSizeMinor) % ((int)itemSizeMinor);
- var extraMinorPixelsForEachItem = remainingSpace / numItemsPerColumn;
+ double extraMinorPixelsForEachItem = 0.0;
+ if (!double.IsInfinity(availableSizeMinor))
+ {
+ var numItemsPerColumn = Math.Min(
+ maxItemsPerLine,
+ Math.Max(1.0, availableSizeMinor / (itemSizeMinor + minorItemSpacing)));
+ var usedSpace = (numItemsPerColumn * (itemSizeMinor + minorItemSpacing)) - minorItemSpacing;
+ var remainingSpace = ((int)(availableSizeMinor - usedSpace));
+ extraMinorPixelsForEachItem = remainingSpace / ((int)numItemsPerColumn);
+ }
if (stretch == UniformGridLayoutItemsStretch.Fill)
{
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index 571475c7eab..756619fa9f2 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -101,7 +101,7 @@ void DoInitialize(AvaloniaNativePlatformOptions options)
.Bind().ToConstant(new DefaultRenderTimer(60))
.Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
- .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
+ .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind().ToConstant(new MacOSMountedVolumeInfoProvider());
}
diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Controls/ISetResourceParent.cs
similarity index 61%
rename from src/Avalonia.Styling/Styling/ISetStyleParent.cs
rename to src/Avalonia.Styling/Controls/ISetResourceParent.cs
index bca3d9d714f..a1264adc343 100644
--- a/src/Avalonia.Styling/Styling/ISetStyleParent.cs
+++ b/src/Avalonia.Styling/Controls/ISetResourceParent.cs
@@ -1,29 +1,27 @@
-using Avalonia.Controls;
-
-namespace Avalonia.Styling
+namespace Avalonia.Controls
{
///
- /// Defines an interface through which a 's parent can be set.
+ /// Defines an interface through which an 's parent can be set.
///
///
/// You should not usually need to use this interface - it is for internal use only.
///
- public interface ISetStyleParent : IStyle
+ public interface ISetResourceParent : IResourceNode
{
///
- /// Sets the style parent.
+ /// Sets the resource parent.
///
/// The parent.
void SetParent(IResourceNode parent);
///
- /// Notifies the style that a change has been made to resources that apply to it.
+ /// Notifies the resource node that a change has been made to the resources in its parent.
///
/// The event args.
///
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
///
- void NotifyResourcesChanged(ResourcesChangedEventArgs e);
+ void ParentResourcesChanged(ResourcesChangedEventArgs e);
}
}
diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
index 901e27b7b7e..acc2db1ff7b 100644
--- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
@@ -12,8 +12,12 @@ namespace Avalonia.Controls
///
/// An indexed dictionary of resources.
///
- public class ResourceDictionary : AvaloniaDictionary