diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 6a9cff6b71..ce5b37043f 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -861,7 +861,7 @@ namespace Avalonia
}
///
- /// Logs a mesage if the notification represents a binding error.
+ /// Logs a message if the notification represents a binding error.
///
/// The property being bound.
/// The binding notification.
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 5117fdb170..94aefb8869 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -465,9 +465,9 @@ namespace Avalonia
/// Uses the visitor pattern to resolve an untyped property to a typed property.
///
/// The type of user data passed.
- /// The visitor which will accept the typed property.
+ /// The visitor which will accept the typed property.
/// The user data to pass.
- public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data)
+ public abstract void Accept(IAvaloniaPropertyVisitor visitor, ref TData data)
where TData : struct;
///
diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
index c1a2832fde..896d86e29d 100644
--- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
@@ -58,8 +58,8 @@ namespace Avalonia
///
/// This will usually be true, except in
///
- /// which recieves notifications for all changes to property values, whether a value with a higher
- /// priority is present or not. When this property is false, the change that is being signalled
+ /// which receives notifications for all changes to property values, whether a value with a higher
+ /// priority is present or not. When this property is false, the change that is being signaled
/// has not resulted in a change to the property value on the object.
///
public bool IsEffectiveValueChange { get; private set; }
diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs
index e50e100d32..2cd9758f12 100644
--- a/src/Avalonia.Base/Collections/Pooled/PooledList.cs
+++ b/src/Avalonia.Base/Collections/Pooled/PooledList.cs
@@ -1271,7 +1271,7 @@ namespace Avalonia.Collections.Pooled
/// Reverses the elements in a range of this list. Following a call to this
/// method, an element in the range given by index and count
/// which was previously located at index i will now be located at
- /// index index + (index + count - i - 1).
+ /// index + (index + count - i - 1).
///
public void Reverse(int index, int count)
{
diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
index 324279e9f0..2a580fe75f 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Data.Core.Plugins
///
/// A weak reference to the object.
/// The property name.
- /// The inner property accessor used to aceess the property.
+ /// The inner property accessor used to access the property.
///
/// An interface through which future interactions with the
/// property will be made.
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index e6cc1edfdf..a057ad2254 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -13,7 +13,7 @@ namespace Avalonia
/// The type of the property's value.
///
/// Whereas is typed on the owner type, this base
- /// class provides a non-owner-typed interface to a direct poperty.
+ /// class provides a non-owner-typed interface to a direct property.
///
public abstract class DirectPropertyBase : AvaloniaProperty
{
@@ -123,9 +123,9 @@ namespace Avalonia
}
///
- public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data)
+ public override void Accept(IAvaloniaPropertyVisitor visitor, ref TData data)
{
- vistor.Visit(this, ref data);
+ visitor.Visit(this, ref data);
}
///
diff --git a/src/Avalonia.Base/DirectPropertyMetadata`1.cs b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
index 205967984d..eabdef05ed 100644
--- a/src/Avalonia.Base/DirectPropertyMetadata`1.cs
+++ b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
@@ -38,7 +38,7 @@ namespace Avalonia
///
/// Data validation is validation performed at the target of a binding, for example in a
/// view model using the INotifyDataErrorInfo interface. Only certain properties on a
- /// control (such as a TextBox's Text property) will be interested in recieving data
+ /// control (such as a TextBox's Text property) will be interested in receiving data
/// validation messages so this feature must be explicitly enabled by setting this flag.
///
public bool? EnableDataValidation { get; private set; }
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
index 5b1e43b8f4..fe6acdc532 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
@@ -877,7 +877,7 @@ namespace Avalonia.Collections
if (!CheckFlag(CollectionViewFlags.IsMoveToPageDeferred))
{
// if the temporaryGroup was not created yet and is out of sync
- // then create it so that we can use it as a refernce while paging.
+ // then create it so that we can use it as a reference while paging.
if (IsGrouping && _temporaryGroup.ItemCount != InternalList.Count)
{
PrepareTemporaryGroups();
@@ -889,7 +889,7 @@ namespace Avalonia.Collections
else if (IsGrouping)
{
// if the temporaryGroup was not created yet and is out of sync
- // then create it so that we can use it as a refernce while paging.
+ // then create it so that we can use it as a reference while paging.
if (_temporaryGroup.ItemCount != InternalList.Count)
{
// update the groups that get created for the
@@ -1951,7 +1951,7 @@ namespace Avalonia.Collections
EnsureCollectionInSync();
VerifyRefreshNotDeferred();
- // for indicies larger than the count
+ // for indices larger than the count
if (index >= Count || index < 0)
{
throw new ArgumentOutOfRangeException("index");
@@ -3800,7 +3800,7 @@ namespace Avalonia.Collections
///
///
/// This method can be called from a constructor - it does not call
- /// any virtuals. The 'count' parameter is substitute for the real Count,
+ /// any virtuals. The 'count' parameter is substitute for the real Count,
/// used only when newItem is null.
/// In that case, this method sets IsCurrentAfterLast to true if and only
/// if newPosition >= count. This distinguishes between a null belonging
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index 83f13fe199..2f170c617d 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -67,7 +67,7 @@ namespace Avalonia.Controls
private const double DATAGRID_minimumColumnHeaderHeight = 4;
internal const double DATAGRID_maximumStarColumnWidth = 10000;
internal const double DATAGRID_minimumStarColumnWidth = 0.001;
- private const double DATAGRID_mouseWheelDelta = 72.0;
+ private const double DATAGRID_mouseWheelDelta = 50.0;
private const double DATAGRID_maxHeadersThickness = 32768;
private const double DATAGRID_defaultRowHeight = 22;
@@ -2217,20 +2217,23 @@ namespace Avalonia.Controls
if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0)
{
double scrollHeight = 0;
- if (e.Delta.Y > 0)
+ var delta = DATAGRID_mouseWheelDelta * e.Delta.Y;
+ var deltaAbs = Math.Abs(delta);
+
+ if (delta > 0)
{
- scrollHeight = Math.Max(-_verticalOffset, -DATAGRID_mouseWheelDelta);
+ scrollHeight = Math.Max(-_verticalOffset, -deltaAbs);
}
- else if (e.Delta.Y < 0)
+ else if (delta < 0)
{
if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible)
{
- scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
+ scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), deltaAbs);
}
else
{
double maximum = EdgedRowsHeightCalculated - CellsHeight;
- scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
+ scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), deltaAbs);
}
}
if (scrollHeight != 0)
@@ -5731,7 +5734,7 @@ namespace Avalonia.Controls
{
if (SelectionMode == DataGridSelectionMode.Single || !ctrl)
{
- // Unselect the currectly selected rows except the new selected row
+ // Unselect the currently selected rows except the new selected row
action = DataGridSelectionAction.SelectCurrent;
}
else
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
index 445dc541a7..0de4612958 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
@@ -197,7 +197,7 @@ namespace Avalonia.Controls
}
// Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the
- // right gridline will be collapsed if this cell belongs to the lastVisibileColumn and there is no filler column
+ // right gridline will be collapsed if this cell belongs to the lastVisibleColumn and there is no filler column
internal void EnsureGridLine(DataGridColumn lastVisibleColumn)
{
if (OwningGrid != null && _rightGridLine != null)
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs b/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
index 2f723154be..26f0b53952 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
@@ -40,7 +40,7 @@ namespace Avalonia.Controls
return false;
}
- // There is build warning if this is missiing
+ // There is build warning if this is missing
public override int GetHashCode()
{
return base.GetHashCode();
diff --git a/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs b/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
index a4bab8b304..ee69f1c768 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
@@ -189,7 +189,7 @@ namespace Avalonia.Controls
}
///
- /// DataGrid row item used for proparing the ClipboardRowContent.
+ /// DataGrid row item used for preparing the ClipboardRowContent.
///
public object Item
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
index 5c09bab678..4ab2869138 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
@@ -795,7 +795,7 @@ namespace Avalonia.Controls
}
///
- /// If the DataGrid is using using layout rounding, the pixel snapping will force all widths to
+ /// If the DataGrid is using layout rounding, the pixel snapping will force all widths to
/// whole numbers. Since the column widths aren't visual elements, they don't go through the normal
/// rounding process, so we need to do it ourselves. If we don't, then we'll end up with some
/// pixel gaps and/or overlaps between columns.
diff --git a/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs b/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
index e659438b43..2b8055dd22 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
@@ -79,7 +79,7 @@ namespace Avalonia.Controls
set;
}
- internal void AddRecylableRow(DataGridRow row)
+ internal void AddRecyclableRow(DataGridRow row)
{
Debug.Assert(!_recyclableRows.Contains(row));
row.DetachFromDataGrid(true);
@@ -120,7 +120,7 @@ namespace Avalonia.Controls
{
if (row.IsRecyclable)
{
- AddRecylableRow(row);
+ AddRecyclableRow(row);
}
else
{
@@ -193,7 +193,7 @@ namespace Avalonia.Controls
internal void FullyRecycleElements()
{
- // Fully recycle Recycleable rows and transfer them to Recycled rows
+ // Fully recycle Recyclable rows and transfer them to Recycled rows
while (_recyclableRows.Count > 0)
{
DataGridRow row = _recyclableRows.Pop();
@@ -202,7 +202,7 @@ namespace Avalonia.Controls
Debug.Assert(!_fullyRecycledRows.Contains(row));
_fullyRecycledRows.Push(row);
}
- // Fully recycle Recycleable GroupHeaders and transfer them to Recycled GroupHeaders
+ // Fully recycle Recyclable GroupHeaders and transfer them to Recycled GroupHeaders
while (_recyclableGroupHeaders.Count > 0)
{
DataGridRowGroupHeader groupHeader = _recyclableGroupHeaders.Pop();
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
index c3562c53a4..7546970498 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
@@ -392,7 +392,7 @@ namespace Avalonia.Controls
set;
}
- // Height that the row will eventually end up at after a possible detalis animation has completed
+ // Height that the row will eventually end up at after a possible details animation has completed
internal double TargetHeight
{
get
@@ -517,7 +517,7 @@ namespace Avalonia.Controls
return base.MeasureOverride(availableSize);
}
- //Allow the DataGrid specific componets to adjust themselves based on new values
+ //Allow the DataGrid specific components to adjust themselves based on new values
if (_headerElement != null)
{
_headerElement.InvalidateMeasure();
@@ -722,7 +722,7 @@ namespace Avalonia.Controls
if (_bottomGridLine != null)
{
// It looks like setting Visibility sometimes has side effects so make sure the value is actually
- // diffferent before setting it
+ // different before setting it
bool newVisibility = OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.Horizontal || OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.All;
if (newVisibility != _bottomGridLine.IsVisible)
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
index 4bfbd7d818..1d5c899993 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
@@ -1193,7 +1193,7 @@ namespace Avalonia.Controls
else
{
groupHeader = element as DataGridRowGroupHeader;
- Debug.Assert(groupHeader != null); // Nothig other and Rows and RowGroups now
+ Debug.Assert(groupHeader != null); // Nothing other and Rows and RowGroups now
if (groupHeader != null)
{
groupHeader.TotalIndent = (groupHeader.Level == 0) ? 0 : RowGroupSublevelIndents[groupHeader.Level - 1];
@@ -1636,7 +1636,7 @@ namespace Avalonia.Controls
if (slot >= DisplayData.FirstScrollingSlot &&
slot <= DisplayData.LastScrollingSlot)
{
- // Additional row takes the spot of a displayed row - it is necessarilly displayed
+ // Additional row takes the spot of a displayed row - it is necessarily displayed
return true;
}
else if (DisplayData.FirstScrollingSlot == -1 &&
@@ -1825,7 +1825,7 @@ namespace Avalonia.Controls
if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset))
{
// We've scrolled off more of the first row than what's possible. This can happen
- // if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling
+ // if the first row got shorter (Ex: Collapsing RowDetails) or if the user has a recycling
// cleanup issue. In this case, simply try to display the next row as the first row instead
if (newFirstScrollingSlot < SlotCount - 1)
{
@@ -2014,7 +2014,7 @@ namespace Avalonia.Controls
if (recycleRow)
{
- DisplayData.AddRecylableRow(dataGridRow);
+ DisplayData.AddRecyclableRow(dataGridRow);
}
else
{
@@ -2265,7 +2265,7 @@ namespace Avalonia.Controls
if (parentGroupInfo.LastSubItemSlot - parentGroupInfo.Slot == 1)
{
// We just added the first item to a RowGroup so the header should transition from Empty to either Expanded or Collapsed
- EnsureAnscestorsExpanderButtonChecked(parentGroupInfo);
+ EnsureAncestorsExpanderButtonChecked(parentGroupInfo);
}
}
}
@@ -2407,7 +2407,7 @@ namespace Avalonia.Controls
return treeCount;
}
- private void EnsureAnscestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
+ private void EnsureAncestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
{
if (IsSlotVisible(parentGroupInfo.Slot))
{
@@ -2789,11 +2789,11 @@ namespace Avalonia.Controls
return null;
}
- internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisibile, bool setCurrent)
+ internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisible, bool setCurrent)
{
Debug.Assert(groupHeader.RowGroupInfo.CollectionViewGroup.ItemCount > 0);
- if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisibile, setCurrent); }) || !CommitEdit())
+ if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisible, setCurrent); }) || !CommitEdit())
{
return;
}
@@ -2804,7 +2804,7 @@ namespace Avalonia.Controls
UpdateSelectionAndCurrency(CurrentColumnIndex, groupHeader.RowGroupInfo.Slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
- UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisibile, isDisplayed: true);
+ UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisible, isDisplayed: true);
ComputeScrollBarsLayout();
// We need force arrange since our Scrollings Rows could update without automatically triggering layout
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
index 1c350a4f14..4eed119240 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
+++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
@@ -140,7 +140,7 @@ namespace Avalonia.Controls.Primitives
if (dataGridColumn.IsFrozen)
{
columnHeader.Arrange(new Rect(frozenLeftEdge, 0, dataGridColumn.LayoutRoundedWidth, finalSize.Height));
- columnHeader.Clip = null; // The layout system could have clipped this becaues it's not aware of our render transform
+ columnHeader.Clip = null; // The layout system could have clipped this because it's not aware of our render transform
if (DragColumn == dataGridColumn && DragIndicator != null)
{
dragIndicatorLeftEdge = frozenLeftEdge + DragIndicatorOffset;
diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs
index f616a42cac..d44b2ab0db 100644
--- a/src/Avalonia.Controls/AppBuilderBase.cs
+++ b/src/Avalonia.Controls/AppBuilderBase.cs
@@ -273,7 +273,7 @@ namespace Avalonia.Controls
}
///
- /// Sets up the platform-speciic services for the .
+ /// Sets up the platform-specific services for the .
///
private void Setup()
{
diff --git a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
index 9f3a6da9da..46affcbe33 100644
--- a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
+++ b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
@@ -26,13 +26,13 @@ namespace Avalonia.Controls.Converters
Right ? Indent * scalarDepth : 0,
Bottom ? Indent * scalarDepth : 0);
}
- else if (value is Thickness thinknessDepth)
+ else if (value is Thickness thicknessDepth)
{
return new Thickness(
- Left ? Indent * thinknessDepth.Left : 0,
- Top ? Indent * thinknessDepth.Top : 0,
- Right ? Indent * thinknessDepth.Right : 0,
- Bottom ? Indent * thinknessDepth.Bottom : 0);
+ Left ? Indent * thicknessDepth.Left : 0,
+ Top ? Indent * thicknessDepth.Top : 0,
+ Right ? Indent * thicknessDepth.Right : 0,
+ Bottom ? Indent * thicknessDepth.Bottom : 0);
}
return new Thickness(0);
diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
index e6420fe342..65f95808ff 100644
--- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
+++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Controls.Converters
if (parameter == null ||
values == null ||
values.Count != 4 ||
- !(values[0] is ScrollBarVisibility visiblity) ||
+ !(values[0] is ScrollBarVisibility visibility) ||
!(values[1] is double offset) ||
!(values[2] is double extent) ||
!(values[3] is double viewport))
@@ -24,7 +24,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue;
}
- if (visiblity == ScrollBarVisibility.Auto)
+ if (visibility == ScrollBarVisibility.Auto)
{
if (extent == viewport)
{
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 43bc7d1df9..5893a02b04 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -71,7 +71,7 @@ namespace Avalonia.Controls
x => x.MonthVisible, (x, v) => x.MonthVisible = v);
///
- /// Defiens the Property
+ /// Defines the Property
///
public static readonly DirectProperty YearFormatProperty =
AvaloniaProperty.RegisterDirect(nameof(YearFormat),
diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
index a0e8c03195..e16e609a15 100644
--- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
@@ -220,15 +220,15 @@ namespace Avalonia.Controls.Primitives
if (dy > 0) // Scroll Down
{
- int numContsToMove = 0;
+ int numCountsToMove = 0;
for (int i = 0; i < children.Count; i++)
{
if (children[i].Bounds.Bottom - dy < 0)
- numContsToMove++;
+ numCountsToMove++;
else
break;
}
- children.MoveRange(0, numContsToMove, children.Count);
+ children.MoveRange(0, numCountsToMove, children.Count);
var scrollHeight = _extent.Height - Viewport.Height;
if (ShouldLoop && value.Y >= scrollHeight - _extentOne)
@@ -236,15 +236,15 @@ namespace Avalonia.Controls.Primitives
}
else if (dy < 0) // Scroll Up
{
- int numContsToMove = 0;
+ int numCountsToMove = 0;
for (int i = children.Count - 1; i >= 0; i--)
{
if (children[i].Bounds.Top - dy > Bounds.Height)
- numContsToMove++;
+ numCountsToMove++;
else
break;
}
- children.MoveRange(children.Count - numContsToMove, numContsToMove, 0);
+ children.MoveRange(children.Count - numCountsToMove, numCountsToMove, 0);
if (ShouldLoop && value.Y < _extentOne)
_offset = new Vector(0, value.Y + (_extentOne * 50));
}
diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs
index 3237f6f37b..37b8691ce9 100644
--- a/src/Avalonia.Controls/DefinitionBase.cs
+++ b/src/Avalonia.Controls/DefinitionBase.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Controls
if (_sharedState == null)
{
// start with getting SharedSizeGroup value.
- // this property is NOT inhereted which should result in better overall perf.
+ // this property is NOT inherited which should result in better overall perf.
string sharedSizeGroupId = SharedSizeGroup;
if (sharedSizeGroupId != null)
{
@@ -52,7 +52,7 @@ namespace Avalonia.Controls
}
///
- /// Callback to notify about exitting model tree.
+ /// Callback to notify about exiting model tree.
///
internal void OnExitParentTree()
{
@@ -458,7 +458,7 @@ namespace Avalonia.Controls
private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content"
private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's
- private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure
+ private double _measureSize; // size, calculated to be the input constraint size for Child.Measure
private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations
private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case)
@@ -556,7 +556,7 @@ namespace Avalonia.Controls
}
///
- /// Propogates invalidations for all registered definitions.
+ /// Propagates invalidations for all registered definitions.
/// Resets its own state.
///
internal void Invalidate()
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index 30fb2c7761..8448dde21e 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
@@ -552,7 +552,7 @@ namespace Avalonia.Controls.Primitives
internal static void SetPresenterClasses(IControl presenter, Classes classes)
{
- //Remove any classes no longer in use, ignoring pseudoclasses
+ //Remove any classes no longer in use, ignoring pseudo classes
for (int i = presenter.Classes.Count - 1; i >= 0; i--)
{
if (!classes.Contains(presenter.Classes[i]) &&
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index a14df1eb43..4e60b52f83 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -330,7 +330,7 @@ namespace Avalonia.Controls
// value of Auto column), "cell 2 1" needs to be calculated first,
// as it contributes to the Auto column's calculated value.
// At the same time in order to accurately calculate constraint
- // height for "cell 2 1", "cell 1 2" needs to be calcualted first,
+ // height for "cell 2 1", "cell 1 2" needs to be calculated first,
// as it contributes to Auto row height, which is used in the
// computation of Star row resolved height.
//
@@ -405,11 +405,11 @@ namespace Avalonia.Controls
//
// where:
// * all [Measure GroupN] - regular children measure process -
- // each cell is measured given contraint size as an input
+ // each cell is measured given constraint size as an input
// and each cell's desired size is accumulated on the
// corresponding column / row;
// * [Measure Group2'] - is when each cell is measured with
- // infinit height as a constraint and a cell's desired
+ // infinite height as a constraint and a cell's desired
// height is ignored;
// * [Measure Groups''] - is when each cell is measured (second
// time during single Grid.MeasureOverride) regularly but its
@@ -780,7 +780,7 @@ namespace Avalonia.Controls
}
///
- /// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection
+ /// Initializes DefinitionsU member either to user supplied ColumnDefinitions collection
/// or to a default single element collection. DefinitionsU gets trimmed to size.
///
///
@@ -821,7 +821,7 @@ namespace Avalonia.Controls
}
///
- /// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection
+ /// Initializes DefinitionsV member either to user supplied RowDefinitions collection
/// or to a default single element collection. DefinitionsV gets trimmed to size.
///
///
@@ -2132,7 +2132,7 @@ namespace Avalonia.Controls
//
// Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
// each being allocated a large number of pixels (~50 or greater), and
- // people don't even notice the kind of 1-pixel anomolies that are
+ // people don't even notice the kind of 1-pixel anomalies that are
// theoretically inevitable, or don't care if they do. At least they shouldn't
// care - no one should be using the results WPF's grid layout to make
// quantitative decisions; its job is to produce a reasonable display, not
@@ -2597,7 +2597,7 @@ namespace Avalonia.Controls
if (scale < 0.0)
{
// if one of the *-weights is Infinity, adjust the weights by mapping
- // Infinty to 1.0 and everything else to 0.0: the infinite items share the
+ // Infinity to 1.0 and everything else to 0.0: the infinite items share the
// available space equally, everyone else gets nothing.
return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0;
}
@@ -2655,7 +2655,7 @@ namespace Avalonia.Controls
private enum Flags
{
//
- // the foolowing flags let grid tracking dirtiness in more granular manner:
+ // the following flags let grid tracking dirtiness in more granular manner:
// * Valid???Structure flags indicate that elements were added or removed.
// * Valid???Layout flags indicate that layout time portion of the information
// stored on the objects should be updated.
@@ -2684,7 +2684,7 @@ namespace Avalonia.Controls
///
/// ShowGridLines property. This property is used mostly
- /// for simplification of visual debuggig. When it is set
+ /// for simplification of visual debugging. When it is set
/// to true grid lines are drawn to visualize location
/// of grid lines.
///
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index f4185650bb..43b4908482 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -89,7 +89,7 @@ namespace Avalonia.Controls
///
///
/// Note that the selection mode only applies to selections made via user interaction.
- /// Multiple selections can be made programatically regardless of the value of this property.
+ /// Multiple selections can be made programmatically regardless of the value of this property.
///
public new SelectionMode SelectionMode
{
diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
index d3d3721c89..49b36e714d 100644
--- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs
+++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
@@ -3,7 +3,7 @@
namespace Avalonia.Controls
{
- [Obsolete("This class exists to maintain backwards compatiblity with existing code. Use NativeMenuItemSeparator instead")]
+ [Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")]
public class NativeMenuItemSeperator : NativeMenuItemSeparator
{
}
diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
index bb3c0288eb..8513dd1697 100644
--- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
+++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Platform
///
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
- /// This is because Windows Chrome can not be shown ontop of user content.
+ /// This is because Windows Chrome can not be shown on top of user content.
///
PreferSystemChrome = 0x02,
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
index dd839a0e9b..0f0dd7311d 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
@@ -27,7 +27,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
///
/// An implementation for platforms on which a popup can be
- /// aritrarily positioned.
+ /// arbitrarily positioned.
///
public class ManagedPopupPositioner : IPopupPositioner
{
diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs
index 0b716ec1ca..acb8e0f006 100644
--- a/src/Avalonia.Controls/Primitives/RangeBase.cs
+++ b/src/Avalonia.Controls/Primitives/RangeBase.cs
@@ -170,7 +170,7 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Checks if the double value is not inifinity nor NaN.
+ /// Checks if the double value is not infinity nor NaN.
///
/// The value.
private static bool ValidateDouble(double value)
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 336ff711c0..07bc789cc1 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -328,7 +328,7 @@ namespace Avalonia.Controls.Primitives
///
///
/// Note that the selection mode only applies to selections made via user interaction.
- /// Multiple selections can be made programatically regardless of the value of this property.
+ /// Multiple selections can be made programmatically regardless of the value of this property.
///
protected SelectionMode SelectionMode
{
diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs
index 6a899a6f26..f424ae29b7 100644
--- a/src/Avalonia.Controls/Repeater/IElementFactory.cs
+++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs
@@ -46,7 +46,7 @@ namespace Avalonia.Controls
}
///
- /// A data template that supports creating and recyling elements for an .
+ /// A data template that supports creating and recycling elements for an .
///
public interface IElementFactory : IDataTemplate
{
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index fb2da09e73..01200e87e3 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -441,9 +441,9 @@ namespace Avalonia.Controls
base.OnPropertyChanged(change);
}
- internal IControl GetElementImpl(int index, bool forceCreate, bool supressAutoRecycle)
+ internal IControl GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle)
{
- var element = _viewManager.GetElement(index, forceCreate, supressAutoRecycle);
+ var element = _viewManager.GetElement(index, forceCreate, suppressAutoRecycle);
return element;
}
diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs
index cf2066b373..a7b6cf7f18 100644
--- a/src/Avalonia.Controls/Repeater/ViewManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewManager.cs
@@ -174,7 +174,7 @@ namespace Avalonia.Controls
}
else
{
- // We could not find a candiate.
+ // We could not find a candidate.
_lastFocusedElement = null;
}
}
diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs
index da3c2b15e6..1a90da5830 100644
--- a/src/Avalonia.Controls/Repeater/ViewportManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs
@@ -186,7 +186,7 @@ namespace Avalonia.Controls
_expectedViewportShift.X + _layoutExtent.X - extent.X,
_expectedViewportShift.Y + _layoutExtent.Y - extent.Y);
- // We tolerate viewport imprecisions up to 1 pixel to avoid invaliding layout too much.
+ // We tolerate viewport imprecisions up to 1 pixel to avoid invalidating layout too much.
if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1)
{
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})",
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index 559edeb204..eee6216587 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -653,7 +653,7 @@ namespace Avalonia.Controls
private void CalculatedPropertiesChanged()
{
// Pass old values of 0 here because we don't have the old values at this point,
- // and it shouldn't matter as only the template uses these properies.
+ // and it shouldn't matter as only the template uses these properties.
RaisePropertyChanged(HorizontalScrollBarMaximumProperty, 0, HorizontalScrollBarMaximum);
RaisePropertyChanged(HorizontalScrollBarValueProperty, 0, HorizontalScrollBarValue);
RaisePropertyChanged(HorizontalScrollBarViewportSizeProperty, 0, HorizontalScrollBarViewportSize);
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index 6ae53a4d59..138a765b43 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -345,7 +345,7 @@ namespace Avalonia.Controls.Selection
private protected override void OnSelectionChanged(IReadOnlyList deselectedItems)
{
// Note: We're *not* putting this in a using scope. A collection update is still in progress
- // so the operation won't get commited by normal means: we have to commit it manually.
+ // so the operation won't get committed by normal means: we have to commit it manually.
var update = BatchUpdate();
update.Operation.DeselectedItems = deselectedItems;
diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs
index 237bc2ce1d..12ae766052 100644
--- a/src/Avalonia.Controls/TickBar.cs
+++ b/src/Avalonia.Controls/TickBar.cs
@@ -193,7 +193,7 @@ namespace Avalonia.Controls
///
/// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or
- /// top and bottom spacing (for vertical orienation).
+ /// top and bottom spacing (for vertical orientation).
/// The space on both sides of TickBar is half of specified ReservedSpace.
/// This property has type of .
///
@@ -210,7 +210,7 @@ namespace Avalonia.Controls
/// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and
/// SelectionStart and SelectionEnd are valid.
///
- /// The primary ticks (for Mininum and Maximum value) height will be 100% of TickBar's render size (use Width or Height
+ /// The primary ticks (for Minimum and Maximum value) height will be 100% of TickBar's render size (use Width or Height
/// depends on Placement property).
///
/// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size.
@@ -221,7 +221,7 @@ namespace Avalonia.Controls
{
var size = new Size(Bounds.Width, Bounds.Height);
var range = Maximum - Minimum;
- var tickLen = 0.0d; // Height for Primary Tick (for Mininum and Maximum value)
+ var tickLen = 0.0d; // Height for Primary Tick (for Minimum and Maximum value)
var tickLen2 = 0.0d; // Height for Secondary Tick
var logicalToPhysical = 1.0;
var startPoint = new Point();
@@ -285,7 +285,7 @@ namespace Avalonia.Controls
tickLen2 = tickLen * 0.75;
- // Invert direciton of the ticks
+ // Invert direction of the ticks
if (IsDirectionReversed)
{
logicalToPhysical *= -1;
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 116e276a3a..5d9a0c8eed 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -90,6 +90,7 @@ namespace Avalonia.Controls
///
static TopLevel()
{
+ KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(KeyboardNavigationMode.Cycle);
AffectsMeasure(ClientSizeProperty);
TransparencyLevelHintProperty.Changed.AddClassHandler(
@@ -224,7 +225,7 @@ namespace Avalonia.Controls
}
///
- /// Gets the acheived that the platform was able to provide.
+ /// Gets the achieved that the platform was able to provide.
///
public WindowTransparencyLevel ActualTransparencyLevel
{
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 7cfc5f8be8..ee008efb04 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -254,7 +254,7 @@ namespace Avalonia.Controls
}
///
- /// Called durung the arrange pass to set the size of the window.
+ /// Called during the arrange pass to set the size of the window.
///
/// The requested size of the window.
/// The actual size of the window.
diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj
index c39c81a965..69a80290d1 100644
--- a/src/Avalonia.Input/Avalonia.Input.csproj
+++ b/src/Avalonia.Input/Avalonia.Input.csproj
@@ -4,6 +4,9 @@
Enable
CS8600;CS8602;CS8603
+
+
+
diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs
index 2b99c51472..122838f682 100644
--- a/src/Avalonia.Input/Cursor.cs
+++ b/src/Avalonia.Input/Cursor.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Input
BottomSize = BottomSide
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
- // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image
+ // We might enable them later, preferably, by loading pixmax directly from theme with fallback image
// SizeNorthWestSouthEast,
// SizeNorthEastSouthWest,
}
diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs
index 474b212a21..1432092ba1 100644
--- a/src/Avalonia.Input/FocusManager.cs
+++ b/src/Avalonia.Input/FocusManager.cs
@@ -92,6 +92,17 @@ namespace Avalonia.Input
}
}
+ public IInputElement? GetFocusedElement(IInputElement e)
+ {
+ if (e is IFocusScope scope)
+ {
+ _focusScopes.TryGetValue(scope, out var result);
+ return result;
+ }
+
+ return null;
+ }
+
///
/// Sets the currently focused element in the specified scope.
///
@@ -151,6 +162,8 @@ namespace Avalonia.Input
Focus(e);
}
+ public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
+
///
/// Checks if the specified element can be focused.
///
diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
index 3858cc04f2..84a26a0cc3 100644
--- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
+++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
@@ -78,7 +78,7 @@ namespace Avalonia.Input.GestureRecognizers
// Arbitrary chosen value, probably need to move that to platform settings or something
private const double ScrollStartDistance = 30;
- // Pixels per second speed that is considered to be the stop of inertiall scroll
+ // Pixels per second speed that is considered to be the stop of inertial scroll
private const double InertialScrollSpeedEnd = 5;
public void PointerMoved(PointerEventArgs e)
diff --git a/src/Avalonia.Input/ICommandSource.cs b/src/Avalonia.Input/ICommandSource.cs
index ba2e8eed4e..eed71759d5 100644
--- a/src/Avalonia.Input/ICommandSource.cs
+++ b/src/Avalonia.Input/ICommandSource.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Input
///
- /// Bor the bheavior CanExecuteChanged
+ /// Bor the behavior CanExecuteChanged
///
///
///
diff --git a/src/Avalonia.Input/ICustomKeyboardNavigation.cs b/src/Avalonia.Input/ICustomKeyboardNavigation.cs
index 3d2927c632..357395c42f 100644
--- a/src/Avalonia.Input/ICustomKeyboardNavigation.cs
+++ b/src/Avalonia.Input/ICustomKeyboardNavigation.cs
@@ -1,4 +1,5 @@
-
+#nullable enable
+
namespace Avalonia.Input
{
///
@@ -6,6 +7,18 @@ namespace Avalonia.Input
///
public interface ICustomKeyboardNavigation
{
- (bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction);
+ ///
+ /// Gets the next element in the specified navigation direction.
+ ///
+ /// The element being navigated from.
+ /// The navigation direction.
+ ///
+ /// A tuple consisting of:
+ /// - A boolean indicating whether the request was handled. If false is returned then
+ /// custom navigation will be ignored and default navigation will take place.
+ /// - If handled is true: the next element in the navigation direction, or null if default
+ /// navigation should continue outside the element.
+ ///
+ (bool handled, IInputElement? next) GetNext(IInputElement element, NavigationDirection direction);
}
}
diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs
index 65b9acae76..63080e74e4 100644
--- a/src/Avalonia.Input/InputElement.cs
+++ b/src/Avalonia.Input/InputElement.cs
@@ -71,6 +71,12 @@ namespace Avalonia.Input
public static readonly DirectProperty IsPointerOverProperty =
AvaloniaProperty.RegisterDirect(nameof(IsPointerOver), o => o.IsPointerOver);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty IsTabStopProperty =
+ KeyboardNavigation.IsTabStopProperty.AddOwner();
+
///
/// Defines the event.
///
@@ -99,6 +105,12 @@ namespace Avalonia.Input
"KeyUp",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TabIndexProperty =
+ KeyboardNavigation.TabIndexProperty.AddOwner();
+
///
/// Defines the event.
///
@@ -426,6 +438,15 @@ namespace Avalonia.Input
internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); }
}
+ ///
+ /// Gets or sets a value that indicates whether the control is included in tab navigation.
+ ///
+ public bool IsTabStop
+ {
+ get => GetValue(IsTabStopProperty);
+ set => SetValue(IsTabStopProperty, value);
+ }
+
///
public bool IsEffectivelyEnabled
{
@@ -437,6 +458,16 @@ namespace Avalonia.Input
}
}
+ ///
+ /// Gets or sets a value that determines the order in which elements receive focus when the
+ /// user navigates through controls by pressing the Tab key.
+ ///
+ public int TabIndex
+ {
+ get => GetValue(TabIndexProperty);
+ set => SetValue(TabIndexProperty, value);
+ }
+
public List KeyBindings { get; } = new List();
///
diff --git a/src/Avalonia.Input/KeyboardNavigation.cs b/src/Avalonia.Input/KeyboardNavigation.cs
index 6ef3c4fd60..a25aed6811 100644
--- a/src/Avalonia.Input/KeyboardNavigation.cs
+++ b/src/Avalonia.Input/KeyboardNavigation.cs
@@ -5,6 +5,15 @@ namespace Avalonia.Input
///
public static class KeyboardNavigation
{
+ ///
+ /// Defines the TabIndex attached property.
+ ///
+ public static readonly AttachedProperty TabIndexProperty =
+ AvaloniaProperty.RegisterAttached(
+ "TabIndex",
+ typeof(KeyboardNavigation),
+ int.MaxValue);
+
///
/// Defines the TabNavigation attached property.
///
@@ -42,6 +51,26 @@ namespace Avalonia.Input
typeof(KeyboardNavigation),
true);
+ ///
+ /// Gets the for an element.
+ ///
+ /// The container.
+ /// The for the container.
+ public static int GetTabIndex(IInputElement element)
+ {
+ return ((IAvaloniaObject)element).GetValue(TabIndexProperty);
+ }
+
+ ///
+ /// Sets the for an element.
+ ///
+ /// The element.
+ /// The tab index.
+ public static void SetTabIndex(IInputElement element, int value)
+ {
+ ((IAvaloniaObject)element).SetValue(TabIndexProperty, value);
+ }
+
///
/// Gets the for a container.
///
@@ -83,7 +112,7 @@ namespace Avalonia.Input
}
///
- /// Sets the for a container.
+ /// Sets the for an element.
///
/// The container.
/// Value indicating whether the container is a tab stop.
@@ -93,7 +122,7 @@ namespace Avalonia.Input
}
///
- /// Gets the for a container.
+ /// Gets the for an element.
///
/// The container.
/// Whether the container is a tab stop.
diff --git a/src/Avalonia.Input/KeyboardNavigationHandler.cs b/src/Avalonia.Input/KeyboardNavigationHandler.cs
index dbefe63789..6493777105 100644
--- a/src/Avalonia.Input/KeyboardNavigationHandler.cs
+++ b/src/Avalonia.Input/KeyboardNavigationHandler.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Input.Navigation;
using Avalonia.VisualTree;
@@ -48,39 +49,24 @@ namespace Avalonia.Input
{
element = element ?? throw new ArgumentNullException(nameof(element));
- var customHandler = element.GetSelfAndVisualAncestors()
- .OfType()
- .FirstOrDefault();
+ // If there's a custom keyboard navigation handler as an ancestor, use that.
+ var custom = element.FindAncestorOfType(true);
+ if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce))
+ return ce;
- if (customHandler != null)
+ var result = direction switch
{
- var (handled, next) = customHandler.GetNext(element, direction);
+ NavigationDirection.Next => TabNavigation.GetNextTab(element, false),
+ NavigationDirection.Previous => TabNavigation.GetPrevTab(element, null, false),
+ _ => throw new NotSupportedException(),
+ };
- if (handled)
- {
- if (next != null)
- {
- return next;
- }
- else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
- {
- return TabNavigation.GetNextInTabOrder((IInputElement)customHandler, direction, true);
- }
- else
- {
- return null;
- }
- }
- }
+ // If there wasn't a custom navigation handler as an ancestor of the current element,
+ // but there is one as an ancestor of the new element, use that.
+ if (custom is null && HandlePostCustomNavigation(element, result, direction, out ce))
+ return ce;
- if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
- {
- return TabNavigation.GetNextInTabOrder(element, direction);
- }
- else
- {
- throw new NotSupportedException();
- }
+ return result;
}
///
@@ -90,7 +76,7 @@ namespace Avalonia.Input
/// The direction to move.
/// Any key modifiers active at the time of focus.
public void Move(
- IInputElement element,
+ IInputElement element,
NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None)
{
@@ -124,5 +110,70 @@ namespace Avalonia.Input
e.Handled = true;
}
}
+
+ private static bool HandlePreCustomNavigation(
+ ICustomKeyboardNavigation customHandler,
+ IInputElement element,
+ NavigationDirection direction,
+ [NotNullWhen(true)] out IInputElement? result)
+ {
+ if (customHandler != null)
+ {
+ var (handled, next) = customHandler.GetNext(element, direction);
+
+ if (handled)
+ {
+ if (next != null)
+ {
+ result = next;
+ return true;
+ }
+ else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
+ {
+ var r = direction switch
+ {
+ NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
+ NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
+ _ => throw new NotSupportedException(),
+ };
+
+ if (r is object)
+ {
+ result = r;
+ return true;
+ }
+ }
+ }
+ }
+
+ result = null;
+ return false;
+ }
+
+ private static bool HandlePostCustomNavigation(
+ IInputElement element,
+ IInputElement? newElement,
+ NavigationDirection direction,
+ [NotNullWhen(true)] out IInputElement? result)
+ {
+ if (newElement is object)
+ {
+ var customHandler = newElement.FindAncestorOfType(true);
+
+ if (customHandler is object)
+ {
+ var (handled, next) = customHandler.GetNext(element, direction);
+
+ if (handled && next is object)
+ {
+ result = next;
+ return true;
+ }
+ }
+ }
+
+ result = null;
+ return false;
+ }
}
}
diff --git a/src/Avalonia.Input/KeyboardNavigationMode.cs b/src/Avalonia.Input/KeyboardNavigationMode.cs
index 41e778bf49..e01ebf0330 100644
--- a/src/Avalonia.Input/KeyboardNavigationMode.cs
+++ b/src/Avalonia.Input/KeyboardNavigationMode.cs
@@ -36,5 +36,10 @@ namespace Avalonia.Input
/// The container's children will not be focused when using the tab key.
///
None,
+
+ ///
+ /// TabIndexes are considered on local subtree only inside this container
+ ///
+ Local,
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Input/Navigation/TabNavigation.cs b/src/Avalonia.Input/Navigation/TabNavigation.cs
index 6f6d68940b..ed7df67bf2 100644
--- a/src/Avalonia.Input/Navigation/TabNavigation.cs
+++ b/src/Avalonia.Input/Navigation/TabNavigation.cs
@@ -10,277 +10,663 @@ namespace Avalonia.Input.Navigation
///
internal static class TabNavigation
{
- ///
- /// Gets the next control in the specified tab direction.
- ///
- /// The element.
- /// The tab direction. Must be Next or Previous.
- ///
- /// If true will not descend into to find next control.
- ///
- ///
- /// The next element in the specified direction, or null if
- /// was the last in the requested direction.
- ///
- public static IInputElement? GetNextInTabOrder(
- IInputElement element,
- NavigationDirection direction,
- bool outsideElement = false)
+ public static IInputElement? GetNextTab(IInputElement e, bool goDownOnly)
{
- element = element ?? throw new ArgumentNullException(nameof(element));
-
- if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous)
- {
- throw new ArgumentException("Invalid direction: must be Next or Previous.");
- }
+ return GetNextTab(e, GetGroupParent(e), goDownOnly);
+ }
- var container = element.GetVisualParent();
+ public static IInputElement? GetNextTab(IInputElement? e, IInputElement container, bool goDownOnly)
+ {
+ var tabbingType = GetKeyNavigationMode(container);
- if (container != null)
+ if (e == null)
{
- var mode = KeyboardNavigation.GetTabNavigation((InputElement)container);
+ if (IsTabStop(container))
+ return container;
- switch (mode)
+ // Using ActiveElement if set
+ var activeElement = GetActiveElement(container);
+ if (activeElement != null)
+ return GetNextTab(null, activeElement, true);
+ }
+ else
+ {
+ if (tabbingType == KeyboardNavigationMode.Once || tabbingType == KeyboardNavigationMode.None)
{
- case KeyboardNavigationMode.Continue:
- return GetNextInContainer(element, container, direction, outsideElement) ??
- GetFirstInNextContainer(element, element, direction);
- case KeyboardNavigationMode.Cycle:
- return GetNextInContainer(element, container, direction, outsideElement) ??
- GetFocusableDescendant(container, direction);
- case KeyboardNavigationMode.Contained:
- return GetNextInContainer(element, container, direction, outsideElement);
- default:
- return GetFirstInNextContainer(element, container, direction);
+ if (container != e)
+ {
+ if (goDownOnly)
+ return null;
+ var parentContainer = GetGroupParent(container);
+ return GetNextTab(container, parentContainer, goDownOnly);
+ }
}
}
- else
+
+ // All groups
+ IInputElement? loopStartElement = null;
+ var nextTabElement = e;
+ var currentTabbingType = tabbingType;
+
+ // Search down inside the container
+ while ((nextTabElement = GetNextTabInGroup(nextTabElement, container, currentTabbingType)) != null)
{
- return GetFocusableDescendants(element, direction).FirstOrDefault();
+ // Avoid the endless loop here for Cycle groups
+ if (loopStartElement == nextTabElement)
+ break;
+ if (loopStartElement == null)
+ loopStartElement = nextTabElement;
+
+ var firstTabElementInside = GetNextTab(null, nextTabElement, true);
+ if (firstTabElementInside != null)
+ return firstTabElementInside;
+
+ // If we want to continue searching inside the Once groups, we should change the navigation mode
+ if (currentTabbingType == KeyboardNavigationMode.Once)
+ currentTabbingType = KeyboardNavigationMode.Contained;
}
+
+ // If there is no next element in the group (nextTabElement == null)
+
+ // Search up in the tree if allowed
+ // consider: Use original tabbingType instead of currentTabbingType
+ if (!goDownOnly && currentTabbingType != KeyboardNavigationMode.Contained && GetParent(container) != null)
+ {
+ return GetNextTab(container, GetGroupParent(container), false);
+ }
+
+ return null;
}
- ///
- /// Gets the first or last focusable descendant of the specified element.
- ///
- /// The element.
- /// The direction to search.
- /// The element or null if not found.##
- private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
+ public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
{
- return direction == NavigationDirection.Next ?
- GetFocusableDescendants(container, direction).FirstOrDefault() :
- GetFocusableDescendants(container, direction).LastOrDefault();
+ if (e is IInputElement container)
+ {
+ var last = GetLastInTree(container);
+
+ if (last is object)
+ return GetNextTab(last, false);
+ }
+
+ return null;
}
- ///
- /// Gets the focusable descendants of the specified element.
- ///
- /// The element.
- /// The tab direction. Must be Next or Previous.
- /// The element's focusable descendants.
- private static IEnumerable GetFocusableDescendants(IInputElement element,
- NavigationDirection direction)
+ public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
{
- var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
+ if (e is null && container is null)
+ throw new InvalidOperationException("Either 'e' or 'container' must be non-null.");
- if (mode == KeyboardNavigationMode.None)
- {
- yield break;
- }
+ if (container is null)
+ container = GetGroupParent(e!);
- var children = element.GetVisualChildren().OfType();
+ KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
- if (mode == KeyboardNavigationMode.Once)
+ if (e == null)
{
- var active = KeyboardNavigation.GetTabOnceActiveElement((InputElement)element);
-
- if (active != null)
+ // Using ActiveElement if set
+ var activeElement = GetActiveElement(container);
+ if (activeElement != null)
+ return GetPrevTab(null, activeElement, true);
+ else
{
- yield return active;
- yield break;
+ // If we Shift+Tab on a container with KeyboardNavigationMode=Once, and ActiveElement is null
+ // then we want to go to the first item (not last) within the container
+ if (tabbingType == KeyboardNavigationMode.Once)
+ {
+ var firstTabElement = GetNextTabInGroup(null, container, tabbingType);
+ if (firstTabElement == null)
+ {
+ if (IsTabStop(container))
+ return container;
+ if (goDownOnly)
+ return null;
+
+ return GetPrevTab(container, null, false);
+ }
+ else
+ {
+ return GetPrevTab(null, firstTabElement, true);
+ }
+ }
}
- else
+ }
+ else
+ {
+ if (tabbingType == KeyboardNavigationMode.Once || tabbingType == KeyboardNavigationMode.None)
{
- children = children.Take(1);
+ if (goDownOnly || container == e)
+ return null;
+
+ // FocusedElement should not be e otherwise we will delegate focus to the same element
+ if (IsTabStop(container))
+ return container;
+
+ return GetPrevTab(container, null, false);
}
}
- foreach (var child in children)
+ // All groups (except Once) - continue
+ IInputElement? loopStartElement = null;
+ IInputElement? nextTabElement = e;
+
+ // Look for element with the same TabIndex before the current element
+ while ((nextTabElement = GetPrevTabInGroup(nextTabElement, container, tabbingType)) != null)
{
- var customNext = GetCustomNext(child, direction);
+ if (nextTabElement == container && tabbingType == KeyboardNavigationMode.Local)
+ break;
+
+ // At this point nextTabElement is TabStop or TabGroup
+ // In case it is a TabStop only return the element
+ if (IsTabStop(nextTabElement) && !IsGroup(nextTabElement))
+ return nextTabElement;
+
+ // Avoid the endless loop here
+ if (loopStartElement == nextTabElement)
+ break;
+ if (loopStartElement == null)
+ loopStartElement = nextTabElement;
+
+ // At this point nextTabElement is TabGroup
+ var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
+ if (lastTabElementInside != null)
+ return lastTabElementInside;
+ }
- if (customNext.handled)
- {
- yield return customNext.next!;
- }
- else
+ if (tabbingType == KeyboardNavigationMode.Contained)
+ return null;
+
+ if (e != container && IsTabStop(container))
+ return container;
+
+ // If end of the subtree is reached or there no other elements above
+ if (!goDownOnly && GetParent(container) != null)
+ {
+ return GetPrevTab(container, null, false);
+ }
+
+ return null;
+ }
+
+ public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
+ {
+ if (e is IInputElement container)
+ {
+ var first = GetFirstChild(container);
+
+ if (first is object)
+ return GetPrevTab(first, null, false);
+ }
+
+ return null;
+ }
+
+ private static IInputElement? FocusedElement(IInputElement e)
+ {
+ var iie = e;
+ // Focus delegation is enabled only if keyboard focus is outside the container
+ if (iie != null && !iie.IsKeyboardFocusWithin)
+ {
+ var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
+ if (focusedElement != null)
{
- if (child.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement)child))
+ if (!IsFocusScope(e))
{
- yield return child;
+ // Verify if focusedElement is a visual descendant of e
+ if (focusedElement is IVisual visualFocusedElement &&
+ visualFocusedElement != e &&
+ e.IsVisualAncestorOf(visualFocusedElement))
+ {
+ return focusedElement;
+ }
}
+ }
+ }
+
+ return null;
+ }
+
+ private static IInputElement? GetFirstChild(IInputElement e)
+ {
+ // If the element has a FocusedElement it should be its first child
+ if (FocusedElement(e) is IInputElement focusedElement)
+ return focusedElement;
- if (child.CanFocusDescendants())
+ // Return the first visible element.
+ var uiElement = e as InputElement;
+
+ if (uiElement is null || uiElement.IsVisible)
+ {
+ if (e is IVisual elementAsVisual)
+ {
+ var children = elementAsVisual.VisualChildren;
+ var count = children.Count;
+
+ for (int i = 0; i < count; i++)
{
- foreach (var descendant in GetFocusableDescendants(child, direction))
+ if (children[i] is InputElement ie)
{
- if (KeyboardNavigation.GetIsTabStop((InputElement)descendant))
+ if (ie.IsVisible)
+ return ie;
+ else
{
- yield return descendant;
+ var firstChild = GetFirstChild(ie);
+ if (firstChild != null)
+ return firstChild;
}
}
}
}
}
+
+ return null;
}
- ///
- /// Gets the next item that should be focused in the specified container.
- ///
- /// The starting element/
- /// The container.
- /// The direction.
- ///
- /// If true will not descend into to find next control.
- ///
- /// The next element, or null if the element is the last.
- private static IInputElement? GetNextInContainer(
- IInputElement element,
- IInputElement container,
- NavigationDirection direction,
- bool outsideElement)
+ private static IInputElement? GetLastChild(IInputElement e)
{
- IInputElement? e = element;
+ // If the element has a FocusedElement it should be its last child
+ if (FocusedElement(e) is IInputElement focusedElement)
+ return focusedElement;
- if (direction == NavigationDirection.Next && !outsideElement)
- {
- var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
+ // Return the last visible element.
+ var uiElement = e as InputElement;
- if (descendant != null)
- {
- return descendant;
- }
- }
-
- if (container != null)
+ if (uiElement == null || uiElement.IsVisible)
{
- var navigable = container as INavigableContainer;
+ var elementAsVisual = e as IVisual;
- // TODO: Do a spatial search here if the container doesn't implement
- // INavigableContainer.
- if (navigable != null)
+ if (elementAsVisual != null)
{
- while (e != null)
- {
- e = navigable.GetControl(direction, e, false);
+ var children = elementAsVisual.VisualChildren;
+ var count = children.Count;
- if (e != null &&
- e.CanFocus() &&
- KeyboardNavigation.GetIsTabStop((InputElement)e))
+ for (int i = count - 1; i >= 0; i--)
+ {
+ if (children[i] is InputElement ie)
{
- break;
+ if (ie.IsVisible)
+ return ie;
+ else
+ {
+ var lastChild = GetLastChild(ie);
+ if (lastChild != null)
+ return lastChild;
+ }
}
}
}
- else
+ }
+
+ return null;
+ }
+
+ private static IInputElement? GetFirstTabInGroup(IInputElement container)
+ {
+ IInputElement? firstTabElement = null;
+ int minIndexFirstTab = int.MinValue;
+
+ var currElement = container;
+ while ((currElement = GetNextInTree(currElement, container)) != null)
+ {
+ if (IsTabStopOrGroup(currElement))
{
- // TODO: Do a spatial search here if the container doesn't implement
- // INavigableContainer.
- e = null;
+ int currPriority = KeyboardNavigation.GetTabIndex(currElement);
+
+ if (currPriority < minIndexFirstTab || firstTabElement == null)
+ {
+ minIndexFirstTab = currPriority;
+ firstTabElement = currElement;
+ }
}
+ }
+ return firstTabElement;
+ }
+
+ private static IInputElement? GetLastInTree(IInputElement container)
+ {
+ IInputElement? result;
+ IInputElement? c = container;
+
+ do
+ {
+ result = c;
+ c = GetLastChild(c);
+ } while (c != null && !IsGroup(c));
+
+ if (c != null)
+ return c;
+
+ return result;
+ }
- if (e != null && direction == NavigationDirection.Previous)
+ private static IInputElement? GetLastTabInGroup(IInputElement container)
+ {
+ IInputElement? lastTabElement = null;
+ int maxIndexFirstTab = int.MaxValue;
+ var currElement = GetLastInTree(container);
+ while (currElement != null && currElement != container)
+ {
+ if (IsTabStopOrGroup(currElement))
{
- var descendant = GetFocusableDescendants(e, direction).LastOrDefault();
+ int currPriority = KeyboardNavigation.GetTabIndex(currElement);
- if (descendant != null)
+ if (currPriority > maxIndexFirstTab || lastTabElement == null)
{
- return descendant;
+ maxIndexFirstTab = currPriority;
+ lastTabElement = currElement;
}
}
+ currElement = GetPreviousInTree(currElement, container);
+ }
+ return lastTabElement;
+ }
+
+ private static IInputElement? GetNextInTree(IInputElement e, IInputElement container)
+ {
+ IInputElement? result = null;
+
+ if (e == container || !IsGroup(e))
+ result = GetFirstChild(e);
+
+ if (result != null || e == container)
+ return result;
+
+ IInputElement? parent = e;
+ do
+ {
+ var sibling = GetNextSibling(parent);
+ if (sibling != null)
+ return sibling;
- return e;
+ parent = GetParent(parent);
+ } while (parent != null && parent != container);
+
+ return null;
+ }
+
+ private static IInputElement? GetNextSibling(IInputElement e)
+ {
+ if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual)
+ {
+ var children = parentAsVisual.VisualChildren;
+ var count = children.Count;
+ var i = 0;
+
+ //go till itself
+ for (; i < count; i++)
+ {
+ var vchild = children[i];
+ if (vchild == elementAsVisual)
+ break;
+ }
+ i++;
+ //search ahead
+ for (; i < count; i++)
+ {
+ var visual = children[i];
+ if (visual is IInputElement ie)
+ return ie;
+ }
}
return null;
}
- ///
- /// Gets the first item that should be focused in the next container.
- ///
- /// The element being navigated away from.
- /// The container.
- /// The direction of the search.
- /// The first element, or null if there are no more elements.
- private static IInputElement? GetFirstInNextContainer(
- IInputElement element,
- IInputElement container,
- NavigationDirection direction)
+ private static IInputElement? GetNextTabInGroup(IInputElement? e, IInputElement container, KeyboardNavigationMode tabbingType)
{
- var parent = container.GetVisualParent();
- IInputElement? next = null;
+ // None groups: Tab navigation is not supported
+ if (tabbingType == KeyboardNavigationMode.None)
+ return null;
- if (parent != null)
+ // e == null or e == container -> return the first TabStopOrGroup
+ if (e == null || e == container)
{
- if (direction == NavigationDirection.Previous &&
- parent.CanFocus() &&
- KeyboardNavigation.GetIsTabStop((InputElement) parent))
+ return GetFirstTabInGroup(container);
+ }
+
+ if (tabbingType == KeyboardNavigationMode.Once)
+ return null;
+
+ var nextTabElement = GetNextTabWithSameIndex(e, container);
+ if (nextTabElement != null)
+ return nextTabElement;
+
+ return GetNextTabWithNextIndex(e, container, tabbingType);
+ }
+
+ private static IInputElement? GetNextTabWithSameIndex(IInputElement e, IInputElement container)
+ {
+ var elementTabPriority = KeyboardNavigation.GetTabIndex(e);
+ var currElement = e;
+ while ((currElement = GetNextInTree(currElement, container)) != null)
+ {
+ if (IsTabStopOrGroup(currElement) && KeyboardNavigation.GetTabIndex(currElement) == elementTabPriority)
{
- return parent;
+ return currElement;
}
+ }
- var allSiblings = parent.GetVisualChildren()
- .OfType()
- .Where(FocusExtensions.CanFocusDescendants);
- var siblings = direction == NavigationDirection.Next ?
- allSiblings.SkipWhile(x => x != container).Skip(1) :
- allSiblings.TakeWhile(x => x != container).Reverse();
+ return null;
+ }
- foreach (var sibling in siblings)
+ private static IInputElement? GetNextTabWithNextIndex(IInputElement e, IInputElement container, KeyboardNavigationMode tabbingType)
+ {
+ // Find the next min index in the tree
+ // min (index>currentTabIndex)
+ IInputElement? nextTabElement = null;
+ IInputElement? firstTabElement = null;
+ int minIndexFirstTab = int.MinValue;
+ int minIndex = int.MinValue;
+ int elementTabPriority = KeyboardNavigation.GetTabIndex(e);
+
+ IInputElement? currElement = container;
+ while ((currElement = GetNextInTree(currElement, container)) != null)
+ {
+ if (IsTabStopOrGroup(currElement))
{
- var customNext = GetCustomNext(sibling, direction);
- if (customNext.handled)
+ int currPriority = KeyboardNavigation.GetTabIndex(currElement);
+ if (currPriority > elementTabPriority)
{
- return customNext.next;
+ if (currPriority < minIndex || nextTabElement == null)
+ {
+ minIndex = currPriority;
+ nextTabElement = currElement;
+ }
}
- if (sibling.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement) sibling))
+ if (currPriority < minIndexFirstTab || firstTabElement == null)
{
- return sibling;
+ minIndexFirstTab = currPriority;
+ firstTabElement = currElement;
}
+ }
+ }
- next = direction == NavigationDirection.Next ?
- GetFocusableDescendants(sibling, direction).FirstOrDefault() :
- GetFocusableDescendants(sibling, direction).LastOrDefault();
+ // Cycle groups: if not found - return first element
+ if (tabbingType == KeyboardNavigationMode.Cycle && nextTabElement == null)
+ nextTabElement = firstTabElement;
+
+ return nextTabElement;
+ }
- if (next != null)
+ private static IInputElement? GetPrevTabInGroup(IInputElement? e, IInputElement container, KeyboardNavigationMode tabbingType)
+ {
+ // None groups: Tab navigation is not supported
+ if (tabbingType == KeyboardNavigationMode.None)
+ return null;
+
+ // Search the last index inside the group
+ if (e == null)
+ {
+ return GetLastTabInGroup(container);
+ }
+
+ if (tabbingType == KeyboardNavigationMode.Once)
+ return null;
+
+ if (e == container)
+ return null;
+
+ var nextTabElement = GetPrevTabWithSameIndex(e, container);
+ if (nextTabElement != null)
+ return nextTabElement;
+
+ return GetPrevTabWithPrevIndex(e, container, tabbingType);
+ }
+
+ private static IInputElement? GetPrevTabWithSameIndex(IInputElement e, IInputElement container)
+ {
+ int elementTabPriority = KeyboardNavigation.GetTabIndex(e);
+ var currElement = GetPreviousInTree(e, container);
+ while (currElement != null)
+ {
+ if (IsTabStopOrGroup(currElement) && KeyboardNavigation.GetTabIndex(currElement) == elementTabPriority && currElement != container)
+ {
+ return currElement;
+ }
+ currElement = GetPreviousInTree(currElement, container);
+ }
+ return null;
+ }
+
+ private static IInputElement? GetPrevTabWithPrevIndex(IInputElement e, IInputElement container, KeyboardNavigationMode tabbingType)
+ {
+ // Find the next max index in the tree
+ // max (index maxIndex || nextTabElement == null)
+ {
+ maxIndex = currPriority;
+ nextTabElement = currElement;
+ }
+ }
+
+ if (currPriority > maxIndexFirstTab || lastTabElement == null)
{
- return next;
+ maxIndexFirstTab = currPriority;
+ lastTabElement = currElement;
}
}
- next = GetFirstInNextContainer(element, parent, direction);
+ currElement = GetPreviousInTree(currElement, container);
+ }
+
+ // Cycle groups: if not found - return first element
+ if (tabbingType == KeyboardNavigationMode.Cycle && nextTabElement == null)
+ nextTabElement = lastTabElement;
+
+ return nextTabElement;
+ }
+
+ private static IInputElement? GetPreviousInTree(IInputElement e, IInputElement container)
+ {
+ if (e == container)
+ return null;
+
+ var result = GetPreviousSibling(e);
+
+ if (result != null)
+ {
+ if (IsGroup(result))
+ return result;
+ else
+ return GetLastInTree(result);
}
else
+ return GetParent(e);
+ }
+
+ private static IInputElement? GetPreviousSibling(IInputElement e)
+ {
+ if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual)
{
- next = direction == NavigationDirection.Next ?
- GetFocusableDescendants(container, direction).FirstOrDefault() :
- GetFocusableDescendants(container, direction).LastOrDefault();
+ var children = parentAsVisual.VisualChildren;
+ var count = children.Count;
+ IInputElement? prev = null;
+
+ for (int i = 0; i < count; i++)
+ {
+ var vchild = children[i];
+ if (vchild == elementAsVisual)
+ break;
+ if (vchild.IsVisible == true && vchild is IInputElement ie)
+ prev = ie;
+ }
+ return prev;
}
+ return null;
+ }
- return next;
+ private static IInputElement? GetActiveElement(IInputElement e)
+ {
+ return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty);
}
- private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element,
- NavigationDirection direction)
+ private static IInputElement GetGroupParent(IInputElement e) => GetGroupParent(e, false);
+
+ private static IInputElement GetGroupParent(IInputElement element, bool includeCurrent)
{
- if (element is ICustomKeyboardNavigation custom)
+ var result = element; // Keep the last non null element
+ var e = element;
+
+ // If we don't want to include the current element,
+ // start at the parent of the element. If the element
+ // is the root, then just return it as the group parent.
+ if (!includeCurrent)
+ {
+ result = e;
+ e = GetParent(e);
+ if (e == null)
+ return result;
+ }
+
+ while (e != null)
{
- return custom.GetNext(element, direction);
+ if (IsGroup(e))
+ return e;
+
+ result = e;
+ e = GetParent(e);
}
- return (false, null);
+ return result;
}
+
+ private static IInputElement? GetParent(IInputElement e)
+ {
+ // For Visual - go up the visual parent chain until we find Visual.
+ if (e is IVisual v)
+ return v.FindAncestorOfType();
+
+ // This will need to be implemented when we have non-visual input elements.
+ throw new NotSupportedException();
+ }
+
+ private static KeyboardNavigationMode GetKeyNavigationMode(IInputElement e)
+ {
+ return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty);
+ }
+ private static bool IsFocusScope(IInputElement e) => FocusManager.GetIsFocusScope(e) || GetParent(e) == null;
+ private static bool IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue;
+
+ private static bool IsTabStop(IInputElement e)
+ {
+ if (e is InputElement ie)
+ return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEnabled;
+ return false;
+ }
+
+ private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e);
}
}
diff --git a/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
index d385f5b162..2b5d8958cc 100644
--- a/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
+++ b/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
@@ -22,11 +22,11 @@ namespace Avalonia.Input.TextInput
///
event EventHandler TextViewVisualChanged;
///
- /// Indicates if TextViewVisual is capable of displaying non-commited input on the cursor position
+ /// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position
///
bool SupportsPreedit { get; }
///
- /// Sets the non-commited input string
+ /// Sets the non-committed input string
///
void SetPreeditText(string text);
///
diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs
index 4cd810af20..2497150b1a 100644
--- a/src/Avalonia.Interactivity/Interactive.cs
+++ b/src/Avalonia.Interactivity/Interactive.cs
@@ -143,7 +143,7 @@ namespace Avalonia.Interactivity
/// The routed event.
/// An describing the route.
///
- /// Usually, calling is sufficent to raise a routed
+ /// Usually, calling is sufficient to raise a routed
/// event, however there are situations in which the construction of the event args is expensive
/// and should be avoided if there are no handlers for an event. In these cases you can call
/// this method to build the event route and check the
diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs
index 3f106708e6..c2f829d020 100644
--- a/src/Avalonia.Layout/ElementManager.cs
+++ b/src/Avalonia.Layout/ElementManager.cs
@@ -40,7 +40,7 @@ namespace Avalonia.Layout
{
if (IsVirtualizingContext)
{
- // We proactively clear elements laid out outside of the realizaton
+ // We proactively clear elements laid out outside of the realization
// rect so that they are available for reuse during the current
// measure pass.
// This is useful during fast panning scenarios in which the realization
diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
index eace54d2e0..63343fd1a7 100644
--- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
+++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Layout
private int _firstRealizedDataIndexInsideRealizationWindow = -1;
private int _lastRealizedDataIndexInsideRealizationWindow = -1;
- // If the scroll orientation is the same as the folow orientation
+ // If the scroll orientation is the same as the follow orientation
// we will only have one line since we will never wrap. In that case
// we do not want to align the line. We could potentially switch the
// meaning of line alignment in this case, but I'll hold off on that
@@ -429,7 +429,7 @@ namespace Avalonia.Layout
// If we did not reach the top or bottom of the extent, we realized one
// extra item before we knew we were outside the realization window. Do not
- // account for that element in the indicies inside the realization window.
+ // account for that element in the indices inside the realization window.
if (direction == GenerateDirection.Forward)
{
int dataCount = _context.ItemCount;
diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
index 2e3408eca5..89efa6af0c 100644
--- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
+++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
@@ -138,7 +138,7 @@ namespace Avalonia.Native
{
_nativeMenu = (__MicroComIAvnMenuProxy)__MicroComIAvnMenuProxy.Create(_factory);
- _nativeMenu.Initialise(this, appMenuHolder, "");
+ _nativeMenu.Initialize(this, appMenuHolder, "");
setMenu = true;
}
@@ -159,7 +159,7 @@ namespace Avalonia.Native
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
- _nativeMenu.Initialise(this, menu, "");
+ _nativeMenu.Initialize(this, menu, "");
setMenu = true;
}
diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs
index f76e9450fc..0e6fdd2df0 100644
--- a/src/Avalonia.Native/IAvnMenu.cs
+++ b/src/Avalonia.Native/IAvnMenu.cs
@@ -96,7 +96,7 @@ namespace Avalonia.Native.Interop.Impl
_menuItems.Remove(item);
RemoveItem(item);
- item.Deinitialise();
+ item.Deinitialize();
item.Dispose();
}
@@ -113,7 +113,7 @@ namespace Avalonia.Native.Interop.Impl
{
var result = CreateNew(factory, item);
- result.Initialise(item);
+ result.Initialize(item);
_menuItemLookup.Add(result.ManagedMenuItem, result);
_menuItems.Insert(index, result);
@@ -133,7 +133,7 @@ namespace Avalonia.Native.Interop.Impl
return nativeItem;
}
- internal void Initialise(AvaloniaNativeMenuExporter exporter, NativeMenu managedMenu, string title)
+ internal void Initialize(AvaloniaNativeMenuExporter exporter, NativeMenu managedMenu, string title)
{
_exporter = exporter;
ManagedMenu = managedMenu;
@@ -150,7 +150,7 @@ namespace Avalonia.Native.Interop.Impl
foreach (var item in _menuItems)
{
- item.Deinitialise();
+ item.Deinitialize();
item.Dispose();
}
}
diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs
index 97838f8dea..ca99cbea4b 100644
--- a/src/Avalonia.Native/IAvnMenuItem.cs
+++ b/src/Avalonia.Native/IAvnMenuItem.cs
@@ -85,7 +85,7 @@ namespace Avalonia.Native.Interop.Impl
SetAction(action, callback);
}
- internal void Initialise(NativeMenuItemBase nativeMenuItem)
+ internal void Initialize(NativeMenuItemBase nativeMenuItem)
{
ManagedMenuItem = nativeMenuItem;
@@ -123,7 +123,7 @@ namespace Avalonia.Native.Interop.Impl
}
}
- internal void Deinitialise()
+ internal void Deinitialize()
{
if (_subMenu != null)
{
@@ -150,7 +150,7 @@ namespace Avalonia.Native.Interop.Impl
{
_subMenu = __MicroComIAvnMenuProxy.Create(factory);
- _subMenu.Initialise(exporter, item.Menu, item.Header);
+ _subMenu.Initialize(exporter, item.Menu, item.Header);
SetSubMenu(_subMenu);
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
index 4d02f94cad..40891a700d 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
@@ -284,7 +284,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
// - U+0028 (Left Opening Parenthesis)
// - U+005B (Opening Square Bracket)
// - U+007B (Left Curly Bracket)
- // See custom colums|rules in the text pair table.
+ // See custom columns|rules in the text pair table.
// https://www.unicode.org/Public/13.0.0/ucd/auxiliary/LineBreakTest.html
_lb30 = _alphaNumericCount > 0
&& cls == LineBreakClass.OpenPunctuation
diff --git a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
index 1e80eabfc8..742bb9c804 100644
--- a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
+++ b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
@@ -25,14 +25,14 @@ namespace Avalonia.Media.Transformation
Matrix.CreateScale(decomposed.Scale);
}
- public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progres)
+ public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress)
{
Matrix.Decomposed result = default;
- result.Translate = InterpolateVectors(from.Translate, to.Translate, progres);
- result.Scale = InterpolateVectors(from.Scale, to.Scale, progres);
- result.Skew = InterpolateVectors(from.Skew, to.Skew, progres);
- result.Angle = InterpolateScalars(from.Angle, to.Angle, progres);
+ result.Translate = InterpolateVectors(from.Translate, to.Translate, progress);
+ result.Scale = InterpolateVectors(from.Scale, to.Scale, progress);
+ result.Skew = InterpolateVectors(from.Skew, to.Skew, progress);
+ result.Angle = InterpolateScalars(from.Angle, to.Angle, progress);
return result;
}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 0eb716badd..f4a1079455 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -231,7 +231,7 @@ namespace Avalonia
///
/// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time.
///
- public bool? EnableMultiTouch { get; set; }
+ public bool? EnableMultiTouch { get; set; } = true;
}
public static class AvaloniaX11PlatformExtensions
{
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index cf46db69eb..86f348bd01 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -63,7 +63,7 @@ namespace Avalonia
///
/// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time.
///
- public bool? EnableMultitouch { get; set; }
+ public bool? EnableMultitouch { get; set; } = true;
///
/// Embeds popups to the window when set to true. The default value is false.
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index f20dae3e50..0b7bd13082 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -451,7 +451,7 @@ namespace Avalonia.Win32
value.Y,
0,
0,
- SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE);
+ SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER);
}
}
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
index f72d6ba9c9..f9c85ee4ca 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
@@ -95,6 +95,7 @@ namespace Avalonia.Input.UnitTests
var root = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
target,
@@ -125,6 +126,7 @@ namespace Avalonia.Input.UnitTests
var root = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(current = new Button { Content = "Outside" }),
@@ -137,6 +139,36 @@ namespace Avalonia.Input.UnitTests
Assert.Same(next, result);
}
+ [Fact]
+ public void ShiftTab_Should_Navigate_Outside_When_Null_Returned_As_Next()
+ {
+ Button current;
+ Button next;
+ var target = new CustomNavigatingStackPanel
+ {
+ Children =
+ {
+ new Button { Content = "Button 1" },
+ (current = new Button { Content = "Button 2" }),
+ new Button { Content = "Button 3" },
+ },
+ };
+
+ var root = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ target,
+ (next = new Button { Content = "Outside" }),
+ }
+ };
+
+ var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous);
+
+ Assert.Same(next, result);
+ }
+
[Fact]
public void Tab_Should_Navigate_Outside_When_Null_Returned_As_Next()
{
@@ -154,6 +186,7 @@ namespace Avalonia.Input.UnitTests
var root = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
target,
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
index 1efbbed2e8..edcbf75a1d 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using Avalonia.Controls;
using Xunit;
@@ -13,6 +14,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -49,6 +51,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -85,6 +88,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -122,6 +126,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -165,6 +170,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -193,6 +199,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -222,6 +229,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -263,6 +271,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(next = new Button { Name = "Button1" }),
@@ -282,6 +291,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -324,6 +334,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -361,6 +372,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -398,6 +410,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -434,6 +447,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -471,6 +485,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -509,6 +524,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(container = new StackPanel
@@ -548,6 +564,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -586,6 +603,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(container = new StackPanel
@@ -625,6 +643,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -661,6 +680,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -697,6 +717,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -725,6 +746,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -767,6 +789,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -828,6 +851,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -865,6 +889,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -902,6 +927,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -938,6 +964,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -975,6 +1002,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -1013,6 +1041,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(container = new StackPanel
@@ -1052,6 +1081,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -1103,5 +1133,97 @@ namespace Avalonia.Input.UnitTests
Assert.Null(result);
}
+
+ [Fact]
+ public void Respects_TabIndex_Moving_Forwards()
+ {
+ Button start;
+
+ var top = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button1", TabIndex = 5 },
+ (start = new Button { Name = "Button2", TabIndex = 2 }),
+ new Button { Name = "Button3", TabIndex = 1 },
+ }
+ },
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button4", TabIndex = 3 },
+ new Button { Name = "Button5", TabIndex = 6 },
+ new Button { Name = "Button6", TabIndex = 4 },
+ }
+ },
+ }
+ };
+
+ var result = new List();
+ var current = (IInputElement)start;
+
+ do
+ {
+ result.Add(((IControl)current).Name);
+ current = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next);
+ } while (current is object && current != start);
+
+ Assert.Equal(new[]
+ {
+ "Button2", "Button4", "Button6", "Button1", "Button5", "Button3"
+ }, result);
+ }
+
+ [Fact]
+ public void Respects_TabIndex_Moving_Backwards()
+ {
+ Button start;
+
+ var top = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button1", TabIndex = 5 },
+ (start = new Button { Name = "Button2", TabIndex = 2 }),
+ new Button { Name = "Button3", TabIndex = 1 },
+ }
+ },
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button4", TabIndex = 3 },
+ new Button { Name = "Button5", TabIndex = 6 },
+ new Button { Name = "Button6", TabIndex = 4 },
+ }
+ },
+ }
+ };
+
+ var result = new List();
+ var current = (IInputElement)start;
+
+ do
+ {
+ result.Add(((IControl)current).Name);
+ current = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous);
+ } while (current is object && current != start);
+
+ Assert.Equal(new[]
+ {
+ "Button2", "Button3", "Button5", "Button1", "Button6", "Button4"
+ }, result);
+ }
}
}
diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs
index b69bf990d9..4601dd7e5b 100644
--- a/tests/Avalonia.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.UnitTests/TestRoot.cs
@@ -21,6 +21,7 @@ namespace Avalonia.UnitTests
Renderer = Mock.Of();
LayoutManager = new LayoutManager(this);
IsVisible = true;
+ KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
public TestRoot(IControl child)