Browse Source

Merge branch 'master' into fixes/macCaretPosition

pull/10693/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
9b741c8998
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  2. 60
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  3. 8
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  4. 6
      samples/IntegrationTestApp/MainWindow.axaml
  5. 6
      samples/IntegrationTestApp/MainWindow.axaml.cs
  6. 24
      samples/IntegrationTestApp/ShowWindowTest.axaml
  7. 2
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  8. 2
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  9. 9
      src/Avalonia.Base/CornerRadius.cs
  10. 2
      src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
  11. 11
      src/Avalonia.Base/Media/BoxShadow.cs
  12. 4
      src/Avalonia.Base/Media/BoxShadows.cs
  13. 2
      src/Avalonia.Base/Media/DrawingGroup.cs
  14. 5
      src/Avalonia.Base/Media/FontFamily.cs
  15. 5
      src/Avalonia.Base/Media/FormattedText.cs
  16. 2
      src/Avalonia.Base/Media/ImageDrawing.cs
  17. 2
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  18. 19
      src/Avalonia.Base/PixelRect.cs
  19. 17
      src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs
  20. 16
      src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
  21. 8
      src/Avalonia.Base/Point.cs
  22. 34
      src/Avalonia.Base/Rect.cs
  23. 12
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  24. 55
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  25. 2
      src/Avalonia.Base/Rendering/DirtyRects.cs
  26. 4
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  27. 11
      src/Avalonia.Base/Size.cs
  28. 10
      src/Avalonia.Base/Thickness.cs
  29. 8
      src/Avalonia.Base/Vector.cs
  30. 8
      src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
  31. 4
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
  32. 2
      src/Avalonia.Controls/BorderVisual.cs
  33. 20
      src/Avalonia.Controls/ContainerClearingEventArgs.cs
  34. 32
      src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs
  35. 26
      src/Avalonia.Controls/ContainerPreparedEventArgs.cs
  36. 103
      src/Avalonia.Controls/ContextMenu.cs
  37. 2
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  38. 2
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  39. 16
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  40. 31
      src/Avalonia.Controls/ItemsControl.cs
  41. 4
      src/Avalonia.Controls/LayoutTransformControl.cs
  42. 2
      src/Avalonia.Controls/NativeControlHost.cs
  43. 3
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  44. 80
      src/Avalonia.Controls/Primitives/Popup.cs
  45. 3
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  46. 2
      src/Avalonia.Controls/SelectableTextBlock.cs
  47. 2
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  48. 1
      src/Avalonia.Controls/TopLevel.cs
  49. 5
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  50. 2
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  51. 2
      src/Avalonia.Themes.Fluent/Controls/Menu.xaml
  52. 2
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  53. 2
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  54. 2
      src/Avalonia.Themes.Simple/Controls/DatePicker.xaml
  55. 2
      src/Avalonia.Themes.Simple/Controls/Menu.xaml
  56. 2
      src/Avalonia.Themes.Simple/Controls/MenuItem.xaml
  57. 2
      src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
  58. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  59. 2
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  60. 130
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  61. 140
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  62. 2
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  63. 20
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  64. 30
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
  65. 19
      tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs
  66. 13
      tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
  67. 11
      tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs
  68. 124
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
  69. 4
      tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs
  70. 2
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  71. 2
      tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
  72. 2
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  73. 2
      tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs
  74. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

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

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

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

@ -1,16 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
public partial class ClipboardPage : UserControl public partial class ClipboardPage : UserControl
{ {
private INotificationManager? _notificationManager;
private INotificationManager NotificationManager => _notificationManager
??= new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
public ClipboardPage() public ClipboardPage()
{ {
InitializeComponent(); InitializeComponent();
@ -31,7 +38,7 @@ namespace ControlCatalog.Pages
private async void PasteText(object? sender, RoutedEventArgs args) 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(); ClipboardContent.Text = await clipboard.GetTextAsync();
} }
@ -59,15 +66,45 @@ namespace ControlCatalog.Pages
{ {
if (Application.Current!.Clipboard is { } clipboard) if (Application.Current!.Clipboard is { } clipboard)
{ {
var files = (ClipboardContent.Text ?? String.Empty) var storageProvider = TopLevel.GetTopLevel(this)!.StorageProvider;
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var filesPath = (ClipboardContent.Text ?? string.Empty)
if (files.Length == 0) .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (filesPath.Length == 0)
{ {
return; return;
} }
var dataObject = new DataObject(); List<string> invalidFile = new(filesPath.Length);
dataObject.Set(DataFormats.FileNames, files); List<IStorageFile> files = new(filesPath.Length);
await clipboard.SetDataObjectAsync(dataObject);
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) if (Application.Current!.Clipboard is { } clipboard)
{ {
var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>; var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
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(); await clipboard.ClearAsync();
} }
} }
} }
} }

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 // 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). // now 0,0 refers to the ViewportCenter(X,Y).
var rotationMatrix = Avalonia.Matrix.CreateRotation(Rotation); var rotationMatrix = Avalonia.Matrix.CreateRotation(Rotation);
var rotationModifier = context.PushPreTransform(rotationMatrix); var rotationModifier = context.PushTransform(rotationMatrix);
// everything is rotated but not scaled // 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 // now everything is rotated and scaled, and at the right position, now we're drawing strictly in world coordinates

6
samples/IntegrationTestApp/MainWindow.axaml

@ -151,6 +151,12 @@
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem> <ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem> <ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox> </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> <CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button> <Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</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 locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
var stateComboBox = this.GetControl<ComboBox>("ShowWindowState"); var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; 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 canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!; var owner = (Window)this.GetVisualRoot()!;
@ -95,6 +97,8 @@ namespace IntegrationTestApp
} }
sizeTextBox.Text = string.Empty; sizeTextBox.Text = string.Empty;
window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
window.WindowState = (WindowState)stateComboBox.SelectedIndex; window.WindowState = (WindowState)stateComboBox.SelectedIndex;
switch (modeComboBox.SelectedIndex) switch (modeComboBox.SelectedIndex)
@ -158,7 +162,7 @@ namespace IntegrationTestApp
var popup = new Popup var popup = new Popup
{ {
WindowManagerAddShadowHint = false, WindowManagerAddShadowHint = false,
PlacementMode = PlacementMode.AnchorAndGravity, Placement = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.Top, PlacementAnchor = PopupAnchor.Top,
PlacementGravity = PopupGravity.Bottom, PlacementGravity = PopupGravity.Bottom,
Width= 200, Width= 200,

24
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -6,7 +6,7 @@
x:DataType="Window" x:DataType="Window"
Title="Show Window Test"> Title="Show Window Test">
<integrationTestApp:MeasureBorder Name="MyBorder"> <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> <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
<TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True" <TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
Text="{Binding ClientSize, Mode=OneWay}" /> Text="{Binding ClientSize, Mode=OneWay}" />
@ -35,13 +35,25 @@
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem> <ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox> </ComboBox>
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label> <Label Grid.Column="0" Grid.Row="8">SystemDecorations</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="8" IsReadOnly="True" /> <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:" /> <Label Grid.Row="11" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="9" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.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> </Grid>
</integrationTestApp:MeasureBorder> </integrationTestApp:MeasureBorder>

2
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@ -29,7 +29,7 @@ namespace RenderDemo.Pages
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
using (var ctx = _bitmap.CreateDrawingContext()) 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.CreateRotation(_st.Elapsed.TotalSeconds)
* Matrix.CreateTranslation(100, 100))) * 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) 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> /// </summary>
public double BottomLeft { get; } 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> /// <summary>
/// Gets a value indicating whether all corner radii are equal. /// Gets a value indicating whether all corner radii are equal.
/// </summary> /// </summary>

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

