Browse Source

Merge branch 'master' into fix/SplitViewOverlay

pull/10697/head
amwx 4 years ago
committed by GitHub
parent
commit
1207673145
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject
  2. 5
      .ncrunch/Generators.Sandbox.v3.ncrunchproject
  3. 10
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  4. 2
      samples/ControlCatalog/MainView.xaml
  5. 60
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  6. 2
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  7. 2
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  8. 2
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  9. 4
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  10. 2
      samples/ControlCatalog/Pages/CursorPage.xaml
  11. 8
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  12. 12
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  13. 2
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  14. 6
      samples/ControlCatalog/Pages/MenuPage.xaml
  15. 8
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs
  16. 6
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  17. 2
      samples/ControlCatalog/Pages/RefreshContainerPage.axaml
  18. 4
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml
  19. 4
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  20. 2
      samples/ControlCatalog/Pages/TabStripPage.xaml
  21. 2
      samples/ControlCatalog/Pages/ThemePage.axaml.cs
  22. 2
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
  23. 2
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  24. 8
      samples/IntegrationTestApp/MainWindow.axaml
  25. 6
      samples/IntegrationTestApp/MainWindow.axaml.cs
  26. 24
      samples/IntegrationTestApp/ShowWindowTest.axaml
  27. 2
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  28. 2
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  29. 9
      src/Avalonia.Base/CornerRadius.cs
  30. 2
      src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
  31. 11
      src/Avalonia.Base/Media/BoxShadow.cs
  32. 4
      src/Avalonia.Base/Media/BoxShadows.cs
  33. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  34. 5
      src/Avalonia.Base/Media/FontFamily.cs
  35. 5
      src/Avalonia.Base/Media/FormattedText.cs
  36. 2
      src/Avalonia.Base/Media/ImageDrawing.cs
  37. 2
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  38. 2
      src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs
  39. 19
      src/Avalonia.Base/PixelRect.cs
  40. 17
      src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
  41. 16
      src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
  42. 8
      src/Avalonia.Base/Point.cs
  43. 2
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  44. 34
      src/Avalonia.Base/Rect.cs
  45. 12
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  46. 55
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  47. 2
      src/Avalonia.Base/Rendering/DirtyRects.cs
  48. 4
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  49. 11
      src/Avalonia.Base/Size.cs
  50. 14
      src/Avalonia.Base/StyledProperty.cs
  51. 10
      src/Avalonia.Base/Thickness.cs
  52. 8
      src/Avalonia.Base/Vector.cs
  53. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
  54. 2
      src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
  55. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml
  56. 2
      src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml
  57. 8
      src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
  58. 5
      src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
  59. 4
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
  60. 12
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  61. 3
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  62. 2
      src/Avalonia.Controls/BorderVisual.cs
  63. 20
      src/Avalonia.Controls/ContainerClearingEventArgs.cs
  64. 32
      src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs
  65. 26
      src/Avalonia.Controls/ContainerPreparedEventArgs.cs
  66. 103
      src/Avalonia.Controls/ContextMenu.cs
  67. 2
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  68. 2
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  69. 8
      src/Avalonia.Controls/Documents/InlineCollection.cs
  70. 4
      src/Avalonia.Controls/Flyouts/MenuFlyout.cs
  71. 16
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  72. 165
      src/Avalonia.Controls/ItemCollection.cs
  73. 243
      src/Avalonia.Controls/ItemsControl.cs
  74. 155
      src/Avalonia.Controls/ItemsSourceView.cs
  75. 4
      src/Avalonia.Controls/LayoutTransformControl.cs
  76. 135
      src/Avalonia.Controls/MaskedTextBox.cs
  77. 2
      src/Avalonia.Controls/NativeControlHost.cs
  78. 18
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  79. 109
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  80. 80
      src/Avalonia.Controls/Primitives/Popup.cs
  81. 3
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  82. 55
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  83. 107
      src/Avalonia.Controls/SelectableTextBlock.cs
  84. 27
      src/Avalonia.Controls/Selection/InternalSelectionModel.cs
  85. 4
      src/Avalonia.Controls/Selection/SelectionModel.cs
  86. 2
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  87. 66
      src/Avalonia.Controls/TextBlock.cs
  88. 433
      src/Avalonia.Controls/TextBox.cs
  89. 19
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  90. 1
      src/Avalonia.Controls/TopLevel.cs
  91. 4
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  92. 4
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs
  93. 20
      src/Avalonia.Controls/VirtualizingPanel.cs
  94. 5
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  95. 29
      src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs
  96. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml
  97. 6
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  98. 6
      src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
  99. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs
  100. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

5
.ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Generators.Sandbox.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

10
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -281,10 +281,13 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
case SystemDecorationsFull:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
if (!_isClientAreaExtended) {
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
}
if (currentWindowState == Maximized) {
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
@ -611,7 +614,8 @@ void WindowImpl::UpdateStyle() {
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
bool hasTrafficLights = (_decorations == SystemDecorationsFull) &&
(_isClientAreaExtended ? wantsChrome : true);
NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];

2
samples/ControlCatalog/MainView.xaml

@ -241,7 +241,7 @@
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}"
ItemsSource="{Binding WindowStates}"
SelectedItem="{Binding WindowState}" />
</StackPanel>
</Flyout>

60
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
namespace ControlCatalog.Pages
{
public partial class ClipboardPage : UserControl
{
private INotificationManager? _notificationManager;
private INotificationManager NotificationManager => _notificationManager
??= new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
public ClipboardPage()
{
InitializeComponent();
@ -31,7 +38,7 @@ namespace ControlCatalog.Pages
private async void PasteText(object? sender, RoutedEventArgs args)
{
if(Application.Current!.Clipboard is { } clipboard)
if (Application.Current!.Clipboard is { } clipboard)
{
ClipboardContent.Text = await clipboard.GetTextAsync();
}
@ -59,15 +66,45 @@ namespace ControlCatalog.Pages
{
if (Application.Current!.Clipboard is { } clipboard)
{
var files = (ClipboardContent.Text ?? String.Empty)
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (files.Length == 0)
var storageProvider = TopLevel.GetTopLevel(this)!.StorageProvider;
var filesPath = (ClipboardContent.Text ?? string.Empty)
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (filesPath.Length == 0)
{
return;
}
var dataObject = new DataObject();
dataObject.Set(DataFormats.FileNames, files);
await clipboard.SetDataObjectAsync(dataObject);
List<string> invalidFile = new(filesPath.Length);
List<IStorageFile> files = new(filesPath.Length);
for (int i = 0; i < filesPath.Length; i++)
{
var file = await storageProvider.TryGetFileFromPathAsync(filesPath[i]);
if (file is null)
{
invalidFile.Add(filesPath[i]);
}
else
{
files.Add(file);
}
}
if (invalidFile.Count > 0)
{
NotificationManager.Show(new Notification("Warning", "There is one o more invalid path.", NotificationType.Warning));
}
if (files.Count > 0)
{
var dataObject = new DataObject();
dataObject.Set(DataFormats.Files, files);
await clipboard.SetDataObjectAsync(dataObject);
NotificationManager.Show(new Notification("Success", "Copy completated.", NotificationType.Success));
}
else
{
NotificationManager.Show(new Notification("Warning", "Any files to copy in Clipboard.", NotificationType.Warning));
}
}
}
@ -75,8 +112,9 @@ namespace ControlCatalog.Pages
{
if (Application.Current!.Clipboard is { } clipboard)
{
var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>;
ClipboardContent.Text = files != null ? string.Join(Environment.NewLine, files.Select(f => f.TryGetLocalPath() ?? f.Name)) : string.Empty;
}
}
@ -95,7 +133,7 @@ namespace ControlCatalog.Pages
{
await clipboard.ClearAsync();
}
}
}
}

2
samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
{
AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Get<ComboBox>("fontComboBox");
fontComboBox.Items = FontManager.Current.SystemFonts;
fontComboBox.ItemsSource = FontManager.Current.SystemFonts;
fontComboBox.SelectedIndex = 0;
}
}

2
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@ -32,7 +32,7 @@ public partial class CompositionPage : UserControl
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
this.Get<ItemsControl>("Items").Items = CreateColorItems();
this.Get<ItemsControl>("Items").ItemsSource = CreateColorItems();
}

2
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@ -61,7 +61,7 @@
<Border.Styles>
<Style Selector="MenuFlyoutPresenter MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>

4
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -51,13 +51,13 @@
<Border.Styles>
<Style Selector="ContextMenu MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</Border.Styles>
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}" />
<ContextMenu ItemsSource="{Binding MenuItems}" />
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>

2
samples/ControlCatalog/Pages/CursorPage.xaml

@ -8,7 +8,7 @@
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>
<ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox Grid.Row="1" ItemsSource="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Cursor" Value="{Binding Cursor}" x:DataType="viewModels:StandardCursorModel"/>

8
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -133,17 +133,17 @@ namespace ControlCatalog.Pages
// 0,0 refers to the top-left of the control now. It is not prime time to draw gui stuff because it'll be under the world
var translateModifier = context.PushPreTransform(Avalonia.Matrix.CreateTranslation(new Avalonia.Vector(halfWidth, halfHeight)));
var translateModifier = context.PushTransform(Avalonia.Matrix.CreateTranslation(new Avalonia.Vector(halfWidth, halfHeight)));
// now 0,0 refers to the ViewportCenter(X,Y).
var rotationMatrix = Avalonia.Matrix.CreateRotation(Rotation);
var rotationModifier = context.PushPreTransform(rotationMatrix);
var rotationModifier = context.PushTransform(rotationMatrix);
// everything is rotated but not scaled
var scaleModifier = context.PushPreTransform(Avalonia.Matrix.CreateScale(Scale, -Scale));
var scaleModifier = context.PushTransform(Avalonia.Matrix.CreateScale(Scale, -Scale));
var mapPositionModifier = context.PushPreTransform(Matrix.CreateTranslation(new Vector(-ViewportCenterX, -ViewportCenterY)));
var mapPositionModifier = context.PushTransform(Matrix.CreateTranslation(new Vector(-ViewportCenterX, -ViewportCenterY)));
// now everything is rotated and scaled, and at the right position, now we're drawing strictly in world coordinates

12
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -106,7 +106,7 @@ namespace ControlCatalog.Pages
Directory = initialDirectory,
InitialFileName = initialFileName
}.ShowAsync(GetWindow());
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("OpenMultipleFiles").Click += async delegate
@ -118,7 +118,7 @@ namespace ControlCatalog.Pages
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowAsync(GetWindow());
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("SaveFile").Click += async delegate
@ -132,7 +132,7 @@ namespace ControlCatalog.Pages
DefaultExtension = filters?.Any() == true ? "txt" : null,
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
results.Items = new[] { result };
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = result != null;
};
this.Get<Button>("SelectFolder").Click += async delegate
@ -149,7 +149,7 @@ namespace ControlCatalog.Pages
else
{
SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
results.Items = new[] { result };
results.ItemsSource = new[] { result };
resultsVisible.IsVisible = true;
}
};
@ -164,7 +164,7 @@ namespace ControlCatalog.Pages
{
AllowDirectorySelection = true
});
results.Items = result;
results.ItemsSource = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.Get<Button>("DecoratedWindow").Click += delegate
@ -332,7 +332,7 @@ namespace ControlCatalog.Pages
}
}
results.Items = mappedResults;
results.ItemsSource = mappedResults;
resultsVisible.IsVisible = mappedResults.Any();
}
}

2
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -30,7 +30,7 @@
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<Button Command="{Binding SelectRandomItemCommand}">Select Random Item</Button>
</StackPanel>
<ListBox Items="{Binding Items}"
<ListBox ItemsSource="{Binding Items}"
Selection="{Binding Selection}"
DisplayMemberBinding="{Binding (viewModels:ItemModel).ID, StringFormat='{}Item {0:N0}'}"
AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}"

6
samples/ControlCatalog/Pages/MenuPage.xaml

@ -45,11 +45,11 @@
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
<Menu Items="{Binding MenuItems}">
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Styles>
<Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
@ -68,7 +68,7 @@
<Separator/>
<MenuItem Header="Execu_te Script..." />
<Separator/>
<MenuItem Header="_Recent" Items="{Binding RecentItems}">
<MenuItem Header="_Recent" ItemsSource="{Binding RecentItems}">
<MenuItem.Styles>
<Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
<Setter Property="Header" Value="{Binding Header}"/>

8
samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs

@ -33,10 +33,10 @@ namespace ControlCatalog.Pages
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
Items =
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}

6
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -27,7 +27,7 @@
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Margin="8" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<ComboBox Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -41,11 +41,11 @@
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
<ComboBox Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}"
<ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Cultures}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>

2
samples/ControlCatalog/Pages/RefreshContainerPage.axaml

@ -21,7 +21,7 @@
Margin="5">
<ListBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Items="{Binding Items}"/>
ItemsSource="{Binding Items}"/>
</RefreshContainer>
</DockPanel>
</UserControl>

4
samples/ControlCatalog/Pages/ScrollSnapPage.xaml

@ -16,14 +16,14 @@
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Type" />
<ComboBox Items="{Binding AvailableSnapPointsType}"
<ComboBox ItemsSource="{Binding AvailableSnapPointsType}"
SelectedItem="{Binding SnapPointsType}" />
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Alignment" />
<ComboBox Items="{Binding AvailableSnapPointsAlignment}"
<ComboBox ItemsSource="{Binding AvailableSnapPointsAlignment}"
SelectedItem="{Binding SnapPointsAlignment}" />
</StackPanel>

4
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -13,12 +13,12 @@
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Horizontal Scroll" />
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" />
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Vertical Scroll" />
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
<ComboBox ItemsSource="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" />
</StackPanel>
</StackPanel>

2
samples/ControlCatalog/Pages/TabStripPage.xaml

@ -18,7 +18,7 @@
<Separator Margin="0 16"/>
<TextBlock Classes="h1">Dynamically generated</TextBlock>
<TabStrip Items="{Binding Tabs}">
<TabStrip ItemsSource="{Binding Tabs}">
<TabStrip.Styles>
<Style Selector="TabStripItem" x:DataType="viewModels:TabControlPageViewModelItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>

2
samples/ControlCatalog/Pages/ThemePage.axaml.cs

