diff --git a/build/System.Drawing.Common.props b/build/System.Drawing.Common.props
index 2b3707d38a..108a0f41e0 100644
--- a/build/System.Drawing.Common.props
+++ b/build/System.Drawing.Common.props
@@ -1,5 +1,6 @@
-
+
+
diff --git a/readme.md b/readme.md
index b38552a010..96c7937559 100644
--- a/readme.md
+++ b/readme.md
@@ -3,13 +3,11 @@
[](https://www.nuget.org/packages/Avalonia) [](https://www.nuget.org/packages/Avalonia) [](https://www.myget.org/gallery/avalonia-ci) 
-## 📖 About AvaloniaUI
+## 📖 About
-Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development.
+Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, MacOs. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM.
-
-
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
+
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
@@ -28,18 +26,15 @@ Install-Package Avalonia.Desktop
## Showcase
Examples of UIs built with Avalonia
-
-
-([Synfonia](https://github.com/jmacato/Synfonia))
+
+([Lunacy](https://icons8.com/lunacy))
-
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
+
+([PlasticSCM](https://www.plasticscm.com/))
-
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
+
+([WasabiWallet](https://www.wasabiwallet.io/))
-
-([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
## JetBrains Rider
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
index decbd763a1..64e80a8e11 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
@@ -1,77 +1,95 @@
-
-
- A drop-down list.
+
+
+ A drop-down list.
-
-
-
-
-
- Inline Items
- Inline Item 2
- Inline Item 3
- Inline Item 4
-
+
+
+
+
+
-
-
-
-
- Hello
- World
-
-
-
-
-
-
-
-
-
-
-
+
+ Inline Items
+ Inline Item 2
+ Inline Item 3
+ Inline Item 4
+
-
-
-
-
- Control Items
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Hello
+ World
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- Inline Items
- Inline Item 2
- Inline Item 3
- Inline Item 4
-
-
-
-
-
+
+
+
+
+ Control Items
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+ Inline Items
+ Inline Item 2
+ Inline Item 3
+ Inline Item 4
+
+
+
+
+
+
+ WrapSelection
+
+
+
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
index d50b051d9f..d304bf227d 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
@@ -2,6 +2,7 @@ using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
+using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@@ -10,6 +11,7 @@ namespace ControlCatalog.Pages
public ComboBoxPage()
{
this.InitializeComponent();
+ DataContext = new ComboBoxPageViewModel();
}
private void InitializeComponent()
diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml
index 41658329df..433592345a 100644
--- a/samples/ControlCatalog/Pages/ListBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml
@@ -21,6 +21,7 @@
Toggle
AlwaysSelected
AutoScrollToSelectedItem
+ WrapSelection
@@ -30,6 +31,7 @@
+ SelectionMode="{Binding SelectionMode^}"
+ WrapSelection="{Binding WrapSelection}"/>
diff --git a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
new file mode 100644
index 0000000000..bbe970afd6
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using Avalonia.Controls;
+using Avalonia.Controls.Selection;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels
+{
+ public class ComboBoxPageViewModel : ViewModelBase
+ {
+ private bool _wrapSelection;
+
+ public bool WrapSelection
+ {
+ get => _wrapSelection;
+ set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
index 7f2d6e9572..59489ebcc0 100644
--- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
@@ -14,6 +14,7 @@ namespace ControlCatalog.ViewModels
private bool _toggle;
private bool _alwaysSelected;
private bool _autoScrollToSelectedItem = true;
+ private bool _wrapSelection;
private int _counter;
private IObservable _selectionMode;
@@ -85,6 +86,12 @@ namespace ControlCatalog.ViewModels
set => this.RaiseAndSetIfChanged(ref _autoScrollToSelectedItem, value);
}
+ public bool WrapSelection
+ {
+ get => _wrapSelection;
+ set => this.RaiseAndSetIfChanged(ref _wrapSelection, value);
+ }
+
public MiniCommand AddItemCommand { get; }
public MiniCommand RemoveItemCommand { get; }
public MiniCommand SelectRandomItemCommand { get; }
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index d38cd3a0fd..72b09b7a3c 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -10,9 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
-using Avalonia.LogicalTree;
using Avalonia.Media;
-using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@@ -91,7 +89,7 @@ namespace Avalonia.Controls
{
ItemsPanelProperty.OverrideDefaultValue(DefaultPanel);
FocusableProperty.OverrideDefaultValue(true);
- SelectedItemProperty.Changed.AddClassHandler((x,e) => x.SelectedItemChanged(e));
+ SelectedItemProperty.Changed.AddClassHandler((x, e) => x.SelectedItemChanged(e));
KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
IsTextSearchEnabledProperty.OverrideDefaultValue(true);
}
@@ -221,8 +219,9 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
+ // This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl.
else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 &&
- (e.Key == Key.Up || e.Key == Key.Down))
+ (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true)
{
var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
if (firstChild != null)
@@ -360,12 +359,12 @@ namespace Avalonia.Controls
var selectedIndex = SelectedIndex;
if (IsDropDownOpen && selectedIndex != -1)
{
- var container = ItemContainerGenerator!.ContainerFromIndex(selectedIndex);
+ var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
if (container == null && SelectedIndex != -1)
{
ScrollIntoView(Selection.SelectedIndex);
- container = ItemContainerGenerator!.ContainerFromIndex(selectedIndex);
+ container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
}
if (container != null && CanFocus(container))
@@ -415,7 +414,7 @@ namespace Avalonia.Controls
private void SelectFocusedItem()
{
- foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator!.Containers)
+ foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers)
{
if (dropdownItem.ContainerControl.IsFocused)
{
@@ -430,7 +429,18 @@ namespace Avalonia.Controls
int next = SelectedIndex + 1;
if (next >= ItemCount)
- next = 0;
+ {
+ if (WrapSelection == true)
+ {
+ next = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+
SelectedIndex = next;
}
@@ -440,7 +450,16 @@ namespace Avalonia.Controls
int prev = SelectedIndex - 1;
if (prev < 0)
- prev = ItemCount - 1;
+ {
+ if (WrapSelection == true)
+ {
+ prev = ItemCount - 1;
+ }
+ else
+ {
+ return;
+ }
+ }
SelectedIndex = prev;
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 70f771ca00..ed8f9efb2e 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -79,7 +79,7 @@ namespace Avalonia.Controls
///
/// Gets the for the control.
///
- public IItemContainerGenerator? ItemContainerGenerator
+ public IItemContainerGenerator ItemContainerGenerator
{
get
{
@@ -87,13 +87,10 @@ namespace Avalonia.Controls
{
_itemContainerGenerator = CreateItemContainerGenerator();
- if (_itemContainerGenerator != null)
- {
- _itemContainerGenerator.ItemTemplate = ItemTemplate;
- _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
- _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
- _itemContainerGenerator.Recycled += (_, e) => OnContainersRecycled(e);
- }
+ _itemContainerGenerator.ItemTemplate = ItemTemplate;
+ _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
+ _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
+ _itemContainerGenerator.Recycled += (_, e) => OnContainersRecycled(e);
}
return _itemContainerGenerator;
@@ -146,6 +143,8 @@ namespace Avalonia.Controls
protected set;
}
+ private protected bool WrapFocus { get; set; }
+
event EventHandler? IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
@@ -240,14 +239,8 @@ namespace Avalonia.Controls
/// Creates the for the control.
///
///
- /// An or null.
+ /// An .
///
- ///
- /// Certain controls such as don't actually create item
- /// containers; however they want it to be ItemsControls so that they have an Items
- /// property etc. In this case, a derived class can override this method to return null
- /// in order to disable the creation of item containers.
- ///
protected virtual IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator(this);
@@ -324,7 +317,7 @@ namespace Avalonia.Controls
{
if (current.VisualParent == container && current is IInputElement inputElement)
{
- var next = GetNextControl(container, direction.Value, inputElement, false);
+ var next = GetNextControl(container, direction.Value, inputElement, WrapFocus);
if (next != null)
{
diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs
index c36fbcc51f..bdcff5cd09 100644
--- a/src/Avalonia.Controls/MenuBase.cs
+++ b/src/Avalonia.Controls/MenuBase.cs
@@ -81,13 +81,13 @@ namespace Avalonia.Controls
{
var index = SelectedIndex;
return (index != -1) ?
- (IMenuItem?)ItemContainerGenerator!.ContainerFromIndex(index) :
+ (IMenuItem?)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = value is not null ?
- ItemContainerGenerator!.IndexFromContainer(value) : -1;
+ ItemContainerGenerator.IndexFromContainer(value) : -1;
}
}
@@ -96,7 +96,7 @@ namespace Avalonia.Controls
{
get
{
- return ItemContainerGenerator!.Containers
+ return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType();
}
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 9540ab05f9..185b834052 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -308,12 +308,12 @@ namespace Avalonia.Controls
{
var index = SelectedIndex;
return (index != -1) ?
- (IMenuItem?)ItemContainerGenerator!.ContainerFromIndex(index) :
+ (IMenuItem?)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
- SelectedIndex = value is not null ? ItemContainerGenerator!.IndexFromContainer(value) : -1;
+ SelectedIndex = value is not null ? ItemContainerGenerator.IndexFromContainer(value) : -1;
}
}
@@ -322,7 +322,7 @@ namespace Avalonia.Controls
{
get
{
- return ItemContainerGenerator!.Containers
+ return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType();
}
diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
index 8883c9e8ce..3d1e7eb5a8 100644
--- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
@@ -170,7 +170,7 @@ namespace Avalonia.Controls.Presenters
{
if (fromIndex != toIndex)
{
- var generator = ItemContainerGenerator!;
+ var generator = ItemContainerGenerator;
IControl? from = null;
IControl? to = null;
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index a34e5d6438..361febf305 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -233,11 +233,6 @@ namespace Avalonia.Controls.Presenters
var itemIndex = generator.IndexFromContainer(from);
var vertical = VirtualizingPanel.ScrollDirection == Orientation.Vertical;
- if (itemIndex == -1)
- {
- return null;
- }
-
var newItemIndex = -1;
switch (direction)
@@ -250,6 +245,16 @@ namespace Avalonia.Controls.Presenters
newItemIndex = ItemCount - 1;
break;
+ default:
+ if (itemIndex == -1)
+ {
+ return null;
+ }
+ break;
+ }
+
+ switch (direction)
+ {
case NavigationDirection.Up:
if (vertical)
{
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index 1c60c0d906..8629af5243 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reactive.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
@@ -26,14 +24,14 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty PasswordCharProperty =
AvaloniaProperty.Register(nameof(PasswordChar));
- public static readonly StyledProperty SelectionBrushProperty =
- AvaloniaProperty.Register(nameof(SelectionBrushProperty));
+ public static readonly StyledProperty SelectionBrushProperty =
+ AvaloniaProperty.Register(nameof(SelectionBrushProperty));
- public static readonly StyledProperty SelectionForegroundBrushProperty =
- AvaloniaProperty.Register(nameof(SelectionForegroundBrushProperty));
+ public static readonly StyledProperty SelectionForegroundBrushProperty =
+ AvaloniaProperty.Register(nameof(SelectionForegroundBrushProperty));
- public static readonly StyledProperty CaretBrushProperty =
- AvaloniaProperty.Register(nameof(CaretBrushProperty));
+ public static readonly StyledProperty CaretBrushProperty =
+ AvaloniaProperty.Register(nameof(CaretBrushProperty));
public static readonly DirectProperty SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner(
@@ -48,8 +46,8 @@ namespace Avalonia.Controls.Presenters
///
/// Defines the property.
///
- public static readonly DirectProperty TextProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty TextProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(Text),
o => o.Text,
(o, v) => o.Text = v);
@@ -77,16 +75,14 @@ namespace Avalonia.Controls.Presenters
private int _selectionStart;
private int _selectionEnd;
private bool _caretBlink;
- private string _text;
+ private string? _text;
private TextLayout? _textLayout;
- private Size _constraint = Size.Infinity;
+ private Size _constraint;
private CharacterHit _lastCharacterHit;
private Rect _caretBounds;
private Point _navigationPosition;
- private ScrollViewer? _scrollViewer;
-
static TextPresenter()
{
AffectsRender(CaretBrushProperty, SelectionBrushProperty);
@@ -114,7 +110,7 @@ namespace Avalonia.Controls.Presenters
/// Gets or sets the text.
///
[Content]
- public string Text
+ public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
@@ -186,7 +182,7 @@ namespace Avalonia.Controls.Presenters
///
/// Gets the used to render the text.
///
- public TextLayout? TextLayout
+ public TextLayout TextLayout
{
get
{
@@ -230,19 +226,19 @@ namespace Avalonia.Controls.Presenters
set => SetValue(RevealPasswordProperty, value);
}
- public IBrush SelectionBrush
+ public IBrush? SelectionBrush
{
get => GetValue(SelectionBrushProperty);
set => SetValue(SelectionBrushProperty, value);
}
- public IBrush SelectionForegroundBrush
+ public IBrush? SelectionForegroundBrush
{
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
- public IBrush CaretBrush
+ public IBrush? CaretBrush
{
get => GetValue(CaretBrushProperty);
set => SetValue(CaretBrushProperty, value);
@@ -284,20 +280,14 @@ namespace Avalonia.Controls.Presenters
///
///
/// A object.
- private TextLayout? CreateTextLayoutInternal(Size constraint, string text, Typeface typeface,
+ private TextLayout CreateTextLayoutInternal(Size constraint, string? text, Typeface typeface,
IReadOnlyList>? textStyleOverrides)
{
var foreground = Foreground;
-
- if (foreground == null)
- {
- return null;
- }
-
var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width;
var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height;
- var textLayout = new TextLayout(text ?? string.Empty, typeface, FontSize, foreground, TextAlignment,
+ var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment,
TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides,
flowDirection: FlowDirection);
@@ -317,11 +307,6 @@ namespace Avalonia.Controls.Presenters
context.FillRectangle(background, new Rect(Bounds.Size));
}
- if (TextLayout == null)
- {
- return;
- }
-
var top = 0d;
var left = 0.0;
@@ -346,17 +331,11 @@ namespace Avalonia.Controls.Presenters
public override void Render(DrawingContext context)
{
- if (double.IsPositiveInfinity (_constraint.Width))
- {
- _constraint = _scrollViewer?.Viewport ?? Size.Infinity;
-
- InvalidateTextLayout();
- }
-
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
+ var selectionBrush = SelectionBrush;
- if (selectionStart != selectionEnd && TextLayout != null)
+ if (selectionStart != selectionEnd && selectionBrush != null)
{
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
@@ -365,7 +344,7 @@ namespace Avalonia.Controls.Presenters
foreach (var rect in rects)
{
- context.FillRectangle(SelectionBrush, rect);
+ context.FillRectangle(selectionBrush, rect);
}
}
@@ -431,38 +410,40 @@ namespace Avalonia.Controls.Presenters
internal void CaretChanged()
{
- if (this.GetVisualParent() != null)
+ if (this.GetVisualParent() == null)
{
- if (_caretTimer.IsEnabled)
- {
- _caretBlink = true;
- _caretTimer.Stop();
- _caretTimer.Start();
- InvalidateVisual();
- }
- else
- {
- _caretTimer.Start();
- InvalidateVisual();
- _caretTimer.Stop();
- }
+ return;
+ }
- if (IsMeasureValid)
- {
- this.BringIntoView(_caretBounds);
- }
- else
- {
- // The measure is currently invalid so there's no point trying to bring the
- // current char into view until a measure has been carried out as the scroll
- // viewer extents may not be up-to-date.
- Dispatcher.UIThread.Post(
- () =>
- {
- this.BringIntoView(_caretBounds);
- },
- DispatcherPriority.Render);
- }
+ if (_caretTimer.IsEnabled)
+ {
+ _caretBlink = true;
+ _caretTimer.Stop();
+ _caretTimer.Start();
+ InvalidateVisual();
+ }
+ else
+ {
+ _caretTimer.Start();
+ InvalidateVisual();
+ _caretTimer.Stop();
+ }
+
+ if (IsMeasureValid)
+ {
+ this.BringIntoView(_caretBounds);
+ }
+ else
+ {
+ // The measure is currently invalid so there's no point trying to bring the
+ // current char into view until a measure has been carried out as the scroll
+ // viewer extents may not be up-to-date.
+ Dispatcher.UIThread.Post(
+ () =>
+ {
+ this.BringIntoView(_caretBounds);
+ },
+ DispatcherPriority.Render);
}
}
@@ -470,9 +451,9 @@ namespace Avalonia.Controls.Presenters
/// Creates the used to render the text.
///
/// A object.
- protected virtual TextLayout? CreateTextLayout()
+ protected virtual TextLayout CreateTextLayout()
{
- TextLayout? result;
+ TextLayout result;
var text = Text;
@@ -517,14 +498,25 @@ namespace Avalonia.Controls.Presenters
protected override Size MeasureOverride(Size availableSize)
{
- if (!double.IsInfinity(availableSize.Width) && availableSize != _constraint)
+ _textLayout = null;
+
+ _constraint = availableSize;
+
+ return TextLayout.Size;
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ if (!double.IsInfinity(_constraint.Width))
{
- _constraint = availableSize;
-
- InvalidateTextLayout();
+ return base.ArrangeOverride(finalSize);
}
+
+ _constraint = finalSize;
+
+ _textLayout = null;
- return TextLayout?.Size ?? default;
+ return base.ArrangeOverride(finalSize);
}
private int CoerceCaretIndex(int value)
@@ -537,16 +529,12 @@ namespace Avalonia.Controls.Presenters
private void CaretTimerTick(object? sender, EventArgs e)
{
_caretBlink = !_caretBlink;
+
InvalidateVisual();
}
public void MoveCaretToTextPosition(int textPosition, bool trailingEdge = false)
{
- if (TextLayout == null)
- {
- return;
- }
-
var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(textPosition, trailingEdge);
var textLine = TextLayout.TextLines[lineIndex];
@@ -569,29 +557,23 @@ namespace Avalonia.Controls.Presenters
}
_navigationPosition = _caretBounds.Position;
+
+ CaretChanged();
}
public void MoveCaretToPoint(Point point)
{
- if (TextLayout == null)
- {
- return;
- }
-
var hit = TextLayout.HitTestPoint(point);
UpdateCaret(hit.CharacterHit);
_navigationPosition = _caretBounds.Position;
+
+ CaretChanged();
}
public void MoveCaretVertical(LogicalDirection direction = LogicalDirection.Forward)
{
- if (TextLayout == null)
- {
- return;
- }
-
var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(CaretIndex, _lastCharacterHit.TrailingLength > 0);
if (lineIndex < 0)
@@ -629,14 +611,23 @@ namespace Avalonia.Controls.Presenters
MoveCaretToPoint(new Point(currentX, currentY));
_navigationPosition = navigationPosition.WithY(_caretBounds.Y);
+
+ CaretChanged();
}
public void MoveCaretHorizontal(LogicalDirection direction = LogicalDirection.Forward)
{
- if (TextLayout == null)
+ if (Text is null)
{
return;
}
+
+ if (FlowDirection == FlowDirection.RightToLeft)
+ {
+ direction = direction == LogicalDirection.Forward ?
+ LogicalDirection.Backward :
+ LogicalDirection.Forward;
+ }
var characterHit = _lastCharacterHit;
var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
@@ -709,15 +700,12 @@ namespace Avalonia.Controls.Presenters
UpdateCaret(characterHit);
_navigationPosition = _caretBounds.Position;
+
+ CaretChanged();
}
private void UpdateCaret(CharacterHit characterHit)
{
- if (TextLayout == null)
- {
- return;
- }
-
_lastCharacterHit = characterHit;
var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
@@ -743,9 +731,7 @@ namespace Avalonia.Controls.Presenters
CaretBoundsChanged?.Invoke(this, EventArgs.Empty);
}
-
- CaretChanged();
-
+
SetAndRaise(CaretIndexProperty, ref _caretIndex, caretIndex);
}
@@ -754,19 +740,10 @@ namespace Avalonia.Controls.Presenters
return _caretBounds;
}
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnAttachedToVisualTree(e);
-
- _scrollViewer = this.FindAncestorOfType();
- }
-
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
- _scrollViewer = null;
-
_caretTimer.Stop();
_caretTimer.Tick -= CaretTimerTick;
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 31de6f6398..b4cfd9404c 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -114,6 +114,12 @@ namespace Avalonia.Controls.Primitives
"SelectionChanged",
RoutingStrategies.Bubble);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty WrapSelectionProperty =
+ AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false);
+
private static readonly IList Empty = Array.Empty