@ -180,7 +180,7 @@ namespace Avalonia.Input.GestureRecognizers
internal Velocity GetVelocity() internal Velocity GetVelocity()
{ {
var estimate = GetVelocityEstimate(); var estimate = GetVelocityEstimate();
if (estimate == null || estimate.PixelsPerSecond.IsDefault) if (estimate == null || estimate.PixelsPerSecond == default(Vector))
{ {
return new Velocity(Vector.Zero); 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' }; private readonly static char[] s_Separator = new char[] { ' ', '\t' };
struct ArrayReader struct ArrayReader
@ -89,7 +80,7 @@ namespace Avalonia.Media
{ {
var sb = StringBuilderCache.Acquire(); var sb = StringBuilderCache.Acquire();
if (IsDefault) if (this == default)
{ {
return "none"; return "none";
} }

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

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

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

@ -73,7 +73,7 @@ namespace Avalonia.Media
{ {
var bounds = GetBounds(); var bounds = GetBounds();
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) using (context.PushTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity, bounds)) using (context.PushOpacity(Opacity, bounds))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default) using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : 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> /// <remarks>Key is only used for custom fonts.</remarks>
public FontFamilyKey? Key { get; } 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> /// <summary>
/// Implicit conversion of string to FontFamily /// Implicit conversion of string to FontFamily
/// </summary> /// </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 null;
} }
return accumulatedBounds; return accumulatedBounds;
} }

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

@ -42,7 +42,7 @@ namespace Avalonia.Media
var imageSource = ImageSource; var imageSource = ImageSource;
var rect = Rect; var rect = Rect;
if (imageSource is object && !rect.IsDefault) if (imageSource is object && (rect.Width != 0 || rect.Height != 0))
{ {
context.DrawImage(imageSource, rect); 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) if (Source is not IBitmap bmp)
return default; return default;
if (SourceRect.IsDefault) if (SourceRect.Width == 0 && SourceRect.Height == 0)
return Source.Size; return Source.Size;
return SourceRect.Size.ToSizeWithDpi(bmp.Dpi); return SourceRect.Size.ToSizeWithDpi(bmp.Dpi);
} }

19
src/Avalonia.Base/PixelRect.cs

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

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

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -23,7 +24,7 @@ internal static class StorageProviderHelpers
return null; return null;
} }
public static Uri FilePathToUri(string path) public static Uri FilePathToUri(string path)
{ {
var uriPath = new StringBuilder(path) var uriPath = new StringBuilder(path)
@ -35,6 +36,20 @@ internal static class StorageProviderHelpers
return new UriBuilder("file", string.Empty) { Path = uriPath }.Uri; 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) public static string NameWithExtension(string path, string? defaultExtension, FilePickerFileType? filter)
{ {
var name = Path.GetFileName(path); 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 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"/> /// <inheritdoc cref="IStorageProvider.TryGetFolderFromPathAsync"/>
@ -29,7 +34,12 @@ public static class StorageProviderExtensions
return Task.FromResult(StorageProviderHelpers.TryCreateBclStorageItem(folderPath) as IStorageFolder); 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> /// <summary>

8
src/Avalonia.Base/Point.cs

@ -288,13 +288,5 @@ namespace Avalonia
x = this._x; x = this._x;
y = this._y; 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); }
}
} }
} }

34
src/Avalonia.Base/Rect.cs

