Browse Source

Merge branch 'master' into fixes/3328-move-treeview

pull/3342/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
65e4470fd2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      native/Avalonia.Native/src/OSX/gl.mm
  2. 2
      native/Avalonia.Native/src/OSX/window.h
  3. 92
      native/Avalonia.Native/src/OSX/window.mm
  4. 2
      readme.md
  5. 6
      samples/ControlCatalog/MainView.xaml
  6. 85
      samples/ControlCatalog/Pages/ImagePage.xaml
  7. 39
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  8. 2
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  9. 64
      scripts/avalonia-rename.ps1
  10. 2
      src/Avalonia.Animation/IterationCount.cs
  11. 40
      src/Avalonia.Base/Collections/Pooled/ClearMode.cs
  12. 31
      src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs
  13. 21
      src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
  14. 1531
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  15. 699
      src/Avalonia.Base/Collections/Pooled/PooledStack.cs
  16. 28
      src/Avalonia.Base/Collections/Pooled/StackDebugView.cs
  17. 691
      src/Avalonia.Base/Collections/Pooled/ThrowHelper.cs
  18. 4
      src/Avalonia.Controls/DrawingPresenter.cs
  19. 45
      src/Avalonia.Controls/Image.cs
  20. 5
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  21. 5
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  22. 7
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  23. 2
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  24. 38
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  25. 126
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  26. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  27. 34
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  28. 77
      src/Avalonia.Controls/Repeater/ViewManager.cs
  29. 6
      src/Avalonia.Controls/TextBox.cs
  30. 3
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
  31. 3
      src/Avalonia.Input/AccessKeyHandler.cs
  32. 14
      src/Avalonia.Input/DragDropDevice.cs
  33. 23
      src/Avalonia.Input/DragEventArgs.cs
  34. 4
      src/Avalonia.Input/InputExtensions.cs
  35. 14
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  36. 7
      src/Avalonia.Input/Raw/RawDragEvent.cs
  37. 32
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  38. 16
      src/Avalonia.Layout/NonVirtualizingLayout.cs
  39. 14
      src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
  40. 2
      src/Avalonia.Layout/StackLayout.cs
  41. 54
      src/Avalonia.Layout/UniformGridLayout.cs
  42. 34
      src/Avalonia.Layout/UniformGridLayoutState.cs
  43. 5
      src/Avalonia.Native/Avalonia.Native.csproj
  44. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  45. 14
      src/Avalonia.Native/CallbackBase.cs
  46. 59
      src/Avalonia.Native/PlatformThreadingInterface.cs
  47. 5
      src/Avalonia.Native/WindowImplBase.cs
  48. 14
      src/Avalonia.Styling/Controls/ISetResourceParent.cs
  49. 62
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  50. 4
      src/Avalonia.Styling/StyledElement.cs
  51. 12
      src/Avalonia.Styling/Styling/Style.cs
  52. 20
      src/Avalonia.Styling/Styling/Styles.cs
  53. 8
      src/Avalonia.Visuals/Media/Color.cs
  54. 6
      src/Avalonia.Visuals/Media/Drawing.cs
  55. 21
      src/Avalonia.Visuals/Media/DrawingContext.cs
  56. 3
      src/Avalonia.Visuals/Media/DrawingGroup.cs
  57. 81
      src/Avalonia.Visuals/Media/DrawingImage.cs
  58. 5
      src/Avalonia.Visuals/Media/GeometryDrawing.cs
  59. 29
      src/Avalonia.Visuals/Media/IImage.cs
  60. 20
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  61. 11
      src/Avalonia.Visuals/Media/Imaging/IBitmap.cs
  62. 90
      src/Avalonia.Visuals/Media/MediaExtensions.cs
  63. 25
      src/Avalonia.Visuals/Media/StretchDirection.cs
  64. 4
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  65. 32
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  66. 13
      src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
  67. 12
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  68. 9
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  69. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  70. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  71. 174
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  72. 7
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  73. 29
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  74. 18
      src/Avalonia.X11/Glx/Glx.cs
  75. 2
      src/Avalonia.X11/Glx/GlxDisplay.cs
  76. 4
      src/Avalonia.X11/X11Framebuffer.cs
  77. 2
      src/Avalonia.X11/X11IconLoader.cs
  78. 2
      src/Avalonia.X11/X11Platform.cs
  79. 8
      src/Avalonia.X11/X11Window.cs
  80. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  81. 26
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  82. 10
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  83. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  84. 2
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  85. 8
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  86. 5
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  87. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  88. 2
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  89. 3
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  90. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  91. 14
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  92. 76
      src/Windows/Avalonia.Win32/Input/KeyInterop.cs
  93. 175
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  94. 8
      src/Windows/Avalonia.Win32/WindowImpl.cs
  95. 2
      tests/Avalonia.Benchmarks/NullRenderer.cs
  96. 70
      tests/Avalonia.Controls.UnitTests/ImageTests.cs
  97. 17
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  98. 54
      tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs
  99. 24
      tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
  100. 3
      tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs

24
native/Avalonia.Native/src/OSX/gl.mm

@ -1,6 +1,7 @@
#include "common.h"
#include <OpenGL/gl.h>
#include <dlfcn.h>
#include "window.h"
template <typename T, size_t N> char (&ArrayCounter(T (&a)[N]))[N];
#define ARRAY_COUNT(a) (sizeof(ArrayCounter(a)))
@ -181,12 +182,12 @@ extern IAvnGlFeature* GetGlFeature()
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession>
{
NSView* _view;
NSWindow* _window;
AvnView* _view;
AvnWindow* _window;
NSOpenGLContext* _context;
public:
FORWARD_IUNKNOWN()
AvnGlRenderingSession(NSWindow*window, NSView* view, NSOpenGLContext* context)
AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context)
{
_context = context;
_window = window;
@ -195,14 +196,12 @@ public:
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
{
auto fsize = [_view convertSizeToBacking: [_view frame].size];
ret->Width = (int)fsize.width;
ret->Height = (int)fsize.height;
*ret = [_view getPixelSize];
return S_OK;
}
virtual HRESULT GetScaling(double* ret) override
{
*ret = [_window backingScaleFactor];
*ret = [_window getScaling];
return S_OK;
}
@ -234,8 +233,17 @@ public:
auto f = GetFeature();
if(f == NULL)
return E_FAIL;
if(![_view lockFocusIfCanDraw])
@try
{
if(![_view lockFocusIfCanDraw])
return E_ABORT;
}
@catch(NSException* exception)
{
return E_ABORT;
}
auto gl = _context;
CGLLockContext([_context CGLContextObj]);

2
native/Avalonia.Native/src/OSX/window.h

@ -12,6 +12,7 @@ class WindowBaseImpl;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
-(AvnPixelSize) getPixelSize;
@end
@interface AvnWindow : NSWindow <NSWindowDelegate>
@ -22,6 +23,7 @@ class WindowBaseImpl;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
-(void) applyMenu:(NSMenu *)menu;
-(double) getScaling;
@end
struct INSWindowHolder

92
native/Avalonia.Native/src/OSX/window.mm

@ -195,7 +195,11 @@ public:
{
@autoreleasepool
{
[Window close];
if (Window != nullptr)
{
[Window close];
}
return S_OK;
}
}
@ -291,7 +295,14 @@ public:
{
@autoreleasepool
{
return [View lockFocusIfCanDraw] == YES;
@try
{
return [View lockFocusIfCanDraw] == YES;
}
@catch (NSException*)
{
return NO;
}
}
}
@ -719,15 +730,33 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver;
NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
}
- (void)dealloc
- (void)onClosed
{
@synchronized (self)
{
_parent = nullptr;
}
}
- (void)onClosed
- (BOOL)lockFocusIfCanDraw
{
@synchronized (self)
{
if(_parent == nullptr)
{
return NO;
}
}
return [super lockFocusIfCanDraw];
}
-(AvnPixelSize) getPixelSize
{
_parent = NULL;
return _lastPixelSize;
}
- (NSEvent*) lastMouseDownEvent
@ -742,6 +771,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self setWantsLayer:YES];
_parent = parent;
_area = nullptr;
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
return self;
}
@ -783,6 +814,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self addTrackingArea:_area];
_parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
@ -812,7 +847,13 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)drawRect:(NSRect)dirtyRect
{
if (_parent == nullptr)
{
return;
}
_parent->BaseEvents->RunRenderPriorityJobs();
@synchronized (self) {
if(_swRenderedFrame != NULL)
{
@ -879,7 +920,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) viewDidChangeBackingProperties
{
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
[super viewDidChangeBackingProperties];
}
@ -1161,6 +1207,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _closed;
NSMenu* _menu;
bool _isAppMenuApplied;
double _lastScaling;
}
-(double) getScaling
{
return _lastScaling;
}
+(void)closeAll
@ -1174,10 +1226,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
- (void)dealloc
{
}
- (void)pollModalSession:(nonnull NSModalSession)session
{
auto response = [NSApp runModalSession:session];
@ -1232,6 +1280,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self setReleasedWhenClosed:false];
_parent = parent;
[self setDelegate:self];
_closed = false;
_lastScaling = [self backingScaleFactor];
[self setOpaque:NO];
[self setBackgroundColor: [NSColor clearColor]];
[self invalidateShadow];
return self;
}
@ -1247,6 +1301,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return true;
}
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
{
_lastScaling = [self backingScaleFactor];
}
- (void)windowWillClose:(NSNotification *)notification
{
_closed = true;
@ -1257,9 +1316,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self restoreParentWindow];
parent->BaseEvents->Closed();
[parent->View onClosed];
dispatch_async(dispatch_get_main_queue(), ^{
[self setContentView: nil];
});
}
}
@ -1406,18 +1462,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position);
}
// TODO this breaks resizing.
/*- (void)windowDidResize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}*/
@end
class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup

2
readme.md