@ -12,7 +12,7 @@ namespace ControlCatalog.Pages
{
InitializeComponent();
Selector.Items = new[]
Selector.ItemsSource = new[]
{
ThemeVariant.Default,
ThemeVariant.Dark,

2
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml

@ -53,7 +53,7 @@
<StackPanel Margin="5" Spacing="5" Grid.IsSharedSizeScope="True">
<HeaderedContentControl Header="Select a transition">
<ComboBox Items="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
<ComboBox ItemsSource="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
</HeaderedContentControl>
<HeaderedContentControl Header="Duration">
<NumericUpDown Value="{Binding Duration}" Increment="250" Minimum="100" />

2
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -11,7 +11,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView ItemsSource="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>

8
samples/IntegrationTestApp/MainWindow.axaml

@ -109,7 +109,7 @@
<StackPanel DockPanel.Dock="Bottom">
<Button Name="ListBoxSelectionClear">Clear Selection</Button>
</StackPanel>
<ListBox Name="BasicListBox" Items="{Binding ListBoxItems}" SelectionMode="Multiple"/>
<ListBox Name="BasicListBox" ItemsSource="{Binding ListBoxItems}" SelectionMode="Multiple"/>
</DockPanel>
</TabItem>
@ -151,6 +151,12 @@
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowSystemDecorations" SelectedIndex="2">
<ComboBoxItem Name="ShowWindowSystemDecorationsNone">None</ComboBoxItem>
<ComboBoxItem Name="ShowWindowSystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
<ComboBoxItem Name="ShowWindowSystemDecorationsFull">Full</ComboBoxItem>
</ComboBox>
<CheckBox Name="ShowWindowExtendClientAreaToDecorationsHint">ExtendClientAreaToDecorationsHint</CheckBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>

6
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -68,6 +68,8 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
var systemDecorations = this.GetControl<ComboBox>("ShowWindowSystemDecorations");
var extendClientArea = this.GetControl<CheckBox>("ShowWindowExtendClientAreaToDecorationsHint");
var canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
@ -95,6 +97,8 @@ namespace IntegrationTestApp
}
sizeTextBox.Text = string.Empty;
window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
window.WindowState = (WindowState)stateComboBox.SelectedIndex;
switch (modeComboBox.SelectedIndex)
@ -158,7 +162,7 @@ namespace IntegrationTestApp
var popup = new Popup
{
WindowManagerAddShadowHint = false,
PlacementMode = PlacementMode.AnchorAndGravity,
Placement = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.Top,
PlacementGravity = PopupGravity.Bottom,
Width= 200,

24
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -6,7 +6,7 @@
x:DataType="Window"
Title="Show Window Test">
<integrationTestApp:MeasureBorder Name="MyBorder">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Label Grid.Column="0" Grid.Row="1">Client Size</Label>
<TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
Text="{Binding ClientSize, Mode=OneWay}" />
@ -35,13 +35,25 @@
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="8" IsReadOnly="True" />
<Label Grid.Column="0" Grid.Row="8">SystemDecorations</Label>
<ComboBox Name="CurrentSystemDecorations" Grid.Column="1" Grid.Row="8" SelectedIndex="{Binding SystemDecorations}">
<ComboBoxItem Name="SystemDecorationsNone">None</ComboBoxItem>
<ComboBoxItem Name="SystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
<ComboBoxItem Name="SystemDecorationsFull">Full</ComboBoxItem>
</ComboBox>
<CheckBox Name="CurrentExtendClientAreaToDecorationsHint" Grid.ColumnSpan="2" Grid.Row="9"
IsChecked="{Binding ExtendClientAreaToDecorationsHint}">
ExtendClientAreaToDecorationsHint
</CheckBox>
<Label Grid.Column="0" Grid.Row="10">Order (mac)</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="10" IsReadOnly="True" />
<Label Grid.Row="9" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="9" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
<Label Grid.Row="11" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="11" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
<Button Name="HideButton" Grid.Row="10" Command="{Binding $parent[Window].Hide}">Hide</Button>
<Button Name="HideButton" Grid.Row="12" Command="{Binding $parent[Window].Hide}">Hide</Button>
</Grid>
</integrationTestApp:MeasureBorder>

2
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@ -29,7 +29,7 @@ namespace RenderDemo.Pages
public override void Render(DrawingContext context)
{
using (var ctx = _bitmap.CreateDrawingContext())
using (ctx.PushPostTransform(Matrix.CreateTranslation(-100, -100)
using (ctx.PushTransform(Matrix.CreateTranslation(-100, -100)
* Matrix.CreateRotation(_st.Elapsed.TotalSeconds)
* Matrix.CreateTranslation(100, 100)))
{

2
samples/SampleControls/HamburgerMenu/HamburgerMenu.cs

@ -57,7 +57,7 @@ namespace ControlSamples
{
if (_splitView is not null && _splitView.DisplayMode == SplitViewDisplayMode.Overlay)
{
_splitView.SetValue(SplitView.IsPaneOpenProperty, false, Avalonia.Data.BindingPriority.Animation);
_splitView.SetCurrentValue(SplitView.IsPaneOpenProperty, false);
}
}
}

9
src/Avalonia.Base/CornerRadius.cs

@ -60,15 +60,6 @@ namespace Avalonia
/// </summary>
public double BottomLeft { get; }
/// <summary>
/// Gets a value indicating whether the instance has default values (all corner radii are set to 0).
/// </summary>
public bool IsDefault => TopLeft == 0 && TopRight == 0 && BottomLeft == 0 && BottomRight == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Gets a value indicating whether all corner radii are equal.
/// </summary>

2
src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs

@ -180,7 +180,7 @@ namespace Avalonia.Input.GestureRecognizers
internal Velocity GetVelocity()
{
var estimate = GetVelocityEstimate();
if (estimate == null || estimate.PixelsPerSecond.IsDefault)
if (estimate == null || estimate.PixelsPerSecond == default(Vector))
{
return new Velocity(Vector.Zero);
}

11
src/Avalonia.Base/Media/BoxShadow.cs

@ -45,15 +45,6 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Gets a value indicating whether the instance has default values.
/// </summary>
public bool IsDefault => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
private readonly static char[] s_Separator = new char[] { ' ', '\t' };
struct ArrayReader
@ -89,7 +80,7 @@ namespace Avalonia.Media
{
var sb = StringBuilderCache.Acquire();
if (IsDefault)
if (this == default)
{
return "none";
}

4
src/Avalonia.Base/Media/BoxShadows.cs

@ -21,7 +21,7 @@ namespace Avalonia.Media
{
_first = shadow;
_list = null;
Count = _first.IsDefault ? 0 : 1;
Count = _first == default ? 0 : 1;
}
public BoxShadows(BoxShadow first, BoxShadow[] rest)
@ -120,7 +120,7 @@ namespace Avalonia.Media
get
{
foreach(var boxShadow in this)
if (!boxShadow.IsDefault && boxShadow.IsInset)
if (boxShadow != default && boxShadow.IsInset)
return true;
return false;
}

2
src/Avalonia.Base/Media/DrawingGroup.cs

@ -73,7 +73,7 @@ namespace Avalonia.Media
{
var bounds = GetBounds();
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity, bounds))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default)

5
src/Avalonia.Base/Media/FontFamily.cs

@ -79,11 +79,6 @@ namespace Avalonia.Media
/// <remarks>Key is only used for custom fonts.</remarks>
public FontFamilyKey? Key { get; }
/// <summary>
/// Returns <c>True</c> if this instance is the system's default.
/// </summary>
public bool IsDefault => Name.Equals(DefaultFontFamilyName);
/// <summary>
/// Implicit conversion of string to FontFamily
/// </summary>

5
src/Avalonia.Base/Media/FormattedText.cs

@ -1393,10 +1393,11 @@ namespace Avalonia.Media
}
}
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsDefault)
if (accumulatedBounds?.PlatformImpl == null ||
(accumulatedBounds.PlatformImpl.Bounds.Width == 0 && accumulatedBounds.PlatformImpl.Bounds.Height == 0))
{
return null;
}
}
return accumulatedBounds;
}

2
src/Avalonia.Base/Media/ImageDrawing.cs

@ -42,7 +42,7 @@ namespace Avalonia.Media
var imageSource = ImageSource;
var rect = Rect;
if (imageSource is object && !rect.IsDefault)
if (imageSource is object && (rect.Width != 0 || rect.Height != 0))
{
context.DrawImage(imageSource, rect);
}

2
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@ -77,7 +77,7 @@ namespace Avalonia.Media.Imaging
{
if (Source is not IBitmap bmp)
return default;
if (SourceRect.IsDefault)
if (SourceRect.Width == 0 && SourceRect.Height == 0)
return Source.Size;
return SourceRect.Size.ToSizeWithDpi(bmp.Dpi);
}

2
src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs

@ -9,7 +9,7 @@ namespace Avalonia.Metadata;
/// A typical usage example is a ListBox control, where <see cref="InheritDataTypeFromItemsAttribute"/> is defined on the ItemTemplate property,
/// allowing the template to inherit the data type from the Items collection binding.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class InheritDataTypeFromItemsAttribute : Attribute
{
/// <summary>

19
src/Avalonia.Base/PixelRect.cs

@ -9,12 +9,6 @@ namespace Avalonia
/// </summary>
public readonly struct PixelRect : IEquatable<PixelRect>
{
/// <summary>
/// An empty rectangle.
/// </summary>
[Obsolete("Use the default keyword instead.")]
public static readonly PixelRect Empty = default;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
@ -133,15 +127,6 @@ namespace Avalonia
/// </summary>
public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2));
/// <summary>
/// Gets a value indicating whether the instance has default values (the rectangle is empty).
/// </summary>
public bool IsDefault => Width == 0 && Height == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Checks for equality between two <see cref="PixelRect"/>s.
/// </summary>
@ -295,11 +280,11 @@ namespace Avalonia
/// <returns>The union.</returns>
public PixelRect Union(PixelRect rect)
{
if (IsDefault)
if (Width == 0 && Height == 0)
{
return rect;
}
else if (rect.IsDefault)
else if (rect.Width == 0 && rect.Height == 0)
{
return this;
}

17
src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
@ -23,7 +24,7 @@ internal static class StorageProviderHelpers
return null;
}
public static Uri FilePathToUri(string path)
{
var uriPath = new StringBuilder(path)
@ -35,6 +36,20 @@ internal static class StorageProviderHelpers
return new UriBuilder("file", string.Empty) { Path = uriPath }.Uri;
}
public static bool TryFilePathToUri(string path, [NotNullWhen(true)] out Uri? uri)
{
try
{
uri = FilePathToUri(path);
return true;
}
catch
{
uri = null;
return false;
}
}
public static string NameWithExtension(string path, string? defaultExtension, FilePickerFileType? filter)
{
var name = Path.GetFileName(path);

16
src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs

@ -16,8 +16,13 @@ public static class StorageProviderExtensions
{
return Task.FromResult(StorageProviderHelpers.TryCreateBclStorageItem(filePath) as IStorageFile);
}
return provider.TryGetFileFromPathAsync(StorageProviderHelpers.FilePathToUri(filePath));
if (StorageProviderHelpers.TryFilePathToUri(filePath, out var uri))
{
return provider.TryGetFileFromPathAsync(uri);
}
return Task.FromResult<IStorageFile?>(null);
}
/// <inheritdoc cref="IStorageProvider.TryGetFolderFromPathAsync"/>
@ -29,7 +34,12 @@ public static class StorageProviderExtensions
return Task.FromResult(StorageProviderHelpers.TryCreateBclStorageItem(folderPath) as IStorageFolder);
}
return provider.TryGetFolderFromPathAsync(StorageProviderHelpers.FilePathToUri(folderPath));
if (StorageProviderHelpers.TryFilePathToUri(folderPath, out var uri))
{
return provider.TryGetFolderFromPathAsync(uri);
}
return Task.FromResult<IStorageFolder?>(null);
}
/// <summary>

8
src/Avalonia.Base/Point.cs

@ -288,13 +288,5 @@ namespace Avalonia
x = this._x;
y = this._y;
}
/// <summary>
/// Gets a value indicating whether the X and Y coordinates are zero.
/// </summary>
public bool IsDefault
{
get { return (_x == 0) && (_y == 0); }
}
}
}

2
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -31,7 +31,7 @@ namespace Avalonia.PropertyStore
var value = inherited is null ? _metadata.DefaultValue : inherited.Value;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
if (_metadata.CoerceValue is { } coerce)
{
_uncommon = new()
{

34
src/Avalonia.Base/Rect.cs

@ -16,12 +16,6 @@ namespace Avalonia
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// An empty rectangle.
/// </summary>
[Obsolete("Use the default keyword instead.")]
public static readonly Rect Empty = default;
/// <summary>
/// The X position.
/// </summary>
@ -170,17 +164,6 @@ namespace Avalonia
/// </summary>
public Point Center => new Point(_x + (_width / 2), _y + (_height / 2));
/// <summary>
/// Gets a value indicating whether the instance has default values (the rectangle is empty).
/// </summary>
// ReSharper disable CompareOfFloatsByEqualityOperator
public bool IsDefault => _width == 0 && _height == 0;
// ReSharper restore CompareOfFloatsByEqualityOperator
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Checks for equality between two <see cref="Rect"/>s.
/// </summary>
@ -517,19 +500,18 @@ namespace Avalonia
return rect;
}
/// <summary>
/// Gets the union of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The union.</returns>
public Rect Union(Rect rect)
/// <summary>
/// Gets the union of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The union.</returns>
public Rect Union(Rect rect)
{
if (IsDefault)
if (Width == 0 && Height == 0)
{
return rect;
}
else if (rect.IsDefault)
else if (rect.Width == 0 && rect.Height == 0)
{
return this;
}

12
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -130,8 +130,8 @@ namespace Avalonia.Rendering.Composition.Server
}
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
if(_dirtyRect.IsDefault && !_redrawRequested)
if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested)
return;
Revision++;
@ -163,7 +163,7 @@ namespace Avalonia.Rendering.Composition.Server
_dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height);
}
if (!_dirtyRect.IsDefault)
if (_dirtyRect.Width != 0 || _dirtyRect.Height != 0)
{
using (var context = _layer.CreateDrawingContext())
{
@ -260,7 +260,7 @@ namespace Avalonia.Rendering.Composition.Server
public void AddDirtyRect(Rect rect)
{
if(rect.IsDefault)
if (rect.Width == 0 && rect.Height == 0)
return;
var snapped = SnapToDevicePixels(rect, Scaling);
DebugEvents?.RectInvalidated(rect);
@ -275,7 +275,7 @@ namespace Avalonia.Rendering.Composition.Server
public void Dispose()
{
if(_disposed)
if (_disposed)
return;
_disposed = true;
using (_compositor.RenderInterface.EnsureCurrent())
@ -302,7 +302,7 @@ namespace Avalonia.Rendering.Composition.Server
{
if (_attachedVisuals.Remove(visual) && IsEnabled)
visual.Deactivate();
if(visual.IsVisibleInFrame)
if (visual.IsVisibleInFrame)
AddDirtyRect(visual.TransformedOwnContentBounds);
}

55
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -23,21 +23,20 @@ namespace Avalonia.Rendering.Composition.Server
private bool _isBackface;
private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
}
public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
if(Visible == false || IsVisibleInFrame == false)
if (Visible == false || IsVisibleInFrame == false)
return;
if(Opacity == 0)
if (Opacity == 0)
return;
currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds);
if(currentTransformedClip.IsDefault)
if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0)
return;
Root!.RenderedVisuals++;
@ -61,7 +60,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null)
canvas.PushGeometryClip(Clip);
if(OpacityMaskBrush != null)
if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
RenderCore(canvas, currentTransformedClip);
@ -78,12 +77,12 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopClip();
if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if(Opacity != 1)
if (Opacity != 1)
canvas.PopOpacity();
}
protected virtual bool HandlesClipToBounds => false;
private ReadbackData _readback0, _readback1, _readback2;
/// <summary>
@ -98,17 +97,17 @@ namespace Avalonia.Rendering.Composition.Server
return ref _readback1;
return ref _readback2;
}
public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity;
public Matrix4x4 GlobalTransformMatrix { get; private set; }
public virtual void Update(ServerCompositionTarget root)
{
if(Parent == null && Root == null)
if (Parent == null && Root == null)
return;
var wasVisible = IsVisibleInFrame;
// Calculate new parent-relative transform
if (_combinedTransformDirty)
{
@ -122,7 +121,7 @@ namespace Avalonia.Rendering.Composition.Server
var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;
var newTransform = CombinedTransformMatrix * parentTransform;
// Check if visual was moved and recalculate face orientation
var positionChanged = false;
if (GlobalTransformMatrix != newTransform)
@ -134,23 +133,23 @@ namespace Avalonia.Rendering.Composition.Server
var oldTransformedContentBounds = TransformedOwnContentBounds;
var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds;
if (_parent?.IsDirtyComposition == true)
{
IsDirtyComposition = true;
_isDirtyForUpdate = true;
}
var invalidateOldBounds = _isDirtyForUpdate;
var invalidateNewBounds = _isDirtyForUpdate;
GlobalTransformMatrix = newTransform;
var ownBounds = OwnContentBounds;
if (ownBounds != _oldOwnContentBounds || positionChanged)
{
_oldOwnContentBounds = ownBounds;
if (ownBounds.IsDefault)
if (ownBounds.Width == 0 && ownBounds.Height == 0)
TransformedOwnContentBounds = default;
else
TransformedOwnContentBounds =
@ -171,16 +170,16 @@ namespace Avalonia.Rendering.Composition.Server
AdornedVisual?._combinedTransformedClipBounds
?? Parent?._combinedTransformedClipBounds
?? new Rect(Root!.Size);
if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1);
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible
&& !_isBackface
&& !_combinedTransformedClipBounds.IsDefault;
&& (_combinedTransformedClipBounds.Width != 0 || _combinedTransformedClipBounds.Height != 0);
IsVisibleInFrame = IsHitTestVisibleInFrame
&& _parent?.IsVisibleInFrame != false
@ -213,11 +212,11 @@ namespace Avalonia.Rendering.Composition.Server
void AddDirtyRect(Rect rc)
{
if(rc == default)
if (rc == default)
return;
Root?.AddDirtyRect(rc);
}
/// <summary>
/// Data that can be read from the UI thread
/// </summary>
@ -228,7 +227,7 @@ namespace Avalonia.Rendering.Composition.Server
public long TargetId;
public bool Visible;
}
partial void DeserializeChangesExtra(BatchStreamReader c)
{
ValuesInvalidated();
@ -245,9 +244,8 @@ namespace Avalonia.Rendering.Composition.Server
protected virtual void OnDetachedFromRoot(ServerCompositionTarget target)
{
}
partial void OnRootChanged()
{
if (Root != null)
@ -256,12 +254,11 @@ namespace Avalonia.Rendering.Composition.Server
OnAttachedToRoot(Root);
}
}
protected virtual void OnAttachedToRoot(ServerCompositionTarget target)
{
}
protected override void ValuesInvalidated()
{
_isDirtyForUpdate = true;
@ -274,6 +271,4 @@ namespace Avalonia.Rendering.Composition.Server
public Rect TransformedOwnContentBounds { get; set; }
public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y);
}
}

2
src/Avalonia.Base/Rendering/DirtyRects.cs

@ -30,7 +30,7 @@ namespace Avalonia.Rendering
/// </remarks>
public void Add(Rect rect)
{
if (!rect.IsDefault)
if (rect.Width != 0 || rect.Height != 0)
{
for (var i = 0; i < _rects.Count; ++i)
{

4
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -83,7 +83,7 @@ namespace Avalonia.Rendering
}
}
using (context.PushPostTransform(m))
using (context.PushTransform(m))
using (context.PushOpacity(opacity, bounds))
using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete
@ -95,7 +95,7 @@ namespace Avalonia.Rendering
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default)
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default)
using (context.PushTransformContainer())
using (context.PushTransform(Matrix.Identity))
{
visual.Render(context);

11
src/Avalonia.Base/Size.cs

@ -27,12 +27,6 @@ namespace Avalonia
/// </summary>
public static readonly Size Infinity = new Size(double.PositiveInfinity, double.PositiveInfinity);
/// <summary>
/// A size representing zero.
/// </summary>
[Obsolete("Use the default keyword instead.")]
public static readonly Size Empty = new Size(0, 0);
/// <summary>
/// The width.
/// </summary>
@ -306,10 +300,5 @@ namespace Avalonia
width = this._width;
height = this._height;
}
/// <summary>
/// Gets a value indicating whether the Width and Height values are zero.
/// </summary>
public bool IsDefault => (_width == 0) && (_height == 0);
}
}

14
src/Avalonia.Base/StyledProperty.cs