@ -16,12 +16,6 @@ namespace Avalonia
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType)); 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> /// <summary>
/// The X position. /// The X position.
/// </summary> /// </summary>
@ -170,17 +164,6 @@ namespace Avalonia
/// </summary> /// </summary>
public Point Center => new Point(_x + (_width / 2), _y + (_height / 2)); 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> /// <summary>
/// Checks for equality between two <see cref="Rect"/>s. /// Checks for equality between two <see cref="Rect"/>s.
/// </summary> /// </summary>
@ -517,19 +500,18 @@ namespace Avalonia
return rect; return rect;
} }
/// <summary>
/// <summary> /// Gets the union of two rectangles.
/// Gets the union of two rectangles. /// </summary>
/// </summary> /// <param name="rect">The other rectangle.</param>
/// <param name="rect">The other rectangle.</param> /// <returns>The union.</returns>
/// <returns>The union.</returns> public Rect Union(Rect rect)
public Rect Union(Rect rect)
{ {
if (IsDefault) if (Width == 0 && Height == 0)
{ {
return rect; return rect;
} }
else if (rect.IsDefault) else if (rect.Width == 0 && rect.Height == 0)
{ {
return this; return this;
} }

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

@ -130,8 +130,8 @@ namespace Avalonia.Rendering.Composition.Server
} }
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces()); _renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
if(_dirtyRect.IsDefault && !_redrawRequested) if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested)
return; return;
Revision++; Revision++;
@ -163,7 +163,7 @@ namespace Avalonia.Rendering.Composition.Server
_dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height); _dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height);
} }
if (!_dirtyRect.IsDefault) if (_dirtyRect.Width != 0 || _dirtyRect.Height != 0)
{ {
using (var context = _layer.CreateDrawingContext()) using (var context = _layer.CreateDrawingContext())
{ {
@ -260,7 +260,7 @@ namespace Avalonia.Rendering.Composition.Server
public void AddDirtyRect(Rect rect) public void AddDirtyRect(Rect rect)
{ {
if(rect.IsDefault) if (rect.Width == 0 && rect.Height == 0)
return; return;
var snapped = SnapToDevicePixels(rect, Scaling); var snapped = SnapToDevicePixels(rect, Scaling);
DebugEvents?.RectInvalidated(rect); DebugEvents?.RectInvalidated(rect);
@ -275,7 +275,7 @@ namespace Avalonia.Rendering.Composition.Server
public void Dispose() public void Dispose()
{ {
if(_disposed) if (_disposed)
return; return;
_disposed = true; _disposed = true;
using (_compositor.RenderInterface.EnsureCurrent()) using (_compositor.RenderInterface.EnsureCurrent())
@ -302,7 +302,7 @@ namespace Avalonia.Rendering.Composition.Server
{ {
if (_attachedVisuals.Remove(visual) && IsEnabled) if (_attachedVisuals.Remove(visual) && IsEnabled)
visual.Deactivate(); visual.Deactivate();
if(visual.IsVisibleInFrame) if (visual.IsVisibleInFrame)
AddDirtyRect(visual.TransformedOwnContentBounds); 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 bool _isBackface;
private Rect? _transformedClipBounds; private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds; private Rect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{ {
} }
public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{ {
if(Visible == false || IsVisibleInFrame == false) if (Visible == false || IsVisibleInFrame == false)
return; return;
if(Opacity == 0) if (Opacity == 0)
return; return;
currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds); currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds);
if(currentTransformedClip.IsDefault) if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0)
return; return;
Root!.RenderedVisuals++; Root!.RenderedVisuals++;
@ -61,7 +60,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null) if (Clip != null)
canvas.PushGeometryClip(Clip); canvas.PushGeometryClip(Clip);
if(OpacityMaskBrush != null) if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect); canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
RenderCore(canvas, currentTransformedClip); RenderCore(canvas, currentTransformedClip);
@ -78,12 +77,12 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopClip(); canvas.PopClip();
if (AdornedVisual != null && AdornerIsClipped) if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip(); canvas.PopClip();
if(Opacity != 1) if (Opacity != 1)
canvas.PopOpacity(); canvas.PopOpacity();
} }
protected virtual bool HandlesClipToBounds => false; protected virtual bool HandlesClipToBounds => false;
private ReadbackData _readback0, _readback1, _readback2; private ReadbackData _readback0, _readback1, _readback2;
/// <summary> /// <summary>
@ -98,17 +97,17 @@ namespace Avalonia.Rendering.Composition.Server
return ref _readback1; return ref _readback1;
return ref _readback2; return ref _readback2;
} }
public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity; public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity;
public Matrix4x4 GlobalTransformMatrix { get; private set; } public Matrix4x4 GlobalTransformMatrix { get; private set; }
public virtual void Update(ServerCompositionTarget root) public virtual void Update(ServerCompositionTarget root)
{ {
if(Parent == null && Root == null) if (Parent == null && Root == null)
return; return;
var wasVisible = IsVisibleInFrame; var wasVisible = IsVisibleInFrame;
// Calculate new parent-relative transform // Calculate new parent-relative transform
if (_combinedTransformDirty) if (_combinedTransformDirty)
{ {
@ -122,7 +121,7 @@ namespace Avalonia.Rendering.Composition.Server
var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity; var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;
var newTransform = CombinedTransformMatrix * parentTransform; var newTransform = CombinedTransformMatrix * parentTransform;
// Check if visual was moved and recalculate face orientation // Check if visual was moved and recalculate face orientation
var positionChanged = false; var positionChanged = false;
if (GlobalTransformMatrix != newTransform) if (GlobalTransformMatrix != newTransform)
@ -134,23 +133,23 @@ namespace Avalonia.Rendering.Composition.Server
var oldTransformedContentBounds = TransformedOwnContentBounds; var oldTransformedContentBounds = TransformedOwnContentBounds;
var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds; var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds;
if (_parent?.IsDirtyComposition == true) if (_parent?.IsDirtyComposition == true)
{ {
IsDirtyComposition = true; IsDirtyComposition = true;
_isDirtyForUpdate = true; _isDirtyForUpdate = true;
} }
var invalidateOldBounds = _isDirtyForUpdate; var invalidateOldBounds = _isDirtyForUpdate;
var invalidateNewBounds = _isDirtyForUpdate; var invalidateNewBounds = _isDirtyForUpdate;
GlobalTransformMatrix = newTransform; GlobalTransformMatrix = newTransform;
var ownBounds = OwnContentBounds; var ownBounds = OwnContentBounds;
if (ownBounds != _oldOwnContentBounds || positionChanged) if (ownBounds != _oldOwnContentBounds || positionChanged)
{ {
_oldOwnContentBounds = ownBounds; _oldOwnContentBounds = ownBounds;
if (ownBounds.IsDefault) if (ownBounds.Width == 0 && ownBounds.Height == 0)
TransformedOwnContentBounds = default; TransformedOwnContentBounds = default;
else else
TransformedOwnContentBounds = TransformedOwnContentBounds =
@ -171,16 +170,16 @@ namespace Avalonia.Rendering.Composition.Server
AdornedVisual?._combinedTransformedClipBounds AdornedVisual?._combinedTransformedClipBounds
?? Parent?._combinedTransformedClipBounds ?? Parent?._combinedTransformedClipBounds
?? new Rect(Root!.Size); ?? new Rect(Root!.Size);
if (_transformedClipBounds != null) if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value); _combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1);
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible && Visible
&& !_isBackface && !_isBackface
&& !_combinedTransformedClipBounds.IsDefault; && (_combinedTransformedClipBounds.Width != 0 || _combinedTransformedClipBounds.Height != 0);
IsVisibleInFrame = IsHitTestVisibleInFrame IsVisibleInFrame = IsHitTestVisibleInFrame
&& _parent?.IsVisibleInFrame != false && _parent?.IsVisibleInFrame != false
@ -213,11 +212,11 @@ namespace Avalonia.Rendering.Composition.Server
void AddDirtyRect(Rect rc) void AddDirtyRect(Rect rc)
{ {
if(rc == default) if (rc == default)
return; return;
Root?.AddDirtyRect(rc); Root?.AddDirtyRect(rc);
} }
/// <summary> /// <summary>
/// Data that can be read from the UI thread /// Data that can be read from the UI thread
/// </summary> /// </summary>
@ -228,7 +227,7 @@ namespace Avalonia.Rendering.Composition.Server
public long TargetId; public long TargetId;
public bool Visible; public bool Visible;
} }
partial void DeserializeChangesExtra(BatchStreamReader c) partial void DeserializeChangesExtra(BatchStreamReader c)
{ {
ValuesInvalidated(); ValuesInvalidated();
@ -245,9 +244,8 @@ namespace Avalonia.Rendering.Composition.Server
protected virtual void OnDetachedFromRoot(ServerCompositionTarget target) protected virtual void OnDetachedFromRoot(ServerCompositionTarget target)
{ {
} }
partial void OnRootChanged() partial void OnRootChanged()
{ {
if (Root != null) if (Root != null)
@ -256,12 +254,11 @@ namespace Avalonia.Rendering.Composition.Server
OnAttachedToRoot(Root); OnAttachedToRoot(Root);
} }
} }
protected virtual void OnAttachedToRoot(ServerCompositionTarget target) protected virtual void OnAttachedToRoot(ServerCompositionTarget target)
{ {
} }
protected override void ValuesInvalidated() protected override void ValuesInvalidated()
{ {
_isDirtyForUpdate = true; _isDirtyForUpdate = true;
@ -274,6 +271,4 @@ namespace Avalonia.Rendering.Composition.Server
public Rect TransformedOwnContentBounds { get; set; } public Rect TransformedOwnContentBounds { get; set; }
public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y); 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> /// </remarks>
public void Add(Rect rect) public void Add(Rect rect)
{ {
if (!rect.IsDefault) if (rect.Width != 0 || rect.Height != 0)
{ {
for (var i = 0; i < _rects.Count; ++i) 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 (context.PushOpacity(opacity, bounds))
using (clipToBounds using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete #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.Clip != null ? context.PushGeometryClip(visual.Clip) : default)
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default)
using (context.PushTransformContainer()) using (context.PushTransform(Matrix.Identity))
{ {
visual.Render(context); visual.Render(context);

11
src/Avalonia.Base/Size.cs

@ -27,12 +27,6 @@ namespace Avalonia
/// </summary> /// </summary>
public static readonly Size Infinity = new Size(double.PositiveInfinity, double.PositiveInfinity); 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> /// <summary>
/// The width. /// The width.
/// </summary> /// </summary>
@ -306,10 +300,5 @@ namespace Avalonia
width = this._width; width = this._width;
height = this._height; height = this._height;
} }
/// <summary>
/// Gets a value indicating whether the Width and Height values are zero.
/// </summary>
public bool IsDefault => (_width == 0) && (_height == 0);
} }
} }

10
src/Avalonia.Base/Thickness.cs

@ -97,10 +97,6 @@ namespace Avalonia
/// </summary> /// </summary>
public double Bottom => _bottom; public double Bottom => _bottom;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary> /// <summary>
/// Gets a value indicating whether all sides are equal. /// Gets a value indicating whether all sides are equal.
/// </summary> /// </summary>
@ -293,11 +289,5 @@ namespace Avalonia
right = this._right; right = this._right;
bottom = this._bottom; 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; x = this._x;
y = this._y; 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); }
}
} }
} }

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

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

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

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

2
src/Avalonia.Controls/BorderVisual.cs