@ -22,7 +22,7 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it
For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
Use these commands in the Package Manager console to install Avalonia manually:
```

6
samples/ControlCatalog/MainView.xaml

@ -32,7 +32,11 @@
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="Image"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ImagePage/>
</TabItem>
<TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">

85
samples/ControlCatalog/Pages/ImagePage.xaml

@ -1,45 +1,52 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ImagePage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical">
<TextBlock>No Stretch</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="None"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Fill</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Fill"/>
</StackPanel>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Uniform</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Uniform"/>
</StackPanel>
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64">
<DockPanel Grid.Column="0" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock>
<ComboBox Name="bitmapStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapStretchChanged">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Fill</ComboBoxItem>
<ComboBoxItem>Uniform</ComboBoxItem>
<ComboBoxItem>UniformToFill</ComboBoxItem>
</ComboBox>
<Image Name="bitmapImage"
Source="/Assets/delicate-arch-896885_640.jpg"/>
</DockPanel>
<StackPanel Orientation="Vertical">
<TextBlock>UniformToFill</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="UniformToFill"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Window Icon as an Image</TextBlock>
<Image Name="Icon" Width="100" Height="200" Stretch="None" />
</StackPanel>
</StackPanel>
<DockPanel Grid.Column="1" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock>
<ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Fill</ComboBoxItem>
<ComboBoxItem>Uniform</ComboBoxItem>
<ComboBoxItem>UniformToFill</ComboBoxItem>
</ComboBox>
<Image Name="drawingImage">
<Image.Source>
<DrawingImage>
<GeometryDrawing Brush="Red">
<PathGeometry>
<PathFigure StartPoint="0,0" IsClosed="True">
<QuadraticBezierSegment Point1="50,0" Point2="50,-50" />
<QuadraticBezierSegment Point1="100,-50" Point2="100,0" />
<LineSegment Point="50,0" />
<LineSegment Point="50,50" />
</PathFigure>
</PathGeometry>
</GeometryDrawing>
</DrawingImage>
</Image.Source>
</Image>
</DockPanel>
</Grid>
</DockPanel>
</UserControl>

39
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -1,40 +1,41 @@
using System.IO;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Media;
namespace ControlCatalog.Pages
{
public class ImagePage : UserControl
{
private Image iconImage;
private readonly Image _bitmapImage;
private readonly Image _drawingImage;
public ImagePage()
{
this.InitializeComponent();
InitializeComponent();
_bitmapImage = this.FindControl<Image>("bitmapImage");
_drawingImage = this.FindControl<Image>("drawingImage");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
iconImage = this.Get<Image>("Icon");
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
public void BitmapStretchChanged(object sender, SelectionChangedEventArgs e)
{
if (_bitmapImage != null)
{
var comboxBox = (ComboBox)sender;
_bitmapImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
public void DrawingStretchChanged(object sender, SelectionChangedEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (iconImage.Source == null)
if (_drawingImage != null)
{
var windowRoot = e.Root as Window;
if (windowRoot != null)
{
using (var stream = new MemoryStream())
{
windowRoot.Icon.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
iconImage.Source = new Bitmap(stream);
}
}
var comboxBox = (ComboBox)sender;
_drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
}

2
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@ -39,7 +39,7 @@ namespace RenderDemo.Pages
ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100));
}
context.DrawImage(_bitmap, 1,
context.DrawImage(_bitmap,
new Rect(0, 0, 200, 200),
new Rect(0, 0, 200, 200));
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);

64
scripts/avalonia-rename.ps1

@ -1,64 +0,0 @@
function Get-NewDirectoryName {
param ([System.IO.DirectoryInfo]$item)
$name = $item.Name.Replace("perspex", "avalonia")
$name = $name.Replace("Perspex", "Avalonia")
Join-Path $item.Parent.FullName $name
}
function Get-NewFileName {
param ([System.IO.FileInfo]$item)
$name = $item.Name.Replace("perspex", "avalonia")
$name = $name.Replace("Perspex", "Avalonia")
Join-Path $item.DirectoryName $name
}
function Rename-Contents {
param ([System.IO.FileInfo] $file)
$extensions = @(".cs",".xaml",".csproj",".sln",".md",".json",".yml",".partial",".ps1",".nuspec",".htm",".html",".gitmodules".".xml",".plist",".targets",".projitems",".shproj",".xib")
if ($extensions.Contains($file.Extension)) {
$text = [IO.File]::ReadAllText($file.FullName)
$text = $text.Replace("github.com/perspex", "github.com/avaloniaui")
$text = $text.Replace("github.com/Perspex", "github.com/AvaloniaUI")
$text = $text.Replace("perspex", "avalonia")
$text = $text.Replace("Perspex", "Avalonia")
$text = $text.Replace("PERSPEX", "AVALONIA")
[IO.File]::WriteAllText($file.FullName, $text)
}
}
function Process-Files {
param ([System.IO.DirectoryInfo] $item)
$dirs = Get-ChildItem -Path $item.FullName -Directory
$files = Get-ChildItem -Path $item.FullName -File
foreach ($dir in $dirs) {
Process-Files $dir.FullName
}
foreach ($file in $files) {
Rename-Contents $file
$renamed = Get-NewFileName $file
if ($file.FullName -ne $renamed) {
Write-Host git mv $file.FullName $renamed
& git mv $file.FullName $renamed
}
}
$renamed = Get-NewDirectoryName $item
if ($item.FullName -ne $renamed) {
Write-Host git mv $item.FullName $renamed
& git mv $item.FullName $renamed
}
}
& git submodule deinit .
& git clean -xdf
Process-Files .

2
src/Avalonia.Animation/IterationCount.cs

@ -63,7 +63,7 @@ namespace Avalonia.Animation
public IterationType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop.
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to Infinite.
/// </summary>
public bool IsInfinite => _type == IterationType.Infinite;

40
src/Avalonia.Base/Collections/Pooled/ClearMode.cs

@ -0,0 +1,40 @@
// This source file is adapted from the Collections.Pooled.
// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
namespace Avalonia.Collections.Pooled
{
/// <summary>
/// This enum allows control over how data is treated when internal
/// arrays are returned to the ArrayPool. Be careful to understand
/// what each option does before using anything other than the default
/// of Auto.
/// </summary>
public enum ClearMode
{
/// <summary>
/// <para><code>Auto</code> has different behavior depending on the host project's target framework.</para>
/// <para>.NET Core 2.1: Reference types and value types that contain reference types are cleared
/// when the internal arrays are returned to the pool. Value types that do not contain reference
/// types are not cleared when returned to the pool.</para>
/// <para>.NET Standard 2.0: All user types are cleared before returning to the pool, in case they
/// contain reference types.
/// For .NET Standard, Auto and Always have the same behavior.</para>
/// </summary>
Auto = 0,
/// <summary>
/// The <para><code>Always</code> setting has the effect of always clearing user types before returning to the pool.
/// This is the default behavior on .NET Standard.</para><para>You might want to turn this on in a .NET Core project
/// if you were concerned about sensitive data stored in value types leaking to other pars of your application.</para>
/// </summary>
Always = 1,
/// <summary>
/// <para><code>Never</code> will cause pooled collections to never clear user types before returning them to the pool.</para>
/// <para>You might want to use this setting in a .NET Standard project when you know that a particular collection stores
/// only value types and you want the performance benefit of not taking time to reset array items to their default value.</para>
/// <para>Be careful with this setting: if used for a collection that contains reference types, or value types that contain
/// reference types, this setting could cause memory issues by making the garbage collector unable to clean up instances
/// that are still being referenced by arrays sitting in the ArrayPool.</para>
/// </summary>
Never = 2
}
}

31
src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs

@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Avalonia.Collections.Pooled
{
internal sealed class ICollectionDebugView<T>
{
private readonly ICollection<T> _collection;
public ICollectionDebugView(ICollection<T> collection)
{
_collection = collection ?? throw new ArgumentNullException(nameof(collection));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
T[] items = new T[_collection.Count];
_collection.CopyTo(items, 0);
return items;
}
}
}
}

21
src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs

@ -0,0 +1,21 @@
// This source file is adapted from the Collections.Pooled.
// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
using System;
using System.Collections.Generic;
namespace Avalonia.Collections.Pooled
{
/// <summary>
/// Represents a read-only collection of pooled elements that can be accessed by index
/// </summary>
/// <typeparam name="T">The type of elements in the read-only pooled list.</typeparam>
public interface IReadOnlyPooledList<T> : IReadOnlyList<T>
{
/// <summary>
/// Gets a <see cref="System.ReadOnlySpan{T}"/> for the items currently in the collection.
/// </summary>
ReadOnlySpan<T> Span { get; }
}
}

1531
src/Avalonia.Base/Collections/Pooled/PooledList.cs

File diff suppressed because it is too large

699
src/Avalonia.Base/Collections/Pooled/PooledStack.cs

@ -0,0 +1,699 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
/*=============================================================================
**
**
** Purpose: An array implementation of a generic stack.
**
**
=============================================================================*/
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Threading;
namespace Avalonia.Collections.Pooled
{
/// <summary>
/// A simple stack of objects. Internally it is implemented as an array,
/// so Push can be O(n). Pop is O(1).
/// </summary>
[DebuggerTypeProxy(typeof(StackDebugView<>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
public class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback
{
[NonSerialized]
private ArrayPool<T> _pool;
[NonSerialized]
private object _syncRoot;
private T[] _array; // Storage for stack elements. Do not rename (binary serialization)
private int _size; // Number of items in the stack. Do not rename (binary serialization)
private int _version; // Used to keep enumerator in sync w/ collection. Do not rename (binary serialization)
private readonly bool _clearOnFree;
private const int DefaultCapacity = 4;
#region Constructors
/// <summary>
/// Create a stack with the default initial capacity.
/// </summary>
public PooledStack() : this(ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with the default initial capacity.
/// </summary>
public PooledStack(ClearMode clearMode) : this(clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with the default initial capacity.
/// </summary>
public PooledStack(ArrayPool<T> customPool) : this(ClearMode.Auto, customPool) { }
/// <summary>
/// Create a stack with the default initial capacity and a custom ArrayPool.
/// </summary>
public PooledStack(ClearMode clearMode, ArrayPool<T> customPool)
{
_pool = customPool ?? ArrayPool<T>.Shared;
_array = Array.Empty<T>();
_clearOnFree = ShouldClear(clearMode);
}
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity, ArrayPool<T> customPool) : this(capacity, ClearMode.Auto, customPool) { }
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity, ClearMode clearMode, ArrayPool<T> customPool)
{
if (capacity < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
_pool = customPool ?? ArrayPool<T>.Shared;
_array = _pool.Rent(capacity);
_clearOnFree = ShouldClear(clearMode);
}
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable) : this(enumerable, ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode) : this(enumerable, clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable, ArrayPool<T> customPool) : this(enumerable, ClearMode.Auto, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode, ArrayPool<T> customPool)
{
_pool = customPool ?? ArrayPool<T>.Shared;
_clearOnFree = ShouldClear(clearMode);
switch (enumerable)
{
case null:
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable);
break;
case ICollection<T> collection:
if (collection.Count == 0)
{
_array = Array.Empty<T>();
}
else
{
_array = _pool.Rent(collection.Count);
collection.CopyTo(_array, 0);
_size = collection.Count;
}
break;
default:
using (var list = new PooledList<T>(enumerable))
{
_array = _pool.Rent(list.Count);
list.Span.CopyTo(_array);
_size = list.Count;
}
break;
}
}
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array, ArrayPool<T> customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array, ClearMode clearMode, ArrayPool<T> customPool) : this(array.AsSpan(), clearMode, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span) : this(span, ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode) : this(span, clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span, ArrayPool<T> customPool) : this(span, ClearMode.Auto, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode, ArrayPool<T> customPool)
{
_pool = customPool ?? ArrayPool<T>.Shared;
_clearOnFree = ShouldClear(clearMode);
_array = _pool.Rent(span.Length);
span.CopyTo(_array);
_size = span.Length;
}
#endregion
/// <summary>
/// The number of items in the stack.
/// </summary>
public int Count => _size;
/// <summary>
/// Returns the ClearMode behavior for the collection, denoting whether values are
/// cleared from internal arrays before returning them to the pool.
/// </summary>
public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot
{
get
{
if (_syncRoot == null)
{
Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
}
return _syncRoot;
}
}
/// <summary>
/// Removes all Objects from the Stack.
/// </summary>
public void Clear()
{
if (_clearOnFree)
{
Array.Clear(_array, 0, _size); // clear the elements so that the gc can reclaim the references.
}
_size = 0;
_version++;
}
/// <summary>
/// Compares items using the default equality comparer
/// </summary>
public bool Contains(T item)
{
// PERF: Internally Array.LastIndexOf calls
// EqualityComparer<T>.Default.LastIndexOf, which
// is specialized for different types. This
// boosts performance since instead of making a
// virtual method call each iteration of the loop,
// via EqualityComparer<T>.Default.Equals, we
// only make one virtual call to EqualityComparer.LastIndexOf.
return _size != 0 && Array.LastIndexOf(_array, item, _size - 1) != -1;
}
/// <summary>
/// This method removes all items which match the predicate.
/// The complexity is O(n).
/// </summary>
public int RemoveWhere(Func<T, bool> match)
{
if (match == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int freeIndex = 0; // the first free slot in items array
// Find the first item which needs to be removed.
while (freeIndex < _size && !match(_array[freeIndex]))
freeIndex++;
if (freeIndex >= _size)
return 0;
int current = freeIndex + 1;
while (current < _size)
{
// Find the first item which needs to be kept.
while (current < _size && match(_array[current]))
current++;
if (current < _size)
{
// copy item to the free slot.
_array[freeIndex++] = _array[current++];
}
}
if (_clearOnFree)
{
// Clear the removed elements so that the gc can reclaim the references.
Array.Clear(_array, freeIndex, _size - freeIndex);
}
int result = _size - freeIndex;
_size = freeIndex;
_version++;
return result;
}
// Copies the stack into an array.
public void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
}
if (array.Length - arrayIndex < _size)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
Debug.Assert(array != _array);
int srcIndex = 0;
int dstIndex = arrayIndex + _size;
while (srcIndex < _size)
{
array[--dstIndex] = _array[srcIndex++];
}
}
public void CopyTo(Span<T> span)
{
if (span.Length < _size)
{
ThrowHelper.ThrowArgumentException_DestinationTooShort();
}
int srcIndex = 0;
int dstIndex = _size;
while (srcIndex < _size)
{
span[--dstIndex] = _array[srcIndex++];
}
}
void ICollection.CopyTo(Array array, int arrayIndex)
{
if (array == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (array.Rank != 1)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
}
if (array.GetLowerBound(0) != 0)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound, ExceptionArgument.array);
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
}
if (array.Length - arrayIndex < _size)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
}
try
{
Array.Copy(_array, 0, array, arrayIndex, _size);
Array.Reverse(array, arrayIndex, _size);
}
catch (ArrayTypeMismatchException)
{
ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
}
}
/// <summary>
/// Returns an IEnumerator for this PooledStack.
/// </summary>
/// <returns></returns>
public Enumerator GetEnumerator()
=> new Enumerator(this);
/// <internalonly/>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
=> new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator()
=> new Enumerator(this);
public void TrimExcess()
{
if (_size == 0)
{
ReturnArray(replaceWith: Array.Empty<T>());
_version++;
return;
}
int threshold = (int)(_array.Length * 0.9);
if (_size < threshold)
{
var newArray = _pool.Rent(_size);
if (newArray.Length < _array.Length)
{
Array.Copy(_array, newArray, _size);
ReturnArray(replaceWith: newArray);
_version++;
}
else
{
// The array from the pool wasn't any smaller than the one we already had,
// (we can only control minimum size) so return it and do nothing.
// If we create an exact-sized array not from the pool, we'll
// get an exception when returning it to the pool.
_pool.Return(newArray);
}
}
}
/// <summary>
/// Returns the top object on the stack without removing it. If the stack
/// is empty, Peek throws an InvalidOperationException.
/// </summary>
public T Peek()
{
int size = _size - 1;
T[] array = _array;
if ((uint)size >= (uint)array.Length)
{
ThrowForEmptyStack();
}
return array[size];
}
public bool TryPeek(out T result)
{
int size = _size - 1;
T[] array = _array;
if ((uint)size >= (uint)array.Length)
{
result = default;
return false;
}
result = array[size];
return true;
}
/// <summary>
/// Pops an item from the top of the stack. If the stack is empty, Pop
/// throws an InvalidOperationException.
/// </summary>
public T Pop()
{
int size = _size - 1;
T[] array = _array;
// if (_size == 0) is equivalent to if (size == -1), and this case
// is covered with (uint)size, thus allowing bounds check elimination
// https://github.com/dotnet/coreclr/pull/9773
if ((uint)size >= (uint)array.Length)
{
ThrowForEmptyStack();
}
_version++;
_size = size;
T item = array[size];
if (_clearOnFree)
{
array[size] = default; // Free memory quicker.
}
return item;
}
public bool TryPop(out T result)
{
int size = _size - 1;
T[] array = _array;
if ((uint)size >= (uint)array.Length)
{
result = default;
return false;
}
_version++;
_size = size;
result = array[size];
if (_clearOnFree)
{
array[size] = default; // Free memory quicker.
}
return true;
}
/// <summary>
/// Pushes an item to the top of the stack.
/// </summary>
public void Push(T item)
{
int size = _size;
T[] array = _array;
if ((uint)size < (uint)array.Length)
{
array[size] = item;
_version++;
_size = size + 1;
}
else
{
PushWithResize(item);
}
}
// Non-inline from Stack.Push to improve its code quality as uncommon path
[MethodImpl(MethodImplOptions.NoInlining)]
private void PushWithResize(T item)
{
var newArray = _pool.Rent((_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
Array.Copy(_array, newArray, _size);
ReturnArray(replaceWith: newArray);
_array[_size] = item;
_version++;
_size++;
}
/// <summary>
/// Copies the Stack to an array, in the same order Pop would return the items.
/// </summary>
public T[] ToArray()
{
if (_size == 0)
return Array.Empty<T>();
T[] objArray = new T[_size];
int i = 0;
while (i < _size)
{
objArray[i] = _array[_size - i - 1];
i++;
}
return objArray;
}
private void ThrowForEmptyStack()
{
Debug.Assert(_size == 0);
throw new InvalidOperationException("Stack was empty.");
}
private void ReturnArray(T[] replaceWith = null)
{
if (_array?.Length > 0)
{
try
{
_pool.Return(_array, clearArray: _clearOnFree);
}
catch (ArgumentException)
{
// oh well, the array pool didn't like our array
}
}
if (!(replaceWith is null))
{
_array = replaceWith;
}
}
private static bool ShouldClear(ClearMode mode)
{
#if NETCOREAPP2_1
return mode == ClearMode.Always
|| (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
#else
return mode != ClearMode.Never;
#endif
}
public void Dispose()
{
ReturnArray(replaceWith: Array.Empty<T>());
_size = 0;
_version++;
}
void IDeserializationCallback.OnDeserialization(object sender)
{
// We can't serialize array pools, so deserialized PooledStacks will
// have to use the shared pool, even if they were using a custom pool
// before serialization.
_pool = ArrayPool<T>.Shared;
}
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")]
public struct Enumerator : IEnumerator<T>, IEnumerator
{
private readonly PooledStack<T> _stack;
private readonly int _version;
private int _index;
private T _currentElement;
internal Enumerator(PooledStack<T> stack)
{
_stack = stack;
_version = stack._version;
_index = -2;
_currentElement = default;
}
public void Dispose()
{
_index = -1;
}
public bool MoveNext()
{
bool retval;
if (_version != _stack._version)
throw new InvalidOperationException("Collection was modified during enumeration.");
if (_index == -2)
{ // First call to enumerator.
_index = _stack._size - 1;
retval = (_index >= 0);
if (retval)
_currentElement = _stack._array[_index];
return retval;
}
if (_index == -1)
{ // End of enumeration.
return false;
}
retval = (--_index >= 0);
if (retval)
_currentElement = _stack._array[_index];
else
_currentElement = default;
return retval;
}
public T Current
{
get
{
if (_index < 0)
ThrowEnumerationNotStartedOrEnded();
return _currentElement;
}
}
private void ThrowEnumerationNotStartedOrEnded()
{
Debug.Assert(_index == -1 || _index == -2);
throw new InvalidOperationException(_index == -2 ? "Enumeration was not started." : "Enumeration has ended.");
}
object IEnumerator.Current
{
get { return Current; }
}
void IEnumerator.Reset()
{
if (_version != _stack._version)
throw new InvalidOperationException("Collection was modified during enumeration.");
_index = -2;
_currentElement = default;
}
}
}
}

28
src/Avalonia.Base/Collections/Pooled/StackDebugView.cs

@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
namespace Avalonia.Collections.Pooled
{
internal sealed class StackDebugView<T>
{
private readonly PooledStack<T> _stack;
public StackDebugView(PooledStack<T> stack)
{
_stack = stack ?? throw new ArgumentNullException(nameof(stack));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
return _stack.ToArray();
}
}
}
}

691
src/Avalonia.Base/Collections/Pooled/ThrowHelper.cs

@ -0,0 +1,691 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// This file defines an internal class used to throw exceptions in BCL code.
// The main purpose is to reduce code size.
//
// The old way to throw an exception generates quite a lot IL code and assembly code.
// Following is an example:
// C# source
// throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
// IL code:
// IL_0003: ldstr "key"
// IL_0008: ldstr "ArgumentNull_Key"
// IL_000d: call string System.Environment::GetResourceString(string)
// IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string)
// IL_0017: throw
// which is 21bytes in IL.
//
// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
// argument name and resource name in a small integer. The source code will be changed to
// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
//
// The IL code will be 7 bytes.
// IL_0008: ldc.i4.4
// IL_0009: ldc.i4.4
// IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
// IL_000f: ldarg.0
//
// This will also reduce the Jitted code size a lot.
//
// It is very important we do this for generic classes because we can easily generate the same code
// multiple times for different instantiation.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
namespace Avalonia.Collections.Pooled
{
internal static class ThrowHelper
{
internal static void ThrowArrayTypeMismatchException()
{
throw new ArrayTypeMismatchException();
}
internal static void ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}
internal static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException();
}
internal static void ThrowArgumentException_DestinationTooShort()
{
throw new ArgumentException("Destination too short.");
}
internal static void ThrowArgumentException_OverlapAlignmentMismatch()
{
throw new ArgumentException("Overlap alignment mismatch.");
}
internal static void ThrowArgumentOutOfRange_IndexException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.index,
ExceptionResource.ArgumentOutOfRange_Index);
}
internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.index,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
internal static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.value,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.length,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex,
ExceptionResource.ArgumentOutOfRange_Index);
}
internal static void ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.count,
ExceptionResource.ArgumentOutOfRange_Count);
}
internal static void ThrowWrongKeyTypeArgumentException<T>(T key, Type targetType)
{
// Generic key to move the boxing to the right hand side of throw
throw GetWrongKeyTypeArgumentException((object)key, targetType);
}
internal static void ThrowWrongValueTypeArgumentException<T>(T value, Type targetType)
{
// Generic key to move the boxing to the right hand side of throw
throw GetWrongValueTypeArgumentException((object)value, targetType);
}
private static ArgumentException GetAddingDuplicateWithKeyArgumentException(object key)
{
return new ArgumentException($"Error adding duplicate with key: {key}.");
}
internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
{
// Generic key to move the boxing to the right hand side of throw
throw GetAddingDuplicateWithKeyArgumentException((object)key);
}
internal static void ThrowKeyNotFoundException<T>(T key)
{
// Generic key to move the boxing to the right hand side of throw
throw GetKeyNotFoundException((object)key);
}
internal static void ThrowArgumentException(ExceptionResource resource)
{
throw GetArgumentException(resource);
}
internal static void ThrowArgumentException(ExceptionResource resource, ExceptionArgument argument)
{
throw GetArgumentException(resource, argument);
}
private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument)
{
return new ArgumentNullException(GetArgumentName(argument));
}
internal static void ThrowArgumentNullException(ExceptionArgument argument)
{
throw GetArgumentNullException(argument);
}
internal static void ThrowArgumentNullException(ExceptionResource resource)
{
throw new ArgumentNullException(GetResourceString(resource));
}
internal static void ThrowArgumentNullException(ExceptionArgument argument, ExceptionResource resource)
{
throw new ArgumentNullException(GetArgumentName(argument), GetResourceString(resource));
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
{
throw new ArgumentOutOfRangeException(GetArgumentName(argument));
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
{
throw GetArgumentOutOfRangeException(argument, resource);
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource)
{
throw GetArgumentOutOfRangeException(argument, paramNumber, resource);
}
internal static void ThrowInvalidOperationException(ExceptionResource resource)
{
throw GetInvalidOperationException(resource);
}
internal static void ThrowInvalidOperationException(ExceptionResource resource, Exception e)
{
throw new InvalidOperationException(GetResourceString(resource), e);
}
internal static void ThrowSerializationException(ExceptionResource resource)
{
throw new SerializationException(GetResourceString(resource));
}
internal static void ThrowSecurityException(ExceptionResource resource)
{
throw new System.Security.SecurityException(GetResourceString(resource));
}
internal static void ThrowRankException(ExceptionResource resource)
{
throw new RankException(GetResourceString(resource));
}
internal static void ThrowNotSupportedException(ExceptionResource resource)
{
throw new NotSupportedException(GetResourceString(resource));
}
internal static void ThrowUnauthorizedAccessException(ExceptionResource resource)
{
throw new UnauthorizedAccessException(GetResourceString(resource));
}
internal static void ThrowObjectDisposedException(string objectName, ExceptionResource resource)
{
throw new ObjectDisposedException(objectName, GetResourceString(resource));
}
internal static void ThrowObjectDisposedException(ExceptionResource resource)
{
throw new ObjectDisposedException(null, GetResourceString(resource));
}
internal static void ThrowNotSupportedException()
{
throw new NotSupportedException();
}
internal static void ThrowAggregateException(List<Exception> exceptions)
{
throw new AggregateException(exceptions);
}
internal static void ThrowOutOfMemoryException()
{
throw new OutOfMemoryException();
}
internal static void ThrowArgumentException_Argument_InvalidArrayType()
{
throw new ArgumentException("Invalid array type.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumNotStarted()
{
throw new InvalidOperationException("Enumeration has not started.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumEnded()
{
throw new InvalidOperationException("Enumeration has ended.");
}
internal static void ThrowInvalidOperationException_EnumCurrent(int index)
{
throw GetInvalidOperationException_EnumCurrent(index);
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
{
throw new InvalidOperationException("Collection was modified during enumeration.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
{
throw new InvalidOperationException("Invalid enumerator state: enumeration cannot proceed.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_NoValue()
{
throw new InvalidOperationException("No value provided.");
}
internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported()
{
throw new InvalidOperationException("Concurrent operations are not supported.");
}
internal static void ThrowInvalidOperationException_HandleIsNotInitialized()
{
throw new InvalidOperationException("Handle is not initialized.");
}
internal static void ThrowFormatException_BadFormatSpecifier()
{
throw new FormatException("Bad format specifier.");
}
private static ArgumentException GetArgumentException(ExceptionResource resource)
{
return new ArgumentException(GetResourceString(resource));
}
private static InvalidOperationException GetInvalidOperationException(ExceptionResource resource)
{
return new InvalidOperationException(GetResourceString(resource));
}
private static ArgumentException GetWrongKeyTypeArgumentException(object key, Type targetType)
{
return new ArgumentException($"Wrong key type. Expected {targetType}, got: '{key}'.", nameof(key));
}
private static ArgumentException GetWrongValueTypeArgumentException(object value, Type targetType)
{
return new ArgumentException($"Wrong value type. Expected {targetType}, got: '{value}'.", nameof(value));
}
private static KeyNotFoundException GetKeyNotFoundException(object key)
{
return new KeyNotFoundException($"Key not found: {key}");
}
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
{
return new ArgumentOutOfRangeException(GetArgumentName(argument), GetResourceString(resource));
}
private static ArgumentException GetArgumentException(ExceptionResource resource, ExceptionArgument argument)
{
return new ArgumentException(GetResourceString(resource), GetArgumentName(argument));
}
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource)
{
return new ArgumentOutOfRangeException(GetArgumentName(argument) + "[" + paramNumber.ToString() + "]", GetResourceString(resource));
}
private static InvalidOperationException GetInvalidOperationException_EnumCurrent(int index)
{
return new InvalidOperationException(
index < 0 ?
"Enumeration has not started" :
"Enumeration has ended");
}
// Allow nulls for reference types and Nullable<U>, but not for value types.
// Aggressively inline so the jit evaluates the if in place and either drops the call altogether
// Or just leaves null test and call to the Non-returning ThrowHelper.ThrowArgumentNullException
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void IfNullAndNullsAreIllegalThenThrow<T>(object value, ExceptionArgument argName)
{
// Note that default(T) is not equal to null for value types except when T is Nullable<U>.
if (!(default(T) == null) && value == null)
ThrowHelper.ThrowArgumentNullException(argName);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ThrowForUnsupportedVectorBaseType<T>() where T : struct
{
if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) &&
typeof(T) != typeof(short) && typeof(T) != typeof(ushort) &&
typeof(T) != typeof(int) && typeof(T) != typeof(uint) &&
typeof(T) != typeof(long) && typeof(T) != typeof(ulong) &&
typeof(T) != typeof(float) && typeof(T) != typeof(double))
{
ThrowNotSupportedException(ExceptionResource.Arg_TypeNotSupported);
}
}
#if false // Reflection-based implementation does not work for CoreRT/ProjectN
// This function will convert an ExceptionArgument enum value to the argument name string.
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetArgumentName(ExceptionArgument argument)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument),
"The enum value is not defined, please check the ExceptionArgument Enum.");
return argument.ToString();
}
#endif
private static string GetArgumentName(ExceptionArgument argument)
{
switch (argument)
{
case ExceptionArgument.obj:
return "obj";
case ExceptionArgument.dictionary:
return "dictionary";
case ExceptionArgument.array:
return "array";
case ExceptionArgument.info:
return "info";
case ExceptionArgument.key:
return "key";
case ExceptionArgument.text:
return "text";
case ExceptionArgument.values:
return "values";
case ExceptionArgument.value:
return "value";
case ExceptionArgument.startIndex:
return "startIndex";
case ExceptionArgument.task:
return "task";
case ExceptionArgument.ch:
return "ch";
case ExceptionArgument.s:
return "s";
case ExceptionArgument.input:
return "input";
case ExceptionArgument.list:
return "list";
case ExceptionArgument.index:
return "index";
case ExceptionArgument.capacity:
return "capacity";
case ExceptionArgument.collection:
return "collection";
case ExceptionArgument.item:
return "item";
case ExceptionArgument.converter:
return "converter";
case ExceptionArgument.match:
return "match";
case ExceptionArgument.count:
return "count";
case ExceptionArgument.action:
return "action";
case ExceptionArgument.comparison:
return "comparison";
case ExceptionArgument.exceptions:
return "exceptions";
case ExceptionArgument.exception:
return "exception";
case ExceptionArgument.enumerable:
return "enumerable";
case ExceptionArgument.start:
return "start";
case ExceptionArgument.format:
return "format";
case ExceptionArgument.culture:
return "culture";
case ExceptionArgument.comparer:
return "comparer";
case ExceptionArgument.comparable:
return "comparable";
case ExceptionArgument.source:
return "source";
case ExceptionArgument.state:
return "state";
case ExceptionArgument.length:
return "length";
case ExceptionArgument.comparisonType:
return "comparisonType";
case ExceptionArgument.manager:
return "manager";
case ExceptionArgument.sourceBytesToCopy:
return "sourceBytesToCopy";
case ExceptionArgument.callBack:
return "callBack";
case ExceptionArgument.creationOptions:
return "creationOptions";
case ExceptionArgument.function:
return "function";
case ExceptionArgument.delay:
return "delay";
case ExceptionArgument.millisecondsDelay:
return "millisecondsDelay";
case ExceptionArgument.millisecondsTimeout:
return "millisecondsTimeout";
case ExceptionArgument.timeout:
return "timeout";
case ExceptionArgument.type:
return "type";
case ExceptionArgument.sourceIndex:
return "sourceIndex";
case ExceptionArgument.sourceArray:
return "sourceArray";
case ExceptionArgument.destinationIndex:
return "destinationIndex";
case ExceptionArgument.destinationArray:
return "destinationArray";
case ExceptionArgument.other:
return "other";
case ExceptionArgument.newSize:
return "newSize";
case ExceptionArgument.lowerBounds:
return "lowerBounds";
case ExceptionArgument.lengths:
return "lengths";
case ExceptionArgument.len:
return "len";
case ExceptionArgument.keys:
return "keys";
case ExceptionArgument.indices:
return "indices";
case ExceptionArgument.endIndex:
return "endIndex";
case ExceptionArgument.elementType:
return "elementType";
case ExceptionArgument.arrayIndex:
return "arrayIndex";
default:
Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum.");
return argument.ToString();
}
}
#if false // Reflection-based implementation does not work for CoreRT/ProjectN
// This function will convert an ExceptionResource enum value to the resource string.
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetResourceString(ExceptionResource resource)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource),
"The enum value is not defined, please check the ExceptionResource Enum.");
return SR.GetResourceString(resource.ToString());
}
#endif
private static string GetResourceString(ExceptionResource resource)
{
switch (resource)
{
case ExceptionResource.ArgumentOutOfRange_Index:
return "Argument 'index' was out of the range of valid values.";
case ExceptionResource.ArgumentOutOfRange_Count:
return "Argument 'count' was out of the range of valid values.";
case ExceptionResource.Arg_ArrayPlusOffTooSmall:
return "Array plus offset too small.";
case ExceptionResource.NotSupported_ReadOnlyCollection:
return "This operation is not supported on a read-only collection.";
case ExceptionResource.Arg_RankMultiDimNotSupported:
return "Multi-dimensional arrays are not supported.";
case ExceptionResource.Arg_NonZeroLowerBound:
return "Arrays with a non-zero lower bound are not supported.";
case ExceptionResource.ArgumentOutOfRange_ListInsert:
return "Insertion index was out of the range of valid values.";
case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum:
return "The number must be non-negative.";
case ExceptionResource.ArgumentOutOfRange_SmallCapacity:
return "The capacity cannot be set below the current Count.";
case ExceptionResource.Argument_InvalidOffLen:
return "Invalid offset length.";
case ExceptionResource.ArgumentOutOfRange_BiggerThanCollection:
return "The given value was larger than the size of the collection.";
case ExceptionResource.Serialization_MissingKeys:
return "Serialization error: missing keys.";
case ExceptionResource.Serialization_NullKey:
return "Serialization error: null key.";
case ExceptionResource.NotSupported_KeyCollectionSet:
return "The KeyCollection does not support modification.";
case ExceptionResource.NotSupported_ValueCollectionSet:
return "The ValueCollection does not support modification.";
case ExceptionResource.InvalidOperation_NullArray:
return "Null arrays are not supported.";
case ExceptionResource.InvalidOperation_HSCapacityOverflow:
return "Set hash capacity overflow. Cannot increase size.";
case ExceptionResource.NotSupported_StringComparison:
return "String comparison not supported.";
case ExceptionResource.ConcurrentCollection_SyncRoot_NotSupported:
return "SyncRoot not supported.";
case ExceptionResource.ArgumentException_OtherNotArrayOfCorrectLength:
return "The other array is not of the correct length.";
case ExceptionResource.ArgumentOutOfRange_EndIndexStartIndex:
return "The end index does not come after the start index.";
case ExceptionResource.ArgumentOutOfRange_HugeArrayNotSupported:
return "Huge arrays are not supported.";
case ExceptionResource.Argument_AddingDuplicate:
return "Duplicate item added.";
case ExceptionResource.Argument_InvalidArgumentForComparison:
return "Invalid argument for comparison.";
case ExceptionResource.Arg_LowerBoundsMustMatch:
return "Array lower bounds must match.";
case ExceptionResource.Arg_MustBeType:
return "Argument must be of type: ";
case ExceptionResource.InvalidOperation_IComparerFailed:
return "IComparer failed.";
case ExceptionResource.NotSupported_FixedSizeCollection:
return "This operation is not suppored on a fixed-size collection.";
case ExceptionResource.Rank_MultiDimNotSupported:
return "Multi-dimensional arrays are not supported.";
case ExceptionResource.Arg_TypeNotSupported:
return "Type not supported.";
default:
Debug.Assert(false,
"The enum value is not defined, please check the ExceptionResource Enum.");
return resource.ToString();
}
}
}
//
// The convention for this enum is using the argument name as the enum name
//
internal enum ExceptionArgument
{
obj,
dictionary,
array,
info,
key,
text,
values,
value,
startIndex,
task,
ch,
s,
input,
list,
index,
capacity,
collection,
item,
converter,
match,
count,
action,
comparison,
exceptions,
exception,
enumerable,
start,
format,
culture,
comparer,
comparable,
source,
state,
length,
comparisonType,
manager,
sourceBytesToCopy,
callBack,
creationOptions,
function,
delay,
millisecondsDelay,
millisecondsTimeout,
timeout,
type,
sourceIndex,
sourceArray,
destinationIndex,
destinationArray,
other,
newSize,
lowerBounds,
lengths,
len,
keys,
indices,
endIndex,
elementType,
arrayIndex
}
//
// The convention for this enum is using the resource name as the enum name
//
internal enum ExceptionResource
{
ArgumentOutOfRange_Index,
ArgumentOutOfRange_Count,
Arg_ArrayPlusOffTooSmall,
NotSupported_ReadOnlyCollection,
Arg_RankMultiDimNotSupported,
Arg_NonZeroLowerBound,
ArgumentOutOfRange_ListInsert,
ArgumentOutOfRange_NeedNonNegNum,
ArgumentOutOfRange_SmallCapacity,
Argument_InvalidOffLen,
ArgumentOutOfRange_BiggerThanCollection,
Serialization_MissingKeys,
Serialization_NullKey,
NotSupported_KeyCollectionSet,
NotSupported_ValueCollectionSet,
InvalidOperation_NullArray,
InvalidOperation_HSCapacityOverflow,
NotSupported_StringComparison,
ConcurrentCollection_SyncRoot_NotSupported,
ArgumentException_OtherNotArrayOfCorrectLength,
ArgumentOutOfRange_EndIndexStartIndex,
ArgumentOutOfRange_HugeArrayNotSupported,
Argument_AddingDuplicate,
Argument_InvalidArgumentForComparison,
Arg_LowerBoundsMustMatch,
Arg_MustBeType,
InvalidOperation_IComparerFailed,
NotSupported_FixedSizeCollection,
Rank_MultiDimNotSupported,
Arg_TypeNotSupported,
}
}

4
src/Avalonia.Controls/DrawingPresenter.cs

@ -1,9 +1,11 @@
using Avalonia.Controls.Shapes;
using System;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
[Obsolete("Use Image control with DrawingImage source")]
public class DrawingPresenter : Control
{
static DrawingPresenter()

45
src/Avalonia.Controls/Image.cs

@ -14,8 +14,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Source"/> property.
/// </summary>
public static readonly StyledProperty<IBitmap> SourceProperty =
AvaloniaProperty.Register<Image, IBitmap>(nameof(Source));
public static readonly StyledProperty<IImage> SourceProperty =
AvaloniaProperty.Register<Image, IImage>(nameof(Source));
/// <summary>
/// Defines the <see cref="Stretch"/> property.
@ -23,6 +23,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
/// <summary>
/// Defines the <see cref="StretchDirection"/> property.
/// </summary>
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<Image, StretchDirection>(
nameof(StretchDirection),
StretchDirection.Both);
static Image()
{
AffectsRender<Image>(SourceProperty, StretchProperty);
@ -30,9 +38,9 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the bitmap image that will be displayed.
/// Gets or sets the image that will be displayed.
/// </summary>
public IBitmap Source
public IImage Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
@ -43,10 +51,19 @@ namespace Avalonia.Controls
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
/// <summary>
/// Gets or sets a value controlling in what direction the image will be stretched.
/// </summary>
public StretchDirection StretchDirection
{
get { return GetValue(StretchDirectionProperty); }
set { SetValue(StretchDirectionProperty, value); }
}
/// <summary>
/// Renders the control.
/// </summary>
@ -58,8 +75,8 @@ namespace Avalonia.Controls
if (source != null)
{
Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
Size sourceSize = source.Size;
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.CenterRect(new Rect(scaledSize))
@ -69,7 +86,7 @@ namespace Avalonia.Controls
var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this);
context.DrawImage(source, 1, sourceRect, destRect, interpolationMode);
context.DrawImage(source, sourceRect, destRect, interpolationMode);
}
}
@ -85,15 +102,7 @@ namespace Avalonia.Controls
if (source != null)
{
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{
result = sourceSize;
}
else
{
result = Stretch.CalculateSize(availableSize, sourceSize);
}
result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
}
return result;
@ -106,7 +115,7 @@ namespace Avalonia.Controls
if (source != null)
{
var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
var sourceSize = source.Size;
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}

5
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Notifications
@ -14,7 +15,7 @@ namespace Avalonia.Controls.Notifications
/// <summary>
/// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
/// </summary>
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{
private IList _items;
@ -153,5 +154,7 @@ namespace Avalonia.Controls.Notifications
adornerLayer?.Children.Add(this);
}
public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
}
}

5
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -138,10 +138,7 @@ namespace Avalonia.Controls.Primitives
}
}
public bool HitTest(Point point)
{
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
}
public bool HitTest(Point point) => Children.HitTestCustom(point);
private class AdornedElementInfo
{

7
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@ -21,11 +21,8 @@ namespace Avalonia.Controls.Primitives
return null;
}
public bool HitTest(Point point)
{
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
}
public bool HitTest(Point point) => Children.HitTestCustom(point);
protected override Size ArrangeOverride(Size finalSize)
{

2
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -306,7 +306,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
if (placement == PlacementMode.Pointer)
{
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else

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

@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@ -240,17 +241,14 @@ namespace Avalonia.Controls.Primitives
public override void BeginInit()
{
base.BeginInit();
++_updateCount;
_updateSelectedIndex = int.MinValue;
InternalBeginInit();
}
/// <inheritdoc/>
public override void EndInit()
{
if (--_updateCount == 0)
{
UpdateFinished();
}
InternalEndInit();
base.EndInit();
}
@ -437,7 +435,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextBeginUpdate();
++_updateCount;
InternalBeginInit();
}
/// <inheritdoc/>
@ -445,10 +444,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnDataContextEndUpdate();
if (--_updateCount == 0)
{
UpdateFinished();
}
InternalEndInit();
}
protected override void OnKeyDown(KeyEventArgs e)
@ -1118,6 +1114,26 @@ namespace Avalonia.Controls.Primitives
}
}
private void InternalBeginInit()
{
if (_updateCount == 0)
{
_updateSelectedIndex = int.MinValue;
}
++_updateCount;
}
private void InternalEndInit()
{
Debug.Assert(_updateCount > 0);
if (--_updateCount == 0)
{
UpdateFinished();
}
}
private class Selection : IEnumerable<int>
{
private readonly List<int> _list = new List<int>();

126
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -7,8 +7,14 @@ using Avalonia.Data;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
/// </summary>
public class ToggleButton : Button
{
/// <summary>
/// Defines the <see cref="IsChecked"/> property.
/// </summary>
public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
nameof(IsChecked),
@ -17,9 +23,30 @@ namespace Avalonia.Controls.Primitives
unsetValue: null,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="IsThreeState"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsThreeStateProperty =
AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState));
/// <summary>
/// Defines the <see cref="Checked"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CheckedEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Checked), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Unchecked"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> UncheckedEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Unchecked), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Unchecked"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> IndeterminateEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Indeterminate), RoutingStrategies.Bubble);
private bool? _isChecked = false;
static ToggleButton()
@ -27,14 +54,49 @@ namespace Avalonia.Controls.Primitives
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
}
/// <summary>
/// Raised when a <see cref="ToggleButton"/> is checked.
/// </summary>
public event EventHandler<RoutedEventArgs> Checked
{
add => AddHandler(CheckedEvent, value);
remove => RemoveHandler(CheckedEvent, value);
}
/// <summary>
/// Raised when a <see cref="ToggleButton"/> is unchecked.
/// </summary>
public event EventHandler<RoutedEventArgs> Unchecked
{
add => AddHandler(UncheckedEvent, value);
remove => RemoveHandler(UncheckedEvent, value);
}
/// <summary>
/// Raised when a <see cref="ToggleButton"/> is neither checked nor unchecked.
/// </summary>
public event EventHandler<RoutedEventArgs> Indeterminate
{
add => AddHandler(IndeterminateEvent, value);
remove => RemoveHandler(IndeterminateEvent, value);
}
/// <summary>
/// Gets or sets whether the <see cref="ToggleButton"/> is checked.
/// </summary>
public bool? IsChecked
{
get { return _isChecked; }
set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
get => _isChecked;
set => SetAndRaise(IsCheckedProperty, ref _isChecked, value);
}
/// <summary>
/// Gets or sets a value that indicates whether the control supports three states.
/// </summary>
public bool IsThreeState
{
get => GetValue(IsThreeStateProperty);
@ -47,18 +109,78 @@ namespace Avalonia.Controls.Primitives
base.OnClick();
}
/// <summary>
/// Toggles the <see cref="IsChecked"/> property.
/// </summary>
protected virtual void Toggle()
{
if (IsChecked.HasValue)
{
if (IsChecked.Value)
{
if (IsThreeState)
{
IsChecked = null;
}
else
{
IsChecked = false;
}
}
else
{
IsChecked = true;
}
}
else
{
IsChecked = false;
}
}
/// <summary>
/// Called when <see cref="IsChecked"/> becomes true.
/// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
protected virtual void OnChecked(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// Called when <see cref="IsChecked"/> becomes false.
/// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
protected virtual void OnUnchecked(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// Called when <see cref="IsChecked"/> becomes null.
/// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
protected virtual void OnIndeterminate(RoutedEventArgs e)
{
RaiseEvent(e);
}
private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
{
var newValue = (bool?)e.NewValue;
switch (newValue)
{
case true:
OnChecked(new RoutedEventArgs(CheckedEvent));
break;
case false:
OnUnchecked(new RoutedEventArgs(UncheckedEvent));
break;
default:
OnIndeterminate(new RoutedEventArgs(IndeterminateEvent));
break;
}
}
}
}

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -83,7 +83,7 @@ namespace Avalonia.Controls.Remote
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
}
context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
context.DrawImage(_bitmap, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
new Rect(Bounds.Size));
}
base.Render(context);

34
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -562,33 +562,35 @@ namespace Avalonia.Controls
if (Layout != null)
{
if (Layout is VirtualizingLayout virtualLayout)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
try
{
_processingItemsSourceChange = args;
try
if (Layout is VirtualizingLayout virtualLayout)
{
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
finally
{
_processingItemsSourceChange = null;
}
}
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
foreach (var element in Children)
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
if (GetVirtualizationInfo(element).IsRealized)
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
foreach (var element in Children)
{
ClearElementImpl(element);
if (GetVirtualizationInfo(element).IsRealized)
{
ClearElementImpl(element);
}
}
Children.Clear();
}
}
finally
{
_processingItemsSourceChange = null;
}
InvalidateMeasure();
}

77
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -109,11 +109,22 @@ namespace Avalonia.Controls
public void ClearElementToElementFactory(IControl element)
{
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
var clearedIndex = virtInfo.Index;
_owner.OnElementClearing(element);
_owner.ItemTemplateShim.RecycleElement(_owner, element);
if (_owner.ItemTemplateShim != null)
{
_owner.ItemTemplateShim.RecycleElement(_owner, element);
}
else
{
// No ItemTemplate to recycle to, remove the element from the children collection.
if (!_owner.Children.Remove(element))
{
throw new InvalidOperationException("ItemsRepeater's child not found in its Children collection.");
}
}
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
virtInfo.MoveOwnershipToElementFactory();
if (_lastFocusedElement == element)
@ -121,9 +132,8 @@ namespace Avalonia.Controls
// Focused element is going away. Remove the tracked last focused element
// and pick a reasonable next focus if we can find one within the layout
// realized elements.
MoveFocusFromClearedIndex(clearedIndex);
MoveFocusFromClearedIndex(virtInfo.Index);
}
}
private void MoveFocusFromClearedIndex(int clearedIndex)
@ -190,7 +200,8 @@ namespace Avalonia.Controls
{
if (virtInfo == null)
{
throw new ArgumentException("Element is not a child of this ItemsRepeater.");
//Element is not a child of this ItemsRepeater.
return -1;
}
return virtInfo.IsRealized || virtInfo.IsInUniqueIdResetPool ? virtInfo.Index : -1;
@ -515,21 +526,52 @@ namespace Avalonia.Controls
return element;
}
// There are several cases handled here with respect to which element gets returned and when DataContext is modified.
//
// 1. If there is no ItemTemplate:
// 1.1 If data is an IControl -> the data is returned
// 1.2 If data is not an IControl -> a default DataTemplate is used to fetch element and DataContext is set to data
//
// 2. If there is an ItemTemplate:
// 2.1 If data is not an IControl -> Element is fetched from ElementFactory and DataContext is set to the data
// 2.2 If data is an IControl:
// 2.2.1 If Element returned by the ElementFactory is the same as the data -> Element (a.k.a. data) is returned as is
// 2.2.2 If Element returned by the ElementFactory is not the same as the data
// -> Element that is fetched from the ElementFactory is returned and
// DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself
private IControl GetElementFromElementFactory(int index)
{
// The view generator is the provider of last resort.
var data = _owner.ItemsSourceView.GetAt(index);
var providedElementFactory = _owner.ItemTemplateShim;
ItemTemplateWrapper GetElementFactory()
{
if (providedElementFactory == null)
{
var factory = FuncDataTemplate.Default;
_owner.ItemTemplate = factory;
return _owner.ItemTemplateShim;
}
var itemTemplateFactory = _owner.ItemTemplateShim;
if (itemTemplateFactory == null)
return providedElementFactory;
}
IControl GetElement()
{
// If no ItemTemplate was provided, use a default
var factory = FuncDataTemplate.Default;
_owner.ItemTemplate = factory;
itemTemplateFactory = _owner.ItemTemplateShim;
if (providedElementFactory == null)
{
if (data is IControl dataAsElement)
{
return dataAsElement;
}
}
var elementFactory = GetElementFactory();
return elementFactory.GetElement(_owner, data);
}
var data = _owner.ItemsSourceView.GetAt(index);
var element = itemTemplateFactory.GetElement(_owner, data);
var element = GetElement();
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
if (virtInfo == null)
@ -537,8 +579,11 @@ namespace Avalonia.Controls
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
}
// Prepare the element
element.DataContext = data;
if (data != element)
{
// Prepare the element
element.DataContext = data;
}
virtInfo.MoveOwnershipToLayoutFromElementFactory(
index,

6
src/Avalonia.Controls/TextBox.cs

@ -390,8 +390,10 @@ namespace Avalonia.Controls
{
return;
}
_undoRedoHelper.Snapshot();
HandleTextInput(text);
_undoRedoHelper.Snapshot();
}
protected override void OnKeyDown(KeyEventArgs e)
@ -401,12 +403,12 @@ namespace Avalonia.Controls
bool movement = false;
bool selection = false;
bool handled = false;
var modifiers = e.Modifiers;
var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
if (Match(keymap.SelectAll))
{

3
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs

@ -47,7 +47,8 @@ namespace Avalonia.FreeDesktop
var fProcMounts = File.ReadAllLines(ProcMountsDir)
.Select(x => x.Split(' '))
.Select(x => (x[0], x[1]));
.Select(x => (x[0], x[1]))
.Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase));
var labelDirEnum = Directory.Exists(DevByLabelDir) ?
new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>();

3
src/Avalonia.Input/AccessKeyHandler.cs

@ -140,7 +140,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.LeftAlt)
if (e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
{
_altIsDown = true;
@ -218,6 +218,7 @@ namespace Avalonia.Input
switch (e.Key)
{
case Key.LeftAlt:
case Key.RightAlt:
_altIsDown = false;
if (_ignoreAltUp)

14
src/Avalonia.Input/DragDropDevice.cs

@ -19,7 +19,7 @@ namespace Avalonia.Input
return null;
}
private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, InputModifiers modifiers)
private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers)
{
if (target == null)
return DragDropEffects.None;
@ -38,13 +38,13 @@ namespace Avalonia.Input
return args.DragEffects;
}
private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
_lastTarget = GetTarget(inputRoot, point);
return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
}
private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
var target = GetTarget(inputRoot, point);
@ -77,7 +77,7 @@ namespace Avalonia.Input
}
}
private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
try
{
@ -100,16 +100,16 @@ namespace Avalonia.Input
switch (e.Type)
{
case RawDragEventType.DragEnter:
e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.DragOver:
e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.DragLeave:
DragLeave(e.Root);
break;
case RawDragEventType.Drop:
e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
}
}

23
src/Avalonia.Input/DragEventArgs.cs

@ -13,8 +13,11 @@ namespace Avalonia.Input
public IDataObject Data { get; private set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; private set; }
public KeyModifiers KeyModifiers { get; private set; }
public Point GetPosition(IVisual relativeTo)
{
var point = new Point(0, 0);
@ -32,13 +35,25 @@ namespace Avalonia.Input
return point;
}
[Obsolete("Use constructor taking KeyModifiers")]
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, InputModifiers modifiers)
: base(routedEvent)
{
this.Data = data;
this._target = target;
this._targetLocation = targetLocation;
this.Modifiers = modifiers;
Data = data;
_target = target;
_targetLocation = targetLocation;
Modifiers = modifiers;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xF);
}
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent)
{
Data = data;
_target = target;
_targetLocation = targetLocation;
Modifiers = (InputModifiers)keyModifiers;
KeyModifiers = keyModifiers;
}
}

4
src/Avalonia.Input/InputExtensions.cs

@ -38,7 +38,9 @@ namespace Avalonia.Input
/// <returns>The topmost <see cref="IInputElement"/> at the specified position.</returns>
public static IInputElement InputHitTest(this IInputElement element, Point p)
{
return element.GetInputElementsAt(p).FirstOrDefault();
Contract.Requires<ArgumentNullException>(element != null);
return element.GetVisualAt(p, s_hitTestDelegate) as IInputElement;
}
private static bool IsHitTestVisible(IVisual visual)

14
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@ -4,14 +4,14 @@ namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{
}
public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
InputModifiers selectionModifiers = InputModifiers.Shift,
InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
KeyModifiers selectionModifiers = KeyModifiers.Shift,
KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
@ -75,9 +75,9 @@ namespace Avalonia.Input.Platform
};
}
public InputModifiers CommandModifiers { get; set; }
public InputModifiers WholeWordTextActionModifiers { get; set; }
public InputModifiers SelectionModifiers { get; set; }
public KeyModifiers CommandModifiers { get; set; }
public KeyModifiers WholeWordTextActionModifiers { get; set; }
public KeyModifiers SelectionModifiers { get; set; }
public List<KeyGesture> Copy { get; set; }
public List<KeyGesture> Cut { get; set; }
public List<KeyGesture> Paste { get; set; }

7
src/Avalonia.Input/Raw/RawDragEvent.cs

@ -1,4 +1,6 @@
namespace Avalonia.Input.Raw
using System;
namespace Avalonia.Input.Raw
{
public class RawDragEvent : RawInputEventArgs
{
@ -6,7 +8,9 @@
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; }
public KeyModifiers KeyModifiers { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
IInputRoot root, Point location, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
@ -17,6 +21,7 @@
Data = data;
Effects = effects;
Modifiers = (InputModifiers)modifiers;
KeyModifiers = KeyModifiersUtils.ConvertToKey(modifiers);
}
}
}

32
src/Avalonia.Layout/FlowLayoutAlgorithm.cs

@ -72,6 +72,7 @@ namespace Avalonia.Layout
bool isWrapping,
double minItemSpacing,
double lineSpacing,
int maxItemsPerLine,
ScrollOrientation orientation,
string layoutId)
{
@ -94,14 +95,14 @@ namespace Avalonia.Layout
_elementManager.OnBeginMeasure(orientation);
int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId);
Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
if (isWrapping && IsReflowRequired())
{
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
_orientation.SetMinorStart(ref firstElementBounds, 0);
_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, layoutId);
Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
}
RaiseLineArranged();
@ -115,10 +116,11 @@ namespace Avalonia.Layout
public Size Arrange(
Size finalSize,
VirtualizingLayoutContext context,
bool isWrapping,
LineAlignment lineAlignment,
string layoutId)
{
ArrangeVirtualizingLayout(finalSize, lineAlignment, layoutId);
ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId);
return new Size(
Math.Max(finalSize.Width, _lastExtent.Width),
@ -270,6 +272,7 @@ namespace Avalonia.Layout
Size availableSize,
double minItemSpacing,
double lineSpacing,
int maxItemsPerLine,
string layoutId)
{
if (anchorIndex != -1)
@ -280,7 +283,7 @@ namespace Avalonia.Layout
var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex);
var lineOffset = _orientation.MajorStart(anchorBounds);
var lineMajorSize = _orientation.MajorSize(anchorBounds);
int countInLine = 1;
var countInLine = 1;
int count = 0;
bool lineNeedsReposition = false;
@ -301,7 +304,7 @@ namespace Avalonia.Layout
if (direction == GenerateDirection.Forward)
{
double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize));
if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// No more space in this row. wrap to next row.
_orientation.SetMinorStart(ref currentBounds, 0);
@ -339,7 +342,7 @@ namespace Avalonia.Layout
{
// Backward
double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing);
if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// Does not fit, wrap to the previous row
var availableSizeMinor = _orientation.Minor(availableSize);
@ -544,6 +547,7 @@ namespace Avalonia.Layout
private void ArrangeVirtualizingLayout(
Size finalSize,
LineAlignment lineAlignment,
bool isWrapping,
string layoutId)
{
// Walk through the realized elements one line at a time and
@ -563,7 +567,7 @@ namespace Avalonia.Layout
if (_orientation.MajorStart(currentBounds) != currentLineOffset)
{
spaceAtLineEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, layoutId);
PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
spaceAtLineStart = _orientation.MinorStart(currentBounds);
countInLine = 0;
currentLineOffset = _orientation.MajorStart(currentBounds);
@ -580,7 +584,7 @@ namespace Avalonia.Layout
if (countInLine > 0)
{
var spaceAtEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, layoutId);
PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
}
}
}
@ -594,6 +598,8 @@ namespace Avalonia.Layout
double spaceAtLineEnd,
double lineSize,
LineAlignment lineAlignment,
bool isWrapping,
Size finalSize,
string layoutId)
{
for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex)
@ -659,6 +665,14 @@ namespace Avalonia.Layout
}
bounds = bounds.Translate(-_lastExtent.Position);
if (!isWrapping)
{
_orientation.SetMinorSize(
ref bounds,
Math.Max(_orientation.MinorSize(bounds), _orientation.Minor(finalSize)));
}
var element = _elementManager.GetAt(rangeIndex);
element.Arrange(bounds);
}

16
src/Avalonia.Layout/NonVirtualizingLayout.cs

@ -20,25 +20,25 @@ namespace Avalonia.Layout
/// <inheritdoc/>
public sealed override void InitializeForContext(LayoutContext context)
{
InitializeForContextCore((VirtualizingLayoutContext)context);
InitializeForContextCore((NonVirtualizingLayoutContext)context);
}
/// <inheritdoc/>
public sealed override void UninitializeForContext(LayoutContext context)
{
UninitializeForContextCore((VirtualizingLayoutContext)context);
UninitializeForContextCore((NonVirtualizingLayoutContext)context);
}
/// <inheritdoc/>
public sealed override Size Measure(LayoutContext context, Size availableSize)
{
return MeasureOverride((VirtualizingLayoutContext)context, availableSize);
return MeasureOverride((NonVirtualizingLayoutContext)context, availableSize);
}
/// <inheritdoc/>
public sealed override Size Arrange(LayoutContext context, Size finalSize)
{
return ArrangeOverride((VirtualizingLayoutContext)context, finalSize);
return ArrangeOverride((NonVirtualizingLayoutContext)context, finalSize);
}
/// <summary>
@ -49,7 +49,7 @@ namespace Avalonia.Layout
/// The context object that facilitates communication between the layout and its host
/// container.
/// </param>
protected virtual void InitializeForContextCore(VirtualizingLayoutContext context)
protected virtual void InitializeForContextCore(LayoutContext context)
{
}
@ -61,7 +61,7 @@ namespace Avalonia.Layout
/// The context object that facilitates communication between the layout and its host
/// container.
/// </param>
protected virtual void UninitializeForContextCore(VirtualizingLayoutContext context)
protected virtual void UninitializeForContextCore(LayoutContext context)
{
}
@ -83,7 +83,7 @@ namespace Avalonia.Layout
/// of the allocated sizes for child objects or based on other considerations such as a
/// fixed container size.
/// </returns>
protected abstract Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize);
protected abstract Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize);
/// <summary>
/// When implemented in a derived class, provides the behavior for the "Arrange" pass of
@ -98,6 +98,6 @@ namespace Avalonia.Layout
/// its children.
/// </param>
/// <returns>The actual size that is used after the element is arranged in layout.</returns>
protected virtual Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) => finalSize;
protected virtual Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize) => finalSize;
}
}

14
src/Avalonia.Layout/NonVirtualizingLayoutContext.cs

@ -0,0 +1,14 @@
// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
namespace Avalonia.Layout
{
/// <summary>
/// Represents the base class for layout context types that do not support virtualization.
/// </summary>
public abstract class NonVirtualizingLayoutContext : LayoutContext
{
}
}

2
src/Avalonia.Layout/StackLayout.cs

@ -267,6 +267,7 @@ namespace Avalonia.Layout
false,
0,
Spacing,
int.MaxValue,
_orientation.ScrollOrientation,
LayoutId);
@ -278,6 +279,7 @@ namespace Avalonia.Layout
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
false,
FlowLayoutAlgorithm.LineAlignment.Start,
LayoutId);

54
src/Avalonia.Layout/UniformGridLayout.cs

@ -110,6 +110,12 @@ namespace Avalonia.Layout
public static readonly StyledProperty<double> MinRowSpacingProperty =
AvaloniaProperty.Register<UniformGridLayout, double>(nameof(MinRowSpacing));
/// <summary>
/// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property.
/// </summary>
public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MinItemWidth));
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
@ -123,6 +129,7 @@ namespace Avalonia.Layout
private double _minColumnSpacing;
private UniformGridLayoutItemsJustification _itemsJustification;
private UniformGridLayoutItemsStretch _itemsStretch;
private int _maximumRowsOrColumns = int.MaxValue;
/// <summary>
/// Initializes a new instance of the <see cref="UniformGridLayout"/> class.
@ -219,6 +226,15 @@ namespace Avalonia.Layout
set => SetValue(MinRowSpacingProperty, value);
}
/// <summary>
/// Gets or sets the maximum row or column count.
/// </summary>
public int MaximumRowsOrColumns
{
get => GetValue(MaximumRowsOrColumnsProperty);
set => SetValue(MaximumRowsOrColumnsProperty, value);
}
/// <summary>
/// Gets or sets the axis along which items are laid out.
/// </summary>
@ -269,15 +285,17 @@ namespace Avalonia.Layout
{
var gridState = (UniformGridLayoutState)context.LayoutState;
var lastExtent = gridState.FlowAlgorithm.LastExtent;
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
double majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
double realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
var itemsPerLine = Math.Min( // note use of unsigned ints
Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
Math.Max(1u, (uint)_maximumRowsOrColumns));
var majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
var realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
if ((realizationWindowStartWithinExtent + _orientation.MajorSize(realizationRect)) >= 0 && realizationWindowStartWithinExtent <= majorSize)
{
double offset = Math.Max(0.0, _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent));
int anchorRowIndex = (int)(offset / GetMajorSizeWithSpacing(context));
anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
anchorIndex = (int)Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
bounds = GetLayoutRectForDataIndex(availableSize, anchorIndex, lastExtent, context);
}
}
@ -299,7 +317,9 @@ namespace Avalonia.Layout
int count = context.ItemCount;
if (targetIndex >= 0 && targetIndex < count)
{
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
int itemsPerLine = (int)Math.Min( // note use of unsigned ints
Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
Math.Max(1u, _maximumRowsOrColumns));
int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine;
index = indexOfFirstInLine;
var state = context.LayoutState as UniformGridLayoutState;
@ -329,17 +349,21 @@ namespace Avalonia.Layout
// Constants
int itemsCount = context.ItemCount;
double availableSizeMinor = _orientation.Minor(availableSize);
int itemsPerLine = Math.Max(1, !double.IsInfinity(availableSizeMinor) ?
(int)(availableSizeMinor / GetMinorSizeWithSpacing(context)) : itemsCount);
int itemsPerLine =
(int)Math.Min( // note use of unsigned ints
Math.Max(1u, !double.IsInfinity(availableSizeMinor)
? (uint)(availableSizeMinor / GetMinorSizeWithSpacing(context))
: (uint)itemsCount),
Math.Max(1u, _maximumRowsOrColumns));
double lineSize = GetMajorSizeWithSpacing(context);
if (itemsCount > 0)
{
_orientation.SetMinorSize(
ref extent,
!double.IsInfinity(availableSizeMinor) ?
!double.IsInfinity(availableSizeMinor) && _itemsStretch == UniformGridLayoutItemsStretch.Fill ?
availableSizeMinor :
Math.Max(0.0, itemsCount * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
Math.Max(0.0, itemsPerLine * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
_orientation.SetMajorSize(
ref extent,
Math.Max(0.0, (itemsCount / itemsPerLine) * lineSize - (double)LineSpacing));
@ -398,7 +422,7 @@ namespace Avalonia.Layout
// Set the width and height on the grid state. If the user already set them then use the preset.
// If not, we have to measure the first element and get back a size which we're going to be using for the rest of the items.
var gridState = (UniformGridLayoutState)context.LayoutState;
gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing);
gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing, _maximumRowsOrColumns);
var desiredSize = GetFlowAlgorithm(context).Measure(
availableSize,
@ -406,6 +430,7 @@ namespace Avalonia.Layout
true,
MinItemSpacing,
LineSpacing,
_maximumRowsOrColumns,
_orientation.ScrollOrientation,
LayoutId);
@ -421,6 +446,7 @@ namespace Avalonia.Layout
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
true,
(FlowLayoutAlgorithm.LineAlignment)_itemsJustification,
LayoutId);
return new Size(value.Width, value.Height);
@ -471,6 +497,10 @@ namespace Avalonia.Layout
{
_minItemHeight = (double)args.NewValue;
}
else if (args.Property == MaximumRowsOrColumnsProperty)
{
_maximumRowsOrColumns = (int)args.NewValue;
}
InvalidateLayout();
}
@ -499,7 +529,9 @@ namespace Avalonia.Layout
Rect lastExtent,
VirtualizingLayoutContext context)
{
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
int itemsPerLine = (int)Math.Min( //note use of unsigned ints
Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
Math.Max(1u, _maximumRowsOrColumns));
int rowIndex = (int)(index / itemsPerLine);
int indexInRow = index - (rowIndex * itemsPerLine);

34
src/Avalonia.Layout/UniformGridLayoutState.cs

@ -48,8 +48,14 @@ namespace Avalonia.Layout
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
double minColumnSpacing)
double minColumnSpacing,
int maxItemsPerLine)
{
if (maxItemsPerLine == 0)
{
maxItemsPerLine = 1;
}
if (context.ItemCount > 0)
{
// If the first element is realized we don't need to cache it or to get it from the context
@ -57,7 +63,7 @@ namespace Avalonia.Layout
if (realizedElement != null)
{
realizedElement.Measure(availableSize);
SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
_cachedFirstElement = null;
}
else
@ -72,7 +78,7 @@ namespace Avalonia.Layout
_cachedFirstElement.Measure(availableSize);
SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
// See if we can move ownership to the flow algorithm. If we can, we do not need a local cache.
bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement);
@ -92,8 +98,14 @@ namespace Avalonia.Layout
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
double minColumnSpacing)
double minColumnSpacing,
int maxItemsPerLine)
{
if (maxItemsPerLine == 0)
{
maxItemsPerLine = 1;
}
EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth);
EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight);
@ -101,11 +113,17 @@ namespace Avalonia.Layout
var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing;
var itemSizeMinor = orientation == Orientation.Horizontal ? EffectiveItemWidth : EffectiveItemHeight;
itemSizeMinor += minorItemSpacing;
var numItemsPerColumn = (int)(Math.Max(1.0, availableSizeMinor / itemSizeMinor));
var remainingSpace = ((int)availableSizeMinor) % ((int)itemSizeMinor);
var extraMinorPixelsForEachItem = remainingSpace / numItemsPerColumn;
double extraMinorPixelsForEachItem = 0.0;
if (!double.IsInfinity(availableSizeMinor))
{
var numItemsPerColumn = Math.Min(
maxItemsPerLine,
Math.Max(1.0, availableSizeMinor / (itemSizeMinor + minorItemSpacing)));
var usedSpace = (numItemsPerColumn * (itemSizeMinor + minorItemSpacing)) - minorItemSpacing;
var remainingSpace = ((int)(availableSizeMinor - usedSpace));
extraMinorPixelsForEachItem = remainingSpace / ((int)numItemsPerColumn);
}
if (stretch == UniformGridLayoutItemsStretch.Fill)
{

5
src/Avalonia.Native/Avalonia.Native.csproj

@ -19,8 +19,9 @@
<ItemGroup>
<SharpGenMapping Include="Mappings.xml" />
<PackageReference Include="SharpGenTools.Sdk" Version="1.1.2" PrivateAssets="all" />
<PackageReference Include="SharpGen.Runtime.COM" Version="1.1.0" />
<PackageReference Include="SharpGenTools.Sdk" Version="1.2.1" PrivateAssets="all" />
<PackageReference Include="SharpGen.Runtime" Version="1.2.1" />
<PackageReference Include="SharpGen.Runtime.COM" Version="1.2.0" />
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
</ItemGroup>

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -101,7 +101,7 @@ namespace Avalonia.Native
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider());
}

14
src/Avalonia.Native/CallbackBase.cs

@ -2,11 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Runtime.ExceptionServices;
using SharpGen.Runtime;
using Avalonia.Platform;
namespace Avalonia.Native
{
public class CallbackBase : SharpGen.Runtime.IUnknown
public class CallbackBase : SharpGen.Runtime.IUnknown, IExceptionCallback
{
private uint _refCount;
private bool _disposed;
@ -76,5 +78,15 @@ namespace Avalonia.Native
{
}
public void RaiseException(Exception e)
{
if (AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>() is PlatformThreadingInterface threadingInterface)
{
threadingInterface.TerminateNativeApp();
threadingInterface.DispatchException(ExceptionDispatchInfo.Capture(e));
}
}
}
}

59
src/Avalonia.Native/PlatformThreadingInterface.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Runtime.ExceptionServices;
using System.Threading;
using Avalonia.Native.Interop;
using Avalonia.Platform;
@ -43,6 +44,8 @@ namespace Avalonia.Native
}
readonly IAvnPlatformThreadingInterface _native;
private ExceptionDispatchInfo _exceptionDispatchInfo;
private CancellationTokenSource _exceptionCancellationSource;
public PlatformThreadingInterface(IAvnPlatformThreadingInterface native)
{
@ -57,32 +60,48 @@ namespace Avalonia.Native
public void RunLoop(CancellationToken cancellationToken)
{
if (cancellationToken.CanBeCanceled == false)
_native.RunLoop(null);
else
var l = new object();
_exceptionCancellationSource = new CancellationTokenSource();
var compositeCancellation = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, _exceptionCancellationSource.Token).Token;
var cancellation = _native.CreateLoopCancellation();
compositeCancellation.Register(() =>
{
var l = new object();
var cancellation = _native.CreateLoopCancellation();
cancellationToken.Register(() =>
lock (l)
{
lock (l)
{
cancellation?.Cancel();
}
});
try
{
_native.RunLoop(cancellation);
cancellation?.Cancel();
}
finally
});
try
{
_native.RunLoop(cancellation);
}
finally
{
lock (l)
{
lock(l)
{
cancellation?.Dispose();
cancellation = null;
}
cancellation?.Dispose();
cancellation = null;
}
}
if (_exceptionDispatchInfo != null)
{
_exceptionDispatchInfo.Throw();
}
}
public void DispatchException (ExceptionDispatchInfo exceptionInfo)
{
_exceptionDispatchInfo = exceptionInfo;
}
public void TerminateNativeApp()
{
_exceptionCancellationSource?.Cancel();
}
public void Signal(DispatcherPriority priority)

5
src/Avalonia.Native/WindowImplBase.cs

@ -353,6 +353,11 @@ namespace Avalonia.Native
public void SetCursor(IPlatformHandle cursor)
{
if (_native == null)
{
return;
}
var newCursor = cursor as AvaloniaNativeCursor;
newCursor = newCursor ?? (_cursorFactory.GetCursor(StandardCursorType.Arrow) as AvaloniaNativeCursor);
_native.Cursor = newCursor.Cursor;

14
src/Avalonia.Styling/Styling/ISetStyleParent.cs → src/Avalonia.Styling/Controls/ISetResourceParent.cs

@ -1,29 +1,27 @@
using Avalonia.Controls;
namespace Avalonia.Styling
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Style"/>'s parent can be set.
/// Defines an interface through which an <see cref="IResourceNode"/>'s parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for internal use only.
/// </remarks>
public interface ISetStyleParent : IStyle
public interface ISetResourceParent : IResourceNode
{
/// <summary>
/// Sets the style parent.
/// Sets the resource parent.
/// </summary>
/// <param name="parent">The parent.</param>
void SetParent(IResourceNode parent);
/// <summary>
/// Notifies the style that a change has been made to resources that apply to it.
/// Notifies the resource node that a change has been made to the resources in its parent.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyResourcesChanged(ResourcesChangedEventArgs e);
void ParentResourcesChanged(ResourcesChangedEventArgs e);
}
}

62
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@ -12,8 +12,12 @@ namespace Avalonia.Controls
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : AvaloniaDictionary<object, object>, IResourceDictionary
public class ResourceDictionary : AvaloniaDictionary<object, object>,
IResourceDictionary,
IResourceNode,
ISetResourceParent
{
private IResourceNode _parent;
private AvaloniaList<IResourceProvider> _mergedDictionaries;
/// <summary>
@ -39,6 +43,12 @@ namespace Avalonia.Controls
_mergedDictionaries.ForEachItem(
x =>
{
if (x is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
OnResourcesChanged();
@ -48,11 +58,18 @@ namespace Avalonia.Controls
},
x =>
{
if (x is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
OnResourcesChanged();
}
(x as ISetResourceParent)?.SetParent(null);
x.ResourcesChanged -= MergedDictionaryResourcesChanged;
},
() => { });
@ -68,6 +85,27 @@ namespace Avalonia.Controls
get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false);
}
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
NotifyMergedDictionariesResourcesChanged(e);
ResourcesChanged?.Invoke(this, e);
}
/// <inheritdoc/>
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The ResourceDictionary already has a parent.");
}
_parent = parent;
}
/// <inheritdoc/>
public bool TryGetResource(object key, out object value)
{
@ -95,7 +133,27 @@ namespace Avalonia.Controls
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged();
private void NotifyMergedDictionariesResourcesChanged(ResourcesChangedEventArgs e)
{
if (_mergedDictionaries != null)
{
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
{
if (_mergedDictionaries[i] is ISetResourceParent merged)
{
merged.ParentResourcesChanged(e);
}
}
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var ev = new ResourcesChangedEventArgs();
NotifyMergedDictionariesResourcesChanged(ev);
OnResourcesChanged();
}
private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged();
}
}

4
src/Avalonia.Styling/StyledElement.cs

@ -223,13 +223,13 @@ namespace Avalonia
{
if (_styles != null)
{
(_styles as ISetStyleParent)?.SetParent(null);
(_styles as ISetResourceParent)?.SetParent(null);
_styles.ResourcesChanged -= ThisResourcesChanged;
}
_styles = value;
if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
if (value is ISetResourceParent setParent && setParent.ResourceParent == null)
{
setParent.SetParent(this);
}

12
src/Avalonia.Styling/Styling/Style.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// <summary>
/// Defines a style.
/// </summary>
public class Style : AvaloniaObject, IStyle, ISetStyleParent
public class Style : AvaloniaObject, IStyle, ISetResourceParent
{
private static Dictionary<IStyleable, CompositeDisposable> _applied =
new Dictionary<IStyleable, CompositeDisposable>();
@ -59,16 +59,16 @@ namespace Avalonia.Styling
if (_resources != null)
{
hadResources = _resources.Count > 0;
hadResources = _resources.HasResources;
_resources.ResourcesChanged -= ResourceDictionaryChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourceDictionaryChanged;
if (hadResources || _resources.Count > 0)
if (hadResources || _resources.HasResources)
{
((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@ -194,13 +194,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{

20
src/Avalonia.Styling/Styling/Styles.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// <summary>
/// A style that consists of a number of child styles.
/// </summary>
public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetStyleParent
public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetResourceParent
{
private IResourceNode _parent;
private IResourceDictionary _resources;
@ -27,10 +27,10 @@ namespace Avalonia.Styling
_styles.ForEachItem(
x =>
{
if (x.ResourceParent == null && x is ISetStyleParent setParent)
if (x.ResourceParent == null && x is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
@ -43,10 +43,10 @@ namespace Avalonia.Styling
},
x =>
{
if (x.ResourceParent == this && x is ISetStyleParent setParent)
if (x.ResourceParent == this && x is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
@ -98,7 +98,7 @@ namespace Avalonia.Styling
if (hadResources || _resources.Count > 0)
{
((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@ -246,7 +246,7 @@ namespace Avalonia.Styling
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
@ -257,7 +257,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
@ -266,7 +266,7 @@ namespace Avalonia.Styling
{
foreach (var child in this)
{
(child as ISetStyleParent)?.NotifyResourcesChanged(e);
(child as ISetResourceParent)?.ParentResourcesChanged(e);
}
ResourcesChanged?.Invoke(this, e);
@ -280,7 +280,7 @@ namespace Avalonia.Styling
{
if (foundSource)
{
(child as ISetStyleParent)?.NotifyResourcesChanged(e);
(child as ISetResourceParent)?.ParentResourcesChanged(e);
}
foundSource |= child == sender;

8
src/Avalonia.Visuals/Media/Color.cs

@ -19,22 +19,22 @@ namespace Avalonia.Media
}
/// <summary>
/// Gets or sets the Alpha component of the color.
/// Gets the Alpha component of the color.
/// </summary>
public byte A { get; }
/// <summary>
/// Gets or sets the Red component of the color.
/// Gets the Red component of the color.
/// </summary>
public byte R { get; }
/// <summary>
/// Gets or sets the Green component of the color.
/// Gets the Green component of the color.
/// </summary>
public byte G { get; }
/// <summary>
/// Gets or sets the Blue component of the color.
/// Gets the Blue component of the color.
/// </summary>
public byte B { get; }

6
src/Avalonia.Visuals/Media/Drawing.cs

@ -1,4 +1,6 @@
namespace Avalonia.Media
using Avalonia.Platform;
namespace Avalonia.Media
{
public abstract class Drawing : AvaloniaObject
{
@ -6,4 +8,4 @@
public abstract Rect GetBounds();
}
}
}

21
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -74,18 +74,29 @@ namespace Avalonia.Media
public Matrix CurrentContainerTransform => _currentContainerTransform;
/// <summary>
/// Draws a bitmap image.
/// Draws an image.
/// </summary>
/// <param name="source">The bitmap image.</param>
/// <param name="opacity">The opacity to draw with.</param>
/// <param name="source">The image.</param>
/// <param name="rect">The rect in the output to draw to.</param>
public void DrawImage(IImage source, Rect rect)
{
Contract.Requires<ArgumentNullException>(source != null);
DrawImage(source, new Rect(source.Size), rect);
}
/// <summary>
/// Draws an image.
/// </summary>
/// <param name="source">The image.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
public void DrawImage(IImage source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
{
Contract.Requires<ArgumentNullException>(source != null);
PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, bitmapInterpolationMode);
source.Draw(this, sourceRect, destRect, bitmapInterpolationMode);
}
/// <summary>

3
src/Avalonia.Visuals/Media/DrawingGroup.cs

@ -1,5 +1,6 @@
using Avalonia.Collections;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Media
{
@ -55,4 +56,4 @@ namespace Avalonia.Media
return rect;
}
}
}
}

81
src/Avalonia.Visuals/Media/DrawingImage.cs

@ -0,0 +1,81 @@
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media
{
/// <summary>
/// An <see cref="IImage"/> that uses a <see cref="Drawing"/> for content.
/// </summary>
public class DrawingImage : AvaloniaObject, IImage, IAffectsRender
{
/// <summary>
/// Defines the <see cref="Drawing"/> property.
/// </summary>
public static readonly StyledProperty<Drawing> DrawingProperty =
AvaloniaProperty.Register<DrawingImage, Drawing>(nameof(Drawing));
/// <inheritdoc/>
public event EventHandler Invalidated;
/// <summary>
/// Gets or sets the drawing content.
/// </summary>
[Content]
public Drawing Drawing
{
get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value);
}
/// <inheritdoc/>
public Size Size => Drawing?.GetBounds().Size ?? default;
/// <inheritdoc/>
void IImage.Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
{
var drawing = Drawing;
if (drawing == null)
{
return;
}
var bounds = drawing.GetBounds();
var scale = Matrix.CreateScale(
destRect.Width / sourceRect.Width,
destRect.Height / sourceRect.Height);
var translate = Matrix.CreateTranslation(
-sourceRect.X + destRect.X - bounds.X,
-sourceRect.Y + destRect.Y - bounds.Y);
using (context.PushClip(destRect))
using (context.PushPreTransform(translate * scale))
{
Drawing?.Draw(context);
}
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DrawingProperty)
{
RaiseInvalidated(EventArgs.Empty);
}
}
/// <summary>
/// Raises the <see cref="Invalidated"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
}
}

5
src/Avalonia.Visuals/Media/GeometryDrawing.cs

@ -1,10 +1,13 @@
namespace Avalonia.Media
using Avalonia.Metadata;
namespace Avalonia.Media
{
public class GeometryDrawing : Drawing
{
public static readonly StyledProperty<Geometry> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry));
[Content]
public Geometry Geometry
{
get => GetValue(GeometryProperty);

29
src/Avalonia.Visuals/Media/IImage.cs

@ -0,0 +1,29 @@
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media
{
/// <summary>
/// Represents a raster or vector image.
/// </summary>
public interface IImage
{
/// <summary>
/// Gets the size of the image, in device independent pixels.
/// </summary>
Size Size { get; }
/// <summary>
/// Draws the image to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The drawing context.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode);
}
}

20
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@ -5,6 +5,7 @@ using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
@ -94,9 +95,28 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Item.Save(fileName);
}
/// <summary>
/// Saves the bitmap to a stream.
/// </summary>
/// <param name="stream">The stream.</param>
public void Save(Stream stream)
{
PlatformImpl.Item.Save(stream);
}
/// <inheritdoc/>
void IImage.Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
{
context.PlatformImpl.DrawBitmap(
PlatformImpl,
1,
sourceRect,
destRect,
bitmapInterpolationMode);
}
}
}

11
src/Avalonia.Visuals/Media/Imaging/IBitmap.cs

@ -11,7 +11,7 @@ namespace Avalonia.Media.Imaging
/// <summary>
/// Represents a bitmap image.
/// </summary>
public interface IBitmap : IDisposable
public interface IBitmap : IImage, IDisposable
{
/// <summary>
/// Gets the dots per inch (DPI) of the image.
@ -32,15 +32,6 @@ namespace Avalonia.Media.Imaging
/// </summary>
IRef<IBitmapImpl> PlatformImpl { get; }
/// <summary>
/// Gets the size of the image, in device independent pixels.
/// </summary>
/// <remarks>
/// Note that Skia does not currently support reading the DPI of an image so this value
/// will equal <see cref="PixelSize"/> on Skia.
/// </remarks>
Size Size { get; }
/// <summary>
/// Saves the bitmap to a file.
/// </summary>

90
src/Avalonia.Visuals/Media/MediaExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -16,24 +17,82 @@ namespace Avalonia.Media
/// <param name="stretch">The stretch mode.</param>
/// <param name="destinationSize">The size of the destination viewport.</param>
/// <param name="sourceSize">The size of the source.</param>
/// <param name="stretchDirection">The stretch direction.</param>
/// <returns>A vector with the X and Y scaling factors.</returns>
public static Vector CalculateScaling(this Stretch stretch, Size destinationSize, Size sourceSize)
public static Vector CalculateScaling(
this Stretch stretch,
Size destinationSize,
Size sourceSize,
StretchDirection stretchDirection = StretchDirection.Both)
{
double scaleX = 1;
double scaleY = 1;
var scaleX = 1.0;
var scaleY = 1.0;
if (stretch != Stretch.None)
bool isConstrainedWidth = !double.IsPositiveInfinity(destinationSize.Width);
bool isConstrainedHeight = !double.IsPositiveInfinity(destinationSize.Height);
if ((stretch == Stretch.Uniform || stretch == Stretch.UniformToFill || stretch == Stretch.Fill)
&& (isConstrainedWidth || isConstrainedHeight))
{
scaleX = destinationSize.Width / sourceSize.Width;
scaleY = destinationSize.Height / sourceSize.Height;
// Compute scaling factors for both axes
scaleX = MathUtilities.IsZero(sourceSize.Width) ? 0.0 : destinationSize.Width / sourceSize.Width;
scaleY = MathUtilities.IsZero(sourceSize.Height) ? 0.0 : destinationSize.Height / sourceSize.Height;
switch (stretch)
if (!isConstrainedWidth)
{
scaleX = scaleY;
}
else if (!isConstrainedHeight)
{
scaleY = scaleX;
}
else
{
case Stretch.Uniform:
scaleX = scaleY = Math.Min(scaleX, scaleY);
// If not preserving aspect ratio, then just apply transform to fit
switch (stretch)
{
case Stretch.Uniform:
// Find minimum scale that we use for both axes
double minscale = scaleX < scaleY ? scaleX : scaleY;
scaleX = scaleY = minscale;
break;
case Stretch.UniformToFill:
// Find maximum scale that we use for both axes
double maxscale = scaleX > scaleY ? scaleX : scaleY;
scaleX = scaleY = maxscale;
break;
case Stretch.Fill:
// We already computed the fill scale factors above, so just use them
break;
}
}
// Apply stretch direction by bounding scales.
// In the uniform case, scaleX=scaleY, so this sort of clamping will maintain aspect ratio
// In the uniform fill case, we have the same result too.
// In the fill case, note that we change aspect ratio, but that is okay
switch (stretchDirection)
{
case StretchDirection.UpOnly:
if (scaleX < 1.0)
scaleX = 1.0;
if (scaleY < 1.0)
scaleY = 1.0;
break;
case StretchDirection.DownOnly:
if (scaleX > 1.0)
scaleX = 1.0;
if (scaleY > 1.0)
scaleY = 1.0;
break;
case Stretch.UniformToFill:
scaleX = scaleY = Math.Max(scaleX, scaleY);
case StretchDirection.Both:
break;
default:
break;
}
}
@ -47,10 +106,15 @@ namespace Avalonia.Media
/// <param name="stretch">The stretch mode.</param>
/// <param name="destinationSize">The size of the destination viewport.</param>
/// <param name="sourceSize">The size of the source.</param>
/// <param name="stretchDirection">The stretch direction.</param>
/// <returns>The size of the stretched source.</returns>
public static Size CalculateSize(this Stretch stretch, Size destinationSize, Size sourceSize)
public static Size CalculateSize(
this Stretch stretch,
Size destinationSize,
Size sourceSize,
StretchDirection stretchDirection = StretchDirection.Both)
{
return sourceSize * stretch.CalculateScaling(destinationSize, sourceSize);
return sourceSize * stretch.CalculateScaling(destinationSize, sourceSize, stretchDirection);
}
}
}

25
src/Avalonia.Visuals/Media/StretchDirection.cs

@ -0,0 +1,25 @@
namespace Avalonia.Media
{
/// <summary>
/// Describes the type of scaling that can be used when scaling content.
/// </summary>
public enum StretchDirection
{
/// <summary>
/// Only scales the content upwards when the content is smaller than the available space.
/// If the content is larger, no scaling downwards is done.
/// </summary>
UpOnly,
/// <summary>
/// Only scales the content downwards when the content is larger than the available space.
/// If the content is smaller, no scaling upwards is done.
/// </summary>
DownOnly,
/// <summary>
/// Always stretches to fit the available space according to the stretch mode.
/// </summary>
Both,
}
}

4
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -33,7 +33,7 @@ namespace Avalonia.Platform
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
/// <summary>
/// Draws a bitmap image.
@ -42,7 +42,7 @@ namespace Avalonia.Platform
/// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
/// <summary>
/// Draws a line.

32
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -160,16 +160,23 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
{
if (_renderLoop == null && (_dirty == null || _dirty.Count > 0))
{
// When unit testing the renderLoop may be null, so update the scene manually.
UpdateScene();
}
EnsureCanHitTest();
//It's safe to access _scene here without a lock since
//it's only changed from UI thread which we are currently on
return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>();
}
/// <inheritdoc/>
public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter)
{
EnsureCanHitTest();
//It's safe to access _scene here without a lock since
//it's only changed from UI thread which we are currently on
return _scene?.Item.HitTestFirst(p, root, filter);
}
/// <inheritdoc/>
public void Paint(Rect rect)
{
@ -235,6 +242,15 @@ namespace Avalonia.Rendering
internal Scene UnitTestScene() => _scene.Item;
private void EnsureCanHitTest()
{
if (_renderLoop == null && (_dirty == null || _dirty.Count > 0))
{
// When unit testing the renderLoop may be null, so update the scene manually.
UpdateScene();
}
}
private void Render(bool forceComposite)
{
using (var l = _lock.TryLock())
@ -469,11 +485,11 @@ namespace Avalonia.Rendering
if (layer.OpacityMask == null)
{
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
context.DrawBitmap(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (layer.GeometryClip != null)
@ -485,7 +501,7 @@ namespace Avalonia.Rendering
if (_overlay != null)
{
var sourceRect = new Rect(0, 0, _overlay.Item.PixelSize.Width, _overlay.Item.PixelSize.Height);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
context.DrawBitmap(_overlay, 0.5, sourceRect, clientRect);
}
if (DrawFps)

13
src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs

@ -1,3 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
/// <summary>
@ -9,4 +13,13 @@ namespace Avalonia.Rendering
{
bool HitTest(Point point);
}
public static class CustomSimpleHitTestExtensions
{
public static bool HitTestCustom(this IVisual visual, Point point)
=> (visual as ICustomSimpleHitTest)?.HitTest(point) ?? visual.TransformedBounds?.Contains(point) == true;
public static bool HitTestCustom(this IEnumerable<IVisual> children, Point point)
=> children.Any(ctrl => ctrl.HitTestCustom(point));
}
}

12
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -50,6 +50,18 @@ namespace Avalonia.Rendering
/// <returns>The visuals at the specified point, topmost first.</returns>
IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter);
/// <summary>
/// Hit tests a location to find first visual at the specified point.
/// </summary>
/// <param name="p">The point, in client coordinates.</param>
/// <param name="root">The root of the subtree to search.</param>
/// <param name="filter">
/// A filter predicate. If the predicate returns false then the visual and all its
/// children will be excluded from the results.
/// </param>
/// <returns>The visual at the specified point, topmost first.</returns>
IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter);
/// <summary>
/// Informs the renderer that the z-ordering of a visual's children has changed.
/// </summary>

9
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -164,6 +164,11 @@ namespace Avalonia.Rendering
return HitTest(root, p, filter);
}
public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter)
{
return HitTest(root, p, filter).FirstOrDefault();
}
/// <inheritdoc/>
public void RecalculateChildren(IVisual visual) => AddDirty(visual);
@ -307,7 +312,9 @@ namespace Avalonia.Rendering
if (!child.ClipToBounds || clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
var childClipRect = child.RenderTransform == null
? clipRect.Translate(-childBounds.Position)
: clipRect;
Render(context, child, childClipRect);
}
else

4
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
var next = NextDrawAs<ImageNode>();
@ -130,7 +130,7 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
{
// This method is currently only used to composite layers so shouldn't be called here.
throw new NotSupportedException();

2
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -100,7 +100,7 @@ namespace Avalonia.Rendering.SceneGraph
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
context.DrawImage(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode);
context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode);
}
/// <inheritdoc/>

174
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -2,8 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections.Pooled;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
@ -128,7 +130,20 @@ namespace Avalonia.Rendering.SceneGraph
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
{
var node = FindNode(root);
return (node != null) ? HitTest(node, p, null, filter) : Enumerable.Empty<IVisual>();
return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty<IVisual>();
}
/// <summary>
/// Gets the visual at a point in the scene.
/// </summary>
/// <param name="p">The point.</param>
/// <param name="root">The root of the subtree to search.</param>
/// <param name="filter">A filter. May be null.</param>
/// <returns>The visual at the specified point.</returns>
public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter)
{
var node = FindNode(root);
return (node != null) ? HitTestFirst(node, p, filter) : null;
}
/// <summary>
@ -158,38 +173,157 @@ namespace Avalonia.Rendering.SceneGraph
return result;
}
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> filter)
private IVisual HitTestFirst(IVisualNode root, Point p, Func<IVisual, bool> filter)
{
if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
using var enumerator = new HitTestEnumerator(root, filter, p, Root);
enumerator.MoveNext();
return enumerator.Current;
}
private class HitTestEnumerable : IEnumerable<IVisual>
{
private readonly IVisualNode _root;
private readonly Func<IVisual, bool> _filter;
private readonly IVisualNode _sceneRoot;
private readonly Point _point;
public HitTestEnumerable(IVisualNode root, Func<IVisual, bool> filter, Point point, IVisualNode sceneRoot)
{
var clipped = false;
_root = root;
_filter = filter;
_point = point;
_sceneRoot = sceneRoot;
}
if (node.ClipToBounds)
{
clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
clipped = !clip.Value.Contains(p);
}
public IEnumerator<IVisual> GetEnumerator()
{
return new HitTestEnumerator(_root, _filter, _point, _sceneRoot);
}
if (node.GeometryClip != null)
{
var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
clipped = !node.GeometryClip.FillContains(controlPoint.Value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private struct HitTestEnumerator : IEnumerator<IVisual>
{
private readonly PooledStack<Entry> _nodeStack;
private readonly Func<IVisual, bool> _filter;
private readonly IVisualNode _sceneRoot;
private IVisual _current;
private readonly Point _point;
public HitTestEnumerator(IVisualNode root, Func<IVisual, bool> filter, Point point, IVisualNode sceneRoot)
{
_nodeStack = new PooledStack<Entry>();
_nodeStack.Push(new Entry(root, false, null, true));
_filter = filter;
_point = point;
_sceneRoot = sceneRoot;
if (!clipped)
_current = null;
}
public bool MoveNext()
{
while (_nodeStack.Count > 0)
{
for (var i = node.Children.Count - 1; i >= 0; --i)
(var wasVisited, var isRoot, IVisualNode node, Rect? clip) = _nodeStack.Pop();
if (wasVisited && isRoot)
{
foreach (var h in HitTest(node.Children[i], p, clip, filter))
break;
}
var children = node.Children;
int childCount = children.Count;
if (childCount == 0 || wasVisited)
{
if ((wasVisited || FilterAndClip(node, ref clip)) && node.HitTest(_point))
{
yield return h;
_current = node.Visual;
return true;
}
}
else if (FilterAndClip(node, ref clip))
{
_nodeStack.Push(new Entry(node, true, null));
for (var i = 0; i < childCount; i++)
{
_nodeStack.Push(new Entry(children[i], false, clip));
}
}
}
return false;
}
public void Reset()
{
throw new NotSupportedException();
}
public IVisual Current => _current;
object IEnumerator.Current => Current;
public void Dispose()
{
_nodeStack.Dispose();
}
if (node.HitTest(p))
private bool FilterAndClip(IVisualNode node, ref Rect? clip)
{
if (_filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
{
var clipped = false;
if (node.ClipToBounds)
{
yield return node.Visual;
clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
clipped = !clip.Value.Contains(_point);
}
if (node.GeometryClip != null)
{
var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual);
clipped = !node.GeometryClip.FillContains(controlPoint.Value);
}
return !clipped;
}
return false;
}
private readonly struct Entry
{
public readonly bool WasVisited;
public readonly bool IsRoot;
public readonly IVisualNode Node;
public readonly Rect? Clip;
public Entry(IVisualNode node, bool wasVisited, Rect? clip, bool isRoot = false)
{
Node = node;
WasVisited = wasVisited;
IsRoot = isRoot;
Clip = clip;
}
public void Deconstruct(out bool wasVisited, out bool isRoot, out IVisualNode node, out Rect? clip)
{
wasVisited = WasVisited;
isRoot = IsRoot;
node = Node;
clip = Clip;
}
}
}

7
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -280,8 +280,13 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/>
public bool HitTest(Point p)
{
foreach (var operation in DrawOperations)
var drawOperations = DrawOperations;
var drawOperationsCount = drawOperations.Count;
for (var i = 0; i < drawOperationsCount; i++)
{
var operation = drawOperations[i];
if (operation?.Item?.HitTest(p) == true)
{
return true;

29
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -213,12 +213,37 @@ namespace Avalonia.VisualTree
/// </summary>
/// <param name="visual">The root visual to test.</param>
/// <param name="p">The point.</param>
/// <returns>The visuals at the requested point.</returns>
/// <returns>The visual at the requested point.</returns>
public static IVisual GetVisualAt(this IVisual visual, Point p)
{
Contract.Requires<ArgumentNullException>(visual != null);
return visual.GetVisualsAt(p).FirstOrDefault();
return visual.GetVisualAt(p, x => x.IsVisible);
}
/// <summary>
/// Gets the first visual in the visual tree whose bounds contain a point.
/// </summary>
/// <param name="visual">The root visual to test.</param>
/// <param name="p">The point.</param>
/// <param name="filter">
/// A filter predicate. If the predicate returns false then the visual and all its
/// children will be excluded from the results.
/// </param>
/// <returns>The visual at the requested point.</returns>
public static IVisual GetVisualAt(this IVisual visual, Point p, Func<IVisual, bool> filter)
{
Contract.Requires<ArgumentNullException>(visual != null);
var root = visual.GetVisualRoot();
var rootPoint = visual.TranslatePoint(p, root);
if (rootPoint.HasValue)
{
return root.Renderer.HitTestFirst(rootPoint.Value, visual, filter);
}
return null;
}
/// <summary>

18
src/Avalonia.X11/Glx/Glx.cs

@ -84,8 +84,24 @@ namespace Avalonia.X11.Glx
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public GlxInterface() : base(GlxGetProcAddress)
public GlxInterface() : base(SafeGetProcAddress)
{
}
// Ignores egl functions.
// On some Linux systems, glXGetProcAddress will return valid pointers for even EGL functions.
// This makes Skia try to load some data from EGL,
// which can then cause segmentation faults because they return garbage.
public static IntPtr SafeGetProcAddress(string proc, bool optional)
{
if (proc.StartsWith("egl", StringComparison.InvariantCulture))
{
return IntPtr.Zero;
}
return GlxConverted(proc, optional);
}
private static readonly Func<string, bool, IntPtr> GlxConverted = ConvertNative(GlxGetProcAddress);
}
}

2
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -87,7 +87,7 @@ namespace Avalonia.X11.Glx
ImmediateContext.MakeCurrent();
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
GlInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (GlInterface.Renderer == null)

4
src/Avalonia.X11/X11Framebuffer.cs

@ -14,6 +14,10 @@ namespace Avalonia.X11
public X11Framebuffer(IntPtr display, IntPtr xid, int depth, int width, int height, double factor)
{
// HACK! Please fix renderer, should never ask for 0x0 bitmap.
width = Math.Max(1, width);
height = Math.Max(1, height);
_display = display;
_xid = xid;
_depth = depth;

2
src/Avalonia.X11/X11IconLoader.cs

@ -59,7 +59,7 @@ namespace Avalonia.X11
}
using(var rt = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[]{this}))
using (var ctx = rt.CreateDrawingContext(null))
ctx.DrawImage(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
new Rect(0, 0, _width, _height));
Data = new UIntPtr[_width * _height + 2];
Data[0] = new UIntPtr((uint)_width);

2
src/Avalonia.X11/X11Platform.cs

@ -44,7 +44,7 @@ namespace Avalonia.X11
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))

8
src/Avalonia.X11/X11Window.cs

@ -102,7 +102,7 @@ namespace Avalonia.X11
valueMask |= SetWindowValuemask.ColorMap;
}
int defaultWidth = 300, defaultHeight = 200;
int defaultWidth = 0, defaultHeight = 0;
if (!_popup && Screen != null)
{
@ -117,6 +117,10 @@ namespace Avalonia.X11
}
}
// check if the calculated size is zero then compensate to hardcoded resolution
defaultWidth = Math.Max(defaultWidth, 300);
defaultHeight = Math.Max(defaultHeight, 200);
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0,
depth,
(int)CreateWindowArgs.InputOutput,
@ -133,7 +137,7 @@ namespace Avalonia.X11
_renderHandle = _handle;
Handle = new PlatformHandle(_handle, "XID");
_realSize = new PixelSize(300, 200);
_realSize = new PixelSize(defaultWidth, defaultHeight);
platform.Windows[_handle] = OnEvent;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
| XEventMask.ResizeRedirectMask

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -41,6 +41,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)),
TargetNullValue = TargetNullValue,
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
};
}
@ -86,5 +87,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public string StringFormat { get; set; }
public RelativeSource RelativeSource { get; set; }
public object TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
}
}

26
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -7,8 +7,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
/// <summary>
/// Loads a resource dictionary from a specified URL.
/// </summary>
public class ResourceInclude :IResourceProvider
public class ResourceInclude : IResourceNode, ISetResourceParent
{
private IResourceNode _parent;
private Uri _baseUri;
private IResourceDictionary _loaded;
@ -26,6 +27,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var loader = new AvaloniaXamlLoader();
_loaded = (IResourceDictionary)loader.Load(Source, _baseUri);
(_loaded as ISetResourceParent)?.SetParent(this);
_loaded.ResourcesChanged += ResourcesChanged;
if (_loaded.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
@ -44,12 +48,32 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
/// <inhertidoc/>
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inhertidoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inhertidoc/>
bool IResourceProvider.TryGetResource(object key, out object value)
{
return Loaded.TryGetResource(key, out value);
}
/// <inhertidoc/>
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The ResourceInclude already has a parent.");
}
_parent = parent;
}
/// <inhertidoc/>
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
(_loaded as ISetResourceParent)?.ParentResourcesChanged(e);
}
public ResourceInclude ProvideValue(IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;

10
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// <summary>
/// Includes a style from a URL.
/// </summary>
public class StyleInclude : IStyle, ISetStyleParent
public class StyleInclude : IStyle, ISetResourceParent
{
private Uri _baseUri;
private IStyle _loaded;
@ -53,7 +53,7 @@ namespace Avalonia.Markup.Xaml.Styling
{
var loader = new AvaloniaXamlLoader();
_loaded = (IStyle)loader.Load(Source, _baseUri);
(_loaded as ISetStyleParent)?.SetParent(this);
(_loaded as ISetResourceParent)?.SetParent(this);
}
return _loaded;
@ -89,13 +89,13 @@ namespace Avalonia.Markup.Xaml.Styling
public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
(Loaded as ISetStyleParent)?.NotifyResourcesChanged(e);
(Loaded as ISetResourceParent)?.ParentResourcesChanged(e);
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -99,7 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
Add("Avalonia.Media.IImage","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1");
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")),
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter"));

2
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -75,7 +75,7 @@ namespace Avalonia.Shared.PlatformSupport
lock (_btlock)
Backtraces.Remove(_backtrace);
#endif
_plat.Free(_address, Size);
_plat?.Free(_address, Size);
GC.RemoveMemoryPressure(Size);
IsDisposed = true;
_address = IntPtr.Zero;

8
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -110,7 +110,7 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
var drawableImage = (IDrawableBitmapImpl)source.Item;
var s = sourceRect.ToSKRect();
@ -146,10 +146,10 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
PushOpacityMask(opacityMask, opacityMaskRect);
DrawImage(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
PopOpacityMask();
}
@ -437,7 +437,7 @@ namespace Avalonia.Skia
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(
context.DrawBitmap(
RefCountable.CreateUnownedNotClonable(tileBrushImage),
1,
sourceRect,

5
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -89,12 +89,15 @@ namespace Avalonia.Skia
if (typeface.FontFamily.Key == null)
{
var defaultName = SKTypeface.Default.FamilyName;
foreach (var familyName in typeface.FontFamily.FamilyNames)
{
skTypeface = SKTypeface.FromFamilyName(familyName, (SKFontStyleWeight)typeface.Weight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
if (skTypeface == SKTypeface.Default)
if (!skTypeface.FamilyName.Equals(familyName, StringComparison.Ordinal) &&
defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal))
{
continue;
}

4
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -109,7 +109,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
{
@ -149,7 +149,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))

2
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -107,7 +107,7 @@ namespace Avalonia.Direct2D1.Media
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect, _bitmapInterpolationMode);
context.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect, _bitmapInterpolationMode);
context.PopClip();
}

3
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.Media
_direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
public override Vector Dpi => _direct2DBitmap.DotsPerInch.ToAvaloniaVector();
public override Vector Dpi => new Vector(96, 96);
public override PixelSize PixelSize => _direct2DBitmap.PixelSize.ToAvalonia();
public override void Dispose()
@ -58,3 +58,4 @@ namespace Avalonia.Direct2D1.Media
}
}
}
;

2
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -58,7 +58,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
using (var dc = wic.CreateDrawingContext(null))
{
dc.DrawImage(
dc.DrawBitmap(
RefCountable.CreateUnownedNotClonable(this),
1,
new Rect(PixelSize.ToSizeWithDpi(Dpi.X)),

14
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -26,6 +26,7 @@ namespace Avalonia.Direct2D1.Media
using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
Dpi = new Vector(96, 96);
}
}
@ -39,6 +40,7 @@ namespace Avalonia.Direct2D1.Media
_decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad);
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, _decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
Dpi = new Vector(96, 96);
}
/// <summary>
@ -62,6 +64,7 @@ namespace Avalonia.Direct2D1.Media
pixelFormat.Value.ToWic(),
BitmapCreateCacheOption.CacheOnLoad);
WicImpl.SetResolution(dpi.X, dpi.Y);
Dpi = dpi;
}
public WicBitmapImpl(APixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
@ -70,6 +73,8 @@ namespace Avalonia.Direct2D1.Media
WicImpl.SetResolution(dpi.X, dpi.Y);
PixelFormat = format;
Dpi = dpi;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{
for (var row = 0; row < size.Height; row++)
@ -82,14 +87,7 @@ namespace Avalonia.Direct2D1.Media
}
}
public override Vector Dpi
{
get
{
WicImpl.GetResolution(out double x, out double y);
return new Vector(x, y);
}
}
public override Vector Dpi { get; }
public override PixelSize PixelSize => WicImpl.Size.ToAvalonia();

76
src/Windows/Avalonia.Win32/Input/KeyInterop.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
@ -211,7 +210,7 @@ namespace Avalonia.Win32.Input
{ 31, Key.ImeModeChange },
{ 32, Key.Space },
{ 33, Key.PageUp },
{ 34, Key.Next },
{ 34, Key.PageDown },
{ 35, Key.End },
{ 36, Key.Home },
{ 37, Key.Left },
@ -364,17 +363,80 @@ namespace Avalonia.Win32.Input
{ 254, Key.OemClear },
};
public static Key KeyFromVirtualKey(int virtualKey)
/// <summary>
/// Indicates whether the key is an extended key, such as the right-hand ALT and CTRL keys.
/// According to https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown.
/// </summary>
private static bool IsExtended(int keyData)
{
Key result;
s_keyFromVirtualKey.TryGetValue(virtualKey, out result);
const int extendedMask = 1 << 24;
return (keyData & extendedMask) != 0;
}
private static int GetVirtualKey(int virtualKey, int keyData)
{
// Adapted from https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/HwndKeyboardInputProvider.cs.
if (virtualKey == (int)UnmanagedMethods.VirtualKeyStates.VK_SHIFT)
{
// Bits from 16 to 23 represent scan code.
const int scanCodeMask = 0xFF0000;
var scanCode = (keyData & scanCodeMask) >> 16;
virtualKey = (int)UnmanagedMethods.MapVirtualKey((uint)scanCode, (uint)UnmanagedMethods.MapVirtualKeyMapTypes.MAPVK_VSC_TO_VK_EX);
if (virtualKey == 0)
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_LSHIFT;
}
}
if (virtualKey == (int)UnmanagedMethods.VirtualKeyStates.VK_MENU)
{
bool isRight = IsExtended(keyData);
if (isRight)
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_RMENU;
}
else
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_LMENU;
}
}
if (virtualKey == (int)UnmanagedMethods.VirtualKeyStates.VK_CONTROL)
{
bool isRight = IsExtended(keyData);
if (isRight)
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_RCONTROL;
}
else
{
virtualKey = (int)UnmanagedMethods.VirtualKeyStates.VK_LCONTROL;
}
}
return virtualKey;
}
public static Key KeyFromVirtualKey(int virtualKey, int keyData)
{
virtualKey = GetVirtualKey(virtualKey, keyData);
s_keyFromVirtualKey.TryGetValue(virtualKey, out var result);
return result;
}
public static int VirtualKeyFromKey(Key key)
{
int result;
s_virtualKeyFromKey.TryGetValue(key, out result);
s_virtualKeyFromKey.TryGetValue(key, out var result);
return result;
}
}

175
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -241,6 +241,170 @@ namespace Avalonia.Win32.Interop
MK_XBUTTON2 = 0x0040
}
public enum VirtualKeyStates : int
{
VK_LBUTTON = 0x01,
VK_RBUTTON = 0x02,
VK_CANCEL = 0x03,
VK_MBUTTON = 0x04,
VK_XBUTTON1 = 0x05,
VK_XBUTTON2 = 0x06,
VK_BACK = 0x08,
VK_TAB = 0x09,
VK_CLEAR = 0x0C,
VK_RETURN = 0x0D,
VK_SHIFT = 0x10,
VK_CONTROL = 0x11,
VK_MENU = 0x12,
VK_PAUSE = 0x13,
VK_CAPITAL = 0x14,
VK_KANA = 0x15,
VK_HANGEUL = 0x15,
VK_HANGUL = 0x15,
VK_JUNJA = 0x17,
VK_FINAL = 0x18,
VK_HANJA = 0x19,
VK_KANJI = 0x19,
VK_ESCAPE = 0x1B,
VK_CONVERT = 0x1C,
VK_NONCONVERT = 0x1D,
VK_ACCEPT = 0x1E,
VK_MODECHANGE = 0x1F,
VK_SPACE = 0x20,
VK_PRIOR = 0x21,
VK_NEXT = 0x22,
VK_END = 0x23,
VK_HOME = 0x24,
VK_LEFT = 0x25,
VK_UP = 0x26,
VK_RIGHT = 0x27,
VK_DOWN = 0x28,
VK_SELECT = 0x29,
VK_PRINT = 0x2A,
VK_EXECUTE = 0x2B,
VK_SNAPSHOT = 0x2C,
VK_INSERT = 0x2D,
VK_DELETE = 0x2E,
VK_HELP = 0x2F,
VK_LWIN = 0x5B,
VK_RWIN = 0x5C,
VK_APPS = 0x5D,
VK_SLEEP = 0x5F,
VK_NUMPAD0 = 0x60,
VK_NUMPAD1 = 0x61,
VK_NUMPAD2 = 0x62,
VK_NUMPAD3 = 0x63,
VK_NUMPAD4 = 0x64,
VK_NUMPAD5 = 0x65,
VK_NUMPAD6 = 0x66,
VK_NUMPAD7 = 0x67,
VK_NUMPAD8 = 0x68,
VK_NUMPAD9 = 0x69,
VK_MULTIPLY = 0x6A,
VK_ADD = 0x6B,
VK_SEPARATOR = 0x6C,
VK_SUBTRACT = 0x6D,
VK_DECIMAL = 0x6E,
VK_DIVIDE = 0x6F,
VK_F1 = 0x70,
VK_F2 = 0x71,
VK_F3 = 0x72,
VK_F4 = 0x73,
VK_F5 = 0x74,
VK_F6 = 0x75,
VK_F7 = 0x76,
VK_F8 = 0x77,
VK_F9 = 0x78,
VK_F10 = 0x79,
VK_F11 = 0x7A,
VK_F12 = 0x7B,
VK_F13 = 0x7C,
VK_F14 = 0x7D,
VK_F15 = 0x7E,
VK_F16 = 0x7F,
VK_F17 = 0x80,
VK_F18 = 0x81,
VK_F19 = 0x82,
VK_F20 = 0x83,
VK_F21 = 0x84,
VK_F22 = 0x85,
VK_F23 = 0x86,
VK_F24 = 0x87,
VK_NUMLOCK = 0x90,
VK_SCROLL = 0x91,
VK_OEM_NEC_EQUAL = 0x92,
VK_OEM_FJ_JISHO = 0x92,
VK_OEM_FJ_MASSHOU = 0x93,
VK_OEM_FJ_TOUROKU = 0x94,
VK_OEM_FJ_LOYA = 0x95,
VK_OEM_FJ_ROYA = 0x96,
VK_LSHIFT = 0xA0,
VK_RSHIFT = 0xA1,
VK_LCONTROL = 0xA2,
VK_RCONTROL = 0xA3,
VK_LMENU = 0xA4,
VK_RMENU = 0xA5,
VK_BROWSER_BACK = 0xA6,
VK_BROWSER_FORWARD = 0xA7,
VK_BROWSER_REFRESH = 0xA8,
VK_BROWSER_STOP = 0xA9,
VK_BROWSER_SEARCH = 0xAA,
VK_BROWSER_FAVORITES = 0xAB,
VK_BROWSER_HOME = 0xAC,
VK_VOLUME_MUTE = 0xAD,
VK_VOLUME_DOWN = 0xAE,
VK_VOLUME_UP = 0xAF,
VK_MEDIA_NEXT_TRACK = 0xB0,
VK_MEDIA_PREV_TRACK = 0xB1,
VK_MEDIA_STOP = 0xB2,
VK_MEDIA_PLAY_PAUSE = 0xB3,
VK_LAUNCH_MAIL = 0xB4,
VK_LAUNCH_MEDIA_SELECT = 0xB5,
VK_LAUNCH_APP1 = 0xB6,
VK_LAUNCH_APP2 = 0xB7,
VK_OEM_1 = 0xBA,
VK_OEM_PLUS = 0xBB,
VK_OEM_COMMA = 0xBC,
VK_OEM_MINUS = 0xBD,
VK_OEM_PERIOD = 0xBE,
VK_OEM_2 = 0xBF,
VK_OEM_3 = 0xC0,
VK_OEM_4 = 0xDB,
VK_OEM_5 = 0xDC,
VK_OEM_6 = 0xDD,
VK_OEM_7 = 0xDE,
VK_OEM_8 = 0xDF,
VK_OEM_AX = 0xE1,
VK_OEM_102 = 0xE2,
VK_ICO_HELP = 0xE3,
VK_ICO_00 = 0xE4,
VK_PROCESSKEY = 0xE5,
VK_ICO_CLEAR = 0xE6,
VK_PACKET = 0xE7,
VK_OEM_RESET = 0xE9,
VK_OEM_JUMP = 0xEA,
VK_OEM_PA1 = 0xEB,
VK_OEM_PA2 = 0xEC,
VK_OEM_PA3 = 0xED,
VK_OEM_WSCTRL = 0xEE,
VK_OEM_CUSEL = 0xEF,
VK_OEM_ATTN = 0xF0,
VK_OEM_FINISH = 0xF1,
VK_OEM_COPY = 0xF2,
VK_OEM_AUTO = 0xF3,
VK_OEM_ENLW = 0xF4,
VK_OEM_BACKTAB = 0xF5,
VK_ATTN = 0xF6,
VK_CRSEL = 0xF7,
VK_EXSEL = 0xF8,
VK_EREOF = 0xF9,
VK_PLAY = 0xFA,
VK_ZOOM = 0xFB,
VK_NONAME = 0xFC,
VK_PA1 = 0xFD,
VK_OEM_CLEAR = 0xFE
}
public enum WindowActivate
{
WA_INACTIVE,
@ -581,6 +745,14 @@ namespace Avalonia.Win32.Interop
WM_DISPATCH_WORK_ITEM = WM_USER,
}
public enum MapVirtualKeyMapTypes : uint
{
MAPVK_VK_TO_VSC = 0x00,
MAPVK_VSC_TO_VK = 0x01,
MAPVK_VK_TO_CHAR = 0x02,
MAPVK_VSC_TO_VK_EX = 0x03,
}
public enum BitmapCompressionMode : uint
{
BI_RGB = 0,
@ -756,6 +928,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll", EntryPoint = "MapVirtualKeyW")]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll", EntryPoint = "GetMessageW")]
public static extern sbyte GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

8
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -516,7 +516,7 @@ namespace Avalonia.Win32
timestamp,
_owner,
RawKeyEventType.KeyDown,
KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers);
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), WindowsKeyboardDevice.Instance.Modifiers);
break;
case UnmanagedMethods.WindowsMessage.WM_MENUCHAR:
@ -530,7 +530,7 @@ namespace Avalonia.Win32
timestamp,
_owner,
RawKeyEventType.KeyUp,
KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers);
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), WindowsKeyboardDevice.Instance.Modifiers);
break;
case UnmanagedMethods.WindowsMessage.WM_CHAR:
// Ignore control chars
@ -795,9 +795,9 @@ namespace Avalonia.Win32
modifiers |= RawInputModifiers.RightMouseButton;
if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_MBUTTON))
modifiers |= RawInputModifiers.MiddleMouseButton;
if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_XBUTTON1))
if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_XBUTTON1))
modifiers |= RawInputModifiers.XButton1MouseButton;
if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_XBUTTON2))
if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_XBUTTON2))
modifiers |= RawInputModifiers.XButton2MouseButton;
return modifiers;
}

2
tests/Avalonia.Benchmarks/NullRenderer.cs

@ -21,6 +21,8 @@ namespace Avalonia.Benchmarks
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) => null;
public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) => null;
public void Paint(Rect rect)
{
}

70
tests/Avalonia.Controls.UnitTests/ImageTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
@ -52,7 +52,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
@ -62,10 +62,59 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(50, 50), target.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_With_StretchDirection_DownOnly()
{
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.StretchDirection = StretchDirection.DownOnly;
target.Source = bitmap;
target.Measure(new Size(150, 150));
Assert.Equal(new Size(50, 100), target.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Height()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(200, double.PositiveInfinity));
Assert.Equal(new Size(200, 400), image.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Width()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(double.PositiveInfinity, 400));
Assert.Equal(new Size(200, 400), image.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Width_Height()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Assert.Equal(new Size(50, 100), image.DesiredSize);
}
[Fact]
public void Arrange_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
@ -79,7 +128,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
@ -93,7 +142,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
@ -107,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
@ -117,5 +166,10 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(25, 100), target.Bounds.Size);
}
private IBitmap CreateBitmap(int width, int height)
{
return Mock.Of<IBitmap>(x => x.Size == new Size(width, height));
}
}
}

17
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -129,6 +129,23 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(-1, target.SelectedIndex);
}
[Fact]
public void SelectedIndex_Should_Be_Minus_1_Without_Initialize()
{
var items = new[]
{
new Item(),
new Item(),
};
var target = new ListBox();
target.Items = items;
target.Template = Template();
target.DataContext = new object();
Assert.Equal(-1, target.SelectedIndex);
}
[Fact]
public void SelectedIndex_Should_Be_0_After_Initialize_With_AlwaysSelected()
{

54
tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs

@ -1,5 +1,4 @@
using Avalonia.Data;
using Avalonia.Markup.Data;
using Avalonia.UnitTests;
using Xunit;
@ -63,6 +62,54 @@ namespace Avalonia.Controls.Primitives.UnitTests
Assert.Null(threeStateButton.IsChecked);
}
[Fact]
public void ToggleButton_Events_Are_Raised_On_Is_Checked_Changes()
{
var threeStateButton = new ToggleButton();
bool checkedRaised = false;
threeStateButton.Checked += (_, __) => checkedRaised = true;
threeStateButton.IsChecked = true;
Assert.True(checkedRaised);
bool uncheckedRaised = false;
threeStateButton.Unchecked += (_, __) => uncheckedRaised = true;
threeStateButton.IsChecked = false;
Assert.True(uncheckedRaised);
bool indeterminateRaised = false;
threeStateButton.Indeterminate += (_, __) => indeterminateRaised = true;
threeStateButton.IsChecked = null;
Assert.True(indeterminateRaised);
}
[Fact]
public void ToggleButton_Events_Are_Raised_When_Toggling()
{
var threeStateButton = new TestToggleButton { IsThreeState = true };
bool checkedRaised = false;
threeStateButton.Checked += (_, __) => checkedRaised = true;
threeStateButton.Toggle();
Assert.True(checkedRaised);
bool indeterminateRaised = false;
threeStateButton.Indeterminate += (_, __) => indeterminateRaised = true;
threeStateButton.Toggle();
Assert.True(indeterminateRaised);
bool uncheckedRaised = false;
threeStateButton.Unchecked += (_, __) => uncheckedRaised = true;
threeStateButton.Toggle();
Assert.True(uncheckedRaised);
}
private class Class1 : NotifyingBase
{
private bool _foo;
@ -80,5 +127,10 @@ namespace Avalonia.Controls.Primitives.UnitTests
set { nullableFoo = value; RaisePropertyChanged(); }
}
}
private class TestToggleButton : ToggleButton
{
public new void Toggle() => base.Toggle();
}
}
}

24
tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs

@ -36,6 +36,30 @@ namespace Avalonia.Direct2D1.UnitTests.Media
}
}
[Fact]
public void Should_Create_Typeface_From_Fallback_Bold()
{
using (AvaloniaLocator.EnterScope())
{
Direct2D1Platform.Initialize();
var fontManager = new FontManagerImpl();
var defaultName = fontManager.GetDefaultFontFamilyName();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(new FontFamily("A, B, Arial"), FontWeight.Bold));
var font = glyphTypeface.DWFont;
Assert.Equal("Arial", font.FontFamily.FamilyNames.GetString(0));
Assert.Equal(SharpDX.DirectWrite.FontWeight.Bold, font.Weight);
Assert.Equal(SharpDX.DirectWrite.FontStyle.Normal, font.Style);
}
}
[Fact]
public void Should_Create_Typeface_For_Unknown_Font()
{

3
tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs

@ -238,6 +238,9 @@ namespace Avalonia.Input.UnitTests
{
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(new[] { hit });
renderer.Setup(x => x.HitTestFirst(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(hit);
}
private IDisposable TestApplication(IRenderer renderer)

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

Loading…
Cancel
Save