@ -34,7 +34,6 @@ namespace Avalonia
{
Inherits = inherits;
ValidateValue = validate;
HasCoercion |= metadata.CoerceValue != null;
if (validate?.Invoke(metadata.DefaultValue) == false)
{
@ -48,12 +47,6 @@ namespace Avalonia
/// </summary>
public Func<TValue, bool>? ValidateValue { get; }
/// <summary>
/// Gets a value indicating whether this property has any value coercion callbacks defined
/// in its metadata.
/// </summary>
internal bool HasCoercion { get; private set; }
/// <summary>
/// Registers the property on another type.
/// </summary>
@ -130,10 +123,7 @@ namespace Avalonia
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="metadata">The metadata.</param>
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject
{
base.OverrideMetadata(typeof(T), metadata);
}
public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject => OverrideMetadata(typeof(T), metadata);
/// <summary>
/// Overrides the metadata for the property on the specified type.
@ -151,8 +141,6 @@ namespace Avalonia
}
}
HasCoercion |= metadata.CoerceValue != null;
base.OverrideMetadata(type, metadata);
}

10
src/Avalonia.Base/Thickness.cs

@ -97,10 +97,6 @@ namespace Avalonia
/// </summary>
public double Bottom => _bottom;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Gets a value indicating whether all sides are equal.
/// </summary>
@ -293,11 +289,5 @@ namespace Avalonia
right = this._right;
bottom = this._bottom;
}
/// <summary>
/// Gets a value indicating whether the instance has default values
/// (the left, top, right and bottom values are zero).
/// </summary>
public bool IsDefault => (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0);
}
}

8
src/Avalonia.Base/Vector.cs

@ -360,13 +360,5 @@ namespace Avalonia
x = this._x;
y = this._y;
}
/// <summary>
/// Gets a value indicating whether the X and Y components are zero.
/// </summary>
public bool IsDefault
{
get { return (_x == 0) && (_y == 0); }
}
}
}

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml

@ -165,7 +165,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml

@ -414,7 +414,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml

@ -165,7 +165,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

2
src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml

@ -376,7 +376,7 @@
</TabItem.Header>
<ListBox Theme="{StaticResource ColorViewPaletteListBoxTheme}"
ItemContainerTheme="{StaticResource ColorViewPaletteListBoxItemTheme}"
Items="{TemplateBinding PaletteColors}"
ItemsSource="{TemplateBinding PaletteColors}"
SelectedItem="{Binding Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource DoNothingForNullConverter}, Mode=TwoWay}"
UseLayoutRounding="False"
Margin="12">

8
src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs

@ -177,14 +177,14 @@ namespace Avalonia.Controls
}
bool? uneditedValue = editingCheckBox.IsChecked;
if(editingEventArgs is PointerPressedEventArgs args)
if (editingEventArgs is PointerPressedEventArgs args)
{
void ProcessPointerArgs()
{
// Editing was triggered by a mouse click
Point position = args.GetPosition(editingCheckBox);
Rect rect = new Rect(0, 0, editingCheckBox.Bounds.Width, editingCheckBox.Bounds.Height);
if(rect.Contains(position))
if (rect.Contains(position))
{
EditValue();
}
@ -192,14 +192,14 @@ namespace Avalonia.Controls
void OnLayoutUpdated(object sender, EventArgs e)
{
if(!editingCheckBox.Bounds.IsDefault)
if (editingCheckBox.Bounds.Width != 0 || editingCheckBox.Bounds.Height != 0)
{
editingCheckBox.LayoutUpdated -= OnLayoutUpdated;
ProcessPointerArgs();
}
}
if(editingCheckBox.Bounds.IsDefault)
if (editingCheckBox.Bounds.Width == 0 && editingCheckBox.Bounds.Height == 0)
{
editingCheckBox.LayoutUpdated += OnLayoutUpdated;
}

5
src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs

@ -39,7 +39,10 @@ namespace Avalonia.Controls
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsRepeater, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsRepeater>(o => o.Items, (o, v) => o.Items = v);
AvaloniaProperty.RegisterDirect<ItemsRepeater, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="Layout"/> property.

4
src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs

@ -441,7 +441,7 @@ namespace Avalonia.Controls
_pendingViewportShift = default;
_unshiftableShift = default;
if (_visibleWindow.IsDefault)
if (_visibleWindow.Width == 0 && _visibleWindow.Height == 0)
{
// We got cleared.
_layoutExtent = default;
@ -527,7 +527,7 @@ namespace Avalonia.Controls
private void TryInvalidateMeasure()
{
// Don't invalidate measure if we have an invalid window.
if (!_visibleWindow.IsDefault)
if (_visibleWindow.Width != 0 || _visibleWindow.Height != 0)
{
// We invalidate measure instead of just invalidating arrange because
// we don't invalidate measure in UpdateViewport if the view is changing to

12
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@ -87,12 +87,10 @@ namespace Avalonia.Controls
/// Identifies the <see cref="Text" /> property.
/// </summary>
/// <value>The identifier for the <see cref="Text" /> property.</value>
public static readonly DirectProperty<AutoCompleteBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<AutoCompleteBox>(
o => o.Text,
(o, v) => o.Text = v,
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<AutoCompleteBox>(new(string.Empty,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
enableDataValidation: true));
/// <summary>
/// Identifies the <see cref="SearchText" /> property.
@ -317,8 +315,8 @@ namespace Avalonia.Controls
/// <see cref="AutoCompleteBox" /> control.</value>
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>

3
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -198,7 +198,6 @@ namespace Avalonia.Controls
private bool _isDropDownOpen;
private bool _isFocused = false;
private string? _text = string.Empty;
private string? _searchText = string.Empty;
private AutoCompleteFilterPredicate<object?>? _itemFilter;
@ -1275,7 +1274,7 @@ namespace Avalonia.Controls
if ((userInitiated ?? true) && Text != value)
{
_ignoreTextPropertyChange++;
Text = value;
SetCurrentValue(TextProperty, value);
callTextChanged = true;
}

2
src/Avalonia.Controls/BorderVisual.cs

@ -50,7 +50,7 @@ class CompositionBorderVisual : CompositionDrawListVisual
if (ClipToBounds)
{
var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y)));
if (_cornerRadius.IsDefault)
if (_cornerRadius == default)
canvas.PushClip(clipRect);
else
canvas.PushClip(new RoundedRect(clipRect, _cornerRadius));

20
src/Avalonia.Controls/ContainerClearingEventArgs.cs

@ -0,0 +1,20 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerClearing"/> event.
/// </summary>
public class ContainerClearingEventArgs : EventArgs
{
public ContainerClearingEventArgs(Control container)
{
Container = container;
}
/// <summary>
/// Gets the prepared container.
/// </summary>
public Control Container { get; }
}
}

32
src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs

@ -0,0 +1,32 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerIndexChanged"/> event.
/// </summary>
public class ContainerIndexChangedEventArgs : EventArgs
{
public ContainerIndexChangedEventArgs(Control container, int oldIndex, int newIndex)
{
Container = container;
OldIndex = oldIndex;
NewIndex = newIndex;
}
/// <summary>
/// Get the container for which the index changed.
/// </summary>
public Control Container { get; }
/// <summary>
/// Gets the index of the container after the change.
/// </summary>
public int NewIndex { get; }
/// <summary>
/// Gets the index of the container before the change.
/// </summary>
public int OldIndex { get; }
}
}

26
src/Avalonia.Controls/ContainerPreparedEventArgs.cs

@ -0,0 +1,26 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerPrepared"/> event.
/// </summary>
public class ContainerPreparedEventArgs : EventArgs
{
public ContainerPreparedEventArgs(Control container, int index)
{
Container = container;
Index = index;
}
/// <summary>
/// Gets the prepared container.
/// </summary>
public Control Container { get; }
/// <summary>
/// Gets the index of the item the container was prepared for.
/// </summary>
public int Index { get; }
}
}

103
src/Avalonia.Controls/ContextMenu.cs

@ -54,11 +54,17 @@ namespace Avalonia.Controls
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
Popup.PlacementGravityProperty.AddOwner<ContextMenu>();
/// <summary>
/// Defines the <see cref="Placement"/> property.
/// </summary>
public static readonly StyledProperty<PlacementMode> PlacementProperty =
Popup.PlacementProperty.AddOwner<ContextMenu>();
/// <summary>
/// Defines the <see cref="PlacementMode"/> property.
/// </summary>
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
Popup.PlacementModeProperty.AddOwner<ContextMenu>();
[Obsolete("Use the Placement property instead.")]
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty;
/// <summary>
/// Defines the <see cref="PlacementRect"/> property.
@ -108,99 +114,80 @@ namespace Avalonia.Controls
static ContextMenu()
{
ItemsPanelProperty.OverrideDefaultValue<ContextMenu>(DefaultPanel);
PlacementModeProperty.OverrideDefaultValue<ContextMenu>(PlacementMode.Pointer);
PlacementProperty.OverrideDefaultValue<ContextMenu>(PlacementMode.Pointer);
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue<ContextMenu>(AccessibilityView.Control);
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<ContextMenu>(AutomationControlType.Menu);
}
/// <summary>
/// Gets or sets the Horizontal offset of the context menu in relation to the <see cref="PlacementTarget"/>.
/// </summary>
/// <inheritdoc cref="Popup.HorizontalOffset"/>
public double HorizontalOffset
{
get { return GetValue(HorizontalOffsetProperty); }
set { SetValue(HorizontalOffsetProperty, value); }
get => GetValue(HorizontalOffsetProperty);
set => SetValue(HorizontalOffsetProperty, value);
}
/// <summary>
/// Gets or sets the Vertical offset of the context menu in relation to the <see cref="PlacementTarget"/>.
/// </summary>
/// <inheritdoc cref="Popup.VerticalOffset"/>
public double VerticalOffset
{
get { return GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
get => GetValue(VerticalOffsetProperty);
set => SetValue(VerticalOffsetProperty, value);
}
/// <summary>
/// Gets or sets the anchor point on the <see cref="PlacementRect"/> when <see cref="PlacementMode"/>
/// is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
/// <inheritdoc cref="Popup.PlacementAnchor"/>
public PopupAnchor PlacementAnchor
{
get { return GetValue(PlacementAnchorProperty); }
set { SetValue(PlacementAnchorProperty, value); }
get => GetValue(PlacementAnchorProperty);
set => SetValue(PlacementAnchorProperty, value);
}
/// <summary>
/// Gets or sets a value describing how the context menu position will be adjusted if the
/// unadjusted position would result in the context menu being partly constrained.
/// </summary>
/// <inheritdoc cref="Popup.PlacementConstraintAdjustment"/>
public PopupPositionerConstraintAdjustment PlacementConstraintAdjustment
{
get { return GetValue(PlacementConstraintAdjustmentProperty); }
set { SetValue(PlacementConstraintAdjustmentProperty, value); }
get => GetValue(PlacementConstraintAdjustmentProperty);
set => SetValue(PlacementConstraintAdjustmentProperty, value);
}
/// <summary>
/// Gets or sets a value which defines in what direction the context menu should open
/// when <see cref="PlacementMode"/> is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
/// <inheritdoc cref="Popup.PlacementGravity"/>
public PopupGravity PlacementGravity
{
get { return GetValue(PlacementGravityProperty); }
set { SetValue(PlacementGravityProperty, value); }
get => GetValue(PlacementGravityProperty);
set => SetValue(PlacementGravityProperty, value);
}
/// <summary>
/// Gets or sets the placement mode of the context menu in relation to the<see cref="PlacementTarget"/>.
/// </summary>
/// <inheritdoc cref="Placement"/>
[Obsolete("Use the Placement property instead.")]
public PlacementMode PlacementMode
{
get { return GetValue(PlacementModeProperty); }
set { SetValue(PlacementModeProperty, value); }
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
/// <inheritdoc cref="Popup.Placement"/>
public PlacementMode Placement
{
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
public bool WindowManagerAddShadowHint
{
get { return GetValue(WindowManagerAddShadowHintProperty); }
set { SetValue(WindowManagerAddShadowHintProperty, value); }
get => GetValue(WindowManagerAddShadowHintProperty);
set => SetValue(WindowManagerAddShadowHintProperty, value);
}
/// <summary>
/// Gets or sets the the anchor rectangle within the parent that the context menu will be placed
/// relative to when <see cref="PlacementMode"/> is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
/// <remarks>
/// The placement rect defines a rectangle relative to <see cref="PlacementTarget"/> around
/// which the popup will be opened, with <see cref="PlacementAnchor"/> determining which edge
/// of the placement target is used.
///
/// If unset, the anchor rectangle will be the bounds of the <see cref="PlacementTarget"/>.
/// </remarks>
/// <inheritdoc cref="Popup.PlacementRect"/>
public Rect? PlacementRect
{
get { return GetValue(PlacementRectProperty); }
set { SetValue(PlacementRectProperty, value); }
get => GetValue(PlacementRectProperty);
set => SetValue(PlacementRectProperty, value);
}
/// <summary>
/// Gets or sets the control that is used to determine the popup's position.
/// </summary>
/// <inheritdoc cref="Popup.PlacementTarget"/>
public Control? PlacementTarget
{
get { return GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
get => GetValue(PlacementTargetProperty);
set => SetValue(PlacementTargetProperty, value);
}
/// <summary>
@ -343,9 +330,9 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control);
}
_popup.PlacementMode = !requestedByPointer && PlacementMode == PlacementMode.Pointer
_popup.Placement = !requestedByPointer && Placement == PlacementMode.Pointer
? PlacementMode.Bottom
: PlacementMode;
: Placement;
//Position of the line below is really important.
//All styles are being applied only when control has logical parent.

2
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -389,7 +389,7 @@ namespace Avalonia.Controls
_presenter.Date = SelectedDate ?? DateTimeOffset.Now;
_popup.PlacementMode = PlacementMode.AnchorAndGravity;
_popup.Placement = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;

2
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -255,7 +255,7 @@ namespace Avalonia.Controls
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
_popup.PlacementMode = PlacementMode.AnchorAndGravity;
_popup.Placement = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;

8
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -91,11 +91,11 @@ namespace Avalonia.Controls.Documents
public override void Add(Inline inline)
{
if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text))
if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock.Text))
{
base.Add(new Run(textBlock._text));
base.Add(new Run(textBlock.Text));
textBlock._text = null;
textBlock.ClearTextInternal();
}
base.Add(inline);
@ -113,7 +113,7 @@ namespace Avalonia.Controls.Documents
{
if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent)
{
textBlock._text += text;
textBlock.Text += text;
}
else
{

4
src/Avalonia.Controls/Flyouts/MenuFlyout.cs

@ -19,7 +19,9 @@ namespace Avalonia.Controls
/// Defines the <see cref="Items"/> property
/// </summary>
public static readonly DirectProperty<MenuFlyout, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<MenuFlyout>(x => x.Items,
AvaloniaProperty.RegisterDirect<MenuFlyout, IEnumerable?>(
nameof(Items),
x => x.Items,
(x, v) => x.Items = v);
/// <summary>

16
src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs

@ -14,9 +14,9 @@ namespace Avalonia.Controls.Primitives
{
public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider
{
/// <inheritdoc cref="Popup.PlacementModeProperty"/>
/// <inheritdoc cref="Popup.PlacementProperty"/>
public static readonly StyledProperty<PlacementMode> PlacementProperty =
Popup.PlacementModeProperty.AddOwner<PopupFlyoutBase>();
Popup.PlacementProperty.AddOwner<PopupFlyoutBase>();
/// <inheritdoc cref="Popup.HorizontalOffsetProperty"/>
public static readonly StyledProperty<double> HorizontalOffsetProperty =
@ -64,15 +64,13 @@ namespace Avalonia.Controls.Primitives
protected Popup Popup => _popupLazy.Value;
/// <summary>
/// Gets or sets the desired placement.
/// </summary>
/// <inheritdoc cref="Popup.Placement"/>
public PlacementMode Placement
{
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
/// <inheritdoc cref="Popup.PlacementGravity"/>
public PopupGravity PlacementGravity
{
@ -407,7 +405,7 @@ namespace Avalonia.Controls.Primitives
{
Size sz;
// Popup.Child can't be null here, it was set in ShowAtCore.
if (Popup.Child!.DesiredSize.IsDefault)
if (Popup.Child!.DesiredSize == default)
{
// Popup may not have been shown yet. Measure content
sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness());
@ -423,11 +421,11 @@ namespace Avalonia.Controls.Primitives
Popup.PlacementGravity = PlacementGravity;
if (showAtPointer)
{
Popup.PlacementMode = PlacementMode.Pointer;
Popup.Placement = PlacementMode.Pointer;
}
else
{
Popup.PlacementMode = Placement;
Popup.Placement = Placement;
Popup.PlacementConstraintAdjustment =
PopupPositioning.PopupPositionerConstraintAdjustment.SlideX |
PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;

165
src/Avalonia.Controls/ItemCollection.cs

@ -0,0 +1,165 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Collections;
namespace Avalonia.Controls
{
/// <summary>
/// Holds the list of items that constitute the content of an <see cref="ItemsControl"/>.
/// </summary>
public class ItemCollection : ItemsSourceView, IList
{
// Suppress "Avoid zero-length array allocations": This is a sentinel value and must be unique.
#pragma warning disable CA1825
private static readonly object?[] s_uninitialized = new object?[0];
#pragma warning restore CA1825
private Mode _mode;
internal ItemCollection()
: base(s_uninitialized)
{
}
public new object? this[int index]
{
get => base[index];
set => WritableSource[index] = value;
}
public bool IsReadOnly => _mode == Mode.ItemsSource;
internal event EventHandler? SourceChanged;
/// <summary>
/// Adds an item to the <see cref="ItemsControl"/>.
/// </summary>
/// <param name="value">The item to add to the collection.</param>
/// <returns>
/// The position into which the new element was inserted, or -1 to indicate that
/// the item was not inserted into the collection.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public int Add(object? value) => WritableSource.Add(value);
/// <summary>
/// Clears the collection and releases the references on all items currently in the
/// collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void Clear() => WritableSource.Clear();
/// <summary>
/// Inserts an element into the collection at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which to insert the item.</param>
/// <param name="value">The item to insert.</param>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void Insert(int index, object? value) => WritableSource.Insert(index, value);
/// <summary>
/// Removes the item at the specified index of the collection or view.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void RemoveAt(int index) => WritableSource.RemoveAt(index);
/// <summary>
/// Removes the specified item reference from the collection or view.
/// </summary>
/// <param name="value">The object to remove.</param>
/// <returns>True if the item was removed; otherwise false.</returns>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public bool Remove(object? value)
{
var c = Count;
WritableSource.Remove(value);
return Count < c;
}
int IList.Add(object? value) => Add(value);
void IList.Clear() => Clear();
void IList.Insert(int index, object? value) => Insert(index, value);
void IList.RemoveAt(int index) => RemoveAt(index);
private IList WritableSource
{
get
{
if (IsReadOnly)
ThrowIsItemsSource();
if (Source == s_uninitialized)
SetSource(CreateDefaultCollection());
return Source;
}
}
internal IList? GetItemsPropertyValue()
{
if (_mode == Mode.ObsoleteItemsSetter)
return Source == s_uninitialized ? null : Source;
return this;
}
internal void SetItems(IList? items)
{
_mode = Mode.ObsoleteItemsSetter;
SetSource(items ?? s_uninitialized);
}
internal void SetItemsSource(IEnumerable? value)
{
if (_mode != Mode.ItemsSource && Count > 0)
throw new InvalidOperationException(
"Items collection must be empty before using ItemsSource.");
_mode = value is not null ? Mode.ItemsSource : Mode.Items;
SetSource(value ?? CreateDefaultCollection());
}
private new void SetSource(IEnumerable source)
{
var oldSource = Source;
base.SetSource(source);
if (oldSource.Count > 0)
RaiseCollectionChanged(new(NotifyCollectionChangedAction.Remove, oldSource, 0));
if (Source.Count > 0)
RaiseCollectionChanged(new(NotifyCollectionChangedAction.Add, Source, 0));
SourceChanged?.Invoke(this, EventArgs.Empty);
}
private static AvaloniaList<object?> CreateDefaultCollection()
{
return new() { ResetBehavior = ResetBehavior.Remove };
}
[DoesNotReturn]
private static void ThrowIsItemsSource()
{
throw new InvalidOperationException(
"Operation is not valid while ItemsSource is in use." +
"Access and modify elements with ItemsControl.ItemsSource instead.");
}
private enum Mode
{
Items,
ItemsSource,
ObsoleteItemsSetter,
}
}
}

243
src/Avalonia.Controls/ItemsControl.cs

@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@ -34,8 +35,13 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, IEnumerable?> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable?>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
public static readonly DirectProperty<ItemsControl, IList?> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IList?>(
nameof(Items),
o => o.Items,
#pragma warning disable CS0618 // Type or member is obsolete
(o, v) => o.Items = v);
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Defines the <see cref="ItemContainerTheme"/> property.
@ -56,23 +62,23 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
/// <summary>
/// Defines the <see cref="ItemTemplate"/> property.
/// Defines the <see cref="ItemsSource"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
AvaloniaProperty.Register<ItemsControl, IEnumerable?>(nameof(ItemsSource));
/// <summary>
/// Defines the <see cref="ItemsView"/> property.
/// Defines the <see cref="ItemTemplate"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, ItemsSourceView> ItemsViewProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, ItemsSourceView>(nameof(ItemsView), o => o.ItemsView);
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
/// <summary>
/// Defines the <see cref="DisplayMemberBinding" /> property
/// </summary>
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
/// <summary>
/// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
/// </summary>
@ -89,15 +95,15 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding
{
get => GetValue(DisplayMemberBindingProperty);
set => SetValue(DisplayMemberBindingProperty, value);
}
private IEnumerable? _items = new AvaloniaList<object>();
private ItemsSourceView _itemsView;
private readonly ItemCollection _items = new();
private int _itemCount;
private ItemContainerGenerator? _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
@ -110,9 +116,8 @@ namespace Avalonia.Controls
/// </summary>
public ItemsControl()
{
_itemsView = ItemsSourceView.GetOrCreate(_items);
_itemsView.PostCollectionChanged += ItemsCollectionChanged;
UpdatePseudoClasses(0);
UpdatePseudoClasses();
_items.CollectionChanged += OnItemsViewCollectionChanged;
}
/// <summary>
@ -128,11 +133,45 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the items to display.
/// </summary>
/// <remarks>
/// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
/// and an <see cref="ItemsSource"/> property. The properties have the following differences:
///
/// <list type="bullet">
/// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
/// meaning that it cannot be styled </item>
/// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
/// is marked as the content property and will be used for items added via inline XAML.</item>
/// </list>
///
/// In Avalonia 11 the two properties can be used almost interchangeably but this will change
/// in a later version. In order to be ready for this change, follow the following guidance:
///
/// <list type="bullet">
/// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
/// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
/// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
/// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
/// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
/// binding a collection of models which will be transformed by a data template.</item>
/// </list>
/// </remarks>
[Content]
public IEnumerable? Items
public IList? Items
{
get => _items;
set => SetAndRaise(ItemsProperty, ref _items, value);
get => _items.GetItemsPropertyValue();
[Obsolete("Use ItemsSource to set or bind items.")]
set
{
var oldItems = _items.GetItemsPropertyValue();
if (value != oldItems)
{
_items.SetItems(value);
RaisePropertyChanged(ItemsProperty, oldItems, value);
}
}
}
/// <summary>
@ -140,17 +179,24 @@ namespace Avalonia.Controls
/// </summary>
public ControlTheme? ItemContainerTheme
{
get => GetValue(ItemContainerThemeProperty);
get => GetValue(ItemContainerThemeProperty);
set => SetValue(ItemContainerThemeProperty, value);
}
/// <summary>
/// Gets the number of items in <see cref="Items"/>.
/// Gets the number of items being displayed by the <see cref="ItemsControl"/>.
/// </summary>
public int ItemCount
{
get => _itemCount;
private set => SetAndRaise(ItemCountProperty, ref _itemCount, value);
private set
{
if (SetAndRaise(ItemCountProperty, ref _itemCount, value))
{
UpdatePseudoClasses();
_childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
}
}
}
/// <summary>
@ -162,13 +208,46 @@ namespace Avalonia.Controls
set => SetValue(ItemsPanelProperty, value);
}
/// <summary>
/// Gets or sets a collection used to generate the content of the <see cref="ItemsControl"/>.
/// </summary>
/// <remarks>
/// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
/// and an <see cref="ItemsSource"/> property. The properties have the following differences:
///
/// <list type="bullet">
/// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
/// meaning that it cannot be styled </item>
/// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
/// is marked as the content property and will be used for items added via inline XAML.</item>
/// </list>
///
/// In Avalonia 11 the two properties can be used almost interchangeably but this will change
/// in a later version. In order to be ready for this change, follow the following guidance:
///
/// <list type="bullet">
/// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
/// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
/// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
/// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
/// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
/// binding a collection of models which will be transformed by a data template.</item>
/// </list>
/// </remarks>
public IEnumerable? ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
/// <summary>
/// Gets or sets the data template used to display the items in the control.
/// </summary>
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
@ -183,31 +262,9 @@ namespace Avalonia.Controls
public Panel? ItemsPanelRoot => Presenter?.Panel;
/// <summary>
/// Gets a standardized view over <see cref="Items"/>.
/// Gets a read-only view of the items in the <see cref="ItemsControl"/>.
/// </summary>
/// <remarks>
/// The <see cref="Items"/> property may be an enumerable which does not implement
/// <see cref="IList"/> or may be null. This view can be used to provide a standardized
/// view of the current items regardless of the type of the concrete collection, and
/// without having to deal with null values.
/// </remarks>
public ItemsSourceView ItemsView
{
get => _itemsView;
private set
{
if (ReferenceEquals(_itemsView, value))
return;
var oldValue = _itemsView;
RemoveControlItemsFromLogicalChildren(_itemsView);
_itemsView.PostCollectionChanged -= ItemsCollectionChanged;
_itemsView = value;
_itemsView.PostCollectionChanged += ItemsCollectionChanged;
AddControlItemsToLogicalChildren(_itemsView);
RaisePropertyChanged(ItemsViewProperty, oldValue, _itemsView);
}
}
public ItemsSourceView ItemsView => _items;
private protected bool WrapFocus { get; set; }
@ -217,6 +274,34 @@ namespace Avalonia.Controls
remove => _childIndexChanged -= value;
}
/// <summary>
/// Occurs each time a container is prepared for use.
/// </summary>
/// <remarks>
/// The prepared element might be newly created or an existing container that is being re-
/// used.
/// </remarks>
public event EventHandler<ContainerPreparedEventArgs>? ContainerPrepared;
/// <summary>
/// Occurs for each realized container when the index for the item it represents has changed.
/// </summary>
/// <remarks>
/// This event is raised for each realized container where the index for the item it
/// represents has changed. For example, when another item is added or removed in the data
/// source, the index for items that come after in the ordering will be impacted.
/// </remarks>
public event EventHandler<ContainerIndexChangedEventArgs>? ContainerIndexChanged;
/// <summary>
/// Occurs each time a container is cleared.
/// </summary>
/// <remarks>
/// This event is raised immediately each time an container is cleared, such as when it
/// falls outside the range of realized items or the corresponding item is removed.
/// </remarks>
public event EventHandler<ContainerClearingEventArgs>? ContainerClearing;
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{
@ -262,7 +347,7 @@ namespace Avalonia.Controls
/// </summary>
public bool AreHorizontalSnapPointsRegular
{
get => GetValue(AreHorizontalSnapPointsRegularProperty);
get => GetValue(AreHorizontalSnapPointsRegularProperty);
set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
@ -271,7 +356,7 @@ namespace Avalonia.Controls
/// </summary>
public bool AreVerticalSnapPointsRegular
{
get => GetValue(AreVerticalSnapPointsRegularProperty);
get => GetValue(AreVerticalSnapPointsRegularProperty);
set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
@ -295,7 +380,7 @@ namespace Avalonia.Controls
/// </returns>
public Control? ContainerFromItem(object item)
{
var index = ItemsView.IndexOf(item);
var index = _items.IndexOf(item);
return index >= 0 ? ContainerFromIndex(index) : null;
}
@ -319,7 +404,7 @@ namespace Avalonia.Controls
public object? ItemFromContainer(Control container)
{
var index = IndexFromContainer(container);
return index >= 0 && index < ItemsView.Count ? ItemsView[index] : null;
return index >= 0 && index < _items.Count ? _items[index] : null;
}
/// <summary>
@ -478,19 +563,13 @@ namespace Avalonia.Controls
{
base.OnPropertyChanged(change);
if (change.Property == ItemsProperty)
{
ItemsView = ItemsSourceView.GetOrCreate(change.GetNewValue<IEnumerable?>());
ItemCount = ItemsView.Count;
}
else if (change.Property == ItemCountProperty)
if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
{
UpdatePseudoClasses(change.GetNewValue<int>());
_childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
RefreshContainers();
}
else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
else if (change.Property == ItemsSourceProperty)
{
RefreshContainers();
_items.SetItemsSource(change.GetNewValue<IEnumerable?>());
}
else if (change.Property == ItemTemplateProperty)
{
@ -517,24 +596,27 @@ namespace Avalonia.Controls
/// <summary>
/// Called when the <see cref="INotifyCollectionChanged.CollectionChanged"/> event is
/// raised on <see cref="Items"/>.
/// raised on <see cref="ItemsView"/>.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
protected virtual void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
private protected virtual void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
ItemCount = _itemsView.Count;
switch (e.Action)
if (!_items.IsReadOnly)
{
case NotifyCollectionChangedAction.Add:
AddControlItemsToLogicalChildren(e.NewItems);
break;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddControlItemsToLogicalChildren(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
}
ItemCount = ItemsView.Count;
}
/// <summary>
@ -578,7 +660,7 @@ namespace Avalonia.Controls
{
var itemContainerTheme = ItemContainerTheme;
if (itemContainerTheme is not null &&
if (itemContainerTheme is not null &&
!container.IsSet(ThemeProperty) &&
((IStyleable)container).StyleKey == itemContainerTheme.TargetType)
{
@ -595,24 +677,23 @@ namespace Avalonia.Controls
{
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, index));
_scrollViewer?.RegisterAnchorCandidate(container);
ContainerPrepared?.Invoke(this, new(container, index));
}
internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex)
{
ContainerIndexChangedOverride(container, oldIndex, newIndex);
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, newIndex));
ContainerIndexChanged?.Invoke(this, new(container, oldIndex, newIndex));
}
internal void ClearItemContainer(Control container)
{
_scrollViewer?.UnregisterAnchorCandidate(container);
ClearContainerForItemOverride(container);
ContainerClearing?.Invoke(this, new(container));
}
/// <summary>
/// Given a collection of items, adds those that are controls to the logical children.
/// </summary>
/// <param name="items">The items.</param>
private void AddControlItemsToLogicalChildren(IEnumerable? items)
{
if (items is null)
@ -633,10 +714,6 @@ namespace Avalonia.Controls
LogicalChildren.AddRange(toAdd);
}
/// <summary>
/// Given a collection of items, removes those that are controls to from logical children.
/// </summary>
/// <param name="items">The items.</param>
private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
{
if (items is null)
@ -674,10 +751,10 @@ namespace Avalonia.Controls
return _displayMemberItemTemplate;
}
private void UpdatePseudoClasses(int itemCount)
private void UpdatePseudoClasses()
{
PseudoClasses.Set(":empty", itemCount == 0);
PseudoClasses.Set(":singleitem", itemCount == 1);
PseudoClasses.Set(":empty", ItemCount == 0);
PseudoClasses.Set(":singleitem", ItemCount == 1);
}
protected static IInputElement? GetNextControl(

155
src/Avalonia.Controls/ItemsSourceView.cs

@ -7,6 +7,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Controls.Utils;
@ -17,15 +18,16 @@ namespace Avalonia.Controls
/// and an items control.
/// </summary>
public class ItemsSourceView : IReadOnlyList<object?>,
IList,
INotifyCollectionChanged,
ICollectionChangedListener
{
/// <summary>
/// Gets an empty <see cref="ItemsSourceView"/>
/// Gets an empty <see cref="ItemsSourceView"/>
/// </summary>
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object?>());
private readonly IList _inner;
private IList _source;
private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler? _preCollectionChanged;
private NotifyCollectionChangedEventHandler? _postCollectionChanged;
@ -35,30 +37,17 @@ namespace Avalonia.Controls
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
/// </summary>
/// <param name="source">The data source.</param>
private protected ItemsSourceView(IEnumerable source)
{
_inner = source switch
{
ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged => throw new ArgumentException(
"Collection implements INotifyCollectionChanged but not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
null => throw new ArgumentNullException(nameof(source)),
_ => new List<object>(source.Cast<object>())
};
}
private protected ItemsSourceView(IEnumerable source) => SetSource(source);
/// <summary>
/// Gets the number of items in the collection.
/// </summary>
public int Count => Inner.Count;
public int Count => Source.Count;
/// <summary>
/// Gets the inner collection.
/// Gets the source collection.
/// </summary>
public IList Inner => _inner;
public IList Source => _source;
/// <summary>
/// Retrieves the item at the specified index.
@ -67,12 +56,20 @@ namespace Avalonia.Controls
/// <returns>The item.</returns>
public object? this[int index] => GetAt(index);
bool IList.IsFixedSize => false;
bool IList.IsReadOnly => true;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
object? IList.this[int index]
{
get => GetAt(index);
set => ThrowReadOnly();
}
/// <summary>
/// Gets a value that indicates whether the items source can provide a unique key for each item.
/// </summary>
/// <remarks>
/// Not implemented in Avalonia, preserved here for ItemsRepeater's usage.
/// </remarks>
/// </summary>
internal bool HasKeyIndexMapping => false;
/// <summary>
@ -131,39 +128,14 @@ namespace Avalonia.Controls
}
}
private void AddListenerIfNecessary()
{
if (!_listening)
{
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.AddListener(incc, this);
_listening = true;
}
}
private void RemoveListenerIfNecessary()
{
if (_listening && _collectionChanged is null && _postCollectionChanged is null)
{
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
_listening = false;
}
}
/// <summary>
/// Retrieves the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public object? GetAt(int index) => Inner[index];
/// <summary>
/// Determines the index of a specific item in the collection.
/// </summary>
/// <param name="item">The object to locate in the collection.</param>
/// <returns>The index of value if found in the list; otherwise, -1.</returns>
public int IndexOf(object? item) => Inner.IndexOf(item);
public object? GetAt(int index) => Source[index];
public bool Contains(object? item) => Source.Contains(item);
public int IndexOf(object? item) => Source.IndexOf(item);
/// <summary>
/// Gets or creates an <see cref="ItemsSourceView"/> for the specified enumerable.
@ -201,7 +173,8 @@ namespace Avalonia.Controls
{
return items switch
{
ItemsSourceView<T> isv => isv,
ItemsSourceView<T> isvt => isvt,
ItemsSourceView isv => new ItemsSourceView<T>(isv.Source),
null => ItemsSourceView<T>.Empty,
_ => new ItemsSourceView<T>(items)
};
@ -236,7 +209,7 @@ namespace Avalonia.Controls
yield return o;
}
var inner = Inner;
var inner = Source;
return inner switch
{
@ -245,7 +218,7 @@ namespace Avalonia.Controls
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Source.GetEnumerator();
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
@ -262,15 +235,69 @@ namespace Avalonia.Controls
_postCollectionChanged?.Invoke(this, e);
}
int IList.Add(object? value) => ThrowReadOnly();
void IList.Clear() => ThrowReadOnly();
void IList.Insert(int index, object? value) => ThrowReadOnly();
void IList.Remove(object? value) => ThrowReadOnly();
void IList.RemoveAt(int index) => ThrowReadOnly();
void ICollection.CopyTo(Array array, int index) => Source.CopyTo(array, index);
/// <summary>
/// Retrieves the index of the item that has the specified unique identifier (key).
/// Not implemented in Avalonia, preserved here for ItemsRepeater's usage.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The key</returns>
/// <remarks>
/// TODO: Not yet implemented in Avalonia.
/// </remarks>
internal string KeyFromIndex(int index) => throw new NotImplementedException();
private protected void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
_preCollectionChanged?.Invoke(this, e);
_collectionChanged?.Invoke(this, e);
_postCollectionChanged?.Invoke(this, e);
}
[MemberNotNull(nameof(_source))]
private protected void SetSource(IEnumerable source)
{
if (_listening && _source is INotifyCollectionChanged inccOld)
CollectionChangedEventManager.Instance.RemoveListener(inccOld, this);
_source = source switch
{
ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged => throw new ArgumentException(
"Collection implements INotifyCollectionChanged but not IList.",
nameof(source)),
IEnumerable<object> iObj => new List<object>(iObj),
null => throw new ArgumentNullException(nameof(source)),
_ => new List<object>(source.Cast<object>())
};
if (_listening && _source is INotifyCollectionChanged inccNew)
CollectionChangedEventManager.Instance.AddListener(inccNew, this);
}
private void AddListenerIfNecessary()
{
if (!_listening)
{
if (_source is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.AddListener(incc, this);
_listening = true;
}
}
private void RemoveListenerIfNecessary()
{
if (_listening && _collectionChanged is null && _postCollectionChanged is null)
{
if (_source is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
_listening = false;
}
}
[DoesNotReturn]
private static int ThrowReadOnly() => throw new NotSupportedException("Collection is read-only.");
}
public sealed class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
@ -306,7 +333,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public new T GetAt(int index) => (T)Inner[index]!;
public new T GetAt(int index) => (T)Source[index]!;
public new IEnumerator<T> GetEnumerator()
{
@ -316,7 +343,7 @@ namespace Avalonia.Controls
yield return (T)o;
}
var inner = Inner;
var inner = Source;
return inner switch
{
@ -325,6 +352,6 @@ namespace Avalonia.Controls
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Source.GetEnumerator();
}
}

4
src/Avalonia.Controls/LayoutTransformControl.cs

@ -91,7 +91,7 @@ namespace Avalonia.Controls
arrangedsize = TransformRoot.Bounds.Size;
// This is the first opportunity under Silverlight to find out the Child's true DesiredSize
if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && _childActualSize.IsDefault)
if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && _childActualSize == default)
{
//// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used
//// Make a note of the actual DesiredSize
@ -122,7 +122,7 @@ namespace Avalonia.Controls
}
Size measureSize;
if (_childActualSize.IsDefault)
if (_childActualSize == default)
{
// Determine the largest size after the transformation
measureSize = ComputeLargestTransformedSize(availableSize);

135
src/Avalonia.Controls/MaskedTextBox.cs

@ -15,9 +15,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> AsciiOnlyProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(AsciiOnly));
public static readonly DirectProperty<MaskedTextBox, CultureInfo?> CultureProperty =
AvaloniaProperty.RegisterDirect<MaskedTextBox, CultureInfo?>(nameof(Culture), o => o.Culture,
(o, v) => o.Culture = v, CultureInfo.CurrentCulture);
public static readonly StyledProperty<CultureInfo?> CultureProperty =
AvaloniaProperty.Register<MaskedTextBox, CultureInfo?>(nameof(Culture), CultureInfo.CurrentCulture);
public static readonly StyledProperty<bool> HidePromptOnLeaveProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(HidePromptOnLeave));
@ -32,26 +31,49 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<MaskedTextBox, string?>(nameof(Mask), string.Empty);
public static readonly StyledProperty<char> PromptCharProperty =
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_');
AvaloniaProperty.Register<MaskedTextBox, char>(nameof(PromptChar), '_', coerce: CoercePromptChar);
public static readonly DirectProperty<MaskedTextBox, bool> ResetOnPromptProperty =
AvaloniaProperty.RegisterDirect<MaskedTextBox, bool>(nameof(ResetOnPrompt), o => o.ResetOnPrompt, (o, v) => o.ResetOnPrompt = v);
public static readonly StyledProperty<bool> ResetOnPromptProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(ResetOnPrompt), true);
public static readonly DirectProperty<MaskedTextBox, bool> ResetOnSpaceProperty =
AvaloniaProperty.RegisterDirect<MaskedTextBox, bool>(nameof(ResetOnSpace), o => o.ResetOnSpace, (o, v) => o.ResetOnSpace = v);
public static readonly StyledProperty<bool> ResetOnSpaceProperty =
AvaloniaProperty.Register<MaskedTextBox, bool>(nameof(ResetOnSpace), true);
private CultureInfo? _culture;
private bool _ignoreTextChanges;
private bool _resetOnPrompt = true;
static MaskedTextBox()
{
PasswordCharProperty.OverrideMetadata<MaskedTextBox>(new('\0', coerce: CoercePasswordChar));
}
private bool _ignoreTextChanges;
private static char CoercePasswordChar(AvaloniaObject sender, char baseValue)
{
if (!MaskedTextProvider.IsValidPasswordChar(baseValue))
{
throw new ArgumentException($"'{baseValue}' is not a valid value for PasswordChar.");
}
var textbox = (MaskedTextBox)sender;
if (textbox.MaskProvider is { } maskProvider && baseValue == maskProvider.PromptChar)
{
// Prompt and password chars must be different.
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
}
private bool _resetOnSpace = true;
return baseValue;
}
static MaskedTextBox()
private static char CoercePromptChar(AvaloniaObject sender, char baseValue)
{
PasswordCharProperty
.OverrideDefaultValue<MaskedTextBox>('\0');
if (!MaskedTextProvider.IsValidInputChar(baseValue))
{
throw new ArgumentException($"'{baseValue}' is not a valid value for PromptChar.");
}
if (baseValue == sender.GetValue(PasswordCharProperty))
{
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
}
return baseValue;
}
public MaskedTextBox() { }
@ -59,6 +81,9 @@ namespace Avalonia.Controls
/// <summary>
/// Constructs the MaskedTextBox with the specified MaskedTextProvider object.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty",
"AVP1012:An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values",
Justification = "These values are being explicitly provided by a constructor parameter.")]
public MaskedTextBox(MaskedTextProvider maskedTextProvider)
{
if (maskedTextProvider == null)
@ -87,8 +112,8 @@ namespace Avalonia.Controls
/// </summary>
public CultureInfo? Culture
{
get => _culture;
set => SetAndRaise(CultureProperty, ref _culture, value);
get => GetValue(CultureProperty);
set => SetValue(CultureProperty, value);
}
/// <summary>
@ -131,15 +156,6 @@ namespace Avalonia.Controls
/// </summary>
public MaskedTextProvider? MaskProvider { get; private set; }
/// <summary>
/// Gets or sets the character to be displayed in substitute for user input.
/// </summary>
public new char PasswordChar
{
get => GetValue(PasswordCharProperty);
set => SetValue(PasswordCharProperty, value);
}
/// <summary>
/// Gets or sets the character used to represent the absence of user input in MaskedTextBox.
/// </summary>
@ -154,16 +170,8 @@ namespace Avalonia.Controls
/// </summary>
public bool ResetOnPrompt
{
get => _resetOnPrompt;
set
{
SetAndRaise(ResetOnPromptProperty, ref _resetOnPrompt, value);
if (MaskProvider != null)
{
MaskProvider.ResetOnPrompt = value;
}
}
get => GetValue(ResetOnPromptProperty);
set => SetValue(ResetOnPromptProperty, value);
}
/// <summary>
@ -171,16 +179,8 @@ namespace Avalonia.Controls
/// </summary>
public bool ResetOnSpace
{
get => _resetOnSpace;
set
{
SetAndRaise(ResetOnSpaceProperty, ref _resetOnSpace, value);
if (MaskProvider != null)
{
MaskProvider.ResetOnSpace = value;
}
}
get => GetValue(ResetOnSpaceProperty);
set => SetValue(ResetOnSpaceProperty, value);
}
Type IStyleable.StyleKey => typeof(TextBox);
@ -190,7 +190,7 @@ namespace Avalonia.Controls
{
if (HidePromptOnLeave == true && MaskProvider != null)
{
Text = MaskProvider.ToDisplayString();
SetCurrentValue(TextProperty, MaskProvider.ToDisplayString());
}
base.OnGotFocus(e);
}
@ -225,11 +225,11 @@ namespace Avalonia.Controls
var index = GetNextCharacterPosition(CaretIndex);
if (MaskProvider.InsertAt(item, index))
{
CaretIndex = ++index;
SetCurrentValue(CaretIndexProperty, ++index);
}
}
Text = MaskProvider.ToDisplayString();
SetCurrentValue(TextProperty, MaskProvider.ToDisplayString());
e.Handled = true;
return;
}
@ -279,7 +279,7 @@ namespace Avalonia.Controls
{
if (HidePromptOnLeave && MaskProvider != null)
{
Text = MaskProvider.ToString(!HidePromptOnLeave, true);
SetCurrentValue(TextProperty, MaskProvider.ToString(!HidePromptOnLeave, true));
}
base.OnLostFocus(e);
}
@ -326,15 +326,6 @@ namespace Avalonia.Controls
}
else if (change.Property == PasswordCharProperty)
{
if (!MaskedTextProvider.IsValidPasswordChar(PasswordChar))
{
throw new ArgumentException("Specified character value is not allowed for this property.", nameof(PasswordChar));
}
if (MaskProvider != null && PasswordChar == MaskProvider.PromptChar)
{
// Prompt and password chars must be different.
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
}
if (MaskProvider != null && MaskProvider.PasswordChar != PasswordChar)
{
UpdateMaskProvider();
@ -342,17 +333,23 @@ namespace Avalonia.Controls
}
else if (change.Property == PromptCharProperty)
{
if (!MaskedTextProvider.IsValidInputChar(PromptChar))
if (MaskProvider != null && MaskProvider.PromptChar != PromptChar)
{
throw new ArgumentException("Specified character value is not allowed for this property.");
UpdateMaskProvider();
}
if (PromptChar == PasswordChar)
}
else if (change.Property == ResetOnPromptProperty)
{
if (MaskProvider != null && change.GetNewValue<bool>() is { } newValue)
{
throw new InvalidOperationException("PasswordChar and PromptChar values cannot be the same.");
MaskProvider.ResetOnPrompt = newValue;
}
if (MaskProvider != null && MaskProvider.PromptChar != PromptChar)
}
else if (change.Property == ResetOnSpaceProperty)
{
if (MaskProvider != null && change.GetNewValue<bool>() is { } newValue)
{
UpdateMaskProvider();
MaskProvider.ResetOnSpace = newValue;
}
}
else if (change.Property == AsciiOnlyProperty && MaskProvider != null && MaskProvider.AsciiOnly != AsciiOnly
@ -390,7 +387,7 @@ namespace Avalonia.Controls
if (CaretIndex < Text?.Length)
{
CaretIndex = GetNextCharacterPosition(CaretIndex);
SetCurrentValue(CaretIndexProperty, GetNextCharacterPosition(CaretIndex));
if (MaskProvider.InsertAt(e.Text!, CaretIndex))
{
@ -399,7 +396,7 @@ namespace Avalonia.Controls
var nextPos = GetNextCharacterPosition(CaretIndex);
if (nextPos != 0 && CaretIndex != Text.Length)
{
CaretIndex = nextPos;
SetCurrentValue(CaretIndexProperty, nextPos);
}
}
@ -434,8 +431,8 @@ namespace Avalonia.Controls
{
if (provider != null)
{
Text = provider.ToDisplayString();
CaretIndex = position;
SetCurrentValue(TextProperty, provider.ToDisplayString());
SetCurrentValue(CaretIndexProperty, position);
}
}

2
src/Avalonia.Controls/NativeControlHost.cs

@ -141,7 +141,7 @@ namespace Avalonia.Controls
if (IsEffectivelyVisible && bounds.HasValue)
{
if (bounds.Value.IsDefault)
if (bounds.Value.Width == 0 && bounds.Value.Height == 0)
return false;
_attachment?.ShowInBounds(bounds.Value);
}

18
src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs

@ -22,7 +22,6 @@ namespace Avalonia.Controls.Presenters
Debug.Assert(presenter.Panel is not null or VirtualizingPanel);
_presenter = presenter;
_presenter.ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
_presenter.ItemsControl.ItemsView.PostCollectionChanged += OnItemsChanged;
OnItemsChanged(null, CollectionUtils.ResetEventArgs);
@ -32,9 +31,7 @@ namespace Avalonia.Controls.Presenters
{
if (_presenter.ItemsControl is { } itemsControl)
{
itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
itemsControl.ItemsView.PostCollectionChanged -= OnItemsChanged;
ClearItemsControlLogicalChildren();
}
@ -43,18 +40,6 @@ namespace Avalonia.Controls.Presenters
internal void Refresh() => OnItemsChanged(null, CollectionUtils.ResetEventArgs);
private void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ItemsControl.ItemsProperty)
{
if (e.OldValue is INotifyCollectionChanged inccOld)
inccOld.CollectionChanged -= OnItemsChanged;
OnItemsChanged(null, CollectionUtils.ResetEventArgs);
if (e.NewValue is INotifyCollectionChanged inccNew)
inccNew.CollectionChanged += OnItemsChanged;
}
}
private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (_presenter.Panel is null || _presenter.ItemsControl is null)
@ -84,8 +69,7 @@ namespace Avalonia.Controls.Presenters
var c = children[index + i];
if (!c.IsSet(ItemIsOwnContainerProperty))
itemsControl.RemoveLogicalChild(children[i + index]);
else
generator.ClearItemContainer(c);
generator.ClearItemContainer(c);
}
children.RemoveRange(index, count);

109
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -9,17 +9,13 @@ using Avalonia.VisualTree;
using Avalonia.Layout;
using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
using Avalonia.Input.TextInput;
using Avalonia.Data;
namespace Avalonia.Controls.Presenters
{
public class TextPresenter : Control
{
public static readonly DirectProperty<TextPresenter, int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>(
o => o.CaretIndex,
(o, v) => o.CaretIndex = v);
public static readonly StyledProperty<int> CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextPresenter, bool>(nameof(RevealPassword));
@ -36,33 +32,23 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<IBrush?> CaretBrushProperty =
AvaloniaProperty.Register<TextPresenter, IBrush?>(nameof(CaretBrush));
public static readonly DirectProperty<TextPresenter, int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>(
o => o.SelectionStart,
(o, v) => o.SelectionStart = v);
public static readonly StyledProperty<int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly DirectProperty<TextPresenter, int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<TextPresenter>(
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<TextPresenter>(new(coerce: TextBox.CoerceCaretIndex));
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly DirectProperty<TextPresenter, string?> TextProperty =
AvaloniaProperty.RegisterDirect<TextPresenter, string?>(
nameof(Text),
o => o.Text,
(o, v) => o.Text = v, defaultBindingMode: BindingMode.OneWay);
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<TextPresenter>(new(string.Empty));
/// <summary>
/// Defines the <see cref="PreeditText"/> property.
/// </summary>
public static readonly DirectProperty<TextPresenter, string?> PreeditTextProperty =
AvaloniaProperty.RegisterDirect<TextPresenter, string?>(
nameof(PreeditText),
o => o.PreeditText,
(o, v) => o.PreeditText = v);
public static readonly StyledProperty<string?> PreeditTextProperty =
AvaloniaProperty.Register<TextPresenter, string?>(nameof(PreeditText));
/// <summary>
/// Defines the <see cref="CompositionRegion"/> property.
@ -104,18 +90,13 @@ namespace Avalonia.Controls.Presenters
Border.BackgroundProperty.AddOwner<TextPresenter>();
private readonly DispatcherTimer _caretTimer;
private int _caretIndex;
private int _selectionStart;
private int _selectionEnd;
private bool _caretBlink;
internal string? _text;
private TextLayout? _textLayout;
private Size _constraint;
private CharacterHit _lastCharacterHit;
private Rect _caretBounds;
private Point _navigationPosition;
private string? _preeditText;
private TextRange? _compositionRegion;
static TextPresenter()
@ -125,7 +106,6 @@ namespace Avalonia.Controls.Presenters
public TextPresenter()
{
_text = string.Empty;
_caretTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
_caretTimer.Tick += CaretTimerTick;
}
@ -147,14 +127,14 @@ namespace Avalonia.Controls.Presenters
[Content]
public string? Text
{
get => _text;
set => SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public string? PreeditText
{
get => _preeditText;
set => SetAndRaise(PreeditTextProperty, ref _preeditText, value);
get => GetValue(PreeditTextProperty);
set => SetValue(PreeditTextProperty, value);
}
public TextRange? CompositionRegion
@ -275,17 +255,8 @@ namespace Avalonia.Controls.Presenters
public int CaretIndex
{
get
{
return _caretIndex;
}
set
{
if (value != _caretIndex)
{
MoveCaretToTextPosition(value);
}
}
get => GetValue(CaretIndexProperty);
set => SetValue(CaretIndexProperty, value);
}
public char PasswordChar
@ -320,30 +291,14 @@ namespace Avalonia.Controls.Presenters
public int SelectionStart
{
get
{
return _selectionStart;
}
set
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
}
get => GetValue(SelectionStartProperty);
set => SetValue(SelectionStartProperty, value);
}
public int SelectionEnd
{
get
{
return _selectionEnd;
}
set
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
}
get => GetValue(SelectionEndProperty);
set => SetValue(SelectionEndProperty, value);
}
protected override bool BypassFlowDirectionPolicies => true;
@ -535,12 +490,12 @@ namespace Avalonia.Controls.Presenters
{
TextLayout result;
var text = _text;
var text = Text;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight);
var selectionStart = CoerceCaretIndex(SelectionStart);
var selectionEnd = CoerceCaretIndex(SelectionEnd);
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
@ -561,9 +516,9 @@ namespace Avalonia.Controls.Presenters
};
}
else if (!string.IsNullOrEmpty(_preeditText))
else if (!string.IsNullOrEmpty(PreeditText))
{
var preeditHighlight = new ValueSpan<TextRunProperties>(_caretIndex, _preeditText.Length,
var preeditHighlight = new ValueSpan<TextRunProperties>(CaretIndex, PreeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
@ -643,13 +598,6 @@ namespace Avalonia.Controls.Presenters
return finalSize;
}
private int CoerceCaretIndex(int value)
{
var text = Text;
var length = text?.Length ?? 0;
return Math.Max(0, Math.Min(length, value));
}
private void CaretTimerTick(object? sender, EventArgs e)
{
_caretBlink = !_caretBlink;
@ -865,7 +813,7 @@ namespace Avalonia.Controls.Presenters
if (notify)
{
SetAndRaise(CaretIndexProperty, ref _caretIndex, caretIndex);
SetCurrentValue(CaretIndexProperty, caretIndex);
}
}
@ -887,6 +835,11 @@ namespace Avalonia.Controls.Presenters
{
base.OnPropertyChanged(change);
if (change.Property == CaretIndexProperty)
{
MoveCaretToTextPosition(change.GetNewValue<int>());
}
switch (change.Property.Name)
{
case nameof(PreeditText):

80
src/Avalonia.Controls/Primitives/Popup.cs

@ -65,11 +65,17 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
AvaloniaProperty.Register<Popup, PopupGravity>(nameof(PlacementGravity));
/// <summary>
/// Defines the <see cref="Placement"/> property.
/// </summary>
public static readonly StyledProperty<PlacementMode> PlacementProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(Placement), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="PlacementMode"/> property.
/// </summary>
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
[Obsolete("Use the Placement property instead.")]
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty;
/// <summary>
/// Defines the <see cref="PlacementRect"/> property.
@ -146,8 +152,8 @@ namespace Avalonia.Controls.Primitives
public bool WindowManagerAddShadowHint
{
get { return GetValue(WindowManagerAddShadowHintProperty); }
set { SetValue(WindowManagerAddShadowHintProperty, value); }
get => GetValue(WindowManagerAddShadowHintProperty);
set => SetValue(WindowManagerAddShadowHintProperty, value);
}
/// <summary>
@ -156,8 +162,8 @@ namespace Avalonia.Controls.Primitives
[Content]
public Control? Child
{
get { return GetValue(ChildProperty); }
set { SetValue(ChildProperty, value); }
get => GetValue(ChildProperty);
set => SetValue(ChildProperty, value);
}
/// <summary>
@ -205,13 +211,13 @@ namespace Avalonia.Controls.Primitives
}
/// <summary>
/// Gets or sets the anchor point on the <see cref="PlacementRect"/> when <see cref="PlacementMode"/>
/// Gets or sets the anchor point on the <see cref="PlacementRect"/> when <see cref="Placement"/>
/// is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
public PopupAnchor PlacementAnchor
{
get { return GetValue(PlacementAnchorProperty); }
set { SetValue(PlacementAnchorProperty, value); }
get => GetValue(PlacementAnchorProperty);
set => SetValue(PlacementAnchorProperty, value);
}
/// <summary>
@ -220,32 +226,40 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public PopupPositionerConstraintAdjustment PlacementConstraintAdjustment
{
get { return GetValue(PlacementConstraintAdjustmentProperty); }
set { SetValue(PlacementConstraintAdjustmentProperty, value); }
get => GetValue(PlacementConstraintAdjustmentProperty);
set => SetValue(PlacementConstraintAdjustmentProperty, value);
}
/// <summary>
/// Gets or sets a value which defines in what direction the popup should open
/// when <see cref="PlacementMode"/> is <see cref="PlacementMode.AnchorAndGravity"/>.
/// when <see cref="Placement"/> is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
public PopupGravity PlacementGravity
{
get { return GetValue(PlacementGravityProperty); }
set { SetValue(PlacementGravityProperty, value); }
get => GetValue(PlacementGravityProperty);
set => SetValue(PlacementGravityProperty, value);
}
/// <inheritdoc cref="Placement"/>
[Obsolete("Use the Placement property instead.")]
public PlacementMode PlacementMode
{
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
/// <summary>
/// Gets or sets the placement mode of the popup in relation to the <see cref="PlacementTarget"/>.
/// Gets or sets the desired placement of the popup in relation to the <see cref="PlacementTarget"/>.
/// </summary>
public PlacementMode PlacementMode
public PlacementMode Placement
{
get { return GetValue(PlacementModeProperty); }
set { SetValue(PlacementModeProperty, value); }
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
/// <summary>
/// Gets or sets the the anchor rectangle within the parent that the popup will be placed
/// relative to when <see cref="PlacementMode"/> is <see cref="PlacementMode.AnchorAndGravity"/>.
/// relative to when <see cref="Placement"/> is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
/// <remarks>
/// The placement rect defines a rectangle relative to <see cref="PlacementTarget"/> around
@ -256,8 +270,8 @@ namespace Avalonia.Controls.Primitives
/// </remarks>
public Rect? PlacementRect
{
get { return GetValue(PlacementRectProperty); }
set { SetValue(PlacementRectProperty, value); }
get => GetValue(PlacementRectProperty);
set => SetValue(PlacementRectProperty, value);
}
/// <summary>
@ -266,8 +280,8 @@ namespace Avalonia.Controls.Primitives
[ResolveByName]
public Control? PlacementTarget
{
get { return GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
get => GetValue(PlacementTargetProperty);
set => SetValue(PlacementTargetProperty, value);
}
/// <summary>
@ -301,8 +315,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public double HorizontalOffset
{
get { return GetValue(HorizontalOffsetProperty); }
set { SetValue(HorizontalOffsetProperty, value); }
get => GetValue(HorizontalOffsetProperty);
set => SetValue(HorizontalOffsetProperty, value);
}
/// <summary>
@ -310,8 +324,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public double VerticalOffset
{
get { return GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
get => GetValue(VerticalOffsetProperty);
set => SetValue(VerticalOffsetProperty, value);
}
/// <summary>
@ -319,8 +333,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public bool Topmost
{
get { return GetValue(TopmostProperty); }
set { SetValue(TopmostProperty, value); }
get => GetValue(TopmostProperty);
set => SetValue(TopmostProperty, value);
}
IPopupHost? IPopupHostProvider.PopupHost => Host;
@ -404,7 +418,7 @@ namespace Avalonia.Controls.Primitives
(x, handler) => x.LostFocus -= handler).DisposeWith(handlerCleanup);
// Recalculate popup position on parent moved/resized, but not if placement was on pointer
if (PlacementMode != PlacementMode.Pointer)
if (Placement != PlacementMode.Pointer)
{
SubscribeToEventHandler<IWindowImpl, Action<PixelPoint>>(window.PlatformImpl, WindowPositionChanged,
(x, handler) => x.PositionChanged += handler,
@ -534,7 +548,7 @@ namespace Avalonia.Controls.Primitives
UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);
}
else if (change.Property == PlacementTargetProperty ||
change.Property == PlacementModeProperty ||
change.Property == PlacementProperty ||
change.Property == HorizontalOffsetProperty ||
change.Property == VerticalOffsetProperty ||
change.Property == PlacementAnchorProperty ||
@ -567,7 +581,7 @@ namespace Avalonia.Controls.Primitives
{
popupHost.ConfigurePosition(
placementTarget,
PlacementMode,
Placement,
new Point(HorizontalOffset, VerticalOffset),
PlacementAnchor,
PlacementGravity,
@ -615,7 +629,7 @@ namespace Avalonia.Controls.Primitives
return;
_openState.PopupHost.ConfigurePosition(
placementTarget,
PlacementMode,
Placement,
new Point(HorizontalOffset, VerticalOffset),
PlacementAnchor,
PlacementGravity,

3
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -112,7 +112,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault();
if (targetScreen != null && targetScreen.WorkingArea.IsDefault)
if (targetScreen != null &&
(targetScreen.WorkingArea.Width == 0 && targetScreen.WorkingArea.Height == 0))
{
return targetScreen.Bounds;
}

55
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -145,6 +145,11 @@ namespace Avalonia.Controls.Primitives
private BindingHelper? _bindingHelper;
private bool _isSelectionChangeActive;
public SelectingItemsControl()
{
((ItemCollection)ItemsView).SourceChanged += OnItemsViewSourceChanged;
}
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
/// </summary>
@ -229,6 +234,7 @@ namespace Avalonia.Controls.Primitives
/// <see cref="SelectedValue"/> property
/// </summary>
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
[InheritDataTypeFromItems(nameof(Items))]
public IBinding? SelectedValueBinding
{
@ -322,7 +328,7 @@ namespace Avalonia.Controls.Primitives
}
else if (_selection != value)
{
if (value.Source != null && value.Source != Items)
if (value.Source != null && value.Source != ItemsView.Source)
{
throw new ArgumentException(
"The supplied ISelectionModel already has an assigned Source but this " +
@ -434,10 +440,9 @@ namespace Avalonia.Controls.Primitives
return null;
}
/// <inheritdoc />
protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
private protected override void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.ItemsCollectionChanged(sender!, e);
base.OnItemsViewCollectionChanged(sender!, e);
if (AlwaysSelected && SelectedIndex == -1 && ItemCount > 0)
{
@ -547,7 +552,7 @@ namespace Avalonia.Controls.Primitives
if (_selection is object)
{
_selection.Source = Items;
_selection.Source = ItemsView.Source;
}
}
@ -635,16 +640,6 @@ namespace Avalonia.Controls.Primitives
{
AutoScrollToSelectedItemIfNecessary();
}
if (change.Property == ItemsProperty && _updateState is null && _selection is object)
{
var newValue = change.GetNewValue<IEnumerable?>();
_selection.Source = newValue;
if (newValue is null)
{
_selection.Clear();
}
}
else if (change.Property == SelectionModeProperty && _selection is object)
{
var newValue = change.GetNewValue<SelectionMode>();
@ -880,6 +875,12 @@ namespace Avalonia.Controls.Primitives
return false;
}
private void OnItemsViewSourceChanged(object? sender, EventArgs e)
{
if (_selection is not null && _updateState is null)
_selection.Source = ItemsView.Source;
}
/// <summary>
/// Called when <see cref="INotifyPropertyChanged.PropertyChanged"/> is raised on
/// <see cref="Selection"/>.
@ -968,7 +969,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="e">The event args.</param>
private void OnSelectionModelLostSelection(object? sender, EventArgs e)
{
if (AlwaysSelected && Items is object)
if (AlwaysSelected && ItemsView.Count > 0)
{
SelectedIndex = 0;
}
@ -998,14 +999,14 @@ namespace Avalonia.Controls.Primitives
}
}
private object FindItemWithValue(object? value)
private object? FindItemWithValue(object? value)
{
if (ItemCount == 0 || value is null)
{
return AvaloniaProperty.UnsetValue;
}
var items = Items;
var items = ItemsView;
var binding = SelectedValueBinding;
if (binding is null)
@ -1169,7 +1170,7 @@ namespace Avalonia.Controls.Primitives
{
if (_updateState is null)
{
model.Source = Items;
model.Source = ItemsView.Source;
}
model.PropertyChanged += OnSelectionModelPropertyChanged;
@ -1231,16 +1232,18 @@ namespace Avalonia.Controls.Primitives
Selection = state.Selection.Value;
}
if (state.SelectedItems.HasValue)
if (_selection is InternalSelectionModel s)
{
SelectedItems = state.SelectedItems.Value;
s.Update(ItemsView.Source, state.SelectedItems);
}
Selection.Source = Items;
if (Items is null)
else
{
Selection.Clear();
if (state.SelectedItems.HasValue)
{
SelectedItems = state.SelectedItems.Value;
}
Selection.Source = ItemsView.Source;
}
if (state.SelectedValue.HasValue)

107
src/Avalonia.Controls/SelectableTextBlock.cs

@ -17,17 +17,11 @@ namespace Avalonia.Controls
/// </summary>
public class SelectableTextBlock : TextBlock, IInlineHost
{
public static readonly DirectProperty<SelectableTextBlock, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, int>(
nameof(SelectionStart),
o => o.SelectionStart,
(o, v) => o.SelectionStart = v);
public static readonly DirectProperty<SelectableTextBlock, int> SelectionEndProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, int>(
nameof(SelectionEnd),
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> SelectionStartProperty =
TextBox.SelectionStartProperty.AddOwner<SelectableTextBlock>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly StyledProperty<int> SelectionEndProperty =
TextBox.SelectionEndProperty.AddOwner<SelectableTextBlock>(new(coerce: TextBox.CoerceCaretIndex));
public static readonly DirectProperty<SelectableTextBlock, string> SelectedTextProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, string>(
@ -35,21 +29,16 @@ namespace Avalonia.Controls
o => o.SelectedText);
public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
AvaloniaProperty.Register<SelectableTextBlock, IBrush?>(nameof(SelectionBrush), Brushes.Blue);
TextBox.SelectionBrushProperty.AddOwner<SelectableTextBlock>(new(new Data.Optional<IBrush?>(Brushes.Blue)));
public static readonly DirectProperty<SelectableTextBlock, bool> CanCopyProperty =
AvaloniaProperty.RegisterDirect<SelectableTextBlock, bool>(
nameof(CanCopy),
o => o.CanCopy);
TextBox.CanCopyProperty.AddOwner<SelectableTextBlock>(o => o.CanCopy);
public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
RoutedEvent.Register<SelectableTextBlock, RoutedEventArgs>(
nameof(CopyingToClipboard), RoutingStrategies.Bubble);
private bool _canCopy;
private int _selectionStart;
private int _selectionEnd;
private int _wordSelectionStart = -1;
static SelectableTextBlock()
@ -78,16 +67,8 @@ namespace Avalonia.Controls
/// </summary>
public int SelectionStart
{
get => _selectionStart;
set
{
if (SetAndRaise(SelectionStartProperty, ref _selectionStart, value))
{
RaisePropertyChanged(SelectedTextProperty, "", "");
UpdateCommandStates();
}
}
get => GetValue(SelectionStartProperty);
set => SetValue(SelectionStartProperty, value);
}
/// <summary>
@ -95,16 +76,8 @@ namespace Avalonia.Controls
/// </summary>
public int SelectionEnd
{
get => _selectionEnd;
set
{
if (SetAndRaise(SelectionEndProperty, ref _selectionEnd, value))
{
RaisePropertyChanged(SelectedTextProperty, "", "");
UpdateCommandStates();
}
}
get => GetValue(SelectionEndProperty);
set => SetValue(SelectionEndProperty, value);
}
/// <summary>
@ -150,7 +123,7 @@ namespace Avalonia.Controls
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
.SetTextAsync(text);
}
}
}
/// <summary>
/// Select all text in the TextBox
@ -159,8 +132,8 @@ namespace Avalonia.Controls
{
var text = Text;
SelectionStart = 0;
SelectionEnd = text?.Length ?? 0;
SetCurrentValue(SelectionStartProperty, 0);
SetCurrentValue(SelectionEndProperty, text?.Length ?? 0);
}
/// <summary>
@ -168,7 +141,7 @@ namespace Avalonia.Controls
/// </summary>
public void ClearSelection()
{
SelectionEnd = SelectionStart;
SetCurrentValue(SelectionEndProperty, SelectionStart);
}
protected override void OnGotFocus(GotFocusEventArgs e)
@ -204,7 +177,7 @@ namespace Avalonia.Controls
var rects = TextLayout.HitTestTextRange(start, length);
using (context.PushPostTransform(Matrix.CreateTranslation(origin)))
using (context.PushTransform(Matrix.CreateTranslation(origin)))
{
foreach (var rect in rects)
{
@ -240,6 +213,17 @@ namespace Avalonia.Controls
e.Handled = handled;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SelectionStartProperty || change.Property == SelectionEndProperty)
{
RaisePropertyChanged(SelectedTextProperty, "", "");
UpdateCommandStates();
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
@ -271,25 +255,26 @@ namespace Avalonia.Controls
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
SetCurrentValue(SelectionStartProperty, previousWord);
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
SetCurrentValue(SelectionStartProperty, Math.Min(oldIndex, index));
SetCurrentValue(SelectionEndProperty, Math.Max(oldIndex, index));
}
}
else
{
if (_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
SetCurrentValue(SelectionStartProperty, index);
SetCurrentValue(SelectionEndProperty, index);
_wordSelectionStart = -1;
}
@ -299,16 +284,16 @@ namespace Avalonia.Controls
case 2:
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, index));
}
_wordSelectionStart = SelectionStart;
if (!StringUtils.IsEndOfWord(text, index))
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
break;
case 3:
_wordSelectionStart = -1;
@ -347,22 +332,22 @@ namespace Avalonia.Controls
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, textPosition);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, textPosition));
}
if (distance >= 0)
{
if (SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
SetCurrentValue(SelectionStartProperty, _wordSelectionStart);
}
SelectionEnd = StringUtils.NextWord(text, textPosition);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, textPosition));
}
}
else
{
SelectionEnd = textPosition;
SetCurrentValue(SelectionEndProperty, textPosition);
}
}
@ -395,7 +380,8 @@ namespace Avalonia.Controls
caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection)
{
SelectionStart = SelectionEnd = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
@ -411,9 +397,8 @@ namespace Avalonia.Controls
private string GetSelection()
{
var text = GetText();
if (string.IsNullOrEmpty(text))
var textLength = Text?.Length ?? 0;
if (textLength == 0)
{
return "";
}
@ -423,14 +408,14 @@ namespace Avalonia.Controls
var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd);
if (start == end || text.Length < end)
if (start == end || textLength < end)
{
return "";
}
var length = Math.Max(0, end - start);
var selectedText = text.Substring(start, length);
var selectedText = Text!.Substring(start, length);
return selectedText;
}

27
src/Avalonia.Controls/Selection/InternalSelectionModel.cs

@ -5,6 +5,7 @@ using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Data;
namespace Avalonia.Controls.Selection
{
@ -13,6 +14,7 @@ namespace Avalonia.Controls.Selection
private IList? _writableSelectedItems;
private int _ignoreModelChanges;
private bool _ignoreSelectedItemsChanges;
private bool _skipSyncFromSelectedItems;
private bool _isResetting;
public InternalSelectionModel()
@ -60,6 +62,29 @@ namespace Avalonia.Controls.Selection
}
}
internal void Update(IEnumerable? source, Optional<IList?> selectedItems)
{
var previousSource = Source;
var previousWritableSelectedItems = _writableSelectedItems;
try
{
_skipSyncFromSelectedItems = true;
SetSource(source);
if (selectedItems.HasValue)
WritableSelectedItems = selectedItems.Value;
}
finally
{
_skipSyncFromSelectedItems = false;
}
// We skipped the sync from WritableSelectedItems before; do it now that both
// the source and WritableSelectedItems are updated.
if (previousSource != Source || previousWritableSelectedItems != _writableSelectedItems)
SyncFromSelectedItems();
}
private protected override void SetSource(IEnumerable? value)
{
if (Source == value)
@ -121,7 +146,7 @@ namespace Avalonia.Controls.Selection
private void SyncFromSelectedItems()
{
if (Source is null || _writableSelectedItems is null)
if (_skipSyncFromSelectedItems || Source is null || _writableSelectedItems is null)
{
return;
}

4
src/Avalonia.Controls/Selection/SelectionModel.cs

@ -30,9 +30,9 @@ namespace Avalonia.Controls.Selection
Source = source;
}
public new IEnumerable<T>? Source
public new IEnumerable? Source
{
get => base.Source as IEnumerable<T>;
get => base.Source;
set => SetSource(value);
}

2
src/Avalonia.Controls/SplitButton/SplitButton.cs

@ -172,7 +172,7 @@ namespace Avalonia.Controls
flyout.Opened += Flyout_Opened;
flyout.Closed += Flyout_Closed;
_flyoutPropertyChangedDisposable = flyout.GetPropertyChangedObservable(Popup.PlacementModeProperty).Subscribe(Flyout_PlacementPropertyChanged);
_flyoutPropertyChangedDisposable = flyout.GetPropertyChangedObservable(Popup.PlacementProperty).Subscribe(Flyout_PlacementPropertyChanged);
}
}