@ -50,7 +50,7 @@ class CompositionBorderVisual : CompositionDrawListVisual
if (ClipToBounds) if (ClipToBounds)
{ {
var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y))); var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y)));
if (_cornerRadius.IsDefault) if (_cornerRadius == default)
canvas.PushClip(clipRect); canvas.PushClip(clipRect);
else else
canvas.PushClip(new RoundedRect(clipRect, _cornerRadius)); 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 = public static readonly StyledProperty<PopupGravity> PlacementGravityProperty =
Popup.PlacementGravityProperty.AddOwner<ContextMenu>(); Popup.PlacementGravityProperty.AddOwner<ContextMenu>();
/// <summary>
/// Defines the <see cref="Placement"/> property.
/// </summary>
public static readonly StyledProperty<PlacementMode> PlacementProperty =
Popup.PlacementProperty.AddOwner<ContextMenu>();
/// <summary> /// <summary>
/// Defines the <see cref="PlacementMode"/> property. /// Defines the <see cref="PlacementMode"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = [Obsolete("Use the Placement property instead.")]
Popup.PlacementModeProperty.AddOwner<ContextMenu>(); public static readonly StyledProperty<PlacementMode> PlacementModeProperty = PlacementProperty;
/// <summary> /// <summary>
/// Defines the <see cref="PlacementRect"/> property. /// Defines the <see cref="PlacementRect"/> property.
@ -108,99 +114,80 @@ namespace Avalonia.Controls
static ContextMenu() static ContextMenu()
{ {
ItemsPanelProperty.OverrideDefaultValue<ContextMenu>(DefaultPanel); ItemsPanelProperty.OverrideDefaultValue<ContextMenu>(DefaultPanel);
PlacementModeProperty.OverrideDefaultValue<ContextMenu>(PlacementMode.Pointer); PlacementProperty.OverrideDefaultValue<ContextMenu>(PlacementMode.Pointer);
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue<ContextMenu>(AccessibilityView.Control); AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue<ContextMenu>(AccessibilityView.Control);
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<ContextMenu>(AutomationControlType.Menu); AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<ContextMenu>(AutomationControlType.Menu);
} }
/// <summary> /// <inheritdoc cref="Popup.HorizontalOffset"/>
/// Gets or sets the Horizontal offset of the context menu in relation to the <see cref="PlacementTarget"/>.
/// </summary>
public double HorizontalOffset public double HorizontalOffset
{ {
get { return GetValue(HorizontalOffsetProperty); } get => GetValue(HorizontalOffsetProperty);
set { SetValue(HorizontalOffsetProperty, value); } set => SetValue(HorizontalOffsetProperty, value);
} }
/// <summary> /// <inheritdoc cref="Popup.VerticalOffset"/>
/// Gets or sets the Vertical offset of the context menu in relation to the <see cref="PlacementTarget"/>.
/// </summary>
public double VerticalOffset public double VerticalOffset
{ {
get { return GetValue(VerticalOffsetProperty); } get => GetValue(VerticalOffsetProperty);
set { SetValue(VerticalOffsetProperty, value); } set => SetValue(VerticalOffsetProperty, value);
} }
/// <summary> /// <inheritdoc cref="Popup.PlacementAnchor"/>
/// Gets or sets the anchor point on the <see cref="PlacementRect"/> when <see cref="PlacementMode"/>
/// is <see cref="PlacementMode.AnchorAndGravity"/>.
/// </summary>
public PopupAnchor PlacementAnchor public PopupAnchor PlacementAnchor
{ {
get { return GetValue(PlacementAnchorProperty); } get => GetValue(PlacementAnchorProperty);
set { SetValue(PlacementAnchorProperty, value); } set => SetValue(PlacementAnchorProperty, value);
} }
/// <summary> /// <inheritdoc cref="Popup.PlacementConstraintAdjustment"/>
/// 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>
public PopupPositionerConstraintAdjustment PlacementConstraintAdjustment public PopupPositionerConstraintAdjustment PlacementConstraintAdjustment
{ {
get { return GetValue(PlacementConstraintAdjustmentProperty); } get => GetValue(PlacementConstraintAdjustmentProperty);
set { SetValue(PlacementConstraintAdjustmentProperty, value); } set => SetValue(PlacementConstraintAdjustmentProperty, value);
} }
/// <summary> /// <inheritdoc cref="Popup.PlacementGravity"/>
/// 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>
public PopupGravity PlacementGravity public PopupGravity PlacementGravity
{ {
get { return GetValue(PlacementGravityProperty); } get => GetValue(PlacementGravityProperty);
set { SetValue(PlacementGravityProperty, value); } set => SetValue(PlacementGravityProperty, value);
} }
/// <summary> /// <inheritdoc cref="Placement"/>
/// Gets or sets the placement mode of the context menu in relation to the<see cref="PlacementTarget"/>. [Obsolete("Use the Placement property instead.")]
/// </summary>
public PlacementMode PlacementMode public PlacementMode PlacementMode
{ {
get { return GetValue(PlacementModeProperty); } get => GetValue(PlacementProperty);
set { SetValue(PlacementModeProperty, value); } set => SetValue(PlacementProperty, value);
}
/// <inheritdoc cref="Popup.Placement"/>
public PlacementMode Placement
{
get => GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
} }
public bool WindowManagerAddShadowHint public bool WindowManagerAddShadowHint
{ {
get { return GetValue(WindowManagerAddShadowHintProperty); } get => GetValue(WindowManagerAddShadowHintProperty);
set { SetValue(WindowManagerAddShadowHintProperty, value); } set => SetValue(WindowManagerAddShadowHintProperty, value);
} }
/// <summary> /// <inheritdoc cref="Popup.PlacementRect"/>
/// 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>
public Rect? PlacementRect public Rect? PlacementRect
{ {
get { return GetValue(PlacementRectProperty); } get => GetValue(PlacementRectProperty);
set { SetValue(PlacementRectProperty, value); } set => SetValue(PlacementRectProperty, value);
} }
/// <summary> /// <inheritdoc cref="Popup.PlacementTarget"/>
/// Gets or sets the control that is used to determine the popup's position.
/// </summary>
public Control? PlacementTarget public Control? PlacementTarget
{ {
get { return GetValue(PlacementTargetProperty); } get => GetValue(PlacementTargetProperty);
set { SetValue(PlacementTargetProperty, value); } set => SetValue(PlacementTargetProperty, value);
} }
/// <summary> /// <summary>
@ -343,9 +330,9 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control); ((ISetLogicalParent)_popup).SetParent(control);
} }
_popup.PlacementMode = !requestedByPointer && PlacementMode == PlacementMode.Pointer _popup.Placement = !requestedByPointer && Placement == PlacementMode.Pointer
? PlacementMode.Bottom ? PlacementMode.Bottom
: PlacementMode; : Placement;
//Position of the line below is really important. //Position of the line below is really important.
//All styles are being applied only when control has logical parent. //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; _presenter.Date = SelectedDate ?? DateTimeOffset.Now;
_popup.PlacementMode = PlacementMode.AnchorAndGravity; _popup.Placement = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom; _popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom; _popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; _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; _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
_popup.PlacementMode = PlacementMode.AnchorAndGravity; _popup.Placement = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom; _popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom; _popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; _popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;

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

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

31
src/Avalonia.Controls/ItemsControl.cs

@ -274,6 +274,34 @@ namespace Avalonia.Controls
remove => _childIndexChanged -= value; 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 /> /// <inheritdoc />
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{ {
@ -649,18 +677,21 @@ namespace Avalonia.Controls
{ {
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, index)); _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, index));
_scrollViewer?.RegisterAnchorCandidate(container); _scrollViewer?.RegisterAnchorCandidate(container);
ContainerPrepared?.Invoke(this, new(container, index));
} }
internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex) internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex)
{ {
ContainerIndexChangedOverride(container, oldIndex, newIndex); ContainerIndexChangedOverride(container, oldIndex, newIndex);
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, newIndex)); _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, newIndex));
ContainerIndexChanged?.Invoke(this, new(container, oldIndex, newIndex));
} }
internal void ClearItemContainer(Control container) internal void ClearItemContainer(Control container)
{ {
_scrollViewer?.UnregisterAnchorCandidate(container); _scrollViewer?.UnregisterAnchorCandidate(container);
ClearContainerForItemOverride(container); ClearContainerForItemOverride(container);
ContainerClearing?.Invoke(this, new(container));
} }
private void AddControlItemsToLogicalChildren(IEnumerable? items) private void AddControlItemsToLogicalChildren(IEnumerable? items)

