Browse Source

Merge branch 'master' into feature-devtools-inspect-popup

pull/6191/head
Dariusz Komosiński 5 years ago
committed by GitHub
parent
commit
de9253f9d6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      native/Avalonia.Native/src/OSX/window.h
  2. 97
      native/Avalonia.Native/src/OSX/window.mm
  3. 149
      samples/RenderDemo/Pages/AnimationsPage.xaml
  4. 84
      samples/RenderDemo/Pages/TransitionsPage.xaml
  5. 4
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  6. 2
      src/Avalonia.Base/AvaloniaObject.cs
  7. 4
      src/Avalonia.Base/AvaloniaProperty.cs
  8. 4
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
  9. 2
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  10. 2
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  11. 6
      src/Avalonia.Base/DirectPropertyBase.cs
  12. 2
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  13. 8
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  14. 17
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  15. 2
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  16. 2
      src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
  17. 2
      src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
  18. 2
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  19. 8
      src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
  20. 6
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  21. 18
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  22. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
  23. 22
      src/Avalonia.Controls/ApiCompatBaseline.txt
  24. 2
      src/Avalonia.Controls/AppBuilderBase.cs
  25. 4
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  26. 17
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  27. 9
      src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs
  28. 10
      src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
  29. 4
      src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
  30. 2
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  31. 12
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  32. 8
      src/Avalonia.Controls/DefinitionBase.cs
  33. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  34. 2
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  35. 18
      src/Avalonia.Controls/Grid.cs
  36. 2
      src/Avalonia.Controls/ListBox.cs
  37. 2
      src/Avalonia.Controls/NativeMenuItemSeparator.cs
  38. 2
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  39. 3
      src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
  40. 37
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  41. 4
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  42. 4
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  43. 2
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  44. 9
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  45. 2
      src/Avalonia.Controls/Primitives/RangeBase.cs
  46. 2
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  47. 2
      src/Avalonia.Controls/Repeater/IElementFactory.cs
  48. 4
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  49. 2
      src/Avalonia.Controls/Repeater/ViewManager.cs
  50. 2
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  51. 2
      src/Avalonia.Controls/ScrollViewer.cs
  52. 2
      src/Avalonia.Controls/Selection/SelectionModel.cs
  53. 8
      src/Avalonia.Controls/TickBar.cs
  54. 9
      src/Avalonia.Controls/TopLevel.cs
  55. 145
      src/Avalonia.Controls/Window.cs
  56. 34
      src/Avalonia.Controls/WindowBase.cs
  57. 8
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  58. 8
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  59. 13
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  60. 3
      src/Avalonia.Input/Avalonia.Input.csproj
  61. 2
      src/Avalonia.Input/Cursor.cs
  62. 13
      src/Avalonia.Input/FocusManager.cs
  63. 2
      src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
  64. 2
      src/Avalonia.Input/ICommandSource.cs
  65. 17
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  66. 31
      src/Avalonia.Input/InputElement.cs
  67. 33
      src/Avalonia.Input/KeyboardNavigation.cs
  68. 111
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  69. 7
      src/Avalonia.Input/KeyboardNavigationMode.cs
  70. 734
      src/Avalonia.Input/Navigation/TabNavigation.cs
  71. 4
      src/Avalonia.Input/TappedEventArgs.cs
  72. 4
      src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
  73. 2
      src/Avalonia.Interactivity/Interactive.cs
  74. 2
      src/Avalonia.Layout/ElementManager.cs
  75. 4
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  76. 5
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  77. 4
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  78. 35
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  79. 8
      src/Avalonia.Native/IAvnMenu.cs
  80. 6
      src/Avalonia.Native/IAvnMenuItem.cs
  81. 6
      src/Avalonia.Native/PopupImpl.cs
  82. 16
      src/Avalonia.Native/WindowImplBase.cs
  83. 15
      src/Avalonia.Native/avn.idl
  84. 138
      src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
  85. 123
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  86. 20
      src/Avalonia.Visuals/Animation/Animators/RelativePointAnimator.cs
  87. 20
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  88. 44
      src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
  89. 11
      src/Avalonia.Visuals/Animation/Transitions/RelativePointTransition.cs
  90. 20
      src/Avalonia.Visuals/Media/DrawingGroup.cs
  91. 2
      src/Avalonia.Visuals/Media/GradientBrush.cs
  92. 1
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  93. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  94. 10
      src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
  95. 7
      src/Avalonia.Visuals/RelativePoint.cs
  96. 46
      src/Avalonia.X11/X11Platform.cs
  97. 37
      src/Avalonia.X11/X11Window.cs
  98. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  99. 6
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  100. 4
      src/Windows/Avalonia.Win32/PopupImpl.cs

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

@ -10,6 +10,8 @@ class WindowBaseImpl;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
-(AvnPixelSize) getPixelSize;
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
@end
@interface AutoFitContentView : NSView
@ -34,6 +36,7 @@ class WindowBaseImpl;
-(double) getScaling;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
-(bool) isDialog;
@end
struct INSWindowHolder
@ -50,4 +53,23 @@ struct IWindowStateChanged
virtual AvnWindowState WindowState () = 0;
};
class ResizeScope
{
public:
ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason)
{
_view = view;
_restore = [view getResizeReason];
[view setResizeReason:reason];
}
~ResizeScope()
{
[_view setResizeReason:_restore];
}
private:
AvnView* _Nonnull _view;
AvnPlatformResizeReason _restore;
};
#endif /* window_h */

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

@ -52,6 +52,7 @@ public:
[Window setBackingType:NSBackingStoreBuffered];
[Window setOpaque:false];
[Window setContentView: StandardContainer];
}
virtual HRESULT ObtainNSWindowHandle(void** ret) override
@ -115,7 +116,7 @@ public:
return Window;
}
virtual HRESULT Show(bool activate) override
virtual HRESULT Show(bool activate, bool isDialog) override
{
START_COM_CALL;
@ -124,7 +125,6 @@ public:
SetPosition(lastPositionSet);
UpdateStyle();
[Window setContentView: StandardContainer];
[Window setTitle:_lastTitle];
if(ShouldTakeFocusOnShow() && activate)
@ -277,7 +277,7 @@ public:
}
}
virtual HRESULT Resize(double x, double y) override
virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
{
if(_inResize)
{
@ -287,6 +287,7 @@ public:
_inResize = true;
START_COM_CALL;
auto resizeBlock = ResizeScope(View, reason);
@autoreleasepool
{
@ -317,7 +318,7 @@ public:
{
if(!_shown)
{
BaseEvents->Resized(AvnSize{x,y});
BaseEvents->Resized(AvnSize{x,y}, reason);
}
[Window setContentSize:NSSize{x, y}];
@ -577,6 +578,11 @@ public:
return S_OK;
}
virtual bool IsDialog()
{
return false;
}
protected:
virtual NSWindowStyleMask GetStyle()
{
@ -607,6 +613,7 @@ private:
NSRect _preZoomSize;
bool _transitioningWindowState;
bool _isClientAreaExtended;
bool _isDialog;
AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN()
@ -667,13 +674,14 @@ private:
}
}
virtual HRESULT Show (bool activate) override
virtual HRESULT Show (bool activate, bool isDialog) override
{
START_COM_CALL;
@autoreleasepool
{
WindowBaseImpl::Show(activate);
_isDialog = isDialog;
WindowBaseImpl::Show(activate, isDialog);
HideOrShowTrafficLights();
@ -1213,6 +1221,11 @@ private:
}
}
virtual bool IsDialog() override
{
return _isDialog;
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
@ -1373,6 +1386,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _renderTarget;
AvnPlatformResizeReason _resizeReason;
}
- (void)onClosed
@ -1484,7 +1498,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
}
}
@ -1983,6 +1998,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
- (AvnPlatformResizeReason)getResizeReason
{
return _resizeReason;
}
- (void)setResizeReason:(AvnPlatformResizeReason)reason
{
_resizeReason = reason;
}
@end
@ -2002,6 +2027,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_isExtended = value;
}
-(bool) isDialog
{
return _parent->IsDialog();
}
-(double) getScaling
{
return _lastScaling;
@ -2180,7 +2210,22 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
-(BOOL)canBecomeKeyWindow
{
return _canBecomeKeyAndMain;
if (_canBecomeKeyAndMain)
{
// If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows])
{
auto ch = objc_cast<AvnWindow>(uch);
if(ch == nil)
continue;
if (ch.isDialog)
return false;
}
return true;
}
return false;
}
-(BOOL)canBecomeMainWindow
@ -2188,22 +2233,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return _canBecomeKeyAndMain;
}
-(bool) activateAppropriateChild: (bool)activating
{
for(NSWindow* uch in [self childWindows])
{
auto ch = objc_cast<AvnWindow>(uch);
if(ch == nil)
continue;
[ch activateAppropriateChild:false];
return FALSE;
}
if(!activating)
[self makeKeyAndOrderFront:self];
return TRUE;
}
-(bool)shouldTryToHandleEvents
{
return _isEnabled;
@ -2214,26 +2243,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_isEnabled = enable;
}
-(void)makeKeyWindow
{
if([self activateAppropriateChild: true])
{
[super makeKeyWindow];
}
}
-(void)becomeKeyWindow
{
[self showWindowMenuWithAppMenu];
if([self activateAppropriateChild: true])
if(_parent != nullptr)
{
if(_parent != nullptr)
{
_parent->BaseEvents->Activated();
}
_parent->BaseEvents->Activated();
}
[super becomeKeyWindow];
}
@ -2243,7 +2261,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
if(parent != nil)
{
[parent removeChildWindow:self];
[parent activateAppropriateChild: false];
}
}
@ -2378,7 +2395,7 @@ protected:
return NSWindowStyleMaskBorderless;
}
virtual HRESULT Resize(double x, double y) override
virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
{
START_COM_CALL;

149
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -161,6 +161,151 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect7">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red" />
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="60%">
<Setter Property="Background" Value="Blue" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect8">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.25" Color="Blue"/>
<GradientStop Offset="0.5" Color="Blue"/>
<GradientStop Offset="0.75" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect9">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background">
<ConicGradientBrush Center="50%,50%" Angle="0">
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="0.5" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</ConicGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<ConicGradientBrush Center="50%,70%" Angle="90">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="0.25" Color="Yellow"/>
<GradientStop Offset="0.5" Color="Red"/>
<GradientStop Offset="0.75" Color="Blue"/>
<GradientStop Offset="1" Color="Green"/>
</ConicGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect10">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Normal">
<KeyFrame Cue="0%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,100%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="25%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,0%" Radius="1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Background">
<RadialGradientBrush Center="100%,0%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter Property="Background">
<RadialGradientBrush Center="100%,100%" Radius="1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,100%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
</UserControl.Styles>
<Grid>
@ -181,6 +326,10 @@
<Border Classes="Test Rect6" Background="Red"/>
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
<Border Classes="Test Rect7" Child="{x:Null}" />
<Border Classes="Test Rect8" Child="{x:Null}" />
<Border Classes="Test Rect9" Child="{x:Null}" />
<Border Classes="Test Rect10" Child="{x:Null}" />
</WrapPanel>
</StackPanel>
</Grid>

84
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -167,13 +167,80 @@
<Style Selector="Border.Rect11:pointerover">
<Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect12">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect12:pointerover">
<Setter Property="Background" >
<LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect13">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<ConicGradientBrush Center="50%,50%" Angle="0">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</ConicGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect13:pointerover">
<Setter Property="Background" >
<ConicGradientBrush Center="70%,70%" Angle="90">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</ConicGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect14">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<RadialGradientBrush Center="50%,50%" Radius="0.5">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect14:pointerover">
<Setter Property="Background" >
<RadialGradientBrush Center="30%,30%" Radius="0.2">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</RadialGradientBrush>
</Setter>
</Style>
</Styles>
</UserControl.Styles>
@ -202,6 +269,15 @@
<Border Classes="Test Rect10" />
<Border Classes="Test Rect11" />
<Border Classes="Test Rect12" Child="{x:Null}"/>
<Border Classes="Test Rect13" Child="{x:Null}"/>
<Border Classes="Test Rect14" Child="{x:Null}"/>
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
</WrapPanel>
</StackPanel>
</Grid>

4
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -67,7 +67,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
@ -134,7 +134,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
protected virtual void OnResized(Size size)
{
Resized?.Invoke(size);
Resized?.Invoke(size, PlatformResizeReason.Unspecified);
}
class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo

2
src/Avalonia.Base/AvaloniaObject.cs

@ -861,7 +861,7 @@ namespace Avalonia
}
/// <summary>
/// Logs a mesage if the notification represents a binding error.
/// Logs a message if the notification represents a binding error.
/// </summary>
/// <param name="property">The property being bound.</param>
/// <param name="value">The binding notification.</param>

4
src/Avalonia.Base/AvaloniaProperty.cs

@ -465,9 +465,9 @@ namespace Avalonia
/// Uses the visitor pattern to resolve an untyped property to a typed property.
/// </summary>
/// <typeparam name="TData">The type of user data passed.</typeparam>
/// <param name="vistor">The visitor which will accept the typed property.</param>
/// <param name="visitor">The visitor which will accept the typed property.</param>
/// <param name="data">The user data to pass.</param>
public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data)
public abstract void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
where TData : struct;
/// <summary>

4
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs

@ -58,8 +58,8 @@ namespace Avalonia
/// <remarks>
/// This will usually be true, except in
/// <see cref="AvaloniaObject.OnPropertyChangedCore{T}(AvaloniaPropertyChangedEventArgs{T})"/>
/// which recieves notifications for all changes to property values, whether a value with a higher
/// priority is present or not. When this property is false, the change that is being signalled
/// which receives notifications for all changes to property values, whether a value with a higher
/// priority is present or not. When this property is false, the change that is being signaled
/// has not resulted in a change to the property value on the object.
/// </remarks>
public bool IsEffectiveValueChange { get; private set; }

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