66
src/Avalonia.Controls/TextBlock.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Layout;
@ -13,6 +14,7 @@ namespace Avalonia.Controls
/// <summary>
/// A control that displays a block of text.
/// </summary>
[DebuggerDisplay("Text = {" + nameof(DebugText) + "}")]
public class TextBlock : Control, IInlineHost
{
/// <summary>
@ -103,11 +105,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly DirectProperty<TextBlock, string?> TextProperty =
AvaloniaProperty.RegisterDirect<TextBlock, string?>(
nameof(Text),
o => o.GetText(),
(o, v) => o.SetText(v));
public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.Register<TextBlock, string?>(nameof(Text));
/// <summary>
/// Defines the <see cref="TextAlignment"/> property.
@ -142,14 +141,14 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Inlines"/> property.
/// </summary>
public static readonly StyledProperty<InlineCollection?> InlinesProperty =
AvaloniaProperty.Register<TextBlock, InlineCollection?>(
nameof(Inlines));
public static readonly DirectProperty<TextBlock, InlineCollection?> InlinesProperty =
AvaloniaProperty.RegisterDirect<TextBlock, InlineCollection?>(
nameof(Inlines), t => t.Inlines, (t, v) => t.Inlines = v);
internal string? _text;
protected TextLayout? _textLayout;
protected Size _constraint;
private IReadOnlyList<TextRun>? _textRuns;
private InlineCollection? _inlines;
/// <summary>
/// Initializes static members of the <see cref="TextBlock"/> class.
@ -173,7 +172,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the <see cref="TextLayout"/> used to render the text.
/// </summary>
public TextLayout TextLayout => _textLayout ??= CreateTextLayout(_text);
public TextLayout TextLayout => _textLayout ??= CreateTextLayout(Text);
/// <summary>
/// Gets or sets the padding to place around the <see cref="Text"/>.
@ -198,10 +197,12 @@ namespace Avalonia.Controls
/// </summary>
public string? Text
{
get => GetText();
set => SetText(value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private string? DebugText => Text ?? Inlines?.Text;
/// <summary>
/// Gets or sets the font family used to draw the control's text.
/// </summary>
@ -325,8 +326,8 @@ namespace Avalonia.Controls
[Content]
public InlineCollection? Inlines
{
get => GetValue(InlinesProperty);
set => SetValue(InlinesProperty, value);
get => _inlines;
set => SetAndRaise(InlinesProperty, ref _inlines, value);
}
protected override bool BypassFlowDirectionPolicies => true;
@ -590,19 +591,18 @@ namespace Avalonia.Controls
TextLayout.Draw(context, origin);
}
protected virtual string? GetText()
{
return _text ?? Inlines?.Text;
}
protected virtual void SetText(string? text)
private bool _clearTextInternal;
internal void ClearTextInternal()
{
if (HasComplexContent)
_clearTextInternal = true;
try
{
SetCurrentValue(TextProperty, null);
}
finally
{
Inlines?.Clear();
_clearTextInternal = false;
}
SetAndRaise(TextProperty, ref _text, text);
}
/// <summary>
@ -780,6 +780,14 @@ namespace Avalonia.Controls
{
base.OnPropertyChanged(change);
if (change.Property == TextProperty)
{
if (HasComplexContent && !_clearTextInternal)
{
Inlines?.Clear();
}
}
switch (change.Property.Name)
{
case nameof(FontSize):
@ -794,10 +802,10 @@ namespace Avalonia.Controls
case nameof(FlowDirection):
case nameof (Padding):
case nameof (LineHeight):
case nameof (LetterSpacing):
case nameof (MaxLines):
case nameof(Padding):
case nameof(LineHeight):
case nameof(LetterSpacing):
case nameof(MaxLines):
case nameof(Text):
case nameof(TextDecorations):
@ -899,7 +907,7 @@ namespace Avalonia.Controls
continue;
}
if (textRun is TextCharacters)
if (textRun is TextCharacters)
{
var skip = Math.Max(0, textSourceIndex - currentPosition);

433
src/Avalonia.Controls/TextBox.cs

@ -61,11 +61,9 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="CaretIndex"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(CaretIndex),
o => o.CaretIndex,
(o, v) => o.CaretIndex = v);
public static readonly StyledProperty<int> CaretIndexProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(CaretIndex),
coerce: CoerceCaretIndex);
/// <summary>
/// Defines the <see cref="IsReadOnly"/> property
@ -100,42 +98,37 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="SelectionStart"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionStart),
o => o.SelectionStart,
(o, v) => o.SelectionStart = v);
public static readonly StyledProperty<int> SelectionStartProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(SelectionStart),
coerce: CoerceCaretIndex);
/// <summary>
/// Defines the <see cref="SelectionEnd"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> SelectionEndProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(SelectionEnd),
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> SelectionEndProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(SelectionEnd),
coerce: CoerceCaretIndex);
/// <summary>
/// Defines the <see cref="MaxLength"/> property
/// </summary>
public static readonly StyledProperty<int> MaxLengthProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength));
/// <summary>
/// Defines the <see cref="MaxLines"/> property
/// </summary>
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines));
/// <summary>
/// Defines the <see cref="Text"/> property
/// </summary>
public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text,
(o, v) => o.Text = v,
public static readonly StyledProperty<string?> TextProperty =
TextBlock.TextProperty.AddOwner<TextBox>(new(
coerce: CoerceText,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
enableDataValidation: true));
/// <summary>
/// Defines the <see cref="TextAlignment"/> property
@ -185,9 +178,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="NewLine"/> property
/// </summary>
public static readonly DirectProperty<TextBox, string> NewLineProperty =
AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine),
textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline);
public static readonly StyledProperty<string> NewLineProperty =
AvaloniaProperty.Register<TextBox, string>(nameof(NewLine), Environment.NewLine);
/// <summary>
/// Defines the <see cref="InnerLeftContent"/> property
@ -242,12 +234,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="UndoLimit"/> property
/// </summary>
public static readonly DirectProperty<TextBox, int> UndoLimitProperty =
AvaloniaProperty.RegisterDirect<TextBox, int>(
nameof(UndoLimit),
o => o.UndoLimit,
(o, v) => o.UndoLimit = v,
unsetValue: -1);
public static readonly StyledProperty<int> UndoLimitProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(UndoLimit), UndoRedoHelper<UndoRedoState>.DefaultUndoLimit);
/// <summary>
/// Defines the <see cref="CanUndo"/> property
@ -318,18 +306,13 @@ namespace Avalonia.Controls
public override int GetHashCode() => Text?.GetHashCode() ?? 0;
}
private string? _text;
private int _caretIndex;
private int _selectionStart;
private int _selectionEnd;
private TextPresenter? _presenter;
private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private readonly TextBoxTextInputMethodClient _imClient = new();
private readonly UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _canCut;
private bool _canCopy;
private bool _canPaste;
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
private bool _canUndo;
private bool _canRedo;
@ -399,18 +382,19 @@ namespace Avalonia.Controls
/// </summary>
public int CaretIndex
{
get => _caretIndex;
set
{
value = CoerceCaretIndex(value);
SetAndRaise(CaretIndexProperty, ref _caretIndex, value);
get => GetValue(CaretIndexProperty);
set => SetValue(CaretIndexProperty, value);
}
UndoRedoState state;
if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
_undoRedoHelper.UpdateLastState();
private void OnCaretIndexChanged(AvaloniaPropertyChangedEventArgs e)
{
UndoRedoState state;
if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
_undoRedoHelper.UpdateLastState();
SelectionStart = SelectionEnd = value;
}
var newValue = e.GetNewValue<int>();
SetCurrentValue(SelectionStartProperty, newValue);
SetCurrentValue(SelectionEndProperty, newValue);
}
/// <summary>
@ -463,21 +447,18 @@ namespace Avalonia.Controls
/// </summary>
public int SelectionStart
{
get => _selectionStart;
set
{
value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
get => GetValue(SelectionStartProperty);
set => SetValue(SelectionStartProperty, value);
}
if (changed)
{
UpdateCommandStates();
}
private void OnSelectionStartChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateCommandStates();
if (SelectionEnd == value && CaretIndex != value)
{
CaretIndex = value;
}
var value = e.GetNewValue<int>();
if (SelectionEnd == value && CaretIndex != value)
{
SetCurrentValue(CaretIndexProperty, value);
}
}
@ -490,21 +471,18 @@ namespace Avalonia.Controls
/// </remarks>
public int SelectionEnd
{
get => _selectionEnd;
set
{
value = CoerceCaretIndex(value);
var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
if (changed)
{
UpdateCommandStates();
}
get => GetValue(SelectionEndProperty);
set => SetValue(SelectionEndProperty, value);
}
private void OnSelectionEndChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateCommandStates();
if (SelectionStart == value && CaretIndex != value)
{
CaretIndex = value;
}
var value = e.GetNewValue<int>();
if (SelectionStart == value && CaretIndex != value)
{
SetCurrentValue(CaretIndexProperty, value);
}
}
@ -550,36 +528,27 @@ namespace Avalonia.Controls
[Content]
public string? Text
{
get => _text;
set
{
var caretIndex = CaretIndex;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
// Before #9490, snapshot here was done AFTER text change - this doesn't make sense
// since intial state would never be no text and you'd always have to make a text
// change before undo would be available
// The undo/redo stacks were also cleared at this point, which also doesn't make sense
// as it is still valid to want to undo a programmatic text set
// So we snapshot text now BEFORE the change so we can always revert
// Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
if (!_isUndoingRedoing)
{
SnapshotUndoRedo();
}
var textChanged = SetAndRaise(TextProperty, ref _text, value);
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
if (textChanged)
{
RaiseTextChangeEvents();
}
private static string? CoerceText(AvaloniaObject sender, string? value)
{
var textBox = (TextBox)sender;
// Before #9490, snapshot here was done AFTER text change - this doesn't make sense
// since intial state would never be no text and you'd always have to make a text
// change before undo would be available
// The undo/redo stacks were also cleared at this point, which also doesn't make sense
// as it is still valid to want to undo a programmatic text set
// So we snapshot text now BEFORE the change so we can always revert
// Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
if (!textBox._isUndoingRedoing)
{
textBox.SnapshotUndoRedo();
}
return value;
}
/// <summary>
@ -691,8 +660,8 @@ namespace Avalonia.Controls
/// </summary>
public string NewLine
{
get => _newLine;
set => SetAndRaise(NewLineProperty, ref _newLine, value);
get => GetValue(NewLineProperty);
set => SetValue(NewLineProperty, value);
}
/// <summary>
@ -700,7 +669,8 @@ namespace Avalonia.Controls
/// </summary>
public void ClearSelection()
{
CaretIndex = SelectionStart;
SetCurrentValue(CaretIndexProperty, SelectionStart);
SetCurrentValue(SelectionEndProperty, SelectionStart);
}
/// <summary>
@ -744,25 +714,20 @@ namespace Avalonia.Controls
/// </summary>
public int UndoLimit
{
get => _undoRedoHelper.Limit;
set
{
if (_undoRedoHelper.Limit != value)
{
// can't use SetAndRaise due to using _undoRedoHelper.Limit
// (can't send a ref of a property to SetAndRaise),
// so use RaisePropertyChanged instead.
var oldValue = _undoRedoHelper.Limit;
_undoRedoHelper.Limit = value;
RaisePropertyChanged(UndoLimitProperty, oldValue, value);
}
// from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
// "Setting UndoLimit clears the undo queue."
_undoRedoHelper.Clear();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
}
get => GetValue(UndoLimitProperty);
set => SetValue(UndoLimitProperty, value);
}
private void OnUndoLimitChanged(int newValue)
{
_undoRedoHelper.Limit = newValue;
// from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
// "Setting UndoLimit clears the undo queue."
_undoRedoHelper.Clear();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
}
/// <summary>
@ -866,9 +831,31 @@ namespace Avalonia.Controls
if (change.Property == TextProperty)
{
CoerceValue(CaretIndexProperty);
CoerceValue(SelectionStartProperty);
CoerceValue(SelectionEndProperty);
RaiseTextChangeEvents();
UpdatePseudoclasses();
UpdateCommandStates();
}
else if (change.Property == CaretIndexProperty)
{
OnCaretIndexChanged(change);
}
else if (change.Property == SelectionStartProperty)
{
OnSelectionStartChanged(change);
}
else if (change.Property == SelectionEndProperty)
{
OnSelectionEndChanged(change);
}
else if (change.Property == UndoLimitProperty)
{
OnUndoLimitChanged(change.GetNewValue<int>());
}
else if (change.Property == IsUndoEnabledProperty && change.GetNewValue<bool>() == false)
{
// from docs at
@ -920,7 +907,7 @@ namespace Avalonia.Controls
(ContextMenu == null || !ContextMenu.IsOpen))
{
ClearSelection();
RevealPassword = false;
SetCurrentValue(RevealPasswordProperty, false);
}
UpdateCommandStates();
@ -986,35 +973,44 @@ namespace Avalonia.Controls
}
}
var text = Text ?? string.Empty;
var newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
var currentText = Text ?? string.Empty;
var selectionLength = Math.Abs(SelectionStart - SelectionEnd);
var newLength = input.Length + currentText.Length - selectionLength;
if (MaxLength > 0 && newLength > MaxLength)
{
input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength)));
newLength = MaxLength;
}
if (!string.IsNullOrEmpty(input))
{
var oldText = _text;
DeleteSelection(false);
var textBuilder = StringBuilderCache.Acquire(Math.Max(currentText.Length, newLength));
textBuilder.Append(currentText);
var caretIndex = CaretIndex;
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
ClearSelection();
if (IsUndoEnabled)
if (selectionLength != 0)
{
_undoRedoHelper.DiscardRedo();
var (start, _) = GetSelectionRange();
textBuilder.Remove(start, selectionLength);
caretIndex = start;
}
if (_text != oldText)
textBuilder.Insert(caretIndex, input);
SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(textBuilder));
ClearSelection();
if (IsUndoEnabled)
{
RaisePropertyChanged(TextProperty, oldText, _text);
_undoRedoHelper.DiscardRedo();
}
CaretIndex = caretIndex + input.Length;
SetCurrentValue(CaretIndexProperty, caretIndex + input.Length);
}
}
@ -1168,7 +1164,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheEndOfDocument))
{
@ -1176,7 +1172,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheStartOfLine))
{
@ -1184,7 +1180,7 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheEndOfLine))
{
@ -1192,31 +1188,31 @@ namespace Avalonia.Controls
movement = true;
selection = false;
handled = true;
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveHome(true);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveEnd(true);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveHome(false);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
@ -1224,9 +1220,9 @@ namespace Avalonia.Controls
}
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
{
SelectionStart = caretIndex;
SetCurrentValue(SelectionStartProperty, caretIndex);
MoveEnd(false);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
movement = true;
selection = true;
handled = true;
@ -1261,11 +1257,11 @@ namespace Avalonia.Controls
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
break;
@ -1283,11 +1279,11 @@ namespace Avalonia.Controls
if (selection)
{
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
break;
@ -1314,11 +1310,13 @@ namespace Avalonia.Controls
var length = end - start;
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
var sb = StringBuilderCache.Acquire(text.Length);
sb.Append(text);
sb.Remove(start, end - start);
SetTextInternal(editedText);
SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(sb));
CaretIndex = start;
SetCurrentValue(CaretIndexProperty, start);
}
}
@ -1346,9 +1344,11 @@ namespace Avalonia.Controls
var start = Math.Min(nextPosition, caretIndex);
var end = Math.Max(nextPosition, caretIndex);
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
var sb = StringBuilderCache.Acquire(text.Length);
sb.Append(text);
sb.Remove(start, end - start);
SetTextInternal(editedText);
SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(sb));
}
}
@ -1425,7 +1425,7 @@ namespace Avalonia.Controls
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
SetAndRaise(CaretIndexProperty, ref _caretIndex, index);
SetCurrentValue(CaretIndexProperty, index);
switch (e.ClickCount)
{
@ -1438,25 +1438,26 @@ namespace Avalonia.Controls
if (index > _wordSelectionStart)
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
{
SelectionStart = previousWord;
SetCurrentValue(SelectionStartProperty, previousWord);
}
}
else
{
SelectionStart = Math.Min(oldIndex, index);
SelectionEnd = Math.Max(oldIndex, index);
SetCurrentValue(SelectionStartProperty, Math.Min(oldIndex, index));
SetCurrentValue(SelectionEndProperty, Math.Max(oldIndex, index));
}
}
else
{
if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
{
SelectionStart = SelectionEnd = index;
SetCurrentValue(SelectionStartProperty, index);
SetCurrentValue(SelectionEndProperty, index);
_wordSelectionStart = -1;
}
}
@ -1466,14 +1467,14 @@ namespace Avalonia.Controls
if (!StringUtils.IsStartOfWord(text, index))
{
SelectionStart = StringUtils.PreviousWord(text, index);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, index));
}
_wordSelectionStart = SelectionStart;
if (!StringUtils.IsEndOfWord(text, index))
{
SelectionEnd = StringUtils.NextWord(text, index);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
}
break;
@ -1517,22 +1518,22 @@ namespace Avalonia.Controls
if (distance <= 0)
{
SelectionStart = StringUtils.PreviousWord(text, caretIndex);
SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, caretIndex));
}
if (distance >= 0)
{
if(SelectionStart != _wordSelectionStart)
{
SelectionStart = _wordSelectionStart;
SetCurrentValue(SelectionStartProperty, _wordSelectionStart);
}
SelectionEnd = StringUtils.NextWord(text, caretIndex);
SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, caretIndex));
}
}
else
{
SelectionEnd = caretIndex;
SetCurrentValue(SelectionEndProperty, caretIndex);
}
}
}
@ -1565,7 +1566,9 @@ namespace Avalonia.Controls
caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection)
{
CaretIndex = SelectionEnd = SelectionStart = caretIndex;
SetCurrentValue(CaretIndexProperty, caretIndex);
SetCurrentValue(SelectionEndProperty, caretIndex);
SetCurrentValue(SelectionStartProperty, caretIndex);
}
}
@ -1588,10 +1591,10 @@ namespace Avalonia.Controls
}
}
private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text);
private static int CoerceCaretIndex(int value, string? text)
internal static int CoerceCaretIndex(AvaloniaObject sender, int value)
{
var text = sender.GetValue(TextProperty); // method also used by TextPresenter and SelectableTextBlock
if (text == null)
{
return 0;
@ -1619,10 +1622,7 @@ namespace Avalonia.Controls
/// <summary>
/// Clears the text in the TextBox
/// </summary>
public void Clear()
{
Text = string.Empty;
}
public void Clear() => SetCurrentValue(TextProperty, string.Empty);
private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
{
@ -1645,7 +1645,7 @@ namespace Avalonia.Controls
LogicalDirection.Forward :
LogicalDirection.Backward);
SelectionEnd = _presenter.CaretIndex;
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
@ -1662,7 +1662,7 @@ namespace Avalonia.Controls
LogicalDirection.Backward);
}
CaretIndex = _presenter.CaretIndex;
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
}
else
@ -1678,17 +1678,17 @@ namespace Avalonia.Controls
offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd;
}
SelectionEnd += offset;
SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
_presenter.MoveCaretToTextPosition(SelectionEnd);
if (!isSelecting)
{
CaretIndex = SelectionEnd;
SetCurrentValue(CaretIndexProperty, SelectionEnd);
}
else
{
SelectionStart = selectionStart;
SetCurrentValue(SelectionStartProperty, selectionStart);
}
}
}
@ -1747,36 +1747,45 @@ namespace Avalonia.Controls
/// </summary>
public void SelectAll()
{
SelectionStart = 0;
SelectionEnd = Text?.Length ?? 0;
SetCurrentValue(SelectionStartProperty, 0);
SetCurrentValue(SelectionEndProperty, Text?.Length ?? 0);
}
internal bool DeleteSelection(bool raiseTextChanged = true)
private (int start, int end) GetSelectionRange()
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
return (Math.Min(selectionStart, selectionEnd), Math.Max(selectionStart, selectionEnd));
}
internal bool DeleteSelection()
{
if (IsReadOnly)
return true;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var (start, end) = GetSelectionRange();
if (selectionStart != selectionEnd)
if (start != end)
{
var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd);
var text = Text!;
var textBuilder = StringBuilderCache.Acquire(text.Length);
SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
textBuilder.Append(text);
textBuilder.Remove(start, end - start);
SetCurrentValue(TextProperty, textBuilder.ToString());
_presenter?.MoveCaretToTextPosition(start);
CaretIndex = start;
SetCurrentValue(CaretIndexProperty, start);
ClearSelection();
return true;
}
CaretIndex = SelectionStart;
SetCurrentValue(CaretIndexProperty, SelectionStart);
return false;
}
@ -1826,46 +1835,30 @@ namespace Avalonia.Controls
}, DispatcherPriority.Normal);
}
private void SetTextInternal(string value, bool raiseTextChanged = true)
{
if (raiseTextChanged)
{
bool textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged)
{
RaiseTextChangeEvents();
}
}
else
{
_text = value;
}
}
private void SetSelectionForControlBackspace()
{
var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false);
SelectionStart = selectionStart;
SetCurrentValue(SelectionStartProperty, selectionStart);
}
private void SetSelectionForControlDelete()
{
if (_text == null || _presenter == null)
var textLength = Text?.Length ?? 0;
if (_presenter == null || textLength == 0)
{
return;
}
SelectionStart = CaretIndex;
SetCurrentValue(SelectionStartProperty, CaretIndex);
MoveHorizontal(1, true, true);
if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ')
if (SelectionEnd < textLength && Text![SelectionEnd] == ' ')
{
SelectionEnd++;
SetCurrentValue(SelectionEndProperty, SelectionEnd + 1);
}
}
@ -1881,8 +1874,8 @@ namespace Avalonia.Controls
get => new UndoRedoState(Text, CaretIndex);
set
{
Text = value.Text;
CaretIndex = value.CaretPosition;
SetCurrentValue(TextProperty, value.Text);
SetCurrentValue(CaretIndexProperty, value.CaretPosition);
ClearSelection();
}
}