4
src/Avalonia.Controls/LayoutTransformControl.cs

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

2
src/Avalonia.Controls/NativeControlHost.cs

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

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

@ -69,8 +69,7 @@ namespace Avalonia.Controls.Presenters
var c = children[index + i]; var c = children[index + i];
if (!c.IsSet(ItemIsOwnContainerProperty)) if (!c.IsSet(ItemIsOwnContainerProperty))
itemsControl.RemoveLogicalChild(children[i + index]); itemsControl.RemoveLogicalChild(children[i + index]);
else generator.ClearItemContainer(c);
generator.ClearItemContainer(c);
} }
children.RemoveRange(index, count); children.RemoveRange(index, count);

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

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

2
src/Avalonia.Controls/SelectableTextBlock.cs

@ -177,7 +177,7 @@ namespace Avalonia.Controls
var rects = TextLayout.HitTestTextRange(start, length); var rects = TextLayout.HitTestTextRange(start, length);
using (context.PushPostTransform(Matrix.CreateTranslation(origin))) using (context.PushTransform(Matrix.CreateTranslation(origin)))
{ {
foreach (var rect in rects) foreach (var rect in rects)
{ {

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

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

1
src/Avalonia.Controls/TopLevel.cs

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

5
src/Avalonia.Controls/VirtualizingStackPanel.cs

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

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

@ -127,7 +127,7 @@
<Popup Name="PART_Popup" WindowManagerAddShadowHint="False" <Popup Name="PART_Popup" WindowManagerAddShadowHint="False"
IsLightDismissEnabled="True" PlacementTarget="{TemplateBinding}" IsLightDismissEnabled="True" PlacementTarget="{TemplateBinding}"
PlacementMode="Bottom"> Placement="Bottom">
<DatePickerPresenter Name="PART_PickerPresenter" /> <DatePickerPresenter Name="PART_PickerPresenter" />
</Popup> </Popup>

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

@ -37,7 +37,7 @@
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}" MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
IsLightDismissEnabled="True" IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}" IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
PlacementMode="BottomEdgeAlignedLeft" Placement="BottomEdgeAlignedLeft"
OverlayInputPassThroughElement="{Binding $parent[Menu]}"> OverlayInputPassThroughElement="{Binding $parent[Menu]}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}" <Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}" BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"

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

@ -118,7 +118,7 @@
</Border> </Border>
<Popup Name="PART_Popup" <Popup Name="PART_Popup"
WindowManagerAddShadowHint="False" WindowManagerAddShadowHint="False"
PlacementMode="Right" Placement="Right"
HorizontalOffset="{DynamicResource MenuFlyoutSubItemPopupHorizontalOffset}" HorizontalOffset="{DynamicResource MenuFlyoutSubItemPopupHorizontalOffset}"
IsLightDismissEnabled="False" IsLightDismissEnabled="False"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"> IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">

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

@ -151,7 +151,7 @@
WindowManagerAddShadowHint="False" WindowManagerAddShadowHint="False"
IsLightDismissEnabled="True" IsLightDismissEnabled="True"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
PlacementMode="Bottom"> Placement="Bottom">
<TimePickerPresenter Name="PART_PickerPresenter" /> <TimePickerPresenter Name="PART_PickerPresenter" />
</Popup> </Popup>

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

@ -142,7 +142,7 @@
<Popup Name="PART_Popup" <Popup Name="PART_Popup"
IsLightDismissEnabled="True" IsLightDismissEnabled="True"
PlacementMode="Bottom" Placement="Bottom"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
WindowManagerAddShadowHint="False"> WindowManagerAddShadowHint="False">
<DatePickerPresenter Name="PART_PickerPresenter" /> <DatePickerPresenter Name="PART_PickerPresenter" />

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

@ -27,7 +27,7 @@
<Popup Name="PART_Popup" <Popup Name="PART_Popup"
IsLightDismissEnabled="True" IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}" IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
PlacementMode="BottomEdgeAlignedLeft" Placement="BottomEdgeAlignedLeft"
OverlayInputPassThroughElement="{Binding $parent[Menu]}"> OverlayInputPassThroughElement="{Binding $parent[Menu]}">
<Border Background="{DynamicResource ThemeBackgroundBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"

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

@ -65,7 +65,7 @@
IsLightDismissEnabled="False" IsLightDismissEnabled="False"
IsOpen="{TemplateBinding IsSubMenuOpen, IsOpen="{TemplateBinding IsSubMenuOpen,
Mode=TwoWay}" Mode=TwoWay}"
PlacementMode="Right"> Placement="Right">
<Border Background="{DynamicResource ThemeBackgroundBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">

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

@ -156,7 +156,7 @@
<Popup Name="PART_Popup" <Popup Name="PART_Popup"
IsLightDismissEnabled="True" IsLightDismissEnabled="True"
PlacementMode="Bottom" Placement="Bottom"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
WindowManagerAddShadowHint="False"> WindowManagerAddShadowHint="False">
<TimePickerPresenter Name="PART_PickerPresenter" /> <TimePickerPresenter Name="PART_PickerPresenter" />