@ -1271,7 +1271,7 @@ namespace Avalonia.Collections.Pooled
/// Reverses the elements in a range of this list. Following a call to this
/// method, an element in the range given by index and count
/// which was previously located at index i will now be located at
/// index index + (index + count - i - 1).
/// index + (index + count - i - 1).
/// </summary>
public void Reverse(int index, int count)
{

2
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -20,7 +20,7 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="inner">The inner property accessor used to aceess the property.</param>
/// <param name="inner">The inner property accessor used to access the property.</param>
/// <returns>
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.

6
src/Avalonia.Base/DirectPropertyBase.cs

@ -13,7 +13,7 @@ namespace Avalonia
/// <typeparam name="TValue">The type of the property's value.</typeparam>
/// <remarks>
/// Whereas <see cref="DirectProperty{TOwner, TValue}"/> is typed on the owner type, this base
/// class provides a non-owner-typed interface to a direct poperty.
/// class provides a non-owner-typed interface to a direct property.
/// </remarks>
public abstract class DirectPropertyBase<TValue> : AvaloniaProperty<TValue>
{
@ -123,9 +123,9 @@ namespace Avalonia
}
/// <inheritdoc/>
public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> vistor, ref TData data)
public override void Accept<TData>(IAvaloniaPropertyVisitor<TData> visitor, ref TData data)
{
vistor.Visit(this, ref data);
visitor.Visit(this, ref data);
}
/// <inheritdoc/>

