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)