4
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -375,7 +375,7 @@ namespace Avalonia.Skia
foreach (var boxShadow in boxShadows) foreach (var boxShadow in boxShadows)
{ {
if (!boxShadow.IsDefault && !boxShadow.IsInset) if (boxShadow != default && !boxShadow.IsInset)
{ {
using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity)) using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity))
{ {
@ -432,7 +432,7 @@ namespace Avalonia.Skia
foreach (var boxShadow in boxShadows) foreach (var boxShadow in boxShadows)
{ {
if (!boxShadow.IsDefault && boxShadow.IsInset) if (boxShadow != default && boxShadow.IsInset)
{ {
using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity)) using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity))
{ {

2
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -303,7 +303,7 @@ namespace Avalonia.Controls.UnitTests
window.Show(); window.Show();
var c = new ContextMenu(); var c = new ContextMenu();
c.PlacementMode = PlacementMode.Bottom; c.Placement = PlacementMode.Bottom;
c.Open(button); c.Open(button);
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);

130
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
@ -831,6 +832,135 @@ namespace Avalonia.Controls.UnitTests
Assert.Throws<InvalidOperationException>(() => target.DisplayMemberBinding = new Binding("Length")); Assert.Throws<InvalidOperationException>(() => target.DisplayMemberBinding = new Binding("Length"));
} }
[Fact]
public void ContainerPrepared_Is_Raised_For_Each_Item_Container_On_Layout()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
var result = new List<Control>();
var index = 0;
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(index++, e.Index);
result.Add(e.Container);
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(3, result.Count);
Assert.Equal(target.GetRealizedContainers(), result);
}
[Fact]
public void ContainerPrepared_Is_Raised_For_Each_ItemsSource_Container_On_Layout()
{
var target = new ItemsControl
{
Template = GetTemplate(),
ItemsSource = new[] { "Foo", "Bar", "Baz" },
};
var result = new List<Control>();
var index = 0;
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(index++, e.Index);
result.Add(e.Container);
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(3, result.Count);
Assert.Equal(target.GetRealizedContainers(), result);
}
[Fact]
public void ContainerPrepared_Is_Raised_For_Added_Item()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var result = new List<Control>();
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(3, e.Index);
result.Add(e.Container);
};
target.Items.Add("Qux");
Assert.Equal(1, result.Count);
}
[Fact]
public void ContainerIndexChanged_Is_Raised_When_Item_Added()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var result = new List<Control>();
var index = 1;
target.ContainerIndexChanged += (s, e) =>
{
Assert.Equal(index++, e.OldIndex);
Assert.Equal(index, e.NewIndex);
result.Add(e.Container);
};
target.Items.Insert(1, "Qux");
Assert.Equal(2, result.Count);
Assert.Equal(target.GetRealizedContainers().Skip(2), result);
}
[Fact]
public void ContainerClearing_Is_Raised_When_Item_Removed()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var expected = target.ContainerFromIndex(1);
var raised = 0;
target.ContainerClearing += (s, e) =>
{
Assert.Same(expected, e.Container);
++raised;
};
target.Items.RemoveAt(1);
Assert.Equal(1, raised);
}
private class Item private class Item
{ {
public Item(string value) public Item(string value)

140
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -785,6 +785,146 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void ContainerPrepared_Is_Raised_For_Each_Item_Container_On_Layout()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var target = new ListBox
{
Template = ListBoxTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
var result = new List<Control>();
var index = 0;
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(index++, e.Index);
result.Add(e.Container);
};
Prepare(target);
Assert.Equal(3, result.Count);
Assert.Equal(target.GetRealizedContainers(), result);
}
[Fact]
public void ContainerPrepared_Is_Raised_For_Each_ItemsSource_Container_On_Layout()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = new[] { "Foo", "Bar", "Baz" },
};
var result = new List<Control>();
var index = 0;
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(index++, e.Index);
result.Add(e.Container);
};
Prepare(target);
Assert.Equal(3, result.Count);
Assert.Equal(target.GetRealizedContainers(), result);
}
[Fact]
public void ContainerPrepared_Is_Raised_For_Added_Item()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var data = new AvaloniaList<string> { "Foo", "Bar", "Baz" };
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = data,
};
Prepare(target);
var result = new List<Control>();
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(3, e.Index);
result.Add(e.Container);
};
data.Add("Qux");
Layout(target);
Assert.Equal(1, result.Count);
}
[Fact]
public void ContainerIndexChanged_Is_Raised_When_Item_Added()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var data = new AvaloniaList<string> { "Foo", "Bar", "Baz" };
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = data,
};
Prepare(target);
var result = new List<Control>();
var index = 1;
target.ContainerIndexChanged += (s, e) =>
{
Assert.Equal(index++, e.OldIndex);
Assert.Equal(index, e.NewIndex);
result.Add(e.Container);
};
data.Insert(1, "Qux");
Layout(target);
Assert.Equal(2, result.Count);
Assert.Equal(target.GetRealizedContainers().Skip(2), result);
}
[Fact]
public void ContainerClearing_Is_Raised_When_Item_Removed()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
var data = new AvaloniaList<string> { "Foo", "Bar", "Baz" };
var target = new ListBox
{
Template = ListBoxTemplate(),
ItemsSource = data,
};
Prepare(target);
var expected = target.ContainerFromIndex(1);
var raised = 0;
target.ContainerClearing += (s, e) =>
{
Assert.Same(expected, e.Container);
++raised;
};
data.RemoveAt(1);
Layout(target);
Assert.Equal(1, raised);
}
private class ResettingCollection : List<string>, INotifyCollectionChanged private class ResettingCollection : List<string>, INotifyCollectionChanged
{ {
public ResettingCollection(int itemCount) public ResettingCollection(int itemCount)

2
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var window = new Window(); var window = new Window();
var target = new Popup {PlacementMode = PlacementMode.Pointer}; var target = new Popup {Placement = PlacementMode.Pointer};
var child = new Control(); var child = new Control();
window.Content = target; window.Content = target;

20
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -207,7 +207,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
using (CreateServices()) using (CreateServices())
{ {
var target = new Popup() {PlacementMode = PlacementMode.Pointer}; var target = new Popup() {Placement = PlacementMode.Pointer};
var root = PreparedWindow(target); var root = PreparedWindow(target);
target.Open(); target.Open();
@ -226,7 +226,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using (CreateServices()) using (CreateServices())
{ {
var window = PreparedWindow(); var window = PreparedWindow();
var target = new Popup() {PlacementMode = PlacementMode.Pointer}; var target = new Popup() {Placement = PlacementMode.Pointer};
window.Content = target; window.Content = target;
@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using (CreateServices()) using (CreateServices())
{ {
var window = PreparedWindow(); var window = PreparedWindow();
var target = new Popup() {PlacementMode = PlacementMode.Pointer}; var target = new Popup() {Placement = PlacementMode.Pointer};
window.Content = target; window.Content = target;
window.ApplyTemplate(); window.ApplyTemplate();
@ -274,7 +274,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
using (CreateServices()) using (CreateServices())
{ {
var window = PreparedWindow(); var window = PreparedWindow();
var target = new Popup() { PlacementMode = PlacementMode.Pointer }; var target = new Popup() { Placement = PlacementMode.Pointer };
window.Content = target; window.Content = target;
window.ApplyTemplate(); window.ApplyTemplate();
@ -742,7 +742,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Width = 400, Width = 400,
Height = 200, Height = 200,
PlacementMode = PlacementMode.Pointer Placement = PlacementMode.Pointer
}; };
var window = PreparedWindow(popup); var window = PreparedWindow(popup);
window.Show(); window.Show();
@ -791,7 +791,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var popup = new Popup() var popup = new Popup()
{ {
PlacementTarget = placementTarget, PlacementTarget = placementTarget,
PlacementMode = PlacementMode.Bottom, Placement = PlacementMode.Bottom,
Width = 10, Width = 10,
Height = 10 Height = 10
}; };
@ -852,7 +852,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var popup = new Popup() var popup = new Popup()
{ {
PlacementTarget = placementTarget, PlacementTarget = placementTarget,
PlacementMode = PlacementMode.Pointer, Placement = PlacementMode.Pointer,
Width = 10, Width = 10,
Height = 10 Height = 10
}; };
@ -907,7 +907,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var popup = new Popup() var popup = new Popup()
{ {
PlacementTarget = placementTarget, PlacementTarget = placementTarget,
PlacementMode = PlacementMode.Bottom, Placement = PlacementMode.Bottom,
Width = 10, Width = 10,
Height = 10 Height = 10
}; };
@ -967,7 +967,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
var popup = new Popup() var popup = new Popup()
{ {
PlacementTarget = placementTarget, PlacementTarget = placementTarget,
PlacementMode = PlacementMode.Pointer, Placement = PlacementMode.Pointer,
Width = 10, Width = 10,
Height = 10 Height = 10
}; };
@ -1020,7 +1020,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Width = 20, Width = 20,
Height = 20, Height = 20,
PlacementTarget = parentPopup, PlacementTarget = parentPopup,
PlacementMode = PlacementMode.AnchorAndGravity, Placement = PlacementMode.AnchorAndGravity,
PlacementAnchor = PopupAnchor.TopLeft, PlacementAnchor = PopupAnchor.TopLeft,
PlacementGravity = PopupGravity.BottomRight PlacementGravity = PopupGravity.BottomRight
}; };

30
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -406,6 +406,36 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void ContainerPrepared_Is_Raised_When_Scrolling()
{
using var app = App();
var (target, scroll, itemsControl) = CreateTarget();
var raised = 0;
itemsControl.ContainerPrepared += (s, e) => ++raised;
scroll.Offset = new Vector(0, 200);
Layout(target);
Assert.Equal(10, raised);
}
[Fact]
public void ContainerClearing_Is_Raised_When_Scrolling()
{
using var app = App();
var (target, scroll, itemsControl) = CreateTarget();
var raised = 0;
itemsControl.ContainerClearing += (s, e) => ++raised;
scroll.Offset = new Vector(0, 200);
Layout(target);
Assert.Equal(10, raised);
}
private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl)
{ {
return target.GetRealizedElements() return target.GetRealizedElements()

19
tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs

@ -11,19 +11,26 @@ using Xunit;
namespace Avalonia.IntegrationTests.Appium namespace Avalonia.IntegrationTests.Appium
{ {
public record class WindowChrome(
AppiumWebElement? Close,
AppiumWebElement? Minimize,
AppiumWebElement? Maximize,
AppiumWebElement? FullScreen);
internal static class ElementExtensions internal static class ElementExtensions
{ {
public static IReadOnlyList<AppiumWebElement> GetChildren(this AppiumWebElement element) => public static IReadOnlyList<AppiumWebElement> GetChildren(this AppiumWebElement element) =>
element.FindElementsByXPath("*/*"); element.FindElementsByXPath("*/*");
public static (AppiumWebElement close, AppiumWebElement minimize, AppiumWebElement maximize) GetChromeButtons(this AppiumWebElement window) public static WindowChrome GetChromeButtons(this AppiumWebElement window)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{ {
var closeButton = window.FindElementByXPath("//XCUIElementTypeButton[1]"); var closeButton = window.FindElementsByAccessibilityId("_XCUI:CloseWindow").FirstOrDefault();
var fullscreenButton = window.FindElementByXPath("//XCUIElementTypeButton[2]"); var fullscreenButton = window.FindElementsByAccessibilityId("_XCUI:FullScreenWindow").FirstOrDefault();
var minimizeButton = window.FindElementByXPath("//XCUIElementTypeButton[3]"); var minimizeButton = window.FindElementsByAccessibilityId("_XCUI:MinimizeWindow").FirstOrDefault();
return (closeButton, minimizeButton, fullscreenButton); var zoomButton = window.FindElementsByAccessibilityId("_XCUI:ZoomWindow").FirstOrDefault();
return new(closeButton, minimizeButton, zoomButton, fullscreenButton);
} }
throw new NotSupportedException("GetChromeButtons not supported on this platform."); throw new NotSupportedException("GetChromeButtons not supported on this platform.");
@ -138,7 +145,7 @@ namespace Avalonia.IntegrationTests.Appium
var text = windows.Select(x => x.Text).ToList(); var text = windows.Select(x => x.Text).ToList();
var newWindow = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")) var newWindow = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"))
.First(x => x.Text == newWindowTitle); .First(x => x.Text == newWindowTitle);
var (close, _, _) = ((AppiumWebElement)newWindow).GetChromeButtons(); var close = ((AppiumWebElement)newWindow).FindElementByAccessibilityId("_XCUI:CloseWindow");
close!.Click(); close!.Click();
Thread.Sleep(1000); Thread.Sleep(1000);
}); });

13
tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs

@ -17,6 +17,7 @@ namespace Avalonia
internal class PlatformFactAttribute : FactAttribute internal class PlatformFactAttribute : FactAttribute
{ {
private readonly string? _reason; private readonly string? _reason;
private string? _skip;
public PlatformFactAttribute(TestPlatforms platforms, string? reason = null) public PlatformFactAttribute(TestPlatforms platforms, string? reason = null)
{ {
@ -28,8 +29,16 @@ namespace Avalonia
public override string? Skip public override string? Skip
{ {
get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}" + (_reason is not null ? $" reason: \"{_reason}\"" : ""); get
set => throw new NotSupportedException(); {
if (_skip is not null)
return _skip;
if (!IsSupported())
return $"Ignored on {RuntimeInformation.OSDescription}" +
(_reason is not null ? $" reason: '{_reason}'" : "");
return null;
}
set => _skip = value;
} }
private bool IsSupported() private bool IsSupported()

11
tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs

@ -7,14 +7,21 @@ namespace Avalonia.IntegrationTests.Appium
{ {
internal class PlatformTheoryAttribute : TheoryAttribute internal class PlatformTheoryAttribute : TheoryAttribute
{ {
private string? _skip;
public PlatformTheoryAttribute(TestPlatforms platforms = TestPlatforms.All) => Platforms = platforms; public PlatformTheoryAttribute(TestPlatforms platforms = TestPlatforms.All) => Platforms = platforms;
public TestPlatforms Platforms { get; } public TestPlatforms Platforms { get; }
public override string? Skip public override string? Skip
{ {
get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}"; get
set => throw new NotSupportedException(); {
if (_skip is not null)
return _skip;
return !IsSupported() ? $"Ignored on {RuntimeInformation.OSDescription}" : null;
}
set => _skip = value;
} }
private bool IsSupported() private bool IsSupported()

124
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@ -83,9 +83,9 @@ namespace Avalonia.IntegrationTests.Appium
public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen()
{ {
var mainWindow = GetWindow("MainWindow"); var mainWindow = GetWindow("MainWindow");
var buttons = mainWindow.GetChromeButtons(); var fullScreen = mainWindow.FindElementByAccessibilityId("_XCUI:FullScreenWindow");
buttons.maximize.Click(); fullScreen.Click();
Thread.Sleep(500); Thread.Sleep(500);
@ -239,17 +239,18 @@ namespace Avalonia.IntegrationTests.Appium
public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown()
{ {
var window = GetWindow("MainWindow"); var window = GetWindow("MainWindow");
var (closeButton, miniaturizeButton, zoomButton) = window.GetChromeButtons(); var windowChrome = window.GetChromeButtons();
Assert.True(closeButton.Enabled); Assert.True(windowChrome.Close!.Enabled);
Assert.True(zoomButton.Enabled); Assert.True(windowChrome.FullScreen!.Enabled);
Assert.True(miniaturizeButton.Enabled); Assert.True(windowChrome.Minimize!.Enabled);
Assert.Null(windowChrome.Maximize);
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner))
{ {
Assert.False(closeButton.Enabled); Assert.False(windowChrome.Close!.Enabled);
Assert.False(zoomButton.Enabled); Assert.False(windowChrome.FullScreen!.Enabled);
Assert.False(miniaturizeButton.Enabled); Assert.False(windowChrome.Minimize!.Enabled);
} }
} }
@ -259,11 +260,11 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner))
{ {
var secondaryWindow = GetWindow("SecondaryWindow"); var secondaryWindow = GetWindow("SecondaryWindow");
var (closeButton, miniaturizeButton, zoomButton) = secondaryWindow.GetChromeButtons(); var windowChrome = secondaryWindow.GetChromeButtons();
Assert.True(closeButton.Enabled); Assert.True(windowChrome.Close!.Enabled);
Assert.True(zoomButton.Enabled); Assert.True(windowChrome.Maximize!.Enabled);
Assert.False(miniaturizeButton.Enabled); Assert.False(windowChrome.Minimize!.Enabled);
} }
} }
@ -274,7 +275,7 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual)) using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
{ {
var secondaryWindow = GetWindow("SecondaryWindow"); var secondaryWindow = GetWindow("SecondaryWindow");
var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons(); var miniaturizeButton = secondaryWindow.FindElementByAccessibilityId("_XCUI:MinimizeWindow");
Assert.False(miniaturizeButton.Enabled); Assert.False(miniaturizeButton.Enabled);
} }
@ -288,7 +289,7 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual)) using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
{ {
var secondaryWindow = GetWindow("SecondaryWindow"); var secondaryWindow = GetWindow("SecondaryWindow");
var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons(); var miniaturizeButton = secondaryWindow.FindElementByAccessibilityId("_XCUI:MinimizeWindow");
miniaturizeButton.Click(); miniaturizeButton.Click();
Thread.Sleep(1000); Thread.Sleep(1000);
@ -332,7 +333,7 @@ namespace Avalonia.IntegrationTests.Appium
// Close the window manually. // Close the window manually.
secondaryWindow = GetWindow("SecondaryWindow"); secondaryWindow = GetWindow("SecondaryWindow");
secondaryWindow.GetChromeButtons().close.Click(); secondaryWindow.FindElementByAccessibilityId("_XCUI:CloseWindow").Click();
} }
[PlatformTheory(TestPlatforms.MacOS)] [PlatformTheory(TestPlatforms.MacOS)]
@ -344,22 +345,92 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false)) using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false))
{ {
var secondaryWindow = GetWindow("SecondaryWindow"); var secondaryWindow = GetWindow("SecondaryWindow");
var (_, _, zoomButton) = secondaryWindow.GetChromeButtons(); var zoomButton = mode == ShowWindowMode.NonOwned ?
secondaryWindow.FindElementByAccessibilityId("_XCUI:FullScreenWindow") :
secondaryWindow.FindElementByAccessibilityId("_XCUI:ZoomWindow");
Assert.False(zoomButton.Enabled); Assert.False(zoomButton.Enabled);
} }
} }
[PlatformFact(TestPlatforms.MacOS)]
public void Toggling_SystemDecorations_Should_Preserve_ExtendClientArea()
{
// #10650
using (OpenWindow(extendClientArea: true))
{
var secondaryWindow = GetWindow("SecondaryWindow");
// The XPath of the title bar text _should_ be "XCUIElementTypeStaticText"
// but Appium seems to put a fake node between the window and the title bar
// https://stackoverflow.com/a/71914227/6448
var titleBar = secondaryWindow.FindElementsByXPath("/*/XCUIElementTypeStaticText").Count;
Assert.Equal(0, titleBar);
secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
_session.FindElementByAccessibilityId("SystemDecorationsNone").SendClick();
secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
_session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
titleBar = secondaryWindow.FindElementsByXPath("/*/XCUIElementTypeStaticText").Count;
Assert.Equal(0, titleBar);
}
}
[PlatformTheory(TestPlatforms.MacOS)]
[InlineData(SystemDecorations.None)]
[InlineData(SystemDecorations.BorderOnly)]
[InlineData(SystemDecorations.Full)]
public void ExtendClientArea_SystemDecorations_Shows_Correct_Buttons(SystemDecorations decorations)
{
// #10650
using (OpenWindow(extendClientArea: true, systemDecorations: decorations))
{
var secondaryWindow = GetWindow("SecondaryWindow");
try
{
var chrome = secondaryWindow.GetChromeButtons();
if (decorations == SystemDecorations.Full)
{
Assert.NotNull(chrome.Close);
Assert.NotNull(chrome.Minimize);
Assert.NotNull(chrome.FullScreen);
}
else
{
Assert.Null(chrome.Close);
Assert.Null(chrome.Minimize);
Assert.Null(chrome.FullScreen);
}
}
finally
{
if (decorations != SystemDecorations.Full)
{
secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
_session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
}
}
}
}
private IDisposable OpenWindow( private IDisposable OpenWindow(
PixelSize? size, PixelSize? size = null,
ShowWindowMode mode, ShowWindowMode mode = ShowWindowMode.NonOwned,
WindowStartupLocation location, WindowStartupLocation location = WindowStartupLocation.Manual,
bool canResize = true) bool canResize = true,
SystemDecorations systemDecorations = SystemDecorations.Full,
bool extendClientArea = false)
{ {
var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");
var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode");
var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation");
var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize");
var showButton = _session.FindElementByAccessibilityId("ShowWindow"); var showButton = _session.FindElementByAccessibilityId("ShowWindow");
var systemDecorationsComboBox = _session.FindElementByAccessibilityId("ShowWindowSystemDecorations");
var extendClientAreaCheckBox = _session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint");
if (size.HasValue) if (size.HasValue)
sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}");
@ -379,6 +450,15 @@ namespace Avalonia.IntegrationTests.Appium
if (canResizeCheckBox.GetIsChecked() != canResize) if (canResizeCheckBox.GetIsChecked() != canResize)
canResizeCheckBox.Click(); canResizeCheckBox.Click();
if (systemDecorationsComboBox.GetComboBoxValue() != systemDecorations.ToString())
{
systemDecorationsComboBox.Click();
_session.FindElementByName(systemDecorations.ToString()).SendClick();
}
if (extendClientAreaCheckBox.GetIsChecked() != extendClientArea)
extendClientAreaCheckBox.Click();
return showButton.OpenWindowWithClick(); return showButton.OpenWindowWithClick();
} }