19
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -106,7 +106,7 @@ namespace Avalonia.Controls
{
if (_presenter != null && _textEditable != null)
{
_presenter.CompositionRegion = new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd);
_presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd));
}
}
@ -177,9 +177,9 @@ namespace Avalonia.Controls
var text = GetText(preeditText);
_presenter._text = text;
_presenter.SetCurrentValue(TextPresenter.TextProperty, text);
_presenter.PreeditText = preeditText;
_presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
_presenter.UpdateCaret(new CharacterHit(_compositionStart + (preeditText != null ? preeditText.Length : 0)), false);
@ -201,9 +201,12 @@ namespace Avalonia.Controls
return preeditText;
}
var text = _presenterText.Substring(0, _compositionStart) + preeditText + _presenterText.Substring(_compositionStart);
var sb = StringBuilderCache.Acquire(_presenterText.Length + preeditText.Length);
return text;
sb.Append(_presenterText);
sb.Insert(_compositionStart, preeditText);
return StringBuilderCache.GetStringAndRelease(sb);
}
public void SetComposingRegion(TextRange? region)
@ -213,7 +216,7 @@ namespace Avalonia.Controls
return;
}
_presenter.CompositionRegion = region;
_presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, region);
}
public void SelectInSurroundingText(int start, int end)
@ -252,9 +255,9 @@ namespace Avalonia.Controls
if (_presenter != null)
{
_presenter.PreeditText = null;
_presenter.ClearValue(TextPresenter.PreeditTextProperty);
_presenter.CompositionRegion = null;
_presenter.ClearValue(TextPresenter.CompositionRegionProperty);
_presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
}