2
src/Avalonia.Base/DirectPropertyMetadata`1.cs

@ -38,7 +38,7 @@ namespace Avalonia
/// <remarks>
/// Data validation is validation performed at the target of a binding, for example in a
/// view model using the INotifyDataErrorInfo interface. Only certain properties on a
/// control (such as a TextBox's Text property) will be interested in recieving data
/// control (such as a TextBox's Text property) will be interested in receiving data
/// validation messages so this feature must be explicitly enabled by setting this flag.
/// </remarks>
public bool? EnableDataValidation { get; private set; }

8
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -877,7 +877,7 @@ namespace Avalonia.Collections
if (!CheckFlag(CollectionViewFlags.IsMoveToPageDeferred))
{
// if the temporaryGroup was not created yet and is out of sync
// then create it so that we can use it as a refernce while paging.
// then create it so that we can use it as a reference while paging.
if (IsGrouping && _temporaryGroup.ItemCount != InternalList.Count)
{
PrepareTemporaryGroups();
@ -889,7 +889,7 @@ namespace Avalonia.Collections
else if (IsGrouping)
{
// if the temporaryGroup was not created yet and is out of sync
// then create it so that we can use it as a refernce while paging.
// then create it so that we can use it as a reference while paging.
if (_temporaryGroup.ItemCount != InternalList.Count)
{
// update the groups that get created for the
@ -1951,7 +1951,7 @@ namespace Avalonia.Collections
EnsureCollectionInSync();
VerifyRefreshNotDeferred();
// for indicies larger than the count
// for indices larger than the count
if (index >= Count || index < 0)
{
throw new ArgumentOutOfRangeException("index");
@ -3800,7 +3800,7 @@ namespace Avalonia.Collections
/// </summary>
/// <remarks>
/// This method can be called from a constructor - it does not call
/// any virtuals. The 'count' parameter is substitute for the real Count,
/// any virtuals. The 'count' parameter is substitute for the real Count,
/// used only when newItem is null.
/// In that case, this method sets IsCurrentAfterLast to true if and only
/// if newPosition >= count. This distinguishes between a null belonging

17
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -67,7 +67,7 @@ namespace Avalonia.Controls
private const double DATAGRID_minimumColumnHeaderHeight = 4;
internal const double DATAGRID_maximumStarColumnWidth = 10000;
internal const double DATAGRID_minimumStarColumnWidth = 0.001;
private const double DATAGRID_mouseWheelDelta = 72.0;
private const double DATAGRID_mouseWheelDelta = 50.0;
private const double DATAGRID_maxHeadersThickness = 32768;
private const double DATAGRID_defaultRowHeight = 22;
@ -2217,20 +2217,23 @@ namespace Avalonia.Controls
if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0)
{
double scrollHeight = 0;
if (e.Delta.Y > 0)
var delta = DATAGRID_mouseWheelDelta * e.Delta.Y;
var deltaAbs = Math.Abs(delta);
if (delta > 0)
{
scrollHeight = Math.Max(-_verticalOffset, -DATAGRID_mouseWheelDelta);
scrollHeight = Math.Max(-_verticalOffset, -deltaAbs);
}
else if (e.Delta.Y < 0)
else if (delta < 0)
{
if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible)
{
scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), deltaAbs);
}
else
{
double maximum = EdgedRowsHeightCalculated - CellsHeight;
scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), deltaAbs);
}
}
if (scrollHeight != 0)
@ -5731,7 +5734,7 @@ namespace Avalonia.Controls
{
if (SelectionMode == DataGridSelectionMode.Single || !ctrl)
{
// Unselect the currectly selected rows except the new selected row
// Unselect the currently selected rows except the new selected row
action = DataGridSelectionAction.SelectCurrent;
}
else

2
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -197,7 +197,7 @@ namespace Avalonia.Controls
}
// Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the
// right gridline will be collapsed if this cell belongs to the lastVisibileColumn and there is no filler column
// right gridline will be collapsed if this cell belongs to the lastVisibleColumn and there is no filler column
internal void EnsureGridLine(DataGridColumn lastVisibleColumn)
{
if (OwningGrid != null && _rightGridLine != null)

2
src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs

@ -40,7 +40,7 @@ namespace Avalonia.Controls
return false;
}
// There is build warning if this is missiing
// There is build warning if this is missing
public override int GetHashCode()
{
return base.GetHashCode();

2
src/Avalonia.Controls.DataGrid/DataGridClipboard.cs

@ -189,7 +189,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// DataGrid row item used for proparing the ClipboardRowContent.
/// DataGrid row item used for preparing the ClipboardRowContent.
/// </summary>
public object Item
{

2
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -795,7 +795,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// If the DataGrid is using using layout rounding, the pixel snapping will force all widths to
/// If the DataGrid is using layout rounding, the pixel snapping will force all widths to
/// whole numbers. Since the column widths aren't visual elements, they don't go through the normal
/// rounding process, so we need to do it ourselves. If we don't, then we'll end up with some
/// pixel gaps and/or overlaps between columns.

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

@ -79,7 +79,7 @@ namespace Avalonia.Controls
set;
}
internal void AddRecylableRow(DataGridRow row)
internal void AddRecyclableRow(DataGridRow row)
{
Debug.Assert(!_recyclableRows.Contains(row));
row.DetachFromDataGrid(true);
@ -120,7 +120,7 @@ namespace Avalonia.Controls
{
if (row.IsRecyclable)
{
AddRecylableRow(row);
AddRecyclableRow(row);
}
else
{
@ -193,7 +193,7 @@ namespace Avalonia.Controls
internal void FullyRecycleElements()
{
// Fully recycle Recycleable rows and transfer them to Recycled rows
// Fully recycle Recyclable rows and transfer them to Recycled rows
while (_recyclableRows.Count > 0)
{
DataGridRow row = _recyclableRows.Pop();
@ -202,7 +202,7 @@ namespace Avalonia.Controls
Debug.Assert(!_fullyRecycledRows.Contains(row));
_fullyRecycledRows.Push(row);
}
// Fully recycle Recycleable GroupHeaders and transfer them to Recycled GroupHeaders
// Fully recycle Recyclable GroupHeaders and transfer them to Recycled GroupHeaders
while (_recyclableGroupHeaders.Count > 0)
{
DataGridRowGroupHeader groupHeader = _recyclableGroupHeaders.Pop();

6
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -392,7 +392,7 @@ namespace Avalonia.Controls
set;
}
// Height that the row will eventually end up at after a possible detalis animation has completed
// Height that the row will eventually end up at after a possible details animation has completed
internal double TargetHeight
{
get
@ -517,7 +517,7 @@ namespace Avalonia.Controls
return base.MeasureOverride(availableSize);
}
//Allow the DataGrid specific componets to adjust themselves based on new values
//Allow the DataGrid specific components to adjust themselves based on new values
if (_headerElement != null)
{
_headerElement.InvalidateMeasure();
@ -722,7 +722,7 @@ namespace Avalonia.Controls
if (_bottomGridLine != null)
{
// It looks like setting Visibility sometimes has side effects so make sure the value is actually
// diffferent before setting it
// different before setting it
bool newVisibility = OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.Horizontal || OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.All;
if (newVisibility != _bottomGridLine.IsVisible)

18
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@ -1193,7 +1193,7 @@ namespace Avalonia.Controls
else
{
groupHeader = element as DataGridRowGroupHeader;
Debug.Assert(groupHeader != null); // Nothig other and Rows and RowGroups now
Debug.Assert(groupHeader != null); // Nothing other and Rows and RowGroups now
if (groupHeader != null)
{
groupHeader.TotalIndent = (groupHeader.Level == 0) ? 0 : RowGroupSublevelIndents[groupHeader.Level - 1];
@ -1636,7 +1636,7 @@ namespace Avalonia.Controls
if (slot >= DisplayData.FirstScrollingSlot &&
slot <= DisplayData.LastScrollingSlot)
{
// Additional row takes the spot of a displayed row - it is necessarilly displayed
// Additional row takes the spot of a displayed row - it is necessarily displayed
return true;
}
else if (DisplayData.FirstScrollingSlot == -1 &&
@ -1825,7 +1825,7 @@ namespace Avalonia.Controls
if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset))
{
// We've scrolled off more of the first row than what's possible. This can happen
// if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling
// if the first row got shorter (Ex: Collapsing RowDetails) or if the user has a recycling
// cleanup issue. In this case, simply try to display the next row as the first row instead
if (newFirstScrollingSlot < SlotCount - 1)
{
@ -2014,7 +2014,7 @@ namespace Avalonia.Controls
if (recycleRow)
{
DisplayData.AddRecylableRow(dataGridRow);
DisplayData.AddRecyclableRow(dataGridRow);
}
else
{
@ -2265,7 +2265,7 @@ namespace Avalonia.Controls
if (parentGroupInfo.LastSubItemSlot - parentGroupInfo.Slot == 1)
{
// We just added the first item to a RowGroup so the header should transition from Empty to either Expanded or Collapsed
EnsureAnscestorsExpanderButtonChecked(parentGroupInfo);
EnsureAncestorsExpanderButtonChecked(parentGroupInfo);
}
}
}
@ -2407,7 +2407,7 @@ namespace Avalonia.Controls
return treeCount;
}
private void EnsureAnscestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
private void EnsureAncestorsExpanderButtonChecked(DataGridRowGroupInfo parentGroupInfo)
{
if (IsSlotVisible(parentGroupInfo.Slot))
{
@ -2789,11 +2789,11 @@ namespace Avalonia.Controls
return null;
}
internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisibile, bool setCurrent)
internal void OnRowGroupHeaderToggled(DataGridRowGroupHeader groupHeader, bool newIsVisible, bool setCurrent)
{
Debug.Assert(groupHeader.RowGroupInfo.CollectionViewGroup.ItemCount > 0);
if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisibile, setCurrent); }) || !CommitEdit())
if (WaitForLostFocus(delegate { OnRowGroupHeaderToggled(groupHeader, newIsVisible, setCurrent); }) || !CommitEdit())
{
return;
}
@ -2804,7 +2804,7 @@ namespace Avalonia.Controls
UpdateSelectionAndCurrency(CurrentColumnIndex, groupHeader.RowGroupInfo.Slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisibile, isDisplayed: true);
UpdateRowGroupVisibility(groupHeader.RowGroupInfo, newIsVisible, isDisplayed: true);
ComputeScrollBarsLayout();
// We need force arrange since our Scrollings Rows could update without automatically triggering layout

2
src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs

@ -140,7 +140,7 @@ namespace Avalonia.Controls.Primitives
if (dataGridColumn.IsFrozen)
{
columnHeader.Arrange(new Rect(frozenLeftEdge, 0, dataGridColumn.LayoutRoundedWidth, finalSize.Height));
columnHeader.Clip = null; // The layout system could have clipped this becaues it's not aware of our render transform
columnHeader.Clip = null; // The layout system could have clipped this because it's not aware of our render transform
if (DragColumn == dataGridColumn && DragIndicator != null)
{
dragIndicatorLeftEdge = frozenLeftEdge + DragIndicatorOffset;

22
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -30,15 +30,29 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<System.ComponentModel.CancelEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<Avalonia.Controls.ApplicationLifetimes.ShutdownRequestedEventArgs>)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size, Avalonia.Platform.PlatformResizeReason> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.Resized.get()' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action<Avalonia.Size, Avalonia.Platform.PlatformResizeReason>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action<Avalonia.Size>)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 42
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
Total Issues: 56

2
src/Avalonia.Controls/AppBuilderBase.cs

@ -273,7 +273,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Sets up the platform-speciic services for the <see cref="Application"/>.
/// Sets up the platform-specific services for the <see cref="Application"/>.
/// </summary>
private void Setup()
{

4
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<CancelEventArgs> ShutdownRequested;
public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
/// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
@ -134,7 +134,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
_activeLifetime = null;
}
private void OnShutdownRequested(object sender, CancelEventArgs e)
private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e)
{
ShutdownRequested?.Invoke(this, e);

17
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -37,14 +37,21 @@ namespace Avalonia.Controls.ApplicationLifetimes
IReadOnlyList<Window> Windows { get; }
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// Raised by the platform when an application shutdown is requested.
/// </summary>
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event
/// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application
/// Application Shutdown can be requested for various reasons like OS shutdown.
///
/// On Windows this will be called when an OS Session (logout or shutdown) terminates. Cancelling the eventargs will
/// block OS shutdown.
///
/// On OSX this has the same behavior as on Windows and in addition:
/// This event is raised via the Quit menu or right-clicking on the application icon and selecting Quit.
///
/// This event provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application
/// will try to close each non-owned open window, invoking the <see cref="Window.Closing"/> event on each and allowing
/// each window to cancel the shutdown.
/// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
}
}

9
src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs

@ -0,0 +1,9 @@
using System.ComponentModel;
namespace Avalonia.Controls.ApplicationLifetimes
{
public class ShutdownRequestedEventArgs : CancelEventArgs
{
}
}

10
src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs

@ -26,13 +26,13 @@ namespace Avalonia.Controls.Converters
Right ? Indent * scalarDepth : 0,
Bottom ? Indent * scalarDepth : 0);
}
else if (value is Thickness thinknessDepth)
else if (value is Thickness thicknessDepth)
{
return new Thickness(
Left ? Indent * thinknessDepth.Left : 0,
Top ? Indent * thinknessDepth.Top : 0,
Right ? Indent * thinknessDepth.Right : 0,
Bottom ? Indent * thinknessDepth.Bottom : 0);
Left ? Indent * thicknessDepth.Left : 0,
Top ? Indent * thicknessDepth.Top : 0,
Right ? Indent * thicknessDepth.Right : 0,
Bottom ? Indent * thicknessDepth.Bottom : 0);
}
return new Thickness(0);

4
src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls.Converters
if (parameter == null ||
values == null ||
values.Count != 4 ||
!(values[0] is ScrollBarVisibility visiblity) ||
!(values[0] is ScrollBarVisibility visibility) ||
!(values[1] is double offset) ||
!(values[2] is double extent) ||
!(values[3] is double viewport))
@ -24,7 +24,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty.UnsetValue;
}
if (visiblity == ScrollBarVisibility.Auto)
if (visibility == ScrollBarVisibility.Auto)
{
if (extent == viewport)
{

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

@ -71,7 +71,7 @@ namespace Avalonia.Controls
x => x.MonthVisible, (x, v) => x.MonthVisible = v);
/// <summary>
/// Defiens the <see cref="YearFormat"/> Property
/// Defines the <see cref="YearFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, string> YearFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat),

12
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -220,15 +220,15 @@ namespace Avalonia.Controls.Primitives
if (dy > 0) // Scroll Down
{
int numContsToMove = 0;
int numCountsToMove = 0;
for (int i = 0; i < children.Count; i++)
{
if (children[i].Bounds.Bottom - dy < 0)
numContsToMove++;
numCountsToMove++;
else
break;
}
children.MoveRange(0, numContsToMove, children.Count);
children.MoveRange(0, numCountsToMove, children.Count);
var scrollHeight = _extent.Height - Viewport.Height;
if (ShouldLoop && value.Y >= scrollHeight - _extentOne)
@ -236,15 +236,15 @@ namespace Avalonia.Controls.Primitives
}
else if (dy < 0) // Scroll Up
{
int numContsToMove = 0;
int numCountsToMove = 0;
for (int i = children.Count - 1; i >= 0; i--)
{
if (children[i].Bounds.Top - dy > Bounds.Height)
numContsToMove++;
numCountsToMove++;
else
break;
}
children.MoveRange(children.Count - numContsToMove, numContsToMove, 0);
children.MoveRange(children.Count - numCountsToMove, numCountsToMove, 0);
if (ShouldLoop && value.Y < _extentOne)
_offset = new Vector(0, value.Y + (_extentOne * 50));
}

8
src/Avalonia.Controls/DefinitionBase.cs

@ -35,7 +35,7 @@ namespace Avalonia.Controls
if (_sharedState == null)
{
// start with getting SharedSizeGroup value.
// this property is NOT inhereted which should result in better overall perf.
// this property is NOT inherited which should result in better overall perf.
string sharedSizeGroupId = SharedSizeGroup;
if (sharedSizeGroupId != null)
{
@ -52,7 +52,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Callback to notify about exitting model tree.
/// Callback to notify about exiting model tree.
/// </summary>
internal void OnExitParentTree()
{
@ -458,7 +458,7 @@ namespace Avalonia.Controls
private Grid.LayoutTimeSizeType _sizeType; // layout-time user size type. it may differ from _userSizeValueCache.UnitType when calculating "to-content"
private double _minSize; // used during measure to accumulate size for "Auto" and "Star" DefinitionBase's
private double _measureSize; // size, calculated to be the input contstraint size for Child.Measure
private double _measureSize; // size, calculated to be the input constraint size for Child.Measure
private double _sizeCache; // cache used for various purposes (sorting, caching, etc) during calculations
private double _offset; // offset of the DefinitionBase from left / top corner (assuming LTR case)
@ -556,7 +556,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Propogates invalidations for all registered definitions.
/// Propagates invalidations for all registered definitions.
/// Resets its own state.
/// </summary>
internal void Invalidate()

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -31,7 +31,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
set
{
_clientSize = value;
Resized?.Invoke(value);
Resized?.Invoke(value, PlatformResizeReason.Unspecified);
}
}
@ -49,7 +49,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

2
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -564,7 +564,7 @@ namespace Avalonia.Controls.Primitives
internal static void SetPresenterClasses(IControl presenter, Classes classes)
{
//Remove any classes no longer in use, ignoring pseudoclasses
//Remove any classes no longer in use, ignoring pseudo classes
for (int i = presenter.Classes.Count - 1; i >= 0; i--)
{
if (!classes.Contains(presenter.Classes[i]) &&

18
src/Avalonia.Controls/Grid.cs

@ -330,7 +330,7 @@ namespace Avalonia.Controls
// value of Auto column), "cell 2 1" needs to be calculated first,
// as it contributes to the Auto column's calculated value.
// At the same time in order to accurately calculate constraint
// height for "cell 2 1", "cell 1 2" needs to be calcualted first,
// height for "cell 2 1", "cell 1 2" needs to be calculated first,
// as it contributes to Auto row height, which is used in the
// computation of Star row resolved height.
//
@ -405,11 +405,11 @@ namespace Avalonia.Controls
//
// where:
// * all [Measure GroupN] - regular children measure process -
// each cell is measured given contraint size as an input
// each cell is measured given constraint size as an input
// and each cell's desired size is accumulated on the
// corresponding column / row;
// * [Measure Group2'] - is when each cell is measured with
// infinit height as a constraint and a cell's desired
// infinite height as a constraint and a cell's desired
// height is ignored;
// * [Measure Groups''] - is when each cell is measured (second
// time during single Grid.MeasureOverride) regularly but its
@ -780,7 +780,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Initializes DefinitionsU memeber either to user supplied ColumnDefinitions collection
/// Initializes DefinitionsU member either to user supplied ColumnDefinitions collection
/// or to a default single element collection. DefinitionsU gets trimmed to size.
/// </summary>
/// <remarks>
@ -821,7 +821,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Initializes DefinitionsV memeber either to user supplied RowDefinitions collection
/// Initializes DefinitionsV member either to user supplied RowDefinitions collection
/// or to a default single element collection. DefinitionsV gets trimmed to size.
/// </summary>
/// <remarks>
@ -2132,7 +2132,7 @@ namespace Avalonia.Controls
//
// Fortunately, our scenarios tend to have a small number of columns (~10 or fewer)
// each being allocated a large number of pixels (~50 or greater), and
// people don't even notice the kind of 1-pixel anomolies that are
// people don't even notice the kind of 1-pixel anomalies that are
// theoretically inevitable, or don't care if they do. At least they shouldn't
// care - no one should be using the results WPF's grid layout to make
// quantitative decisions; its job is to produce a reasonable display, not
@ -2597,7 +2597,7 @@ namespace Avalonia.Controls
if (scale < 0.0)
{
// if one of the *-weights is Infinity, adjust the weights by mapping
// Infinty to 1.0 and everything else to 0.0: the infinite items share the
// Infinity to 1.0 and everything else to 0.0: the infinite items share the
// available space equally, everyone else gets nothing.
return (Double.IsPositiveInfinity(def.UserSize.Value)) ? 1.0 : 0.0;
}
@ -2655,7 +2655,7 @@ namespace Avalonia.Controls
private enum Flags
{
//
// the foolowing flags let grid tracking dirtiness in more granular manner:
// the following flags let grid tracking dirtiness in more granular manner:
// * Valid???Structure flags indicate that elements were added or removed.
// * Valid???Layout flags indicate that layout time portion of the information
// stored on the objects should be updated.
@ -2684,7 +2684,7 @@ namespace Avalonia.Controls
/// <summary>
/// ShowGridLines property. This property is used mostly
/// for simplification of visual debuggig. When it is set
/// for simplification of visual debugging. When it is set
/// to <c>true</c> grid lines are drawn to visualize location
/// of grid lines.
/// </summary>

2
src/Avalonia.Controls/ListBox.cs

@ -89,7 +89,7 @@ namespace Avalonia.Controls
/// </summary>
/// <remarks>
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programatically regardless of the value of this property.
/// Multiple selections can be made programmatically regardless of the value of this property.
/// </remarks>
public new SelectionMode SelectionMode
{

2
src/Avalonia.Controls/NativeMenuItemSeparator.cs

@ -3,7 +3,7 @@
namespace Avalonia.Controls
{
[Obsolete("This class exists to maintain backwards compatiblity with existing code. Use NativeMenuItemSeparator instead")]
[Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")]
public class NativeMenuItemSeperator : NativeMenuItemSeparator
{
}

2
src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs

@ -25,7 +25,7 @@ namespace Avalonia.Platform
/// <summary>
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
/// This is because Windows Chrome can not be shown ontop of user content.
/// This is because Windows Chrome can not be shown on top of user content.
/// </summary>
PreferSystemChrome = 0x02,

3
src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Platform
{
@ -11,6 +12,6 @@ namespace Avalonia.Platform
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
}
}

37
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -3,11 +3,46 @@ using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Rendering;
using JetBrains.Annotations;
namespace Avalonia.Platform
{
/// <summary>
/// Describes the reason for a <see cref="ITopLevelImpl.Resized"/> message.
/// </summary>
public enum PlatformResizeReason
{
/// <summary>
/// The resize reason is unknown or unspecified.
/// </summary>
Unspecified,
/// <summary>
/// The resize was due to the user resizing the window, for example by dragging the
/// window frame.
/// </summary>
User,
/// <summary>
/// The resize was initiated by the application, for example by setting one of the sizing-
/// related properties on <see cref="Window"/> such as <see cref="Layoutable.Width"/> or
/// <see cref="Layoutable.Height"/>.
/// </summary>
Application,
/// <summary>
/// The resize was initiated by the layout system.
/// </summary>
Layout,
/// <summary>
/// The resize was due to a change in DPI.
/// </summary>
DpiChange,
}
/// <summary>
/// Defines a platform-specific top-level window implementation.
/// </summary>
@ -57,7 +92,7 @@ namespace Avalonia.Platform
/// <summary>
/// Gets or sets a method called when the toplevel is resized.
/// </summary>
Action<Size> Resized { get; set; }
Action<Size, PlatformResizeReason> Resized { get; set; }
/// <summary>
/// Gets or sets a method called when the toplevel's scaling changes.

4
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -7,7 +7,9 @@ namespace Avalonia.Platform
/// <summary>
/// Shows the window.
/// </summary>
void Show(bool activate);
/// <param name="activate">Whether to activate the shown window.</param>
/// <param name="isDialog">Whether the window is being shown as a dialog.</param>
void Show(bool activate, bool isDialog);
/// <summary>
/// Hides the window.

4
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -110,7 +110,9 @@ namespace Avalonia.Platform
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Resize(Size clientSize);
/// <param name="clientSize">The new client size.</param>
/// <param name="reason">The reason for the resize.</param>
void Resize(Size clientSize, PlatformResizeReason reason = PlatformResizeReason.Application);
/// <summary>
/// Sets the client size of the top level.

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

@ -27,7 +27,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
/// <summary>
/// An <see cref="IPopupPositioner"/> implementation for platforms on which a popup can be
/// aritrarily positioned.
/// arbitrarily positioned.
/// </summary>
public class ManagedPopupPositioner : IPopupPositioner
{

9
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -161,12 +161,9 @@ namespace Avalonia.Controls.Primitives
protected override sealed Size ArrangeSetBounds(Size size)
{
using (BeginAutoSizing())
{
_positionerParameters.Size = size;
UpdatePosition();
return ClientSize;
}
_positionerParameters.Size = size;
UpdatePosition();
return ClientSize;
}
}
}

2
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -170,7 +170,7 @@ namespace Avalonia.Controls.Primitives
}
/// <summary>
/// Checks if the double value is not inifinity nor NaN.
/// Checks if the double value is not infinity nor NaN.
/// </summary>
/// <param name="value">The value.</param>
private static bool ValidateDouble(double value)

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

@ -328,7 +328,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
/// <remarks>
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programatically regardless of the value of this property.
/// Multiple selections can be made programmatically regardless of the value of this property.
/// </remarks>
protected SelectionMode SelectionMode
{

2
src/Avalonia.Controls/Repeater/IElementFactory.cs

@ -46,7 +46,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// A data template that supports creating and recyling elements for an <see cref="ItemsRepeater"/>.
/// A data template that supports creating and recycling elements for an <see cref="ItemsRepeater"/>.
/// </summary>
public interface IElementFactory : IDataTemplate
{

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

@ -441,9 +441,9 @@ namespace Avalonia.Controls
base.OnPropertyChanged(change);
}
internal IControl GetElementImpl(int index, bool forceCreate, bool supressAutoRecycle)
internal IControl GetElementImpl(int index, bool forceCreate, bool suppressAutoRecycle)
{
var element = _viewManager.GetElement(index, forceCreate, supressAutoRecycle);
var element = _viewManager.GetElement(index, forceCreate, suppressAutoRecycle);
return element;
}

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

@ -174,7 +174,7 @@ namespace Avalonia.Controls
}
else
{
// We could not find a candiate.
// We could not find a candidate.
_lastFocusedElement = null;
}
}

2
src/Avalonia.Controls/Repeater/ViewportManager.cs

@ -186,7 +186,7 @@ namespace Avalonia.Controls
_expectedViewportShift.X + _layoutExtent.X - extent.X,
_expectedViewportShift.Y + _layoutExtent.Y - extent.Y);
// We tolerate viewport imprecisions up to 1 pixel to avoid invaliding layout too much.
// We tolerate viewport imprecisions up to 1 pixel to avoid invalidating layout too much.
if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1)
{
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})",

2
src/Avalonia.Controls/ScrollViewer.cs

@ -653,7 +653,7 @@ namespace Avalonia.Controls
private void CalculatedPropertiesChanged()
{
// Pass old values of 0 here because we don't have the old values at this point,
// and it shouldn't matter as only the template uses these properies.
// and it shouldn't matter as only the template uses these properties.
RaisePropertyChanged(HorizontalScrollBarMaximumProperty, 0, HorizontalScrollBarMaximum);
RaisePropertyChanged(HorizontalScrollBarValueProperty, 0, HorizontalScrollBarValue);
RaisePropertyChanged(HorizontalScrollBarViewportSizeProperty, 0, HorizontalScrollBarViewportSize);

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

@ -345,7 +345,7 @@ namespace Avalonia.Controls.Selection
private protected override void OnSelectionChanged(IReadOnlyList<T> deselectedItems)
{
// Note: We're *not* putting this in a using scope. A collection update is still in progress
// so the operation won't get commited by normal means: we have to commit it manually.
// so the operation won't get committed by normal means: we have to commit it manually.
var update = BatchUpdate();
update.Operation.DeselectedItems = deselectedItems;

8
src/Avalonia.Controls/TickBar.cs

@ -193,7 +193,7 @@ namespace Avalonia.Controls
/// <summary>
/// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or
/// top and bottom spacing (for vertical orienation).
/// top and bottom spacing (for vertical orientation).
/// The space on both sides of TickBar is half of specified ReservedSpace.
/// This property has type of <see cref="Rect" />.
/// </summary>
@ -210,7 +210,7 @@ namespace Avalonia.Controls
/// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and
/// SelectionStart and SelectionEnd are valid.
///
/// The primary ticks (for Mininum and Maximum value) height will be 100% of TickBar's render size (use Width or Height
/// The primary ticks (for Minimum and Maximum value) height will be 100% of TickBar's render size (use Width or Height
/// depends on Placement property).
///
/// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size.
@ -221,7 +221,7 @@ namespace Avalonia.Controls
{
var size = new Size(Bounds.Width, Bounds.Height);
var range = Maximum - Minimum;
var tickLen = 0.0d; // Height for Primary Tick (for Mininum and Maximum value)
var tickLen = 0.0d; // Height for Primary Tick (for Minimum and Maximum value)
var tickLen2 = 0.0d; // Height for Secondary Tick
var logicalToPhysical = 1.0;
var startPoint = new Point();
@ -285,7 +285,7 @@ namespace Avalonia.Controls
tickLen2 = tickLen * 0.75;
// Invert direciton of the ticks
// Invert direction of the ticks
if (IsDirectionReversed)
{
logicalToPhysical *= -1;

9
src/Avalonia.Controls/TopLevel.cs

@ -90,6 +90,7 @@ namespace Avalonia.Controls
/// </summary>
static TopLevel()
{
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<TopLevel>(KeyboardNavigationMode.Cycle);
AffectsMeasure<TopLevel>(ClientSizeProperty);
TransparencyLevelHintProperty.Changed.AddClassHandler<TopLevel>(
@ -224,7 +225,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets the acheived <see cref="WindowTransparencyLevel"/> that the platform was able to provide.
/// Gets the achieved <see cref="WindowTransparencyLevel"/> that the platform was able to provide.
/// </summary>
public WindowTransparencyLevel ActualTransparencyLevel
{
@ -376,11 +377,15 @@ namespace Avalonia.Controls
LayoutManager?.Dispose();
}
[Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
/// <summary>
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>
/// <param name="clientSize">The new client size.</param>
protected virtual void HandleResized(Size clientSize)
/// <param name="reason">The reason for the resize.</param>
protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;

145
src/Avalonia.Controls/Window.cs

@ -244,7 +244,7 @@ namespace Avalonia.Controls
impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size);
impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged;
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application));
PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
}
@ -258,6 +258,18 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets a value indicating how the window will size itself to fit its content.
/// </summary>
/// <remarks>
/// If <see cref="SizeToContent"/> has a value other than <see cref="SizeToContent.Manual"/>,
/// <see cref="SizeToContent"/> is automatically set to <see cref="SizeToContent.Manual"/>
/// if a user resizes the window by using the resize grip or dragging the border.
///
/// NOTE: Because of a limitation of X11, <see cref="SizeToContent"/> will be reset on X11 to
/// <see cref="SizeToContent.Manual"/> on any resize - including the resize that happens when
/// the window is first shown. This is because X11 resize notifications are asynchronous and
/// there is no way to know whether a resize came from the user or the layout system. To avoid
/// this, consider setting <see cref="CanResize"/> to false, which will disable user resizing
/// of the window.
/// </remarks>
public SizeToContent SizeToContent
{
get { return GetValue(SizeToContentProperty); }
@ -583,28 +595,23 @@ namespace Avalonia.Controls
return;
}
using (BeginAutoSizing())
{
Renderer?.Stop();
Renderer?.Stop();
if (Owner is Window owner)
{
owner.RemoveChild(this);
}
if (Owner is Window owner)
{
owner.RemoveChild(this);
}
if (_children.Count > 0)
if (_children.Count > 0)
{
foreach (var child in _children.ToArray())
{
foreach (var child in _children.ToArray())
{
child.child.Hide();
}
child.child.Hide();
}
Owner = null;
PlatformImpl?.Hide();
}
Owner = null;
PlatformImpl?.Hide();
IsVisible = false;
}
@ -675,29 +682,23 @@ namespace Avalonia.Controls
if (initialSize != ClientSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(initialSize);
}
PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
}
LayoutManager.ExecuteInitialLayoutPass();
using (BeginAutoSizing())
if (parent != null)
{
if (parent != null)
{
PlatformImpl?.SetParent(parent.PlatformImpl);
}
Owner = parent;
parent?.AddChild(this, false);
SetWindowStartupLocation(Owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
Renderer?.Start();
PlatformImpl?.SetParent(parent.PlatformImpl);
}
Owner = parent;
parent?.AddChild(this, false);
SetWindowStartupLocation(Owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
@ -760,41 +761,34 @@ namespace Avalonia.Controls
if (initialSize != ClientSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(initialSize);
}
PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
}
LayoutManager.ExecuteInitialLayoutPass();
var result = new TaskCompletionSource<TResult>();
using (BeginAutoSizing())
{
PlatformImpl?.SetParent(owner.PlatformImpl);
Owner = owner;
owner.AddChild(this, true);
SetWindowStartupLocation(owner.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
PlatformImpl?.SetParent(owner.PlatformImpl);
Owner = owner;
owner.AddChild(this, true);
Renderer?.Start();
SetWindowStartupLocation(owner.PlatformImpl);
Observable.FromEventPattern<EventHandler, EventArgs>(
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
PlatformImpl?.Show(ShowActivated, true);
OnOpened(EventArgs.Empty);
}
Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>(
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
OnOpened(EventArgs.Empty);
return result.Task;
}
@ -937,11 +931,8 @@ namespace Avalonia.Controls
protected sealed override Size ArrangeSetBounds(Size size)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(size);
return ClientSize;
}
PlatformImpl?.Resize(size, PlatformResizeReason.Layout);
return ClientSize;
}
protected sealed override void HandleClosed()
@ -958,18 +949,36 @@ namespace Avalonia.Controls
Owner = null;
}
[Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
protected sealed override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
/// <inheritdoc/>
protected sealed override void HandleResized(Size clientSize)
protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
if (!AutoSizing)
if (ClientSize == clientSize)
return;
var sizeToContent = SizeToContent;
// If auto-sizing is enabled, and the resize came from a user resize (or the reason was
// unspecified) then turn off auto-resizing for any window dimension that is not equal
// to the requested size.
if (sizeToContent != SizeToContent.Manual &&
CanResize &&
reason == PlatformResizeReason.Unspecified ||
reason == PlatformResizeReason.User)
{
SizeToContent = SizeToContent.Manual;
if (clientSize.Width != ClientSize.Width)
sizeToContent &= ~SizeToContent.Width;
if (clientSize.Height != ClientSize.Height)
sizeToContent &= ~SizeToContent.Height;
SizeToContent = sizeToContent;
}
Width = clientSize.Width;
Height = clientSize.Height;
base.HandleResized(clientSize);
base.HandleResized(clientSize, reason);
}
/// <summary>

34
src/Avalonia.Controls/WindowBase.cs

@ -39,7 +39,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<WindowBase, bool>(nameof(Topmost));
private int _autoSizing;
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
@ -95,10 +94,8 @@ namespace Avalonia.Controls
public Screens Screens { get; private set; }
/// <summary>
/// Whether an auto-size operation is in progress.
/// </summary>
protected bool AutoSizing => _autoSizing > 0;
[Obsolete("No longer used. Always returns false.")]
protected bool AutoSizing => false;
/// <summary>
/// Gets or sets the owner of the window.
@ -162,7 +159,7 @@ namespace Avalonia.Controls
LayoutManager.ExecuteInitialLayoutPass();
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show(true);
PlatformImpl?.Show(true, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
@ -172,20 +169,9 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Begins an auto-resize operation.
/// </summary>
/// <returns>A disposable used to finish the operation.</returns>
/// <remarks>
/// When an auto-resize operation is in progress any resize events received will not be
/// cause the new size to be written to the <see cref="Layoutable.Width"/> and
/// <see cref="Layoutable.Height"/> properties.
/// </remarks>
protected IDisposable BeginAutoSizing()
{
++_autoSizing;
return Disposable.Create(() => --_autoSizing);
}
[Obsolete("No longer used. Has no effect.")]
protected IDisposable BeginAutoSizing() => Disposable.Empty;
/// <summary>
/// Ensures that the window is initialized.
@ -215,11 +201,15 @@ namespace Avalonia.Controls
}
}
[Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
protected override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
/// <summary>
/// Handles a resize notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>
/// <param name="clientSize">The new client size.</param>
protected override void HandleResized(Size clientSize)
/// <param name="reason">The reason for the resize.</param>
protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;
@ -264,7 +254,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Called durung the arrange pass to set the size of the window.
/// Called during the arrange pass to set the size of the window.
/// </summary>
/// <param name="size">The requested size of the window.</param>
/// <returns>The actual size of the window.</returns>

8
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote
ClientSize = new Size(1, 1);
}
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
}
@ -58,7 +58,7 @@ namespace Avalonia.DesignerSupport.Remote
base.OnMessage(transport, obj);
}
public void Resize(Size clientSize)
public void Resize(Size clientSize, PlatformResizeReason reason)
{
_transport.Send(new RequestViewportResizeMessage
{
@ -99,10 +99,6 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void ShowDialog(IWindowImpl parent)
{
}
public void SetSystemDecorations(SystemDecorations enabled)
{
}

8
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -27,7 +27,7 @@ namespace Avalonia.DesignerSupport.Remote
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Func<bool> Closing { get; set; }
public Action Closed { get; set; }
@ -54,7 +54,7 @@ namespace Avalonia.DesignerSupport.Remote
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent,
(_, size, __) =>
{
Resize(size);
Resize(size, PlatformResizeReason.Unspecified);
}));
}
@ -78,7 +78,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
}
@ -98,7 +98,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void Resize(Size clientSize)
public void Resize(Size clientSize, PlatformResizeReason reason)
{
}

13
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -47,7 +47,7 @@ namespace Avalonia.Headless
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root)
@ -76,7 +76,7 @@ namespace Avalonia.Headless
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; }
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
if (activate)
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
@ -108,7 +108,7 @@ namespace Avalonia.Headless
public Action Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize)
public void Resize(Size clientSize, PlatformResizeReason reason)
{
// Emulate X11 behavior here
if (IsPopup)
@ -126,7 +126,7 @@ namespace Avalonia.Headless
if (ClientSize != clientSize)
{
ClientSize = clientSize;
Resized?.Invoke(clientSize);
Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified);
}
}
@ -148,11 +148,6 @@ namespace Avalonia.Headless
}
public void ShowDialog(IWindowImpl parent)
{
Show(true);
}
public void SetSystemDecorations(bool enabled)
{

3
src/Avalonia.Input/Avalonia.Input.csproj

@ -4,6 +4,9 @@
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />

2
src/Avalonia.Input/Cursor.cs

@ -37,7 +37,7 @@ namespace Avalonia.Input
BottomSize = BottomSide
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax direclty from theme with fallback image
// We might enable them later, preferably, by loading pixmax directly from theme with fallback image
// SizeNorthWestSouthEast,
// SizeNorthEastSouthWest,
}

13
src/Avalonia.Input/FocusManager.cs

@ -92,6 +92,17 @@ namespace Avalonia.Input
}
}
public IInputElement? GetFocusedElement(IInputElement e)
{
if (e is IFocusScope scope)
{
_focusScopes.TryGetValue(scope, out var result);
return result;
}
return null;
}
/// <summary>
/// Sets the currently focused element in the specified scope.
/// </summary>
@ -151,6 +162,8 @@ namespace Avalonia.Input
Focus(e);
}
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
/// <summary>
/// Checks if the specified element can be focused.
/// </summary>

2
src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -78,7 +78,7 @@ namespace Avalonia.Input.GestureRecognizers
// Arbitrary chosen value, probably need to move that to platform settings or something
private const double ScrollStartDistance = 30;
// Pixels per second speed that is considered to be the stop of inertiall scroll
// Pixels per second speed that is considered to be the stop of inertial scroll
private const double InertialScrollSpeedEnd = 5;
public void PointerMoved(PointerEventArgs e)

2
src/Avalonia.Input/ICommandSource.cs

@ -22,7 +22,7 @@ namespace Avalonia.Input
/// <summary>
/// Bor the bheavior CanExecuteChanged
/// Bor the behavior CanExecuteChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>

17
src/Avalonia.Input/ICustomKeyboardNavigation.cs

@ -1,4 +1,5 @@

#nullable enable
namespace Avalonia.Input
{
/// <summary>
@ -6,6 +7,18 @@ namespace Avalonia.Input
/// </summary>
public interface ICustomKeyboardNavigation
{
(bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction);
/// <summary>
/// Gets the next element in the specified navigation direction.
/// </summary>
/// <param name="element">The element being navigated from.</param>
/// <param name="direction">The navigation direction.</param>
/// <returns>
/// A tuple consisting of:
/// - A boolean indicating whether the request was handled. If false is returned then
/// custom navigation will be ignored and default navigation will take place.
/// - If handled is true: the next element in the navigation direction, or null if default
/// navigation should continue outside the element.
/// </returns>
(bool handled, IInputElement? next) GetNext(IInputElement element, NavigationDirection direction);
}
}

31
src/Avalonia.Input/InputElement.cs

@ -71,6 +71,12 @@ namespace Avalonia.Input
public static readonly DirectProperty<InputElement, bool> IsPointerOverProperty =
AvaloniaProperty.RegisterDirect<InputElement, bool>(nameof(IsPointerOver), o => o.IsPointerOver);
/// <summary>
/// Defines the <see cref="IsTabStop"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsTabStopProperty =
KeyboardNavigation.IsTabStopProperty.AddOwner<InputElement>();
/// <summary>
/// Defines the <see cref="GotFocus"/> event.
/// </summary>
@ -99,6 +105,12 @@ namespace Avalonia.Input
"KeyUp",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TabIndex"/> property.
/// </summary>
public static readonly StyledProperty<int> TabIndexProperty =
KeyboardNavigation.TabIndexProperty.AddOwner<InputElement>();
/// <summary>
/// Defines the <see cref="TextInput"/> event.
/// </summary>
@ -426,6 +438,15 @@ namespace Avalonia.Input
internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); }
}
/// <summary>
/// Gets or sets a value that indicates whether the control is included in tab navigation.
/// </summary>
public bool IsTabStop
{
get => GetValue(IsTabStopProperty);
set => SetValue(IsTabStopProperty, value);
}
/// <inheritdoc/>
public bool IsEffectivelyEnabled
{
@ -437,6 +458,16 @@ namespace Avalonia.Input
}
}
/// <summary>
/// Gets or sets a value that determines the order in which elements receive focus when the
/// user navigates through controls by pressing the Tab key.
/// </summary>
public int TabIndex
{
get => GetValue(TabIndexProperty);
set => SetValue(TabIndexProperty, value);
}
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
/// <summary>

33
src/Avalonia.Input/KeyboardNavigation.cs

@ -5,6 +5,15 @@ namespace Avalonia.Input
/// </summary>
public static class KeyboardNavigation
{
/// <summary>
/// Defines the TabIndex attached property.
/// </summary>
public static readonly AttachedProperty<int> TabIndexProperty =
AvaloniaProperty.RegisterAttached<StyledElement, int>(
"TabIndex",
typeof(KeyboardNavigation),
int.MaxValue);
/// <summary>
/// Defines the TabNavigation attached property.
/// </summary>
@ -42,6 +51,26 @@ namespace Avalonia.Input
typeof(KeyboardNavigation),
true);
/// <summary>
/// Gets the <see cref="TabIndexProperty"/> for an element.
/// </summary>
/// <param name="element">The container.</param>
/// <returns>The <see cref="KeyboardNavigationMode"/> for the container.</returns>
public static int GetTabIndex(IInputElement element)
{
return ((IAvaloniaObject)element).GetValue(TabIndexProperty);
}
/// <summary>
/// Sets the <see cref="TabIndexProperty"/> for an element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="value">The tab index.</param>
public static void SetTabIndex(IInputElement element, int value)
{
((IAvaloniaObject)element).SetValue(TabIndexProperty, value);
}
/// <summary>
/// Gets the <see cref="TabNavigationProperty"/> for a container.
/// </summary>
@ -83,7 +112,7 @@ namespace Avalonia.Input
}
/// <summary>
/// Sets the <see cref="IsTabStopProperty"/> for a container.
/// Sets the <see cref="IsTabStopProperty"/> for an element.
/// </summary>
/// <param name="element">The container.</param>
/// <param name="value">Value indicating whether the container is a tab stop.</param>
@ -93,7 +122,7 @@ namespace Avalonia.Input
}
/// <summary>
/// Gets the <see cref="IsTabStopProperty"/> for a container.
/// Gets the <see cref="IsTabStopProperty"/> for an element.
/// </summary>
/// <param name="element">The container.</param>
/// <returns>Whether the container is a tab stop.</returns>

111
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Input.Navigation;
using Avalonia.VisualTree;
@ -48,39 +49,24 @@ namespace Avalonia.Input
{
element = element ?? throw new ArgumentNullException(nameof(element));
var customHandler = element.GetSelfAndVisualAncestors()
.OfType<ICustomKeyboardNavigation>()
.FirstOrDefault();
// If there's a custom keyboard navigation handler as an ancestor, use that.
var custom = element.FindAncestorOfType<ICustomKeyboardNavigation>(true);
if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce))
return ce;
if (customHandler != null)
var result = direction switch
{
var (handled, next) = customHandler.GetNext(element, direction);
NavigationDirection.Next => TabNavigation.GetNextTab(element, false),
NavigationDirection.Previous => TabNavigation.GetPrevTab(element, null, false),
_ => throw new NotSupportedException(),
};
if (handled)
{
if (next != null)
{
return next;
}
else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{
return TabNavigation.GetNextInTabOrder((IInputElement)customHandler, direction, true);
}
else
{
return null;
}
}
}
// If there wasn't a custom navigation handler as an ancestor of the current element,
// but there is one as an ancestor of the new element, use that.
if (custom is null && HandlePostCustomNavigation(element, result, direction, out ce))
return ce;
if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{
return TabNavigation.GetNextInTabOrder(element, direction);
}
else
{
throw new NotSupportedException();
}
return result;
}
/// <summary>
@ -90,7 +76,7 @@ namespace Avalonia.Input
/// <param name="direction">The direction to move.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Move(
IInputElement element,
IInputElement element,
NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None)
{
@ -124,5 +110,70 @@ namespace Avalonia.Input
e.Handled = true;
}
}
private static bool HandlePreCustomNavigation(
ICustomKeyboardNavigation customHandler,
IInputElement element,
NavigationDirection direction,
[NotNullWhen(true)] out IInputElement? result)
{
if (customHandler != null)
{
var (handled, next) = customHandler.GetNext(element, direction);
if (handled)
{
if (next != null)
{
result = next;
return true;
}
else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{
var r = direction switch
{
NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler),
NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler),
_ => throw new NotSupportedException(),
};
if (r is object)
{
result = r;
return true;
}
}
}
}
result = null;
return false;
}
private static bool HandlePostCustomNavigation(
IInputElement element,
IInputElement? newElement,
NavigationDirection direction,
[NotNullWhen(true)] out IInputElement? result)
{
if (newElement is object)
{
var customHandler = newElement.FindAncestorOfType<ICustomKeyboardNavigation>(true);
if (customHandler is object)
{
var (handled, next) = customHandler.GetNext(element, direction);
if (handled && next is object)
{
result = next;
return true;
}
}
}
result = null;
return false;
}
}
}

7
src/Avalonia.Input/KeyboardNavigationMode.cs

@ -36,5 +36,10 @@ namespace Avalonia.Input
/// The container's children will not be focused when using the tab key.
/// </summary>
None,
/// <summary>
/// TabIndexes are considered on local subtree only inside this container
/// </summary>
Local,
}
}
}

734
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -10,277 +10,663 @@ namespace Avalonia.Input.Navigation
/// </summary>
internal static class TabNavigation
{
/// <summary>
/// Gets the next control in the specified tab direction.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <param name="outsideElement">
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns>
/// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction.
/// </returns>
public static IInputElement? GetNextInTabOrder(
IInputElement element,
NavigationDirection direction,
bool outsideElement = false)
public static IInputElement? GetNextTab(IInputElement e, bool goDownOnly)
{
element = element ?? throw new ArgumentNullException(nameof(element));
if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous)
{
throw new ArgumentException("Invalid direction: must be Next or Previous.");
}
return GetNextTab(e, GetGroupParent(e), goDownOnly);
}
var container = element.GetVisualParent<IInputElement>();
public static IInputElement? GetNextTab(IInputElement? e, IInputElement container, bool goDownOnly)
{
var tabbingType = GetKeyNavigationMode(container);
if (container != null)
if (e == null)
{
var mode = KeyboardNavigation.GetTabNavigation((InputElement)container);
if (IsTabStop(container))
return container;
switch (mode)
// Using ActiveElement if set
var activeElement = GetActiveElement(container);
if (activeElement != null)
return GetNextTab(null, activeElement, true);
}
else
{
if (tabbingType == KeyboardNavigationMode.Once || tabbingType == KeyboardNavigationMode.None)
{
case KeyboardNavigationMode.Continue:
return GetNextInContainer(element, container, direction, outsideElement) ??
GetFirstInNextContainer(element, element, direction);
case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction, outsideElement) ??
GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction, outsideElement);
default:
return GetFirstInNextContainer(element, container, direction);
if (container != e)
{
if (goDownOnly)
return null;
var parentContainer = GetGroupParent(container);
return GetNextTab(container, parentContainer, goDownOnly);
}
}
}
else
// All groups
IInputElement? loopStartElement = null;
var nextTabElement = e;
var currentTabbingType = tabbingType;
// Search down inside the container
while ((nextTabElement = GetNextTabInGroup(nextTabElement, container, currentTabbingType)) != null)
{
return GetFocusableDescendants(element, direction).FirstOrDefault();
// Avoid the endless loop here for Cycle groups
if (loopStartElement == nextTabElement)
break;
if (loopStartElement == null)
loopStartElement = nextTabElement;
var firstTabElementInside = GetNextTab(null, nextTabElement, true);
if (firstTabElementInside != null)
return firstTabElementInside;
// If we want to continue searching inside the Once groups, we should change the navigation mode
if (currentTabbingType == KeyboardNavigationMode.Once)
currentTabbingType = KeyboardNavigationMode.Contained;
}
// If there is no next element in the group (nextTabElement == null)
// Search up in the tree if allowed
// consider: Use original tabbingType instead of currentTabbingType
if (!goDownOnly && currentTabbingType != KeyboardNavigationMode.Contained && GetParent(container) != null)
{
return GetNextTab(container, GetGroupParent(container), false);
}
return null;
}
/// <summary>
/// Gets the first or last focusable descendant of the specified element.
/// </summary>
/// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e)
{
return direction == NavigationDirection.Next ?
GetFocusableDescendants(container, direction).FirstOrDefault() :
GetFocusableDescendants(container, direction).LastOrDefault();
if (e is IInputElement container)
{
var last = GetLastInTree(container);
if (last is object)
return GetNextTab(last, false);
}
return null;
}
/// <summary>
/// Gets the focusable descendants of the specified element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element,
NavigationDirection direction)
public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly)
{
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
if (e is null && container is null)
throw new InvalidOperationException("Either 'e' or 'container' must be non-null.");
if (mode == KeyboardNavigationMode.None)
{
yield break;
}
if (container is null)
container = GetGroupParent(e!);
var children = element.GetVisualChildren().OfType<IInputElement>();
KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container);
if (mode == KeyboardNavigationMode.Once)
if (e == null)
{
var active = KeyboardNavigation.GetTabOnceActiveElement((InputElement)element);
if (active != null)
// Using ActiveElement if set
var activeElement = GetActiveElement(container);
if (activeElement != null)
return GetPrevTab(null, activeElement, true);
else
{
yield return active;
yield break;
// If we Shift+Tab on a container with KeyboardNavigationMode=Once, and ActiveElement is null
// then we want to go to the first item (not last) within the container
if (tabbingType == KeyboardNavigationMode.Once)
{
var firstTabElement = GetNextTabInGroup(null, container, tabbingType);
if (firstTabElement == null)
{
if (IsTabStop(container))
return container;
if (goDownOnly)
return null;
return GetPrevTab(container, null, false);
}
else
{
return GetPrevTab(null, firstTabElement, true);
}
}
}
else
}
else
{
if (tabbingType == KeyboardNavigationMode.Once || tabbingType == KeyboardNavigationMode.None)
{
children = children.Take(1);
if (goDownOnly || container == e)
return null;
// FocusedElement should not be e otherwise we will delegate focus to the same element
if (IsTabStop(container))
return container;
return GetPrevTab(container, null, false);
}
}
foreach (var child in children)
// All groups (except Once) - continue
IInputElement? loopStartElement = null;
IInputElement? nextTabElement = e;
// Look for element with the same TabIndex before the current element
while ((nextTabElement = GetPrevTabInGroup(nextTabElement, container, tabbingType)) != null)
{
var customNext = GetCustomNext(child, direction);
if (nextTabElement == container && tabbingType == KeyboardNavigationMode.Local)
break;
// At this point nextTabElement is TabStop or TabGroup
// In case it is a TabStop only return the element
if (IsTabStop(nextTabElement) && !IsGroup(nextTabElement))
return nextTabElement;
// Avoid the endless loop here
if (loopStartElement == nextTabElement)
break;
if (loopStartElement == null)
loopStartElement = nextTabElement;
// At this point nextTabElement is TabGroup
var lastTabElementInside = GetPrevTab(null, nextTabElement, true);
if (lastTabElementInside != null)
return lastTabElementInside;
}
if (customNext.handled)
{
yield return customNext.next!;
}
else
if (tabbingType == KeyboardNavigationMode.Contained)
return null;
if (e != container && IsTabStop(container))
return container;
// If end of the subtree is reached or there no other elements above
if (!goDownOnly && GetParent(container) != null)
{
return GetPrevTab(container, null, false);
}
return null;
}
public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e)
{
if (e is IInputElement container)
{
var first = GetFirstChild(container);
if (first is object)
return GetPrevTab(first, null, false);
}
return null;
}
private static IInputElement? FocusedElement(IInputElement e)
{
var iie = e;
// Focus delegation is enabled only if keyboard focus is outside the container
if (iie != null && !iie.IsKeyboardFocusWithin)
{
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
if (focusedElement != null)
{
if (child.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement)child))
if (!IsFocusScope(e))
{
yield return child;
// Verify if focusedElement is a visual descendant of e
if (focusedElement is IVisual visualFocusedElement &&
visualFocusedElement != e &&
e.IsVisualAncestorOf(visualFocusedElement))
{
return focusedElement;
}
}
}
}
return null;
}
private static IInputElement? GetFirstChild(IInputElement e)
{
// If the element has a FocusedElement it should be its first child
if (FocusedElement(e) is IInputElement focusedElement)
return focusedElement;
if (child.CanFocusDescendants())
// Return the first visible element.
var uiElement = e as InputElement;
if (uiElement is null || uiElement.IsVisible)
{
if (e is IVisual elementAsVisual)
{
var children = elementAsVisual.VisualChildren;
var count = children.Count;
for (int i = 0; i < count; i++)
{
foreach (var descendant in GetFocusableDescendants(child, direction))
if (children[i] is InputElement ie)
{
if (KeyboardNavigation.GetIsTabStop((InputElement)descendant))
if (ie.IsVisible)
return ie;
else
{
yield return descendant;
var firstChild = GetFirstChild(ie);
if (firstChild != null)
return firstChild;
}
}
}
}
}
return null;
}
/// <summary>
/// Gets the next item that should be focused in the specified container.
/// </summary>
/// <param name="element">The starting element/</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction.</param>
/// <param name="outsideElement">
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns>The next element, or null if the element is the last.</returns>
private static IInputElement? GetNextInContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction,
bool outsideElement)
private static IInputElement? GetLastChild(IInputElement e)
{
IInputElement? e = element;
// If the element has a FocusedElement it should be its last child
if (FocusedElement(e) is IInputElement focusedElement)
return focusedElement;
if (direction == NavigationDirection.Next && !outsideElement)
{
var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
// Return the last visible element.
var uiElement = e as InputElement;
if (descendant != null)
{
return descendant;
}
}
if (container != null)
if (uiElement == null || uiElement.IsVisible)
{
var navigable = container as INavigableContainer;
var elementAsVisual = e as IVisual;
// TODO: Do a spatial search here if the container doesn't implement
// INavigableContainer.
if (navigable != null)
if (elementAsVisual != null)
{
while (e != null)
{
e = navigable.GetControl(direction, e, false);
var children = elementAsVisual.VisualChildren;
var count = children.Count;
if (e != null &&
e.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement)e))
for (int i = count - 1; i >= 0; i--)
{
if (children[i] is InputElement ie)
{
break;
if (ie.IsVisible)
return ie;
else
{
var lastChild = GetLastChild(ie);
if (lastChild != null)
return lastChild;
}
}
}
}
else
}
return null;
}
private static IInputElement? GetFirstTabInGroup(IInputElement container)
{
IInputElement? firstTabElement = null;
int minIndexFirstTab = int.MinValue;
var currElement = container;
while ((currElement = GetNextInTree(currElement, container)) != null)
{
if (IsTabStopOrGroup(currElement))
{
// TODO: Do a spatial search here if the container doesn't implement
// INavigableContainer.
e = null;
int currPriority = KeyboardNavigation.GetTabIndex(currElement);
if (currPriority < minIndexFirstTab || firstTabElement == null)
{
minIndexFirstTab = currPriority;
firstTabElement = currElement;
}
}
}
return firstTabElement;
}
private static IInputElement? GetLastInTree(IInputElement container)
{
IInputElement? result;
IInputElement? c = container;
do
{
result = c;
c = GetLastChild(c);
} while (c != null && !IsGroup(c));
if (c != null)
return c;
return result;
}
if (e != null && direction == NavigationDirection.Previous)
private static IInputElement? GetLastTabInGroup(IInputElement container)
{
IInputElement? lastTabElement = null;
int maxIndexFirstTab = int.MaxValue;
var currElement = GetLastInTree(container);
while (currElement != null && currElement != container)
{
if (IsTabStopOrGroup(currElement))
{
var descendant = GetFocusableDescendants(e, direction).LastOrDefault();
int currPriority = KeyboardNavigation.GetTabIndex(currElement);
if (descendant != null)
if (currPriority > maxIndexFirstTab || lastTabElement == null)
{
return descendant;
maxIndexFirstTab = currPriority;
lastTabElement = currElement;
}
}
currElement = GetPreviousInTree(currElement, container);
}
return lastTabElement;
}
private static IInputElement? GetNextInTree(IInputElement e, IInputElement container)
{
IInputElement? result = null;
if (e == container || !IsGroup(e))
result = GetFirstChild(e);
if (result != null || e == container)
return result;
IInputElement? parent = e;
do
{
var sibling = GetNextSibling(parent);
if (sibling != null)
return sibling;
return e;
parent = GetParent(parent);
} while (parent != null && parent != container);
return null;
}
private static IInputElement? GetNextSibling(IInputElement e)
{
if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual)
{
var children = parentAsVisual.VisualChildren;
var count = children.Count;
var i = 0;
//go till itself
for (; i < count; i++)
{
var vchild = children[i];
if (vchild == elementAsVisual)
break;
}
i++;
//search ahead
for (; i < count; i++)
{
var visual = children[i];
if (visual is IInputElement ie)
return ie;
}
}
return null;
}
/// <summary>
/// Gets the first item that should be focused in the next container.
/// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement? GetFirstInNextContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction)
private static IInputElement? GetNextTabInGroup(IInputElement? e, IInputElement container, KeyboardNavigationMode tabbingType)
{
var parent = container.GetVisualParent<IInputElement>();
IInputElement? next = null;
// None groups: Tab navigation is not supported
if (tabbingType == KeyboardNavigationMode.None)
return null;
if (parent != null)
// e == null or e == container -> return the first TabStopOrGroup
if (e == null || e == container)
{
if (direction == NavigationDirection.Previous &&
parent.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement) parent))
return GetFirstTabInGroup(container);
}
if (tabbingType == KeyboardNavigationMode.Once)
return null;
var nextTabElement = GetNextTabWithSameIndex(e, container);
if (nextTabElement != null)
return nextTabElement;
return GetNextTabWithNextIndex(e, container, tabbingType);
}
private static IInputElement? GetNextTabWithSameIndex(IInputElement e, IInputElement container)
{
var elementTabPriority = KeyboardNavigation.GetTabIndex(e);
var currElement = e;
while ((currElement = GetNextInTree(currElement, container)) != null)
{
if (IsTabStopOrGroup(currElement) && KeyboardNavigation.GetTabIndex(currElement) == elementTabPriority)
{
return parent;
return currElement;
}
}
var allSiblings = parent.GetVisualChildren()
.OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendants);
var siblings = direction == NavigationDirection.Next ?
allSiblings.SkipWhile(x => x != container).Skip(1) :
allSiblings.TakeWhile(x => x != container).Reverse();
return null;
}
foreach (var sibling in siblings)
private static IInputElement? GetNextTabWithNextIndex(IInputElement e, IInputElement container, KeyboardNavigationMode tabbingType)
{
// Find the next min index in the tree
// min (index>currentTabIndex)
IInputElement? nextTabElement = null;
IInputElement? firstTabElement = null;
int minIndexFirstTab = int.MinValue;
int minIndex = int.MinValue;
int elementTabPriority = KeyboardNavigation.GetTabIndex(e);
IInputElement? currElement = container;
while ((currElement = GetNextInTree(currElement, container)) != null)
{
if (IsTabStopOrGroup(currElement))
{
var customNext = GetCustomNext(sibling, direction);
if (customNext.handled)
int currPriority = KeyboardNavigation.GetTabIndex(currElement);
if (currPriority > elementTabPriority)
{
return customNext.next;
if (currPriority < minIndex || nextTabElement == null)
{
minIndex = currPriority;
nextTabElement = currElement;
}
}
if (sibling.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement) sibling))
if (currPriority < minIndexFirstTab || firstTabElement == null)
{
return sibling;
minIndexFirstTab = currPriority;
firstTabElement = currElement;
}
}
}
next = direction == NavigationDirection.Next ?
GetFocusableDescendants(sibling, direction).FirstOrDefault() :
GetFocusableDescendants(sibling, direction).LastOrDefault();
// Cycle groups: if not found - return first element
if (tabbingType == KeyboardNavigationMode.Cycle && nextTabElement == null)
nextTabElement = firstTabElement;
return nextTabElement;
}
if (next != null)
private static IInputElement? GetPrevTabInGroup(IInputElement? e, IInputElement container, KeyboardNavigationMode tabbingType)
{
// None groups: Tab navigation is not supported
if (tabbingType == KeyboardNavigationMode.None)
return null;
// Search the last index inside the group
if (e == null)
{
return GetLastTabInGroup(container);
}
if (tabbingType == KeyboardNavigationMode.Once)
return null;
if (e == container)
return null;
var nextTabElement = GetPrevTabWithSameIndex(e, container);
if (nextTabElement != null)
return nextTabElement;
return GetPrevTabWithPrevIndex(e, container, tabbingType);
}
private static IInputElement? GetPrevTabWithSameIndex(IInputElement e, IInputElement container)
{
int elementTabPriority = KeyboardNavigation.GetTabIndex(e);
var currElement = GetPreviousInTree(e, container);
while (currElement != null)
{
if (IsTabStopOrGroup(currElement) && KeyboardNavigation.GetTabIndex(currElement) == elementTabPriority && currElement != container)
{
return currElement;
}
currElement = GetPreviousInTree(currElement, container);
}
return null;
}
private static IInputElement? GetPrevTabWithPrevIndex(IInputElement e, IInputElement container, KeyboardNavigationMode tabbingType)
{
// Find the next max index in the tree
// max (index<currentTabIndex)
IInputElement? lastTabElement = null;
IInputElement? nextTabElement = null;
int elementTabPriority = KeyboardNavigation.GetTabIndex(e);
int maxIndexFirstTab = Int32.MaxValue;
int maxIndex = Int32.MaxValue;
var currElement = GetLastInTree(container);
while (currElement != null)
{
if (IsTabStopOrGroup(currElement) && currElement != container)
{
int currPriority = KeyboardNavigation.GetTabIndex(currElement);
if (currPriority < elementTabPriority)
{
if (currPriority > maxIndex || nextTabElement == null)
{
maxIndex = currPriority;
nextTabElement = currElement;
}
}
if (currPriority > maxIndexFirstTab || lastTabElement == null)
{
return next;
maxIndexFirstTab = currPriority;
lastTabElement = currElement;
}
}
next = GetFirstInNextContainer(element, parent, direction);
currElement = GetPreviousInTree(currElement, container);
}
// Cycle groups: if not found - return first element
if (tabbingType == KeyboardNavigationMode.Cycle && nextTabElement == null)
nextTabElement = lastTabElement;
return nextTabElement;
}
private static IInputElement? GetPreviousInTree(IInputElement e, IInputElement container)
{
if (e == container)
return null;
var result = GetPreviousSibling(e);
if (result != null)
{
if (IsGroup(result))
return result;
else
return GetLastInTree(result);
}
else
return GetParent(e);
}
private static IInputElement? GetPreviousSibling(IInputElement e)
{
if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual)
{
next = direction == NavigationDirection.Next ?
GetFocusableDescendants(container, direction).FirstOrDefault() :
GetFocusableDescendants(container, direction).LastOrDefault();
var children = parentAsVisual.VisualChildren;
var count = children.Count;
IInputElement? prev = null;
for (int i = 0; i < count; i++)
{
var vchild = children[i];
if (vchild == elementAsVisual)
break;
if (vchild.IsVisible == true && vchild is IInputElement ie)
prev = ie;
}
return prev;
}
return null;
}
return next;
private static IInputElement? GetActiveElement(IInputElement e)
{
return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabOnceActiveElementProperty);
}
private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element,
NavigationDirection direction)
private static IInputElement GetGroupParent(IInputElement e) => GetGroupParent(e, false);
private static IInputElement GetGroupParent(IInputElement element, bool includeCurrent)
{
if (element is ICustomKeyboardNavigation custom)
var result = element; // Keep the last non null element
var e = element;
// If we don't want to include the current element,
// start at the parent of the element. If the element
// is the root, then just return it as the group parent.
if (!includeCurrent)
{
result = e;
e = GetParent(e);
if (e == null)
return result;
}
while (e != null)
{
return custom.GetNext(element, direction);
if (IsGroup(e))
return e;
result = e;
e = GetParent(e);
}
return (false, null);
return result;
}
private static IInputElement? GetParent(IInputElement e)
{
// For Visual - go up the visual parent chain until we find Visual.
if (e is IVisual v)
return v.FindAncestorOfType<IInputElement>();
// This will need to be implemented when we have non-visual input elements.
throw new NotSupportedException();
}
private static KeyboardNavigationMode GetKeyNavigationMode(IInputElement e)
{
return ((IAvaloniaObject)e).GetValue(KeyboardNavigation.TabNavigationProperty);
}
private static bool IsFocusScope(IInputElement e) => FocusManager.GetIsFocusScope(e) || GetParent(e) == null;
private static bool IsGroup(IInputElement e) => GetKeyNavigationMode(e) != KeyboardNavigationMode.Continue;
private static bool IsTabStop(IInputElement e)
{
if (e is InputElement ie)
return ie.Focusable && KeyboardNavigation.GetIsTabStop(ie) && ie.IsVisible && ie.IsEnabled;
return false;
}
private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e);
}
}

4
src/Avalonia.Input/TappedEventArgs.cs

@ -13,6 +13,10 @@ namespace Avalonia.Input
this.lastPointerEventArgs = lastPointerEventArgs;
}
public IPointer Pointer => lastPointerEventArgs.Pointer;
public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers;
public ulong Timestamp => lastPointerEventArgs.Timestamp;
public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo);
}
}

4
src/Avalonia.Input/TextInput/ITextInputMethodClient.cs

@ -22,11 +22,11 @@ namespace Avalonia.Input.TextInput
/// </summary>
event EventHandler TextViewVisualChanged;
/// <summary>
/// Indicates if TextViewVisual is capable of displaying non-commited input on the cursor position
/// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position
/// </summary>
bool SupportsPreedit { get; }
/// <summary>
/// Sets the non-commited input string
/// Sets the non-committed input string
/// </summary>
void SetPreeditText(string text);
/// <summary>

2
src/Avalonia.Interactivity/Interactive.cs

@ -143,7 +143,7 @@ namespace Avalonia.Interactivity
/// <param name="e">The routed event.</param>
/// <returns>An <see cref="EventRoute"/> describing the route.</returns>
/// <remarks>
/// Usually, calling <see cref="RaiseEvent(RoutedEventArgs)"/> is sufficent to raise a routed
/// Usually, calling <see cref="RaiseEvent(RoutedEventArgs)"/> is sufficient to raise a routed
/// event, however there are situations in which the construction of the event args is expensive
/// and should be avoided if there are no handlers for an event. In these cases you can call
/// this method to build the event route and check the <see cref="EventRoute.HasHandlers"/>

2
src/Avalonia.Layout/ElementManager.cs

@ -40,7 +40,7 @@ namespace Avalonia.Layout
{
if (IsVirtualizingContext)
{
// We proactively clear elements laid out outside of the realizaton
// We proactively clear elements laid out outside of the realization
// rect so that they are available for reuse during the current
// measure pass.
// This is useful during fast panning scenarios in which the realization

4
src/Avalonia.Layout/FlowLayoutAlgorithm.cs

@ -22,7 +22,7 @@ namespace Avalonia.Layout
private int _firstRealizedDataIndexInsideRealizationWindow = -1;
private int _lastRealizedDataIndexInsideRealizationWindow = -1;
// If the scroll orientation is the same as the folow orientation
// If the scroll orientation is the same as the follow orientation
// we will only have one line since we will never wrap. In that case
// we do not want to align the line. We could potentially switch the
// meaning of line alignment in this case, but I'll hold off on that
@ -429,7 +429,7 @@ namespace Avalonia.Layout
// If we did not reach the top or bottom of the extent, we realized one
// extra item before we knew we were outside the realization window. Do not
// account for that element in the indicies inside the realization window.
// account for that element in the indices inside the realization window.
if (direction == GenerateDirection.Forward)
{
int dataCount = _context.ItemCount;

5
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Native.Interop;
using Avalonia.Platform;
@ -7,7 +8,7 @@ namespace Avalonia.Native
{
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
{
public event EventHandler<CancelEventArgs> ShutdownRequested;
public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{
@ -17,7 +18,7 @@ namespace Avalonia.Native
public int TryShutdown()
{
if (ShutdownRequested is null) return 1;
var e = new CancelEventArgs();
var e = new ShutdownRequestedEventArgs();
ShutdownRequested(this, e);
return (!e.Cancel).AsComBool();
}

4
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -138,7 +138,7 @@ namespace Avalonia.Native
{
_nativeMenu = (__MicroComIAvnMenuProxy)__MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialise(this, appMenuHolder, "");
_nativeMenu.Initialize(this, appMenuHolder, "");
setMenu = true;
}
@ -159,7 +159,7 @@ namespace Avalonia.Native
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialise(this, menu, "");
_nativeMenu.Initialize(this, menu, "");
setMenu = true;
}

35
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -26,19 +26,52 @@ namespace Avalonia
}
}
/// <summary>
/// OSX backend options.
/// </summary>
public class AvaloniaNativePlatformOptions
{
/// <summary>
/// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
/// </summary>
/// <remarks>
/// Avalonia has two rendering modes: Immediate and Deferred rendering.
/// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements.
/// </remarks>
public bool UseDeferredRendering { get; set; } = true;
/// <summary>
/// Determines whether to use GPU for rendering in your project. The default value is true.
/// </summary>
public bool UseGpu { get; set; } = true;
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>
public bool OverlayPopups { get; set; }
/// <summary>
/// This property should be used in case you want to build Avalonia OSX native part by yourself
/// and make your Avalonia app run with it. The default value is null.
/// </summary>
public string AvaloniaNativeLibraryPath { get; set; }
}
// ReSharper disable once InconsistentNaming
/// <summary>
/// OSX front-end options.
/// </summary>
public class MacOSPlatformOptions
{
/// <summary>
/// Determines whether to show your application in the dock when it runs. The default value is true.
/// </summary>
public bool ShowInDock { get; set; } = true;
/// <summary>
/// By default, Avalonia adds items like Quit, Hide to the OSX Application Menu.
/// You can prevent Avalonia from adding those items to the OSX Application Menu with this property. The default value is false.
/// </summary>
public bool DisableDefaultApplicationMenuItems { get; set; }
}
}

8
src/Avalonia.Native/IAvnMenu.cs

@ -96,7 +96,7 @@ namespace Avalonia.Native.Interop.Impl
_menuItems.Remove(item);
RemoveItem(item);
item.Deinitialise();
item.Deinitialize();
item.Dispose();
}
@ -113,7 +113,7 @@ namespace Avalonia.Native.Interop.Impl
{
var result = CreateNew(factory, item);
result.Initialise(item);
result.Initialize(item);
_menuItemLookup.Add(result.ManagedMenuItem, result);
_menuItems.Insert(index, result);
@ -133,7 +133,7 @@ namespace Avalonia.Native.Interop.Impl
return nativeItem;
}
internal void Initialise(AvaloniaNativeMenuExporter exporter, NativeMenu managedMenu, string title)
internal void Initialize(AvaloniaNativeMenuExporter exporter, NativeMenu managedMenu, string title)
{
_exporter = exporter;
ManagedMenu = managedMenu;
@ -150,7 +150,7 @@ namespace Avalonia.Native.Interop.Impl
foreach (var item in _menuItems)
{
item.Deinitialise();
item.Deinitialize();
item.Dispose();
}
}

6
src/Avalonia.Native/IAvnMenuItem.cs

@ -85,7 +85,7 @@ namespace Avalonia.Native.Interop.Impl
SetAction(action, callback);
}
internal void Initialise(NativeMenuItemBase nativeMenuItem)
internal void Initialize(NativeMenuItemBase nativeMenuItem)
{
ManagedMenuItem = nativeMenuItem;
@ -123,7 +123,7 @@ namespace Avalonia.Native.Interop.Impl
}
}
internal void Deinitialise()
internal void Deinitialize()
{
if (_subMenu != null)
{
@ -150,7 +150,7 @@ namespace Avalonia.Native.Interop.Impl
{
_subMenu = __MicroComIAvnMenuProxy.Create(factory);
_subMenu.Initialise(exporter, item.Menu, item.Header);
_subMenu.Initialize(exporter, item.Menu, item.Header);
SetSubMenu(_subMenu);
}

6
src/Avalonia.Native/PopupImpl.cs

@ -32,7 +32,7 @@ namespace Avalonia.Native
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Position = position;
Resize(size);
Resize(size, PlatformResizeReason.Layout);
//TODO: We ignore the scaling override for now
}
@ -60,14 +60,14 @@ namespace Avalonia.Native
}
}
public override void Show(bool activate)
public override void Show(bool activate, bool isDialog)
{
var parent = _parent;
while (parent is PopupImpl p)
parent = p._parent;
if (parent is WindowImpl w)
w.Native.TakeFocusFromChildren();
base.Show(false);
base.Show(false, isDialog);
}
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);

16
src/Avalonia.Native/WindowImplBase.cs

@ -87,7 +87,7 @@ namespace Avalonia.Native
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d));
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout);
}
public Size ClientSize
@ -146,7 +146,7 @@ namespace Avalonia.Native
public Action LostFocus { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup();
@ -186,13 +186,13 @@ namespace Avalonia.Native
_parent.Paint?.Invoke(new Rect(0, 0, s.Width, s.Height));
}
void IAvnWindowBaseEvents.Resized(AvnSize* size)
void IAvnWindowBaseEvents.Resized(AvnSize* size, AvnPlatformResizeReason reason)
{
if (_parent?._native != null)
{
var s = new Size(size->Width, size->Height);
_parent._savedLogicalSize = s;
_parent.Resized?.Invoke(s);
_parent.Resized?.Invoke(s, (PlatformResizeReason)reason);
}
}
@ -320,9 +320,9 @@ namespace Avalonia.Native
}
}
public void Resize(Size clientSize)
public void Resize(Size clientSize, PlatformResizeReason reason)
{
_native.Resize(clientSize.Width, clientSize.Height);
_native.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason);
}
public IRenderer CreateRenderer(IRenderRoot root)
@ -365,9 +365,9 @@ namespace Avalonia.Native
}
public virtual void Show(bool activate)
public virtual void Show(bool activate, bool isDialog)
{
_native.Show(activate.AsComBool());
_native.Show(activate.AsComBool(), isDialog.AsComBool());
}

15
src/Avalonia.Native/avn.idl

@ -400,6 +400,15 @@ enum AvnExtendClientAreaChromeHints
AvnDefaultChrome = AvnPreferSystemChrome,
}
enum AvnPlatformResizeReason
{
ResizeUnspecified,
ResizeUser,
ResizeApplication,
ResizeLayout,
ResizeDpiChange,
}
[uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)]
interface IAvaloniaNativeFactory : IUnknown
{
@ -430,7 +439,7 @@ interface IAvnString : IUnknown
[uuid(e5aca675-02b7-4129-aa79-d6e417210bda)]
interface IAvnWindowBase : IUnknown
{
HRESULT Show(bool activate);
HRESULT Show(bool activate, bool isDialog);
HRESULT Hide();
HRESULT Close();
HRESULT Activate();
@ -438,7 +447,7 @@ interface IAvnWindowBase : IUnknown
HRESULT GetFrameSize(AvnSize*ret);
HRESULT GetScaling(double*ret);
HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize);
HRESULT Resize(double width, double height);
HRESULT Resize(double width, double height, AvnPlatformResizeReason reason);
HRESULT Invalidate(AvnRect rect);
HRESULT BeginMoveDrag();
HRESULT BeginResizeDrag(AvnWindowEdge edge);
@ -492,7 +501,7 @@ interface IAvnWindowBaseEvents : IUnknown
void Closed();
void Activated();
void Deactivated();
void Resized([const] AvnSize& size);
void Resized([const] AvnSize& size, AvnPlatformResizeReason reason);
void PositionChanged(AvnPoint position);
void RawMouseEvent(AvnRawMouseEventType type,
uint timeStamp,

138
src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Logging;
using Avalonia.Media;
#nullable enable
namespace Avalonia.Animation.Animators
{
/// <summary>
@ -12,10 +15,8 @@ namespace Avalonia.Animation.Animators
/// redirect them to the properly registered
/// animators in this class.
/// </summary>
public class BaseBrushAnimator : Animator<IBrush>
public class BaseBrushAnimator : Animator<IBrush?>
{
private IAnimator _targetAnimator;
private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
new List<(Func<Type, bool> Match, Type AnimatorType)>();
@ -31,7 +32,7 @@ namespace Avalonia.Animation.Animators
/// The type of the animator to instantiate.
/// </typeparam>
public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition)
where TAnimator : IAnimator
where TAnimator : IAnimator, new()
{
_brushAnimators.Insert(0, (condition, typeof(TAnimator)));
}
@ -40,30 +41,127 @@ namespace Avalonia.Animation.Animators
public override IDisposable Apply(Animation animation, Animatable control, IClock clock,
IObservable<bool> match, Action onComplete)
{
foreach (var valueType in _brushAnimators)
if (TryCreateCustomRegisteredAnimator(out var animator)
|| TryCreateGradientAnimator(out animator)
|| TryCreateSolidColorBrushAnimator(out animator))
{
if (!valueType.Match(this[0].Value.GetType())) continue;
return animator.Apply(animation, control, clock, match, onComplete);
}
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
this,
"The animation's keyframe value types set is not supported.");
return base.Apply(animation, control, clock, match, onComplete);
}
_targetAnimator = (IAnimator)Activator.CreateInstance(valueType.AnimatorType);
/// <summary>
/// Fallback implementation of <see cref="IBrush"/> animation.
/// </summary>
public override IBrush? Interpolate(double progress, IBrush? oldValue, IBrush? newValue) => progress >= 0.5 ? newValue : oldValue;
foreach (var keyframe in this)
private bool TryCreateGradientAnimator([NotNullWhen(true)] out IAnimator? animator)
{
IGradientBrush? firstGradient = null;
foreach (var keyframe in this)
{
if (keyframe.Value is IGradientBrush gradientBrush)
{
_targetAnimator.Add(keyframe);
firstGradient = gradientBrush;
break;
}
}
_targetAnimator.Property = this.Property;
return _targetAnimator.Apply(animation, control, clock, match, onComplete);
if (firstGradient is null)
{
animator = null;
return false;
}
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
this,
"The animation's keyframe values didn't match any brush animators registered in BaseBrushAnimator.");
return Disposable.Empty;
var gradientAnimator = new GradientBrushAnimator();
gradientAnimator.Property = Property;
foreach (var keyframe in this)
{
if (keyframe.Value is ISolidColorBrush solidColorBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = GradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
});
}
else if (keyframe.Value is IGradientBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
else
{
animator = null;
return false;
}
}
animator = gradientAnimator;
return true;
}
/// <inheritdoc/>
public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null;
private bool TryCreateSolidColorBrushAnimator([NotNullWhen(true)] out IAnimator? animator)
{
var solidColorBrushAnimator = new ISolidColorBrushAnimator();
solidColorBrushAnimator.Property = Property;
foreach (var keyframe in this)
{
if (keyframe.Value is ISolidColorBrush)
{
solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
else
{
animator = null;
return false;
}
}
animator = solidColorBrushAnimator;
return true;
}
private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator)
{
if (_brushAnimators.Count > 0)
{
var firstKeyType = this[0].Value.GetType();
foreach (var (match, animatorType) in _brushAnimators)
{
if (!match(firstKeyType))
continue;
animator = (IAnimator)Activator.CreateInstance(animatorType);
if (animator != null)
{
animator.Property = Property;
foreach (var keyframe in this)
{
animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
return true;
}
}
}
animator = null;
return false;
}
}
}

123
src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
#nullable enable
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary>
public class GradientBrushAnimator : Animator<IGradientBrush?>
{
private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator();
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
public override IGradientBrush? Interpolate(double progress, IGradientBrush? oldValue, IGradientBrush? newValue)
{
if (oldValue is null || newValue is null)
{
return progress >= 0.5 ? newValue : oldValue;
}
switch (oldValue)
{
case IRadialGradientBrush oldRadial when newValue is IRadialGradientBrush newRadial:
return new ImmutableRadialGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin),
s_doubleAnimator.Interpolate(progress, oldRadial.Radius, newRadial.Radius));
case IConicGradientBrush oldConic when newValue is IConicGradientBrush newConic:
return new ImmutableConicGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle));
case ILinearGradientBrush oldLinear when newValue is ILinearGradientBrush newLinear:
return new ImmutableLinearGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
default:
return progress >= 0.5 ? newValue : oldValue;
}
}
public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush?> instance)
{
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
}
private IReadOnlyList<ImmutableGradientStop> InterpolateStops(double progress, IReadOnlyList<IGradientStop> oldValue, IReadOnlyList<IGradientStop> newValue)
{
var resultCount = Math.Max(oldValue.Count, newValue.Count);
var stops = new ImmutableGradientStop[resultCount];
for (int index = 0, oldIndex = 0, newIndex = 0; index < resultCount; index++)
{
stops[index] = new ImmutableGradientStop(
s_doubleAnimator.Interpolate(progress, oldValue[oldIndex].Offset, newValue[newIndex].Offset),
ColorAnimator.InterpolateCore(progress, oldValue[oldIndex].Color, newValue[newIndex].Color));
if (oldIndex < oldValue.Count - 1)
{
oldIndex++;
}
if (newIndex < newValue.Count - 1)
{
newIndex++;
}
}
return stops;
}
internal static IGradientBrush ConvertSolidColorBrushToGradient(IGradientBrush gradientBrush, ISolidColorBrush solidColorBrush)
{
switch (gradientBrush)
{
case IRadialGradientBrush oldRadial:
return new ImmutableRadialGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity,
oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius);
case IConicGradientBrush oldConic:
return new ImmutableConicGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity,
oldConic.SpreadMethod, oldConic.Center, oldConic.Angle);
case ILinearGradientBrush oldLinear:
return new ImmutableLinearGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity,
oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint);
default:
throw new NotSupportedException($"Gradient of type {gradientBrush?.GetType()} is not supported");
}
static IReadOnlyList<ImmutableGradientStop> CreateStopsFromSolidColorBrush(ISolidColorBrush solidColorBrush, IReadOnlyList<IGradientStop> baseStops)
{
var stops = new ImmutableGradientStop[baseStops.Count];
for (int index = 0; index < baseStops.Count; index++)
{
stops[index] = new ImmutableGradientStop(baseStops[index].Offset, solidColorBrush.Color);
}
return stops;
}
}
}
}

20
src/Avalonia.Visuals/Animation/Animators/RelativePointAnimator.cs

@ -0,0 +1,20 @@
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="RelativePoint"/> properties.
/// </summary>
public class RelativePointAnimator : Animator<RelativePoint>
{
private static readonly PointAnimator s_pointAnimator = new PointAnimator();
public override RelativePoint Interpolate(double progress, RelativePoint oldValue, RelativePoint newValue)
{
if (oldValue.Unit != newValue.Unit)
{
return progress >= 0.5 ? newValue : oldValue;
}
return new RelativePoint(s_pointAnimator.Interpolate(progress, oldValue.Point, newValue.Point), oldValue.Unit);
}
}
}

20
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -3,37 +3,39 @@ using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
#nullable enable
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
{
public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue)
public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue)
{
if (oldValue is null || newValue is null)
{
return oldValue;
return progress >= 0.5 ? newValue : oldValue;
}
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush> instance)
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)
{
return control.Bind((AvaloniaProperty<IBrush>)Property, instance, BindingPriority.Animation);
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
}
}
[Obsolete]
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
[Obsolete("Use ISolidColorBrushAnimator instead")]
public class SolidColorBrushAnimator : Animator<SolidColorBrush?>
{
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)
public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
{
if (oldValue is null || newValue is null)
{
return oldValue;
return progress >= 0.5 ? newValue : oldValue;
}
return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));

44
src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Media;
@ -9,37 +10,46 @@ namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type.
/// Only values of <see cref="ISolidColorBrush"/> will transition correctly at the moment.
/// </summary>
public class BrushTransition : Transition<IBrush?>
{
private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator();
private static readonly GradientBrushAnimator s_gradientAnimator = new GradientBrushAnimator();
private static readonly ISolidColorBrushAnimator s_solidColorBrushAnimator = new ISolidColorBrushAnimator();
public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
{
var oldSolidColorBrush = TryGetSolidColorBrush(oldValue);
var newSolidColorBrush = TryGetSolidColorBrush(newValue);
if (oldSolidColorBrush != null && newSolidColorBrush != null)
if (oldValue is null || newValue is null)
{
return new AnimatorTransitionObservable<ISolidColorBrush, ISolidColorBrushAnimator>(
s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
if (oldValue is IGradientBrush oldGradient)
{
if (newValue is IGradientBrush newGradient)
{
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, newGradient);
}
else if (newValue is ISolidColorBrush newSolidColorBrushToConvert)
{
var convertedSolidColorBrush = GradientBrushAnimator.ConvertSolidColorBrushToGradient(oldGradient, newSolidColorBrushToConvert);
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, convertedSolidColorBrush);
}
}
else if (newValue is IGradientBrush newGradient && oldValue is ISolidColorBrush oldSolidColorBrushToConvert)
{
var convertedSolidColorBrush = GradientBrushAnimator.ConvertSolidColorBrushToGradient(newGradient, oldSolidColorBrushToConvert);
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, convertedSolidColorBrush, newGradient);
}
private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush)
{
if (brush is null)
if (oldValue is ISolidColorBrush oldSolidColorBrush && newValue is ISolidColorBrush newSolidColorBrush)
{
return Brushes.Transparent;
return new AnimatorTransitionObservable<ISolidColorBrush?, ISolidColorBrushAnimator>(s_solidColorBrushAnimator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
}
return brush as ISolidColorBrush;
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
private class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
private sealed class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
{
private readonly IBrush? _from;
private readonly IBrush? _to;
@ -52,7 +62,7 @@ namespace Avalonia.Animation
protected override IBrush? ProduceValue(double progress)
{
return progress < 0.5 ? _from : _to;
return progress >= 0.5 ? _to : _from;
}
}
}

11
src/Avalonia.Visuals/Animation/Transitions/RelativePointTransition.cs

@ -0,0 +1,11 @@
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="RelativePoint"/> type.
/// </summary>
public class RelativePointTransition : AnimatorDrivenTransition<RelativePoint, RelativePointAnimator>
{
}
}

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

@ -12,6 +12,12 @@ namespace Avalonia.Media
public static readonly StyledProperty<Transform> TransformProperty =
AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform));
public static readonly StyledProperty<Geometry> ClipGeometryProperty =
AvaloniaProperty.Register<DrawingGroup, Geometry>(nameof(ClipGeometry));
public static readonly StyledProperty<IBrush> OpacityMaskProperty =
AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask));
public double Opacity
{
get => GetValue(OpacityProperty);
@ -24,6 +30,18 @@ namespace Avalonia.Media
set => SetValue(TransformProperty, value);
}
public Geometry ClipGeometry
{
get => GetValue(ClipGeometryProperty);
set => SetValue(ClipGeometryProperty, value);
}
public IBrush OpacityMask
{
get => GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value);
}
[Content]
public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>();
@ -31,6 +49,8 @@ namespace Avalonia.Media
{
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState))
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState))
{
foreach (var drawing in Children)
{

2
src/Avalonia.Visuals/Media/GradientBrush.cs

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Animation.Animators;
using Avalonia.Collections;
using Avalonia.Metadata;

1
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -16,7 +16,6 @@ namespace Avalonia.Media
static SolidColorBrush()
{
BaseBrushAnimator.RegisterBrushAnimator<ISolidColorBrushAnimator>(match => typeof(ISolidColorBrush).IsAssignableFrom(match));
AffectsRender<SolidColorBrush>(ColorProperty);
}

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@ -284,7 +284,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
// - U+0028 (Left Opening Parenthesis)
// - U+005B (Opening Square Bracket)
// - U+007B (Left Curly Bracket)
// See custom colums|rules in the text pair table.
// See custom columns|rules in the text pair table.
// https://www.unicode.org/Public/13.0.0/ucd/auxiliary/LineBreakTest.html
_lb30 = _alphaNumericCount > 0
&& cls == LineBreakClass.OpenPunctuation

10
src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs

@ -25,14 +25,14 @@ namespace Avalonia.Media.Transformation
Matrix.CreateScale(decomposed.Scale);
}
public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progres)
public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress)
{
Matrix.Decomposed result = default;
result.Translate = InterpolateVectors(from.Translate, to.Translate, progres);
result.Scale = InterpolateVectors(from.Scale, to.Scale, progres);
result.Skew = InterpolateVectors(from.Skew, to.Skew, progres);
result.Angle = InterpolateScalars(from.Angle, to.Angle, progres);
result.Translate = InterpolateVectors(from.Translate, to.Translate, progress);
result.Scale = InterpolateVectors(from.Scale, to.Scale, progress);
result.Skew = InterpolateVectors(from.Skew, to.Skew, progress);
result.Angle = InterpolateScalars(from.Angle, to.Angle, progress);
return result;
}

7
src/Avalonia.Visuals/RelativePoint.cs

@ -1,5 +1,7 @@
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia
@ -45,6 +47,11 @@ namespace Avalonia
private readonly RelativeUnit _unit;
static RelativePoint()
{
Animation.Animation.RegisterAnimator<RelativePointAnimator>(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// Initializes a new instance of the <see cref="RelativePoint"/> struct.
/// </summary>

46
src/Avalonia.X11/X11Platform.cs

@ -162,14 +162,49 @@ namespace Avalonia.X11
namespace Avalonia
{
/// <summary>
/// Platform-specific options which apply to Linux.
/// </summary>
public class X11PlatformOptions
{
/// <summary>
/// Enables native Linux EGL when set to true. The default value is false.
/// </summary>
public bool UseEGL { get; set; }
/// <summary>
/// Determines whether to use GPU for rendering in your project. The default value is true.
/// </summary>
public bool UseGpu { get; set; } = true;
/// <summary>
/// Embeds popups to the window when set to true. The default value is false.
/// </summary>
public bool OverlayPopups { get; set; }
/// <summary>
/// Enables global menu support on Linux desktop environments where it's supported (e. g. XFCE and MATE with plugin, KDE, etc).
/// The default value is false.
/// </summary>
public bool UseDBusMenu { get; set; }
/// <summary>
/// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
/// </summary>
/// <remarks>
/// Avalonia has two rendering modes: Immediate and Deferred rendering.
/// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements.
/// </remarks>
public bool UseDeferredRendering { get; set; } = true;
/// <summary>
/// Determines whether to use IME.
/// IME would be enabled by default if the current user input language is one of the following: Mandarin, Japanese, Vietnamese or Korean.
/// </summary>
/// <remarks>
/// Input method editor is a component that enables users to generate characters not natively available
/// on their input devices by using sequences of characters or mouse operations that are natively available on their input devices.
/// </remarks>
public bool? EnableIme { get; set; }
public IList<GlVersion> GlProfiles { get; set; } = new List<GlVersion>
@ -190,7 +225,14 @@ namespace Avalonia
"llvmpipe"
};
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
public bool? EnableMultiTouch { get; set; }
/// <summary>
/// Enables multitouch support. The default value is true.
/// </summary>
/// <remarks>
/// Multitouch allows a surface (a touchpad or touchscreen) to recognize the presence of more than one point of contact with the surface at the same time.
/// </remarks>
public bool? EnableMultiTouch { get; set; } = true;
}
public static class AvaloniaX11PlatformExtensions
{

37
src/Avalonia.X11/X11Window.cs

@ -336,7 +336,7 @@ namespace Avalonia.X11
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
//TODO
public Action<double> ScalingChanged { get; set; }
public Action Deactivated { get; set; }
@ -510,7 +510,7 @@ namespace Avalonia.X11
UpdateImePosition();
if (changedSize && !updatedSizeViaScaling && !_popup)
Resized?.Invoke(ClientSize);
Resized?.Invoke(ClientSize, PlatformResizeReason.Unspecified);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}, DispatcherPriority.Layout);
@ -565,7 +565,7 @@ namespace Avalonia.X11
UpdateImePosition();
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize)
Resize(oldScaledSize, true);
Resize(oldScaledSize, true, PlatformResizeReason.DpiChange);
return true;
}
@ -616,7 +616,7 @@ namespace Avalonia.X11
{
// Occurs once the window has been mapped, which is the earliest the extents
// can be retrieved, so invoke event to force update of TopLevel.FrameSize.
Resized.Invoke(ClientSize);
Resized.Invoke(ClientSize, PlatformResizeReason.Unspecified);
}
if (atom == _x11.Atoms._NET_WM_STATE)
@ -839,7 +839,7 @@ namespace Avalonia.X11
XSetTransientForHint(_x11.Display, _handle, parent.Handle.Handle);
}
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
_wasMappedAtLeastOnce = true;
XMapWindow(_x11.Display, _handle);
@ -862,19 +862,19 @@ namespace Avalonia.X11
}
public void Resize(Size clientSize) => Resize(clientSize, false);
public void Resize(Size clientSize, PlatformResizeReason reason) => Resize(clientSize, false, reason);
public void Move(PixelPoint point) => Position = point;
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Move(position);
_scalingOverride = scaling;
UpdateScaling(true);
Resize(size, true);
Resize(size, true, PlatformResizeReason.Layout);
}
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling));
void Resize(Size clientSize, bool force)
void Resize(Size clientSize, bool force, PlatformResizeReason reason)
{
if (!force && clientSize == ClientSize)
return;
@ -891,7 +891,7 @@ namespace Avalonia.X11
if (force || !_wasMappedAtLeastOnce || (_popup && needImmediatePopupResize))
{
_realSize = pixelSize;
Resized?.Invoke(ClientSize);
Resized?.Invoke(ClientSize, reason);
}
}
@ -1024,15 +1024,22 @@ namespace Avalonia.X11
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
BeginMoveResize(side, e);
}
public void SetTitle(string title)
{
var data = Encoding.UTF8.GetBytes(title);
fixed (void* pdata = data)
if (string.IsNullOrEmpty(title))
{
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8,
PropertyMode.Replace, pdata, data.Length);
XStoreName(_x11.Display, _handle, title);
XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME);
}
else
{
var data = Encoding.UTF8.GetBytes(title);
fixed (void* pdata = data)
{
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8,
PropertyMode.Replace, pdata, data.Length);
XStoreName(_x11.Display, _handle, title);
}
}
}

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -69,7 +69,7 @@ namespace Avalonia.LinuxFramebuffer
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<Size, PlatformResizeReason> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

6
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -44,7 +44,7 @@ namespace Avalonia.Win32.Interop.Wpf
((FrameworkElement)PlatformImpl)?.InvalidateMeasure();
}
protected override void HandleResized(Size clientSize)
protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass();
@ -114,7 +114,7 @@ namespace Avalonia.Win32.Interop.Wpf
if (_finalSize == _previousSize)
return finalSize;
_previousSize = _finalSize;
_ttl.Resized?.Invoke(finalSize.ToAvaloniaSize());
_ttl.Resized?.Invoke(finalSize.ToAvaloniaSize(), PlatformResizeReason.Unspecified);
return base.ArrangeOverride(finalSize);
}
@ -236,7 +236,7 @@ namespace Avalonia.Win32.Interop.Wpf
Action<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO
Action<Rect> ITopLevelImpl.Paint { get; set; }
Action<Size> ITopLevelImpl.Resized { get; set; }
Action<Size, PlatformResizeReason> ITopLevelImpl.Resized { get; set; }
Action<double> ITopLevelImpl.ScalingChanged { get; set; }
Action<WindowTransparencyLevel> ITopLevelImpl.TransparencyLevelChanged { get; set; }

4
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -17,7 +17,7 @@ namespace Avalonia.Win32
[ThreadStatic]
private static IntPtr s_parentHandle;
public override void Show(bool activate)
public override void Show(bool activate, bool isDialog)
{
// Popups are always shown non-activated.
UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate);
@ -135,7 +135,7 @@ namespace Avalonia.Win32
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Move(position);
Resize(size);
Resize(size, PlatformResizeReason.Layout);
//TODO: We ignore the scaling override for now
}

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

Loading…
Cancel
Save