4
tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs

@ -88,7 +88,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Height = 200, Height = 200,
Child = new CustomRenderer((control, context) => Child = new CustomRenderer((control, context) =>
{ {
using (var transform = context.PushPreTransform(Matrix.CreateTranslation(100, 100))) using (var transform = context.PushTransform(Matrix.CreateTranslation(100, 100)))
using (var clip = context.PushClip(new Rect(0, 0, 100, 100))) using (var clip = context.PushClip(new Rect(0, 0, 100, 100)))
{ {
context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200)); context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200));
@ -112,7 +112,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Height = 200, Height = 200,
Child = new CustomRenderer((control, context) => Child = new CustomRenderer((control, context) =>
{ {
using (var transform = context.PushPreTransform(Matrix.CreateTranslation(100, 100))) using (var transform = context.PushTransform(Matrix.CreateTranslation(100, 100)))
using (var clip = context.PushClip(new Rect(0, 0, 100, 100))) using (var clip = context.PushClip(new Rect(0, 0, 100, 100)))
{ {
context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200)); context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200));

2
tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs

@ -200,7 +200,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
Child = new DrawnControl(c => Child = new DrawnControl(c =>
{ {
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
using (c.PushPreTransform(Matrix.CreateTranslation(100, 100))) using (c.PushTransform(Matrix.CreateTranslation(100, 100)))
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
}), }),
}; };

2
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@ -95,7 +95,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{ {
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
using (c.PushPreTransform(Matrix.CreateTranslation(100, 100))) using (c.PushTransform(Matrix.CreateTranslation(100, 100)))
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
}), }),
}; };