1
src/Avalonia.Controls/TopLevel.cs

@ -15,7 +15,6 @@ using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Reactive;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;

4
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@ -144,13 +144,13 @@ namespace Avalonia.Controls.Utils
{
get
{
return SelectorControl?.Items;
return SelectorControl?.ItemsSource;
}
set
{
if (SelectorControl != null)
{
SelectorControl.Items = value;
SelectorControl.ItemsSource = value;
}
}
}

4
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@ -4,6 +4,8 @@ namespace Avalonia.Controls.Utils
{
class UndoRedoHelper<TState>
{
public const int DefaultUndoLimit = 10;
private readonly IUndoRedoHost _host;
public interface IUndoRedoHost
@ -23,7 +25,7 @@ namespace Avalonia.Controls.Utils
/// Maximum number of states this helper can store for undo/redo.
/// If -1, no limit is imposed.
/// </summary>
public int Limit { get; set; } = 10;
public int Limit { get; set; } = DefaultUndoLimit;
public bool CanUndo => _currentNode?.Previous != null;

20
src/Avalonia.Controls/VirtualizingPanel.cs

@ -34,7 +34,8 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the items to display.
/// </summary>
protected IReadOnlyList<object?> Items => ItemsControl?.ItemsView ?? ItemsSourceView.Empty;
protected IReadOnlyList<object?> Items => (IReadOnlyList<object?>?)ItemsControl?.ItemsView ??
Array.Empty<object?>();
/// <summary>
/// Gets the <see cref="ItemsControl"/> that the panel is displaying items for.
@ -192,17 +193,13 @@ namespace Avalonia.Controls
throw new InvalidOperationException("The VirtualizingPanel is already attached to an ItemsControl");
ItemsControl = itemsControl;
ItemsControl.PropertyChanged += OnItemsControlPropertyChanged;
ItemsControl.ItemsView.PostCollectionChanged += OnItemsControlItemsChanged;
}
internal void Detach()
{
var itemsControl = EnsureItemsControl();
itemsControl.PropertyChanged -= OnItemsControlPropertyChanged;
itemsControl.ItemsView.PostCollectionChanged -= OnItemsControlItemsChanged;
ItemsControl = null;
Children.Clear();
}
@ -216,20 +213,9 @@ namespace Avalonia.Controls
return ItemsControl;
}
private protected virtual void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ItemsControl.ItemsViewProperty)
{
var (oldValue, newValue) = e.GetOldAndNewValue<ItemsSourceView>();
oldValue.PostCollectionChanged -= OnItemsControlItemsChanged;
Refresh();
newValue.PostCollectionChanged += OnItemsControlItemsChanged;
}
}
private void OnItemsControlItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
OnItemsChanged(_itemsControl?.ItemsView ?? ItemsSourceView.Empty, e);
OnItemsChanged(Items, e);
}
[DoesNotReturn]