2
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -185,7 +185,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
Child = new DrawnControl(c => Child = new DrawnControl(c =>
{ {
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
using (c.PushPreTransform(Matrix.CreateTranslation(100, 100))) using (c.PushTransform(Matrix.CreateTranslation(100, 100)))
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100)); c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
}), }),
}; };

2
tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs

@ -312,7 +312,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var rotate = Matrix.CreateTranslation(-100, -100) * var rotate = Matrix.CreateTranslation(-100, -100) *
Matrix.CreateRotation(MathUtilities.Deg2Rad(90)) * Matrix.CreateRotation(MathUtilities.Deg2Rad(90)) *
Matrix.CreateTranslation(100, 100); Matrix.CreateTranslation(100, 100);
using var transform = c.PushPreTransform(rotate); using var transform = c.PushTransform(rotate);
c.DrawRectangle(Brushes.Yellow, null, rect); c.DrawRectangle(Brushes.Yellow, null, rect);
t.Draw(c, rect.Position); t.Draw(c, rect.Position);
}), }),

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -792,7 +792,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
public override double Baseline => 0; public override double Baseline => 0;
public override void Draw(DrawingContext drawingContext, Point origin) public override void Draw(DrawingContext drawingContext, Point origin)
{ {
using (drawingContext.PushPreTransform(Matrix.CreateTranslation(new Vector(origin.X, 0)))) using (drawingContext.PushTransform(Matrix.CreateTranslation(new Vector(origin.X, 0))))
{ {
drawingContext.FillRectangle(_fill, _rect); drawingContext.FillRectangle(_fill, _rect);
} }

Loading…
Cancel
Save