5
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -459,7 +459,8 @@ namespace Avalonia.Controls
while (c is not null)
{
if (!c.Bounds.IsDefault && c.TransformToVisual(this) is Matrix transform)
if ((c.Bounds.Width != 0 || c.Bounds.Height != 0) &&
c.TransformToVisual(this) is Matrix transform)
{
viewport = new Rect(0, 0, c.Bounds.Width, c.Bounds.Height)
.TransformToAABB(transform);
@ -1078,7 +1079,7 @@ namespace Avalonia.Controls
// elements after the insertion point.
var elementCount = _elements.Count;
var start = Math.Max(realizedIndex, 0);
var newIndex = first + count;
var newIndex = realizedIndex + count;
for (var i = start; i < elementCount; ++i)
{

29
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs

@ -7,23 +7,18 @@ namespace Avalonia.Diagnostics.Controls
{
internal class FilterTextBox : TextBox, IStyleable
{
public static readonly DirectProperty<FilterTextBox, bool> UseRegexFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseRegexFilter),
o => o.UseRegexFilter, (o, v) => o.UseRegexFilter = v,
public static readonly StyledProperty<bool> UseRegexFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseRegexFilter),
defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<FilterTextBox, bool> UseCaseSensitiveFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseCaseSensitiveFilter),
o => o.UseCaseSensitiveFilter, (o, v) => o.UseCaseSensitiveFilter = v,
public static readonly StyledProperty<bool> UseCaseSensitiveFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseCaseSensitiveFilter),
defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<FilterTextBox, bool> UseWholeWordFilterProperty =
AvaloniaProperty.RegisterDirect<FilterTextBox, bool>(nameof(UseWholeWordFilter),
o => o.UseWholeWordFilter, (o, v) => o.UseWholeWordFilter = v,
public static readonly StyledProperty<bool> UseWholeWordFilterProperty =
AvaloniaProperty.Register<FilterTextBox, bool>(nameof(UseWholeWordFilter),
defaultBindingMode: BindingMode.TwoWay);
private bool _useRegexFilter, _useCaseSensitiveFilter, _useWholeWordFilter;
public FilterTextBox()
{
Classes.Add("filter-text-box");
@ -31,20 +26,20 @@ namespace Avalonia.Diagnostics.Controls
public bool UseRegexFilter
{
get => _useRegexFilter;
set => SetAndRaise(UseRegexFilterProperty, ref _useRegexFilter, value);
get => GetValue(UseRegexFilterProperty);
set => SetValue(UseRegexFilterProperty, value);
}
public bool UseCaseSensitiveFilter
{
get => _useCaseSensitiveFilter;
set => SetAndRaise(UseCaseSensitiveFilterProperty, ref _useCaseSensitiveFilter, value);
get => GetValue(UseCaseSensitiveFilterProperty);
set => SetValue(UseCaseSensitiveFilterProperty,value);
}
public bool UseWholeWordFilter
{
get => _useWholeWordFilter;
set => SetAndRaise(UseWholeWordFilterProperty, ref _useWholeWordFilter, value);
get => GetValue(UseWholeWordFilterProperty);
set => SetValue(UseWholeWordFilterProperty, value);
}
Type IStyleable.StyleKey => typeof(TextBox);

2
src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml

@ -40,7 +40,7 @@
BorderBrush="{DynamicResource ThemeControlMidBrush}"
BorderThickness="0,0,0,1"
FontFamily="/Assets/Fonts/SourceSansPro-Regular.ttf"
Items="{Binding History}">
ItemsSource="{Binding History}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">

6
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -124,7 +124,7 @@
</Grid>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled">
<ItemsControl Items="{Binding AppliedStyles}" >
<ItemsControl ItemsSource="{Binding AppliedStyles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#6C6C6C" Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}">
@ -142,7 +142,7 @@
<TextBlock Grid.Row="0" Text="{Binding Name}" />
</Expander.Header>
<ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
<ItemsControl Margin="20,0,0,0" Grid.Row="1" ItemsSource="{Binding Setters}">
<ItemsControl.Styles>
<Style Selector="TextBlock.property-name">
@ -253,7 +253,7 @@
</ScrollViewer>
<Expander Header="Pseudo Classes" Grid.Row="2">
<ItemsControl Items="{Binding PseudoClasses}">
<ItemsControl ItemsSource="{Binding PseudoClasses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />

6
src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml

@ -45,7 +45,7 @@
UseWholeWordFilter="{Binding UseWholeWordFilter}"
UseRegexFilter="{Binding UseRegexFilter}" />
<TreeView Grid.Row="1" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" >
<TreeView Grid.Row="1" ItemsSource="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}" >
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:EventTreeNodeBase"
ItemsSource="{Binding Children}">
@ -71,7 +71,7 @@
<Grid RowDefinitions="*,4,2*,Auto" Grid.Column="2">
<ListBox Name="EventsList" Items="{Binding RecordedEvents}"
<ListBox Name="EventsList" ItemsSource="{Binding RecordedEvents}"
SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
<ListBox.ItemTemplate>
@ -108,7 +108,7 @@
<DockPanel Grid.Row="2" LastChildFill="True">
<TextBlock DockPanel.Dock="Top" FontSize="16" Text="Event chain:" />
<ListBox Items="{Binding SelectedEvent.EventChain}">
<ListBox ItemsSource="{Binding SelectedEvent.EventChain}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Classes.handled="{Binding Handled}">

2
src/Avalonia.Diagnostics/Diagnostics/Views/PropertyValueEditorView.cs

@ -221,7 +221,7 @@ namespace Avalonia.Diagnostics.Views
return CreateControl<ComboBox>(
SelectingItemsControl.SelectedItemProperty, init: c =>
{
c.Items = Enum.GetValues(propertyType);
c.ItemsSource = Enum.GetValues(propertyType);
});
var tb = CreateControl<CommitTextBox>(

2
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -6,7 +6,7 @@
<Grid ColumnDefinitions="0.35*,4,0.65*">
<TreeView Name="tree"
BorderThickness="0"
Items="{Binding Nodes}"
ItemsSource="{Binding Nodes}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TreeNode"

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save