diff --git a/build/MicroCom.targets b/build/MicroCom.targets
index b48e377fd4..49d2cdce72 100644
--- a/build/MicroCom.targets
+++ b/build/MicroCom.targets
@@ -15,7 +15,8 @@
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs"
Outputs="%(AvnComIdl.OutputFile)">
-
+
diff --git a/native/Avalonia.Native/src/OSX/KeyTransform.mm b/native/Avalonia.Native/src/OSX/KeyTransform.mm
index 6b7d95b619..4817ad0ccf 100644
--- a/native/Avalonia.Native/src/OSX/KeyTransform.mm
+++ b/native/Avalonia.Native/src/OSX/KeyTransform.mm
@@ -222,6 +222,7 @@ std::map s_QwertyKeyMap =
{ 45, "n" },
{ 46, "m" },
{ 47, "." },
+ { 48, "\t" },
{ 49, " " },
{ 50, "`" },
{ 51, "" },
diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h
index b1f64bca88..3a54bd4b79 100644
--- a/native/Avalonia.Native/src/OSX/window.h
+++ b/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 */
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index bb686e3902..14fe60ab0b 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/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();
@@ -705,6 +713,12 @@ private:
if(cparent == nullptr)
return E_INVALIDARG;
+ // If one tries to show a child window with a minimized parent window, then the parent window will be
+ // restored but MacOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
+ // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
+ if (cparent->WindowState() == Minimized)
+ cparent->SetWindowState(Normal);
+
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
UpdateStyle();
@@ -1196,6 +1210,7 @@ private:
}
_actualWindowState = _lastWindowState;
+ WindowEvents->WindowStateChanged(_actualWindowState);
}
@@ -1213,6 +1228,11 @@ private:
}
}
+ virtual bool IsDialog() override
+ {
+ return _isDialog;
+ }
+
protected:
virtual NSWindowStyleMask GetStyle() override
{
@@ -1373,6 +1393,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
NSObject* _renderTarget;
+ AvnPlatformResizeReason _resizeReason;
}
- (void)onClosed
@@ -1484,7 +1505,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 +2005,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
+- (AvnPlatformResizeReason)getResizeReason
+{
+ return _resizeReason;
+}
+
+- (void)setResizeReason:(AvnPlatformResizeReason)reason
+{
+ _resizeReason = reason;
+}
+
@end
@@ -2002,6 +2034,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_isExtended = value;
}
+-(bool) isDialog
+{
+ return _parent->IsDialog();
+}
+
-(double) getScaling
{
return _lastScaling;
@@ -2180,7 +2217,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(uch);
+ if(ch == nil)
+ continue;
+ if (ch.isDialog)
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
}
-(BOOL)canBecomeMainWindow
@@ -2188,22 +2240,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return _canBecomeKeyAndMain;
}
--(bool) activateAppropriateChild: (bool)activating
-{
- for(NSWindow* uch in [self childWindows])
- {
- auto ch = objc_cast(uch);
- if(ch == nil)
- continue;
- [ch activateAppropriateChild:false];
- return FALSE;
- }
-
- if(!activating)
- [self makeKeyAndOrderFront:self];
- return TRUE;
-}
-
-(bool)shouldTryToHandleEvents
{
return _isEnabled;
@@ -2214,26 +2250,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 +2268,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
if(parent != nil)
{
[parent removeChildWindow:self];
- [parent activateAppropriateChild: false];
}
}
@@ -2378,7 +2402,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;
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 820c1324e3..340b3376f5 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -28,7 +28,7 @@
DockPanel.Dock="Top"/>
-
+
diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml
index 7c911e91e9..2b5215a3fe 100644
--- a/samples/ControlCatalog/SideBar.xaml
+++ b/samples/ControlCatalog/SideBar.xaml
@@ -16,7 +16,6 @@
diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml
index fd23067f61..b82a7b0514 100644
--- a/samples/RenderDemo/SideBar.xaml
+++ b/samples/RenderDemo/SideBar.xaml
@@ -7,7 +7,6 @@
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index a72742580c..0afb1db141 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -67,7 +67,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action 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
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 6a9cff6b71..ce5b37043f 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -861,7 +861,7 @@ namespace Avalonia
}
///
- /// Logs a mesage if the notification represents a binding error.
+ /// Logs a message if the notification represents a binding error.
///
/// The property being bound.
/// The binding notification.
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 5117fdb170..94aefb8869 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -465,9 +465,9 @@ namespace Avalonia
/// Uses the visitor pattern to resolve an untyped property to a typed property.
///
/// The type of user data passed.
- /// The visitor which will accept the typed property.
+ /// The visitor which will accept the typed property.
/// The user data to pass.
- public abstract void Accept(IAvaloniaPropertyVisitor vistor, ref TData data)
+ public abstract void Accept(IAvaloniaPropertyVisitor visitor, ref TData data)
where TData : struct;
///
diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
index c1a2832fde..896d86e29d 100644
--- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
@@ -58,8 +58,8 @@ namespace Avalonia
///
/// This will usually be true, except in
///
- /// 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.
///
public bool IsEffectiveValueChange { get; private set; }
diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs
index e50e100d32..2cd9758f12 100644
--- a/src/Avalonia.Base/Collections/Pooled/PooledList.cs
+++ b/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).
///
public void Reverse(int index, int count)
{
diff --git a/src/Avalonia.Base/Data/Converters/BoolConverters.cs b/src/Avalonia.Base/Data/Converters/BoolConverters.cs
index 9329cdd6af..3985c5e32f 100644
--- a/src/Avalonia.Base/Data/Converters/BoolConverters.cs
+++ b/src/Avalonia.Base/Data/Converters/BoolConverters.cs
@@ -18,5 +18,11 @@ namespace Avalonia.Data.Converters
///
public static readonly IMultiValueConverter Or =
new FuncMultiValueConverter(x => x.Any(y => y));
+
+ ///
+ /// A value converter that returns true when input is false and false when input is true.
+ ///
+ public static readonly IValueConverter Not =
+ new FuncValueConverter(x => !x);
}
}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
index 324279e9f0..2a580fe75f 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Data.Core.Plugins
///
/// A weak reference to the object.
/// The property name.
- /// The inner property accessor used to aceess the property.
+ /// The inner property accessor used to access the property.
///
/// An interface through which future interactions with the
/// property will be made.
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index e6cc1edfdf..a057ad2254 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -13,7 +13,7 @@ namespace Avalonia
/// The type of the property's value.
///
/// Whereas 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.
///
public abstract class DirectPropertyBase : AvaloniaProperty
{
@@ -123,9 +123,9 @@ namespace Avalonia
}
///
- public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data)
+ public override void Accept(IAvaloniaPropertyVisitor visitor, ref TData data)
{
- vistor.Visit(this, ref data);
+ visitor.Visit(this, ref data);
}
///
diff --git a/src/Avalonia.Base/DirectPropertyMetadata`1.cs b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
index 205967984d..eabdef05ed 100644
--- a/src/Avalonia.Base/DirectPropertyMetadata`1.cs
+++ b/src/Avalonia.Base/DirectPropertyMetadata`1.cs
@@ -38,7 +38,7 @@ namespace Avalonia
///
/// 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.
///
public bool? EnableDataValidation { get; private set; }
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
index 5b1e43b8f4..fe6acdc532 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
+++ b/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
///
///
/// 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
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index 83f13fe199..ab1aff9220 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -25,6 +25,7 @@ using System.ComponentModel.DataAnnotations;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Controls.Metadata;
+using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Controls
{
@@ -67,7 +68,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;
@@ -2214,32 +2215,71 @@ namespace Avalonia.Controls
/// PointerWheelEventArgs
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
- if (IsEnabled && !e.Handled && DisplayData.NumDisplayedScrollingElements > 0)
+ e.Handled = e.Handled || UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta);
+ }
+
+ internal bool UpdateScroll(Vector delta)
+ {
+ if (IsEnabled && DisplayData.NumDisplayedScrollingElements > 0)
{
- double scrollHeight = 0;
- if (e.Delta.Y > 0)
+ var handled = false;
+ var scrollHeight = 0d;
+
+ // Vertical scroll handling
+ if (delta.Y > 0)
{
- scrollHeight = Math.Max(-_verticalOffset, -DATAGRID_mouseWheelDelta);
+ scrollHeight = Math.Max(-_verticalOffset, -delta.Y);
}
- else if (e.Delta.Y < 0)
+ else if (delta.Y < 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), -delta.Y);
}
else
{
double maximum = EdgedRowsHeightCalculated - CellsHeight;
- scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), DATAGRID_mouseWheelDelta);
+ scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), -delta.Y);
}
}
+
if (scrollHeight != 0)
{
DisplayData.PendingVerticalScrollHeight = scrollHeight;
+ handled = true;
+ }
+
+ // Horizontal scroll handling
+ if (delta.X != 0)
+ {
+ var originalHorizontalOffset = HorizontalOffset;
+ var horizontalOffset = originalHorizontalOffset - delta.X;
+ var widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth);
+
+ if (horizontalOffset < 0)
+ {
+ horizontalOffset = 0;
+ }
+ if (horizontalOffset > widthNotVisible)
+ {
+ horizontalOffset = widthNotVisible;
+ }
+
+ if (horizontalOffset != originalHorizontalOffset)
+ {
+ HorizontalOffset = horizontalOffset;
+ handled = true;
+ }
+ }
+
+ if (handled)
+ {
InvalidateRowsMeasure(invalidateIndividualElements: false);
- e.Handled = true;
+ return true;
}
}
+
+ return false;
}
///
@@ -5731,7 +5771,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
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
index 445dc541a7..7dda936317 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
@@ -173,7 +173,15 @@ namespace Avalonia.Controls
}
if (OwningRow != null)
{
- e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
+ var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
+
+ // Do not handle PointerPressed with touch,
+ // so we can start scroll gesture on the same event.
+ if (e.Pointer.Type != PointerType.Touch)
+ {
+ e.Handled = handled;
+ }
+
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
@@ -197,7 +205,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)
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs b/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
index 2f723154be..26f0b53952 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs
+++ b/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();
diff --git a/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs b/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
index a4bab8b304..ee69f1c768 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridClipboard.cs
@@ -189,7 +189,7 @@ namespace Avalonia.Controls
}
///
- /// DataGrid row item used for proparing the ClipboardRowContent.
+ /// DataGrid row item used for preparing the ClipboardRowContent.
///
public object Item
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
index 5c09bab678..4ab2869138 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs
@@ -795,7 +795,7 @@ namespace Avalonia.Controls
}
///
- /// 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.
diff --git a/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs b/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
index e659438b43..2b8055dd22 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridDisplayData.cs
+++ b/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();
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
index c3562c53a4..7546970498 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs
+++ b/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)
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs
index 4bfbd7d818..1d5c899993 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs
+++ b/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
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
index 1c350a4f14..4eed119240 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs
+++ b/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;
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
index 0d19f4c479..308ebc69d4 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
+++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
@@ -5,6 +5,9 @@
using System;
using System.Diagnostics;
+
+using Avalonia.Input;
+using Avalonia.Input.GestureRecognizers;
using Avalonia.Layout;
using Avalonia.Media;
@@ -16,6 +19,11 @@ namespace Avalonia.Controls.Primitives
///
public sealed class DataGridRowsPresenter : Panel
{
+ public DataGridRowsPresenter()
+ {
+ AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture);
+ }
+
internal DataGrid OwningGrid
{
get;
@@ -176,6 +184,11 @@ namespace Avalonia.Controls.Primitives
return new Size(totalCellsWidth + headerWidth, totalHeight);
}
+ private void OnScrollGesture(object sender, ScrollGestureEventArgs e)
+ {
+ e.Handled = e.Handled || OwningGrid.UpdateScroll(-e.Delta);
+ }
+
#if DEBUG
internal void PrintChildren()
{
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
index 09d19c8e43..ca0873e183 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
@@ -247,7 +247,11 @@
-
+
+
+
+
+
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
index f2dbf42196..8ccf717d94 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
@@ -601,7 +601,11 @@
+ Grid.ColumnSpan="3">
+
+
+
+
Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
-InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler 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)' is present in the implementation but not in the contract.
-InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler 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)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract.
+MembersMustExist : Member 'public System.Action 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)' 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.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Action Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the contract but not in the implementation.
+MembersMustExist : Member 'public System.Action 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)' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' is present in the contract but not in the implementation.
+MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.set(System.Action)' 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
diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs
index f616a42cac..d44b2ab0db 100644
--- a/src/Avalonia.Controls/AppBuilderBase.cs
+++ b/src/Avalonia.Controls/AppBuilderBase.cs
@@ -273,7 +273,7 @@ namespace Avalonia.Controls
}
///
- /// Sets up the platform-speciic services for the .
+ /// Sets up the platform-specific services for the .
///
private void Setup()
{
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
index 79780dbd0b..2a42d99ac5 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
@@ -48,7 +48,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
public event EventHandler Startup;
///
- public event EventHandler ShutdownRequested;
+ public event EventHandler ShutdownRequested;
///
public event EventHandler 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);
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
index ecf8a0358f..a70d5dd2f1 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
@@ -37,14 +37,21 @@ namespace Avalonia.Controls.ApplicationLifetimes
IReadOnlyList Windows { get; }
///
- /// Raised by the platform when a shutdown is requested.
+ /// Raised by the platform when an application shutdown is requested.
///
///
- /// 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 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.
///
- event EventHandler ShutdownRequested;
+ event EventHandler ShutdownRequested;
}
}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs
new file mode 100644
index 0000000000..62bc3a8904
--- /dev/null
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs
@@ -0,0 +1,9 @@
+using System.ComponentModel;
+
+namespace Avalonia.Controls.ApplicationLifetimes
+{
+ public class ShutdownRequestedEventArgs : CancelEventArgs
+ {
+
+ }
+}
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index c656ba6f6c..5a6e78f441 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -2005,7 +2005,7 @@ namespace Avalonia.Controls
// The TextBox.TextChanged event was not firing immediately and
// was causing an immediate update, even with wrapping. If there is
// a selection currently, no update should happen.
- if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != TextBox.Text.Length)
+ if (IsTextCompletionEnabled && TextBox != null && TextBoxSelectionLength > 0 && TextBoxSelectionStart != (TextBox.Text?.Length ?? 0))
{
return;
}
@@ -2303,7 +2303,7 @@ namespace Avalonia.Controls
{
if (IsTextCompletionEnabled && TextBox != null && userInitiated)
{
- int currentLength = TextBox.Text.Length;
+ int currentLength = TextBox.Text?.Length ?? 0;
int selectionStart = TextBoxSelectionStart;
if (selectionStart == text.Length && selectionStart > _textSelectionStart)
{
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index ead5b0d9f3..0c8fc31df1 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
-
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
@@ -21,7 +21,7 @@ namespace Avalonia.Controls
///
/// A control context menu.
///
- public class ContextMenu : MenuBase, ISetterValue
+ public class ContextMenu : MenuBase, ISetterValue, IPopupHostProvider
{
///
/// Defines the property.
@@ -82,6 +82,7 @@ namespace Avalonia.Controls
private Popup? _popup;
private List? _attachedControls;
private IInputElement? _previousFocus;
+ private Action? _popupHostChangedHandler;
///
/// Initializes a new instance of the class.
@@ -304,6 +305,14 @@ namespace Avalonia.Controls
}
}
+ IPopupHost? IPopupHostProvider.PopupHost => _popup?.Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
@@ -364,6 +373,8 @@ namespace Avalonia.Controls
{
_previousFocus = FocusManager.Instance?.Current;
Focus();
+
+ _popupHostChangedHandler?.Invoke(_popup!.Host);
}
private void PopupClosing(object sender, CancelEventArgs e)
@@ -397,6 +408,8 @@ namespace Avalonia.Controls
RoutedEvent = MenuClosedEvent,
Source = this,
});
+
+ _popupHostChangedHandler?.Invoke(null);
}
private void PopupKeyUp(object sender, KeyEventArgs e)
diff --git a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs b/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
index 9f3a6da9da..46affcbe33 100644
--- a/src/Avalonia.Controls/Converters/MarginMultiplierConverter.cs
+++ b/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);
diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
index e6420fe342..65f95808ff 100644
--- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
+++ b/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)
{
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 43bc7d1df9..5893a02b04 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -71,7 +71,7 @@ namespace Avalonia.Controls
x => x.MonthVisible, (x, v) => x.MonthVisible = v);
///
- /// Defiens the Property
+ /// Defines the Property
///
public static readonly DirectProperty YearFormatProperty =
AvaloniaProperty.RegisterDirect(nameof(YearFormat),
diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
index a0e8c03195..e16e609a15 100644
--- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
+++ b/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));
}
diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs
index 3237f6f37b..37b8691ce9 100644
--- a/src/Avalonia.Controls/DefinitionBase.cs
+++ b/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
}
///
- /// Callback to notify about exitting model tree.
+ /// Callback to notify about exiting model tree.
///
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
}
///
- /// Propogates invalidations for all registered definitions.
+ /// Propagates invalidations for all registered definitions.
/// Resets its own state.
///
internal void Invalidate()
diff --git a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
new file mode 100644
index 0000000000..11d3b1792a
--- /dev/null
+++ b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
@@ -0,0 +1,23 @@
+using System;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Diagnostics
+{
+ ///
+ /// Diagnostics interface to retrieve an associated .
+ ///
+ public interface IPopupHostProvider
+ {
+ ///
+ /// The popup host.
+ ///
+ IPopupHost? PopupHost { get; }
+
+ ///
+ /// Raised when the popup host changes.
+ ///
+ event Action? PopupHostChanged;
+ }
+}
diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs
new file mode 100644
index 0000000000..4acf2a217f
--- /dev/null
+++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs
@@ -0,0 +1,15 @@
+#nullable enable
+
+namespace Avalonia.Controls.Diagnostics
+{
+ ///
+ /// Helper class to provide diagnostics information for .
+ ///
+ public static class ToolTipDiagnostics
+ {
+ ///
+ /// Provides access to the internal for use in DevTools.
+ ///
+ public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty;
+ }
+}
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index 83470f161d..e2afbd3bdc 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/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 Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Action TransparencyLevelChanged { get; set; }
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index 30fb2c7761..230b4954fe 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
@@ -1,19 +1,18 @@
using System;
using System.ComponentModel;
+using Avalonia.Controls.Diagnostics;
using System.Linq;
-
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
-using Avalonia.Rendering;
#nullable enable
namespace Avalonia.Controls.Primitives
{
- public abstract class FlyoutBase : AvaloniaObject
+ public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider
{
static FlyoutBase()
{
@@ -59,6 +58,7 @@ namespace Avalonia.Controls.Primitives
private Rect? _enlargedPopupRect;
private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable;
+ private Action? _popupHostChangedHandler;
public FlyoutBase()
{
@@ -103,6 +103,14 @@ namespace Avalonia.Controls.Primitives
private set => SetAndRaise(TargetProperty, ref _target, value);
}
+ IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
public event EventHandler? Closed;
public event EventHandler? Closing;
public event EventHandler? Opened;
@@ -363,6 +371,8 @@ namespace Avalonia.Controls.Primitives
private void OnPopupOpened(object sender, EventArgs e)
{
IsOpen = true;
+
+ _popupHostChangedHandler?.Invoke(Popup!.Host);
}
private void OnPopupClosing(object sender, CancelEventArgs e)
@@ -376,6 +386,8 @@ namespace Avalonia.Controls.Primitives
private void OnPopupClosed(object sender, EventArgs e)
{
HideCore(false);
+
+ _popupHostChangedHandler?.Invoke(null);
}
// This method is handling both popup logical tree and target logical tree.
@@ -552,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]) &&
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs
index 10f97794d7..0f257224dd 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs
@@ -6,15 +6,6 @@ namespace Avalonia.Controls
{
public class FlyoutPresenter : ContentControl
{
- public static readonly StyledProperty CornerRadiusProperty =
- Border.CornerRadiusProperty.AddOwner();
-
- public CornerRadius CornerRadius
- {
- get => GetValue(CornerRadiusProperty);
- set => SetValue(CornerRadiusProperty, value);
- }
-
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Escape)
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index a14df1eb43..4e60b52f83 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/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
}
///
- /// 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.
///
///
@@ -821,7 +821,7 @@ namespace Avalonia.Controls
}
///
- /// 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.
///
///
@@ -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
///
/// 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 true grid lines are drawn to visualize location
/// of grid lines.
///
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index f4185650bb..43b4908482 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -89,7 +89,7 @@ namespace Avalonia.Controls
///
///
/// 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.
///
public new SelectionMode SelectionMode
{
diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
index d3d3721c89..49b36e714d 100644
--- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs
+++ b/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
{
}
diff --git a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
index bb3c0288eb..8513dd1697 100644
--- a/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
+++ b/src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Platform
///
/// 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.
///
PreferSystemChrome = 0x02,
diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
index 8e660777e9..4cd6640453 100644
--- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
+++ b/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
///
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
///
- event EventHandler ShutdownRequested;
+ event EventHandler ShutdownRequested;
}
}
diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
index 6e53233898..f4f4d29168 100644
--- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs
+++ b/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
{
+ ///
+ /// Describes the reason for a message.
+ ///
+ public enum PlatformResizeReason
+ {
+ ///
+ /// The resize reason is unknown or unspecified.
+ ///
+ Unspecified,
+
+ ///
+ /// The resize was due to the user resizing the window, for example by dragging the
+ /// window frame.
+ ///
+ User,
+
+ ///
+ /// The resize was initiated by the application, for example by setting one of the sizing-
+ /// related properties on such as or
+ /// .
+ ///
+ Application,
+
+ ///
+ /// The resize was initiated by the layout system.
+ ///
+ Layout,
+
+ ///
+ /// The resize was due to a change in DPI.
+ ///
+ DpiChange,
+ }
+
///
/// Defines a platform-specific top-level window implementation.
///
@@ -57,7 +92,7 @@ namespace Avalonia.Platform
///
/// Gets or sets a method called when the toplevel is resized.
///
- Action Resized { get; set; }
+ Action Resized { get; set; }
///
/// Gets or sets a method called when the toplevel's scaling changes.
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index 0d303a6666..5172569726 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -7,7 +7,9 @@ namespace Avalonia.Platform
///
/// Shows the window.
///
- void Show(bool activate);
+ /// Whether to activate the shown window.
+ /// Whether the window is being shown as a dialog.
+ void Show(bool activate, bool isDialog);
///
/// Hides the window.
diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs
index 8a1554d344..17a90eddfe 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -110,7 +110,9 @@ namespace Avalonia.Platform
///
/// Sets the client size of the top level.
///
- void Resize(Size clientSize);
+ /// The new client size.
+ /// The reason for the resize.
+ void Resize(Size clientSize, PlatformResizeReason reason = PlatformResizeReason.Application);
///
/// Sets the client size of the top level.
diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
index cb1291410a..a5495fdfc9 100644
--- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
+++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
@@ -21,10 +21,12 @@ namespace Avalonia.Controls.Platform
public void RunLoop(CancellationToken cancellationToken)
{
- while (true)
+ var handles = new[] { _signaled, cancellationToken.WaitHandle };
+
+ while (!cancellationToken.IsCancellationRequested)
{
Signaled?.Invoke(null);
- _signaled.WaitOne();
+ WaitHandle.WaitAny(handles);
}
}
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index 52c8273b6c..2ead82f95d 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -183,7 +183,7 @@ namespace Avalonia.Controls.Primitives
info.Subscription = adorned.GetObservable(TransformedBoundsProperty).Subscribe(x =>
{
info.Bounds = x;
- InvalidateArrange();
+ InvalidateMeasure();
});
}
}
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index 762d8d37a6..403902f676 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -140,10 +140,5 @@ namespace Avalonia.Controls.Primitives
return new OverlayPopupHost(overlayLayer);
}
-
- public override void Render(DrawingContext context)
- {
- context.FillRectangle(Brushes.White, new Rect(default, Bounds.Size));
- }
}
}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index f23a27e67a..d5fb69a672 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -2,6 +2,7 @@ using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
@@ -18,7 +19,7 @@ namespace Avalonia.Controls.Primitives
///
/// Displays a popup window.
///
- public class Popup : Control, IVisualTreeHost
+ public class Popup : Control, IVisualTreeHost, IPopupHostProvider
{
public static readonly StyledProperty WindowManagerAddShadowHintProperty =
AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), true);
@@ -134,6 +135,7 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState;
private IInputElement _overlayInputPassThroughElement;
+ private Action? _popupHostChangedHandler;
///
/// Initializes static members of the class.
@@ -351,6 +353,14 @@ namespace Avalonia.Controls.Primitives
///
IVisual? IVisualTreeHost.Root => _openState?.PopupHost.HostedVisualTreeRoot;
+ IPopupHost? IPopupHostProvider.PopupHost => Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
///
/// Opens the popup.
///
@@ -482,6 +492,8 @@ namespace Avalonia.Controls.Primitives
}
Opened?.Invoke(this, EventArgs.Empty);
+
+ _popupHostChangedHandler?.Invoke(Host);
}
///
@@ -591,6 +603,8 @@ namespace Avalonia.Controls.Primitives
_openState.Dispose();
_openState = null;
+ _popupHostChangedHandler?.Invoke(null);
+
using (BeginIgnoringIsOpen())
{
IsOpen = false;
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
index dd839a0e9b..0f0dd7311d 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
@@ -27,7 +27,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
///
/// An implementation for platforms on which a popup can be
- /// aritrarily positioned.
+ /// arbitrarily positioned.
///
public class ManagedPopupPositioner : IPopupPositioner
{
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index da7352b77f..1a11778db2 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/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;
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs
index 0b716ec1ca..acb8e0f006 100644
--- a/src/Avalonia.Controls/Primitives/RangeBase.cs
+++ b/src/Avalonia.Controls/Primitives/RangeBase.cs
@@ -170,7 +170,7 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Checks if the double value is not inifinity nor NaN.
+ /// Checks if the double value is not infinity nor NaN.
///
/// The value.
private static bool ValidateDouble(double value)
diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs
index d264ed76cc..d5d6af8bfa 100644
--- a/src/Avalonia.Controls/Primitives/ScrollBar.cs
+++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs
@@ -57,6 +57,18 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty AllowAutoHideProperty =
AvaloniaProperty.Register(nameof(AllowAutoHide), true);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty HideDelayProperty =
+ AvaloniaProperty.Register(nameof(HideDelay), TimeSpan.FromSeconds(2));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ShowDelayProperty =
+ AvaloniaProperty.Register(nameof(ShowDelay), TimeSpan.FromSeconds(0.5));
+
private Button _lineUpButton;
private Button _lineDownButton;
private Button _pageUpButton;
@@ -126,6 +138,24 @@ namespace Avalonia.Controls.Primitives
get => GetValue(AllowAutoHideProperty);
set => SetValue(AllowAutoHideProperty, value);
}
+
+ ///
+ /// Gets a value that determines how long will be the hide delay after user stops interacting with the scrollbar.
+ ///
+ public TimeSpan HideDelay
+ {
+ get => GetValue(HideDelayProperty);
+ set => SetValue(HideDelayProperty, value);
+ }
+
+ ///
+ /// Gets a value that determines how long will be the show delay when user starts interacting with the scrollbar.
+ ///
+ public TimeSpan ShowDelay
+ {
+ get => GetValue(ShowDelayProperty);
+ set => SetValue(ShowDelayProperty, value);
+ }
public event EventHandler Scroll;
@@ -296,12 +326,12 @@ namespace Avalonia.Controls.Primitives
private void CollapseAfterDelay()
{
- InvokeAfterDelay(Collapse, TimeSpan.FromSeconds(2));
+ InvokeAfterDelay(Collapse, HideDelay);
}
private void ExpandAfterDelay()
{
- InvokeAfterDelay(Expand, TimeSpan.FromMilliseconds(400));
+ InvokeAfterDelay(Expand, ShowDelay);
}
private void Collapse()
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 336ff711c0..07bc789cc1 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -328,7 +328,7 @@ namespace Avalonia.Controls.Primitives
///
///
/// 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.
///
protected SelectionMode SelectionMode
{
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index 9c73ff2411..59975b072d 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -5,7 +5,8 @@ using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Styling;
-using Avalonia.VisualTree;
+
+#nullable enable
namespace Avalonia.Controls.Primitives
{
@@ -17,13 +18,13 @@ namespace Avalonia.Controls.Primitives
///
/// Defines the property.
///
- public static readonly StyledProperty BackgroundProperty =
+ public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
///
/// Defines the property.
///
- public static readonly StyledProperty BorderBrushProperty =
+ public static readonly StyledProperty BorderBrushProperty =
Border.BorderBrushProperty.AddOwner();
///
@@ -32,6 +33,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner();
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty CornerRadiusProperty =
+ Border.CornerRadiusProperty.AddOwner();
+
///
/// Defines the property.
///
@@ -59,7 +66,7 @@ namespace Avalonia.Controls.Primitives
///
/// Defines the property.
///
- public static readonly StyledProperty ForegroundProperty =
+ public static readonly StyledProperty ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner();
///
@@ -71,8 +78,8 @@ namespace Avalonia.Controls.Primitives
///
/// Defines the property.
///
- public static readonly StyledProperty TemplateProperty =
- AvaloniaProperty.Register(nameof(Template));
+ public static readonly StyledProperty TemplateProperty =
+ AvaloniaProperty.Register(nameof(Template));
///
/// Defines the IsTemplateFocusTarget attached property.
@@ -88,7 +95,7 @@ namespace Avalonia.Controls.Primitives
"TemplateApplied",
RoutingStrategies.Direct);
- private IControlTemplate _appliedTemplate;
+ private IControlTemplate? _appliedTemplate;
///
/// Initializes static members of the class.
@@ -111,7 +118,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets or sets the brush used to draw the control's background.
///
- public IBrush Background
+ public IBrush? Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
@@ -120,7 +127,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets or sets the brush used to draw the control's border.
///
- public IBrush BorderBrush
+ public IBrush? BorderBrush
{
get { return GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
@@ -135,6 +142,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(BorderThicknessProperty, value); }
}
+ ///
+ /// Gets or sets the radius of the border rounded corners.
+ ///
+ public CornerRadius CornerRadius
+ {
+ get { return GetValue(CornerRadiusProperty); }
+ set { SetValue(CornerRadiusProperty, value); }
+ }
+
///
/// Gets or sets the font family used to draw the control's text.
///
@@ -174,7 +190,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets or sets the brush used to draw the control's text and other foreground elements.
///
- public IBrush Foreground
+ public IBrush? Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
@@ -192,7 +208,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets or sets the template that defines the control's appearance.
///
- public IControlTemplate Template
+ public IControlTemplate? Template
{
get { return GetValue(TemplateProperty); }
set { SetValue(TemplateProperty, value); }
@@ -265,7 +281,9 @@ namespace Avalonia.Controls.Primitives
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
+#pragma warning disable CS0618 // Type or member is obsolete
OnTemplateApplied(e);
+#pragma warning restore CS0618 // Type or member is obsolete
RaiseEvent(e);
}
diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs
index 6a899a6f26..f424ae29b7 100644
--- a/src/Avalonia.Controls/Repeater/IElementFactory.cs
+++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs
@@ -46,7 +46,7 @@ namespace Avalonia.Controls
}
///
- /// A data template that supports creating and recyling elements for an .
+ /// A data template that supports creating and recycling elements for an .
///
public interface IElementFactory : IDataTemplate
{
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index fb2da09e73..01200e87e3 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/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;
}
diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs
index cf2066b373..a7b6cf7f18 100644
--- a/src/Avalonia.Controls/Repeater/ViewManager.cs
+++ b/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;
}
}
diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs
index da3c2b15e6..1a90da5830 100644
--- a/src/Avalonia.Controls/Repeater/ViewportManager.cs
+++ b/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})",
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index 559edeb204..eee6216587 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/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);
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index 6ae53a4d59..138a765b43 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -345,7 +345,7 @@ namespace Avalonia.Controls.Selection
private protected override void OnSelectionChanged(IReadOnlyList 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;
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 91411cbd3b..0eade8d6df 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Controls
public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current
.GetService()?.Paste.FirstOrDefault();
-
+
public static readonly StyledProperty AcceptsReturnProperty =
AvaloniaProperty.Register(nameof(AcceptsReturn));
@@ -117,7 +117,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty RevealPasswordProperty =
AvaloniaProperty.Register(nameof(RevealPassword));
-
+
public static readonly DirectProperty CanCutProperty =
AvaloniaProperty.RegisterDirect(
nameof(CanCut),
@@ -135,7 +135,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty IsUndoEnabledProperty =
AvaloniaProperty.Register(
- nameof(IsUndoEnabled),
+ nameof(IsUndoEnabled),
defaultValue: true);
public static readonly DirectProperty UndoLimitProperty =
@@ -157,6 +157,10 @@ namespace Avalonia.Controls
}
public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text);
+
+ public override bool Equals(object obj) => obj is UndoRedoState other && Equals(other);
+
+ public override int GetHashCode() => Text.GetHashCode();
}
private string _text;
@@ -174,6 +178,10 @@ namespace Avalonia.Controls
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
+ private int _selectedTextChangesMadeSinceLastUndoSnapshot;
+ private bool _hasDoneSnapshotOnce;
+ private const int _maxCharsBeforeUndoSnapshot = 7;
+
static TextBox()
{
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
@@ -202,7 +210,8 @@ namespace Avalonia.Controls
horizontalScrollBarVisibility,
BindingPriority.Style);
_undoRedoHelper = new UndoRedoHelper(this);
-
+ _selectedTextChangesMadeSinceLastUndoSnapshot = 0;
+ _hasDoneSnapshotOnce = false;
UpdatePseudoclasses();
}
@@ -331,6 +340,7 @@ namespace Avalonia.Controls
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
{
_undoRedoHelper.Clear();
+ SnapshotUndoRedo(); // so we always have an initial state
}
}
}
@@ -341,16 +351,16 @@ namespace Avalonia.Controls
get { return GetSelection(); }
set
{
- SnapshotUndoRedo();
if (string.IsNullOrEmpty(value))
{
+ _selectedTextChangesMadeSinceLastUndoSnapshot++;
+ SnapshotUndoRedo(ignoreChangeCount: false);
DeleteSelection();
}
else
{
HandleTextInput(value);
}
- SnapshotUndoRedo();
}
}
@@ -422,7 +432,7 @@ namespace Avalonia.Controls
get { return _newLine; }
set { SetAndRaise(NewLineProperty, ref _newLine, value); }
}
-
+
///
/// Clears the current selection, maintaining the
///
@@ -480,11 +490,13 @@ namespace Avalonia.Controls
var oldValue = _undoRedoHelper.Limit;
_undoRedoHelper.Limit = value;
RaisePropertyChanged(UndoLimitProperty, oldValue, value);
- }
+ }
// from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
// "Setting UndoLimit clears the undo queue."
_undoRedoHelper.Clear();
+ _selectedTextChangesMadeSinceLastUndoSnapshot = 0;
+ _hasDoneSnapshotOnce = false;
}
}
@@ -515,6 +527,8 @@ namespace Avalonia.Controls
// Therefore, if you disable undo and then re-enable it, undo commands still do not work
// because the undo stack was emptied when you disabled undo."
_undoRedoHelper.Clear();
+ _selectedTextChangesMadeSinceLastUndoSnapshot = 0;
+ _hasDoneSnapshotOnce = false;
}
}
@@ -577,23 +591,25 @@ namespace Avalonia.Controls
{
return;
}
-
+
input = RemoveInvalidCharacters(input);
-
+
if (string.IsNullOrEmpty(input))
{
return;
}
-
+ _selectedTextChangesMadeSinceLastUndoSnapshot++;
+ SnapshotUndoRedo(ignoreChangeCount: false);
+
string text = Text ?? string.Empty;
int caretIndex = CaretIndex;
int newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
-
+
if (MaxLength > 0 && newLength > MaxLength)
{
input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength)));
}
-
+
if (!string.IsNullOrEmpty(input))
{
DeleteSelection();
@@ -627,7 +643,6 @@ namespace Avalonia.Controls
SnapshotUndoRedo();
Copy();
DeleteSelection();
- SnapshotUndoRedo();
}
public async void Copy()
@@ -647,7 +662,6 @@ namespace Avalonia.Controls
SnapshotUndoRedo();
HandleTextInput(text);
- SnapshotUndoRedo();
}
protected override void OnKeyDown(KeyEventArgs e)
@@ -696,6 +710,7 @@ namespace Avalonia.Controls
{
try
{
+ SnapshotUndoRedo();
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
@@ -830,7 +845,6 @@ namespace Avalonia.Controls
CaretIndex -= removedCharacters;
ClearSelection();
}
- SnapshotUndoRedo();
handled = true;
break;
@@ -858,7 +872,6 @@ namespace Avalonia.Controls
SetTextInternal(text.Substring(0, caretIndex) +
text.Substring(caretIndex + removedCharacters));
}
- SnapshotUndoRedo();
handled = true;
break;
@@ -868,7 +881,6 @@ namespace Avalonia.Controls
{
SnapshotUndoRedo();
HandleTextInput(NewLine);
- SnapshotUndoRedo();
handled = true;
}
@@ -879,7 +891,6 @@ namespace Avalonia.Controls
{
SnapshotUndoRedo();
HandleTextInput("\t");
- SnapshotUndoRedo();
handled = true;
}
else
@@ -889,6 +900,10 @@ namespace Avalonia.Controls
break;
+ case Key.Space:
+ SnapshotUndoRedo(); // always snapshot in between words
+ break;
+
default:
handled = false;
break;
@@ -1319,11 +1334,19 @@ namespace Avalonia.Controls
}
}
- private void SnapshotUndoRedo()
+ private void SnapshotUndoRedo(bool ignoreChangeCount = true)
{
if (IsUndoEnabled)
{
- _undoRedoHelper.Snapshot();
+ if (ignoreChangeCount ||
+ !_hasDoneSnapshotOnce ||
+ (!ignoreChangeCount &&
+ _selectedTextChangesMadeSinceLastUndoSnapshot >= _maxCharsBeforeUndoSnapshot))
+ {
+ _undoRedoHelper.Snapshot();
+ _selectedTextChangesMadeSinceLastUndoSnapshot = 0;
+ _hasDoneSnapshotOnce = true;
+ }
}
}
}
diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs
index 237bc2ce1d..12ae766052 100644
--- a/src/Avalonia.Controls/TickBar.cs
+++ b/src/Avalonia.Controls/TickBar.cs
@@ -193,7 +193,7 @@ namespace Avalonia.Controls
///
/// 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 .
///
@@ -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;
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index ab507d07a2..ab310d60ef 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -1,9 +1,8 @@
#nullable enable
using System;
-using System.Reactive.Linq;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -17,7 +16,7 @@ namespace Avalonia.Controls
/// assigning the content that you want displayed.
///
[PseudoClasses(":open")]
- public class ToolTip : ContentControl
+ public class ToolTip : ContentControl, IPopupHostProvider
{
///
/// Defines the ToolTip.Tip attached property.
@@ -61,7 +60,8 @@ namespace Avalonia.Controls
internal static readonly AttachedProperty ToolTipProperty =
AvaloniaProperty.RegisterAttached("ToolTip");
- private IPopupHost? _popup;
+ private IPopupHost? _popupHost;
+ private Action? _popupHostChangedHandler;
///
/// Initializes static members of the class.
@@ -251,35 +251,45 @@ namespace Avalonia.Controls
tooltip.RecalculatePosition(control);
}
+
+ IPopupHost? IPopupHostProvider.PopupHost => _popupHost;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
internal void RecalculatePosition(Control control)
{
- _popup?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
+ _popupHost?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
}
private void Open(Control control)
{
Close();
- _popup = OverlayPopupHost.CreatePopupHost(control, null);
- _popup.SetChild(this);
- ((ISetLogicalParent)_popup).SetParent(control);
+ _popupHost = OverlayPopupHost.CreatePopupHost(control, null);
+ _popupHost.SetChild(this);
+ ((ISetLogicalParent)_popupHost).SetParent(control);
- _popup.ConfigurePosition(control, GetPlacement(control),
+ _popupHost.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
- WindowManagerAddShadowHintChanged(_popup, false);
+ WindowManagerAddShadowHintChanged(_popupHost, false);
- _popup.Show();
+ _popupHost.Show();
+ _popupHostChangedHandler?.Invoke(_popupHost);
}
private void Close()
{
- if (_popup != null)
+ if (_popupHost != null)
{
- _popup.SetChild(null);
- _popup.Dispose();
- _popup = null;
+ _popupHost.SetChild(null);
+ _popupHost.Dispose();
+ _popupHost = null;
+ _popupHostChangedHandler?.Invoke(null);
}
}
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 7028dca769..5d9a0c8eed 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -90,6 +90,7 @@ namespace Avalonia.Controls
///
static TopLevel()
{
+ KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(KeyboardNavigationMode.Cycle);
AffectsMeasure(ClientSizeProperty);
TransparencyLevelHintProperty.Changed.AddClassHandler(
@@ -224,7 +225,7 @@ namespace Avalonia.Controls
}
///
- /// Gets the acheived that the platform was able to provide.
+ /// Gets the achieved that the platform was able to provide.
///
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);
+
///
/// Handles a resize notification from .
///
/// The new client size.
- protected virtual void HandleResized(Size clientSize)
+ /// The reason for the resize.
+ protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;
diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs
index 7374f20a0c..fd1ca54b57 100644
--- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs
+++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs
@@ -7,7 +7,7 @@ using Avalonia.Utilities;
namespace Avalonia.Controls.Utils
{
- class UndoRedoHelper : WeakTimer.IWeakTimerSubscriber where TState : struct, IEquatable
+ class UndoRedoHelper
{
private readonly IUndoRedoHost _host;
@@ -31,7 +31,6 @@ namespace Avalonia.Controls.Utils
public UndoRedoHelper(IUndoRedoHost host)
{
_host = host;
- WeakTimer.StartWeakTimer(this, TimeSpan.FromSeconds(1));
}
public void Undo()
@@ -61,7 +60,7 @@ namespace Avalonia.Controls.Utils
if (_states.Last != null)
{
_states.Last.Value = state;
- }
+ }
}
public void UpdateLastState()
@@ -103,11 +102,5 @@ namespace Avalonia.Controls.Utils
_states.Clear();
_currentNode = null;
}
-
- bool WeakTimer.IWeakTimerSubscriber.Tick()
- {
- Snapshot();
- return true;
- }
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index ae314a33ce..0ca28ca196 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/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
///
/// Gets or sets a value indicating how the window will size itself to fit its content.
///
+ ///
+ /// If has a value other than ,
+ /// is automatically set to
+ /// if a user resizes the window by using the resize grip or dragging the border.
+ ///
+ /// NOTE: Because of a limitation of X11, will be reset on X11 to
+ /// 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 to false, which will disable user resizing
+ /// of the window.
+ ///
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();
- 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(
- 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(
+ 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);
+
///
- 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);
}
///
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 2b31cef8bd..ee008efb04 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -39,7 +39,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty TopmostProperty =
AvaloniaProperty.Register(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; }
- ///
- /// Whether an auto-size operation is in progress.
- ///
- protected bool AutoSizing => _autoSizing > 0;
+ [Obsolete("No longer used. Always returns false.")]
+ protected bool AutoSizing => false;
///
/// 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
}
}
- ///
- /// Begins an auto-resize operation.
- ///
- /// A disposable used to finish the operation.
- ///
- /// When an auto-resize operation is in progress any resize events received will not be
- /// cause the new size to be written to the and
- /// properties.
- ///
- protected IDisposable BeginAutoSizing()
- {
- ++_autoSizing;
- return Disposable.Create(() => --_autoSizing);
- }
+
+ [Obsolete("No longer used. Has no effect.")]
+ protected IDisposable BeginAutoSizing() => Disposable.Empty;
///
/// 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);
+
///
/// Handles a resize notification from .
///
/// The new client size.
- protected override void HandleResized(Size clientSize)
+ /// The reason for the resize.
+ protected override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;
@@ -264,7 +254,7 @@ namespace Avalonia.Controls
}
///
- /// Called durung the arrange pass to set the size of the window.
+ /// Called during the arrange pass to set the size of the window.
///
/// The requested size of the window.
/// The actual size of the window.
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index 787f44887f..5cae29cafd 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/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)
{
}
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index c8203686f9..9dcd4d8e87 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -27,7 +27,7 @@ namespace Avalonia.DesignerSupport.Remote
public IEnumerable
internal static class TabNavigation
{
- ///
- /// Gets the next control in the specified tab direction.
- ///
- /// The element.
- /// The tab direction. Must be Next or Previous.
- ///
- /// If true will not descend into to find next control.
- ///
- ///
- /// The next element in the specified direction, or null if
- /// was the last in the requested direction.
- ///
- 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();
+ 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;
}
- ///
- /// Gets the first or last focusable descendant of the specified element.
- ///
- /// The element.
- /// The direction to search.
- /// The element or null if not found.##
- 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;
}
- ///
- /// Gets the focusable descendants of the specified element.
- ///
- /// The element.
- /// The tab direction. Must be Next or Previous.
- /// The element's focusable descendants.
- private static IEnumerable 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();
+ 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;
}
- ///
- /// Gets the next item that should be focused in the specified container.
- ///
- /// The starting element/
- /// The container.
- /// The direction.
- ///
- /// If true will not descend into to find next control.
- ///
- /// The next element, or null if the element is the last.
- 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;
}
- ///
- /// Gets the first item that should be focused in the next container.
- ///
- /// The element being navigated away from.
- /// The container.
- /// The direction of the search.
- /// The first element, or null if there are no more elements.
- 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? 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()
- .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 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();
+
+ // 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);
}
}
diff --git a/src/Avalonia.Input/TappedEventArgs.cs b/src/Avalonia.Input/TappedEventArgs.cs
index 02add509cd..daaab70632 100644
--- a/src/Avalonia.Input/TappedEventArgs.cs
+++ b/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);
}
}
diff --git a/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
index d385f5b162..2b5d8958cc 100644
--- a/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
+++ b/src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
@@ -22,11 +22,11 @@ namespace Avalonia.Input.TextInput
///
event EventHandler TextViewVisualChanged;
///
- /// 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
///
bool SupportsPreedit { get; }
///
- /// Sets the non-commited input string
+ /// Sets the non-committed input string
///
void SetPreeditText(string text);
///
diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs
index 4cd810af20..2497150b1a 100644
--- a/src/Avalonia.Interactivity/Interactive.cs
+++ b/src/Avalonia.Interactivity/Interactive.cs
@@ -143,7 +143,7 @@ namespace Avalonia.Interactivity
/// The routed event.
/// An describing the route.
///
- /// Usually, calling is sufficent to raise a routed
+ /// Usually, calling 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
diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs
index 3f106708e6..c2f829d020 100644
--- a/src/Avalonia.Layout/ElementManager.cs
+++ b/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
diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
index eace54d2e0..63343fd1a7 100644
--- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
+++ b/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;
diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
index 77c0794d04..8084e06d28 100644
--- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
+++ b/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 ShutdownRequested;
+ public event EventHandler 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();
}
diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
index 2e3408eca5..89efa6af0c 100644
--- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
+++ b/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;
}
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index 60c0b36891..a7d05e416f 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -109,11 +109,17 @@ namespace Avalonia.Native
.Bind().ToConstant(new RenderLoop())
.Bind().ToConstant(new DefaultRenderTimer(60))
.Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
- .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
+ .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind().ToConstant(applicationPlatform);
+ var hotkeys = AvaloniaLocator.Current.GetService();
+ hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));
+ hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
+ hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
+ hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
+
if (_options.UseGpu)
{
try
diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
index 76cb7a8057..eef765e7ec 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
@@ -26,19 +26,52 @@ namespace Avalonia
}
}
+ ///
+ /// OSX backend options.
+ ///
public class AvaloniaNativePlatformOptions
{
+ ///
+ /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
+ ///
+ ///
+ /// 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.
+ ///
public bool UseDeferredRendering { get; set; } = true;
+
+ ///
+ /// Determines whether to use GPU for rendering in your project. The default value is true.
+ ///
public bool UseGpu { get; set; } = true;
+
+ ///
+ /// Embeds popups to the window when set to true. The default value is false.
+ ///
public bool OverlayPopups { get; set; }
+
+ ///
+ /// 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.
+ ///
public string AvaloniaNativeLibraryPath { get; set; }
}
// ReSharper disable once InconsistentNaming
+ ///
+ /// OSX front-end options.
+ ///
public class MacOSPlatformOptions
{
+ ///
+ /// Determines whether to show your application in the dock when it runs. The default value is true.
+ ///
public bool ShowInDock { get; set; } = true;
-
+
+ ///
+ /// 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.
+ ///
public bool DisableDefaultApplicationMenuItems { get; set; }
}
}
diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs
index f76e9450fc..0e6fdd2df0 100644
--- a/src/Avalonia.Native/IAvnMenu.cs
+++ b/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();
}
}
diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs
index 97838f8dea..ca99cbea4b 100644
--- a/src/Avalonia.Native/IAvnMenuItem.cs
+++ b/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);
}
diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs
index c36675afcd..4680a2af17 100644
--- a/src/Avalonia.Native/PopupImpl.cs
+++ b/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);
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index ced9cea3a8..4a3baa2788 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/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 Paint { get; set; }
- public Action Resized { get; set; }
+ public Action 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());
}
diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl
index d75d914044..70d85dacdd 100644
--- a/src/Avalonia.Native/avn.idl
+++ b/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,
diff --git a/src/Avalonia.Themes.Default/AutoCompleteBox.xaml b/src/Avalonia.Themes.Default/AutoCompleteBox.xaml
index 66d0f17ede..fe4cd48e72 100644
--- a/src/Avalonia.Themes.Default/AutoCompleteBox.xaml
+++ b/src/Avalonia.Themes.Default/AutoCompleteBox.xaml
@@ -11,6 +11,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}"
Watermark="{TemplateBinding Watermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" />
diff --git a/src/Avalonia.Themes.Default/Button.xaml b/src/Avalonia.Themes.Default/Button.xaml
index 698ddec2a8..81d96aaa14 100644
--- a/src/Avalonia.Themes.Default/Button.xaml
+++ b/src/Avalonia.Themes.Default/Button.xaml
@@ -13,6 +13,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
@@ -31,4 +32,4 @@
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/ButtonSpinner.xaml b/src/Avalonia.Themes.Default/ButtonSpinner.xaml
index 89fbb9d64d..ce2b85d2b5 100644
--- a/src/Avalonia.Themes.Default/ButtonSpinner.xaml
+++ b/src/Avalonia.Themes.Default/ButtonSpinner.xaml
@@ -47,6 +47,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
@@ -73,6 +74,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
diff --git a/src/Avalonia.Themes.Default/Calendar.xaml b/src/Avalonia.Themes.Default/Calendar.xaml
index 6bbee4ef17..4b67aa232b 100644
--- a/src/Avalonia.Themes.Default/Calendar.xaml
+++ b/src/Avalonia.Themes.Default/Calendar.xaml
@@ -22,10 +22,11 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
HeaderBackground="{TemplateBinding HeaderBackground}"/>
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml
index aab7d06c46..57b77f70ea 100644
--- a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml
+++ b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml
@@ -88,7 +88,8 @@
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml
index 9b84253c8a..0df4866184 100644
--- a/src/Avalonia.Themes.Default/ContextMenu.xaml
+++ b/src/Avalonia.Themes.Default/ContextMenu.xaml
@@ -9,6 +9,7 @@
diff --git a/src/Avalonia.Themes.Default/DatePicker.xaml b/src/Avalonia.Themes.Default/DatePicker.xaml
index da878c88e2..c6c117138d 100644
--- a/src/Avalonia.Themes.Default/DatePicker.xaml
+++ b/src/Avalonia.Themes.Default/DatePicker.xaml
@@ -134,6 +134,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{DynamicResource DatePickerThemeMinWidth}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
@@ -148,6 +149,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
@@ -242,6 +244,7 @@
diff --git a/src/Avalonia.Themes.Default/Expander.xaml b/src/Avalonia.Themes.Default/Expander.xaml
index 08d8b4c995..5e0958c54c 100644
--- a/src/Avalonia.Themes.Default/Expander.xaml
+++ b/src/Avalonia.Themes.Default/Expander.xaml
@@ -10,7 +10,10 @@
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml
index 4bfae4c223..18bf79ce6c 100644
--- a/src/Avalonia.Themes.Default/MenuItem.xaml
+++ b/src/Avalonia.Themes.Default/MenuItem.xaml
@@ -14,7 +14,8 @@
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}">
@@ -96,7 +97,8 @@
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}">
diff --git a/src/Avalonia.Themes.Default/NumericUpDown.xaml b/src/Avalonia.Themes.Default/NumericUpDown.xaml
index 025e822404..6740be69bb 100644
--- a/src/Avalonia.Themes.Default/NumericUpDown.xaml
+++ b/src/Avalonia.Themes.Default/NumericUpDown.xaml
@@ -9,6 +9,7 @@
-
+
diff --git a/src/Avalonia.Themes.Default/RepeatButton.xaml b/src/Avalonia.Themes.Default/RepeatButton.xaml
index 702e4e6ebd..a9a03c8ed5 100644
--- a/src/Avalonia.Themes.Default/RepeatButton.xaml
+++ b/src/Avalonia.Themes.Default/RepeatButton.xaml
@@ -20,6 +20,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
diff --git a/src/Avalonia.Themes.Default/Separator.xaml b/src/Avalonia.Themes.Default/Separator.xaml
index cf0db16ee6..6a318d2e85 100644
--- a/src/Avalonia.Themes.Default/Separator.xaml
+++ b/src/Avalonia.Themes.Default/Separator.xaml
@@ -6,6 +6,7 @@
diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml
index ed2e67df28..afb5010baa 100644
--- a/src/Avalonia.Themes.Default/TabControl.xaml
+++ b/src/Avalonia.Themes.Default/TabControl.xaml
@@ -3,9 +3,9 @@
diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml
index 6e344ce58e..c7748299a0 100644
--- a/src/Avalonia.Themes.Default/TabItem.xaml
+++ b/src/Avalonia.Themes.Default/TabItem.xaml
@@ -12,11 +12,11 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
- Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"/>
diff --git a/src/Avalonia.Themes.Default/TabStripItem.xaml b/src/Avalonia.Themes.Default/TabStripItem.xaml
index 28c4c68a3d..61eecc0395 100644
--- a/src/Avalonia.Themes.Default/TabStripItem.xaml
+++ b/src/Avalonia.Themes.Default/TabStripItem.xaml
@@ -9,6 +9,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
@@ -20,4 +21,4 @@
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml
index 12df4b6213..9471beaaeb 100644
--- a/src/Avalonia.Themes.Default/TextBox.xaml
+++ b/src/Avalonia.Themes.Default/TextBox.xaml
@@ -30,7 +30,8 @@
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}">
diff --git a/src/Avalonia.Themes.Default/TimePicker.xaml b/src/Avalonia.Themes.Default/TimePicker.xaml
index c76f900cfe..a58fd62a99 100644
--- a/src/Avalonia.Themes.Default/TimePicker.xaml
+++ b/src/Avalonia.Themes.Default/TimePicker.xaml
@@ -58,6 +58,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{DynamicResource TimePickerThemeMinWidth}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
@@ -71,6 +72,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
@@ -178,6 +180,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Padding="{DynamicResource DateTimeFlyoutBorderPadding}"
MaxHeight="398">
diff --git a/src/Avalonia.Themes.Default/ToggleButton.xaml b/src/Avalonia.Themes.Default/ToggleButton.xaml
index 9e05c38eef..ffebd4f63d 100644
--- a/src/Avalonia.Themes.Default/ToggleButton.xaml
+++ b/src/Avalonia.Themes.Default/ToggleButton.xaml
@@ -13,6 +13,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
@@ -35,4 +36,4 @@
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/ToolTip.xaml b/src/Avalonia.Themes.Default/ToolTip.xaml
index 1fc0202dd3..35c1dceb8d 100644
--- a/src/Avalonia.Themes.Default/ToolTip.xaml
+++ b/src/Avalonia.Themes.Default/ToolTip.xaml
@@ -9,9 +9,10 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Default/TreeView.xaml b/src/Avalonia.Themes.Default/TreeView.xaml
index 026bed5899..990d5d0823 100644
--- a/src/Avalonia.Themes.Default/TreeView.xaml
+++ b/src/Avalonia.Themes.Default/TreeView.xaml
@@ -8,7 +8,8 @@
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}">
-
+
+
@@ -36,6 +37,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml
index 597f5d00ec..53d53ef127 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml
@@ -16,6 +16,7 @@
+
@@ -29,6 +30,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
@@ -93,8 +95,4 @@
-
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
index 12b4845522..d228c37912 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
@@ -58,6 +58,7 @@
+
@@ -69,7 +70,7 @@
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml
index 5110d70a80..df800b4a06 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml
@@ -39,6 +39,7 @@
+
@@ -55,7 +56,7 @@
MaxWidth="{TemplateBinding MaxWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
- CornerRadius="{DynamicResource OverlayCornerRadius}">
+ CornerRadius="{TemplateBinding CornerRadius}">
@@ -88,6 +89,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
diff --git a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
index 032fdd9ae4..3e4471cada 100644
--- a/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
@@ -8,6 +8,12 @@
+
+
+
+
+
+
0,0,0,4
40
@@ -50,6 +56,7 @@
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml
index 3f70939953..d5d44e1270 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml
@@ -1,23 +1,23 @@
-
-
+
+
Expanded content
-
+
Expanded content
-
+
Expanded content
-
+
Expanded content
@@ -51,6 +51,7 @@
+
@@ -140,31 +141,31 @@
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
index 1c52c6272c..3320fc9a41 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
@@ -37,6 +37,7 @@
+
@@ -59,6 +60,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{DynamicResource TimePickerThemeMinWidth}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
@@ -72,11 +74,11 @@
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
- VerticalContentAlignment="Stretch"
- CornerRadius="{DynamicResource ControlCornerRadius}" />
+ VerticalContentAlignment="Stretch" />
@@ -176,13 +178,14 @@
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
index dd8e51e4e5..b1d07059b8 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
@@ -18,6 +18,7 @@
+
@@ -29,6 +30,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
diff --git a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
index f7a1ebbc6b..debdfb2772 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
@@ -49,6 +49,7 @@
+
@@ -61,7 +62,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}"
- CornerRadius="{DynamicResource OverlayCornerRadius}">
+ CornerRadius="{TemplateBinding CornerRadius}">
+ BorderThickness="{TemplateBinding BorderThickness}"
+ CornerRadius="{TemplateBinding CornerRadius}">
TransformProperty =
AvaloniaProperty.Register(nameof(Transform));
+ public static readonly StyledProperty ClipGeometryProperty =
+ AvaloniaProperty.Register(nameof(ClipGeometry));
+
+ public static readonly StyledProperty OpacityMaskProperty =
+ AvaloniaProperty.Register(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 Children { get; } = new AvaloniaList();
@@ -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)
{
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
index 4d02f94cad..40891a700d 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
+++ b/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
diff --git a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
index 1e80eabfc8..742bb9c804 100644
--- a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
+++ b/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;
}
diff --git a/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs b/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs
index b63c49bd5a..01212b238d 100644
--- a/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs
+++ b/src/Avalonia.Visuals/VisualTree/IVisualTreeHost.cs
@@ -1,8 +1,11 @@
+using System;
+
namespace Avalonia.VisualTree
{
///
/// Interface for controls that host their own separate visual tree, such as popups.
///
+ [Obsolete]
public interface IVisualTreeHost
{
///
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index a57bdbdf87..3a919c8814 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -162,14 +162,49 @@ namespace Avalonia.X11
namespace Avalonia
{
-
+ ///
+ /// Platform-specific options which apply to Linux.
+ ///
public class X11PlatformOptions
{
+ ///
+ /// Enables native Linux EGL when set to true. The default value is false.
+ ///
public bool UseEGL { get; set; }
+
+ ///
+ /// Determines whether to use GPU for rendering in your project. The default value is true.
+ ///
public bool UseGpu { get; set; } = true;
+
+ ///
+ /// Embeds popups to the window when set to true. The default value is false.
+ ///
public bool OverlayPopups { get; set; }
+
+ ///
+ /// 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.
+ ///
public bool UseDBusMenu { get; set; }
+
+ ///
+ /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
+ ///
+ ///
+ /// 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.
+ ///
public bool UseDeferredRendering { get; set; } = true;
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ ///
public bool? EnableIme { get; set; }
public IList GlProfiles { get; set; } = new List
@@ -190,7 +225,14 @@ namespace Avalonia
"llvmpipe"
};
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
- public bool? EnableMultiTouch { get; set; }
+
+ ///
+ /// Enables multitouch support. The default value is true.
+ ///
+ ///
+ /// 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.
+ ///
+ public bool? EnableMultiTouch { get; set; } = true;
}
public static class AvaloniaX11PlatformExtensions
{
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index bcb655245a..149d7fb9b2 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -336,7 +336,7 @@ namespace Avalonia.X11
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
//TODO
public Action 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);
+ }
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
index 5a1da9058a..b097e0917f 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
@@ -69,7 +69,7 @@ namespace Avalonia.LinuxFramebuffer
public IEnumerable Surfaces { get; }
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Action TransparencyLevelChanged { get; set; }
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 2352b8b076..c9a5544672 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -506,7 +506,7 @@ namespace Avalonia.Skia
public void PushGeometryClip(IGeometryImpl clip)
{
Canvas.Save();
- Canvas.ClipPath(((GeometryImpl)clip).EffectivePath);
+ Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true);
}
///
@@ -534,7 +534,11 @@ namespace Avalonia.Skia
public void PushOpacityMask(IBrush mask, Rect bounds)
{
// TODO: This should be disposed
- var paint = new SKPaint();
+ var paint = new SKPaint()
+ {
+ IsAntialias = true,
+ Style = SKPaintStyle.StrokeAndFill
+ };
Canvas.SaveLayer(paint);
_maskStack.Push(CreatePaint(paint, mask, bounds, true));
@@ -543,7 +547,14 @@ namespace Avalonia.Skia
///
public void PopOpacityMask()
{
- using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn })
+ using (var paint = new SKPaint
+ {
+ IsAntialias = true,
+ Style = SKPaintStyle.StrokeAndFill,
+ BlendMode = SKBlendMode.DstIn,
+ Color = new SKColor(0, 0, 0, 255),
+ ColorFilter = SKColorFilter.CreateLumaColor()
+ })
{
Canvas.SaveLayer(paint);
using (var paintWrapper = _maskStack.Pop())
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 9336c9a7bb..09e5b7c71a 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -541,7 +541,8 @@ namespace Avalonia.Direct2D1.Media
ContentBounds = PrimitiveExtensions.RectangleInfinite,
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = 1,
- GeometricMask = ((GeometryImpl)clip).Geometry
+ GeometricMask = ((GeometryImpl)clip).Geometry,
+ MaskAntialiasMode = AntialiasMode.PerPrimitive
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 73e46b9e13..d36db107e3 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/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 ITopLevelImpl.Input { get; set; } //TODO
Action ITopLevelImpl.Paint { get; set; }
- Action ITopLevelImpl.Resized { get; set; }
+ Action ITopLevelImpl.Resized { get; set; }
Action ITopLevelImpl.ScalingChanged { get; set; }
Action ITopLevelImpl.TransparencyLevelChanged { get; set; }
diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs
index dd3fd1342c..38dc4a16dc 100644
--- a/src/Windows/Avalonia.Win32/PopupImpl.cs
+++ b/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
}
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 84e61ca007..a881c45cd0 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -6,18 +6,16 @@ using System.IO;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading;
-using Avalonia.Animation;
using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
-using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
-using Avalonia.Win32;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -37,15 +35,46 @@ namespace Avalonia
}
}
+ ///
+ /// Platform-specific options which apply to Windows.
+ ///
public class Win32PlatformOptions
{
+ ///
+ /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.
+ ///
+ ///
+ /// 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.
+ ///
public bool UseDeferredRendering { get; set; } = true;
-
+
+ ///
+ /// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false.
+ ///
+ ///
+ /// GPU rendering will not be enabled if this is set to false.
+ ///
public bool? AllowEglInitialization { get; set; }
-
- public bool? EnableMultitouch { get; set; }
+
+ ///
+ /// Enables multitouch support. The default value is true.
+ ///
+ ///
+ /// 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.
+ ///
+ public bool? EnableMultitouch { get; set; } = true;
+
+ ///
+ /// Embeds popups to the window when set to true. The default value is false.
+ ///
public bool OverlayPopups { get; set; }
+
+ ///
+ /// Avalonia would try to use native Widows OpenGL when set to true. The default value is false.
+ ///
public bool UseWgl { get; set; }
+
public IList WglProfiles { get; set; } = new List
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
@@ -65,7 +94,7 @@ namespace Avalonia
namespace Avalonia.Win32
{
- class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader
+ class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl
{
private static readonly Win32Platform s_instance = new Win32Platform();
private static Thread _uiThread;
@@ -122,7 +151,8 @@ namespace Avalonia.Win32
})
.Bind().ToConstant(s_instance)
.Bind().ToConstant(new NonPumpingSyncContext.HelperImpl())
- .Bind().ToConstant(new WindowsMountedVolumeInfoProvider());
+ .Bind().ToConstant(new WindowsMountedVolumeInfoProvider())
+ .Bind().ToConstant(s_instance);
Win32GlManager.Initialize();
@@ -207,6 +237,8 @@ namespace Avalonia.Win32
public event Action Signaled;
+ public event EventHandler ShutdownRequested;
+
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
@@ -214,6 +246,22 @@ namespace Avalonia.Win32
{
Signaled?.Invoke(null);
}
+
+ if(msg == (uint)WindowsMessage.WM_QUERYENDSESSION)
+ {
+ if (ShutdownRequested != null)
+ {
+ var e = new ShutdownRequestedEventArgs();
+
+ ShutdownRequested(this, e);
+
+ if(e.Cancel)
+ {
+ return IntPtr.Zero;
+ }
+ }
+ }
+
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}
@@ -253,7 +301,7 @@ namespace Avalonia.Win32
public IWindowImpl CreateEmbeddableWindow()
{
var embedded = new EmbeddedWindowImpl();
- embedded.Show(true);
+ embedded.Show(true, false);
return embedded;
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 8e755a33bc..d163b3d068 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -2,9 +2,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Avalonia.Controls;
-using Avalonia.Controls.Platform;
+using Avalonia.Controls.Remote;
using Avalonia.Input;
using Avalonia.Input.Raw;
+using Avalonia.Platform;
using Avalonia.Win32.Input;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -18,7 +19,6 @@ namespace Avalonia.Win32
{
const double wheelDelta = 120.0;
uint timestamp = unchecked((uint)GetMessageTime());
-
RawInputEventArgs e = null;
var shouldTakeFocus = false;
@@ -94,14 +94,19 @@ namespace Avalonia.Win32
var newDisplayRect = Marshal.PtrToStructure(lParam);
_scaling = dpi / 96.0;
ScalingChanged?.Invoke(_scaling);
- SetWindowPos(hWnd,
- IntPtr.Zero,
- newDisplayRect.left,
- newDisplayRect.top,
- newDisplayRect.right - newDisplayRect.left,
- newDisplayRect.bottom - newDisplayRect.top,
- SetWindowPosFlags.SWP_NOZORDER |
- SetWindowPosFlags.SWP_NOACTIVATE);
+
+ using (SetResizeReason(PlatformResizeReason.DpiChange))
+ {
+ SetWindowPos(hWnd,
+ IntPtr.Zero,
+ newDisplayRect.left,
+ newDisplayRect.top,
+ newDisplayRect.right - newDisplayRect.left,
+ newDisplayRect.bottom - newDisplayRect.top,
+ SetWindowPosFlags.SWP_NOZORDER |
+ SetWindowPosFlags.SWP_NOACTIVATE);
+ }
+
return IntPtr.Zero;
}
@@ -364,6 +369,11 @@ namespace Avalonia.Win32
return IntPtr.Zero;
}
+
+ case WindowsMessage.WM_ENTERSIZEMOVE:
+ _resizeReason = PlatformResizeReason.User;
+ break;
+
case WindowsMessage.WM_SIZE:
{
using(NonPumpingSyncContext.Use())
@@ -379,7 +389,7 @@ namespace Avalonia.Win32
size == SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
- Resized(clientSize / RenderScaling);
+ Resized(clientSize / RenderScaling, _resizeReason);
}
var windowState = size == SizeCommand.Maximized ?
@@ -403,6 +413,10 @@ namespace Avalonia.Win32
return IntPtr.Zero;
}
+ case WindowsMessage.WM_EXITSIZEMOVE:
+ _resizeReason = PlatformResizeReason.Unspecified;
+ break;
+
case WindowsMessage.WM_MOVE:
{
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff),
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 646a6f5739..0b7bd13082 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -53,6 +53,7 @@ namespace Avalonia.Win32
private double _extendTitleBarHint = -1;
private bool _isUsingComposition;
private IBlurHost _blurHost;
+ private PlatformResizeReason _resizeReason;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
@@ -160,7 +161,7 @@ namespace Avalonia.Win32
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
@@ -450,7 +451,7 @@ namespace Avalonia.Win32
value.Y,
0,
0,
- SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE);
+ SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER);
}
}
@@ -482,7 +483,7 @@ namespace Avalonia.Win32
: new ImmediateRenderer(root);
}
- public void Resize(Size value)
+ public void Resize(Size value, PlatformResizeReason reason)
{
int requestedClientWidth = (int)(value.Width * RenderScaling);
int requestedClientHeight = (int)(value.Height * RenderScaling);
@@ -494,6 +495,7 @@ namespace Avalonia.Win32
{
GetWindowRect(_hwnd, out var windowRect);
+ using var scope = SetResizeReason(reason);
SetWindowPos(
_hwnd,
IntPtr.Zero,
@@ -585,7 +587,7 @@ namespace Avalonia.Win32
_shown = false;
}
- public virtual void Show(bool activate)
+ public virtual void Show(bool activate, bool isDialog)
{
SetParent(_parent);
ShowWindow(_showWindowState, activate);
@@ -930,7 +932,7 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();
- Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling));
+ Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout);
}
if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
@@ -1135,7 +1137,7 @@ namespace Avalonia.Win32
SetParent(null);
if (shown)
- Show(activated);
+ Show(activated, false);
}
}
else
@@ -1311,6 +1313,13 @@ namespace Avalonia.Win32
///
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0);
+ private ResizeReasonScope SetResizeReason(PlatformResizeReason reason)
+ {
+ var old = _resizeReason;
+ _resizeReason = reason;
+ return new ResizeReasonScope(this, old);
+ }
+
private struct SavedWindowInfo
{
public WindowStyles Style { get; set; }
@@ -1325,5 +1334,19 @@ namespace Avalonia.Win32
public SystemDecorations Decorations;
public bool IsFullScreen;
}
+
+ private struct ResizeReasonScope : IDisposable
+ {
+ private readonly WindowImpl _owner;
+ private readonly PlatformResizeReason _restore;
+
+ public ResizeReasonScope(WindowImpl owner, PlatformResizeReason restore)
+ {
+ _owner = owner;
+ _restore = restore;
+ }
+
+ public void Dispose() => _owner._resizeReason = _restore;
+ }
}
}
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 0371a7759a..5bb2f64879 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -96,7 +96,7 @@ namespace Avalonia.iOS
public IEnumerable Surfaces { get; set; }
public Action Input { get; set; }
public Action Paint { get; set; }
- public Action Resized { get; set; }
+ public Action Resized { get; set; }
public Action ScalingChanged { get; set; }
public Action TransparencyLevelChanged { get; set; }
public Action Closed { get; set; }
@@ -127,7 +127,7 @@ namespace Avalonia.iOS
public override void LayoutSubviews()
{
- _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize);
+ _topLevelImpl.Resized?.Invoke(_topLevelImpl.ClientSize, PlatformResizeReason.Layout);
base.LayoutSubviews();
}
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index 3ed0af39a2..ba01f3db40 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -230,7 +230,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
- popupImpl.Setup(x => x.Show(true)).Verifiable();
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var window = PreparedWindow();
@@ -268,7 +268,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(c.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Never);
- popupImpl.Verify(x => x.Show(true), Times.Exactly(1));
+ popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1));
}
}
@@ -277,7 +277,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
- popupImpl.Setup(x => x.Show(true)).Verifiable();
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var window = PreparedWindow();
@@ -306,7 +306,7 @@ namespace Avalonia.Controls.UnitTests
Assert.False(c.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Exactly(1));
- popupImpl.Verify(x => x.Show(true), Times.Exactly(1));
+ popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1));
}
}
@@ -315,7 +315,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
- popupImpl.Setup(x => x.Show(true)).Verifiable();
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var sut = new ContextMenu();
@@ -336,7 +336,7 @@ namespace Avalonia.Controls.UnitTests
_mouse.Up(target);
Assert.False(sut.IsOpen);
- popupImpl.Verify(x => x.Show(true), Times.Once);
+ popupImpl.Verify(x => x.Show(true, false), Times.Once);
popupImpl.Verify(x => x.Hide(), Times.Once);
}
}
@@ -346,7 +346,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
- popupImpl.Setup(x => x.Show(true)).Verifiable();
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var sut = new ContextMenu();
@@ -368,7 +368,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(sut.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Once);
- popupImpl.Verify(x => x.Show(true), Times.Exactly(2));
+ popupImpl.Verify(x => x.Show(true, false), Times.Exactly(2));
}
}
@@ -413,7 +413,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
- popupImpl.Setup(x => x.Show(true)).Verifiable();
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
bool eventCalled = false;
var sut = new ContextMenu();
@@ -429,7 +429,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(eventCalled);
Assert.False(sut.IsOpen);
- popupImpl.Verify(x => x.Show(true), Times.Never);
+ popupImpl.Verify(x => x.Show(true, false), Times.Never);
}
}
@@ -533,7 +533,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
- popupImpl.Setup(x => x.Show(true)).Verifiable();
+ popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
bool eventCalled = false;
@@ -560,7 +560,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(eventCalled);
Assert.True(sut.IsOpen);
- popupImpl.Verify(x => x.Show(true), Times.Once());
+ popupImpl.Verify(x => x.Show(true, false), Times.Once());
popupImpl.Verify(x => x.Hide(), Times.Never);
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
index 38713834c3..f7a3bdea1c 100644
--- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
@@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests
++raised;
};
- lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs());
+ lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedEventArgs());
Assert.Equal(1, raised);
Assert.Equal(new[] { window }, lifetime.Windows);
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index 6b30aed257..9c2d760733 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests
// The user has resized the window, so we can no longer auto-size.
var target = new TestTopLevel(impl.Object);
- impl.Object.Resized(new Size(100, 200));
+ impl.Object.Resized(new Size(100, 200), PlatformResizeReason.Unspecified);
Assert.Equal(100, target.Width);
Assert.Equal(200, target.Height);
diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
index 8109b037c5..1b4214e0c7 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
@@ -137,7 +137,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TestWindowBase(windowImpl.Object);
target.IsVisible = true;
- windowImpl.Verify(x => x.Show(true));
+ windowImpl.Verify(x => x.Show(true, false));
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index 6b9921d83d..eb128ef038 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -663,10 +663,11 @@ namespace Avalonia.Controls.UnitTests
var clientSize = new Size(200, 200);
var maxClientSize = new Size(480, 480);
- windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(size =>
+ windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny()))
+ .Callback((size, reason) =>
{
clientSize = size.Constrain(maxClientSize);
- windowImpl.Object.Resized?.Invoke(clientSize);
+ windowImpl.Object.Resized?.Invoke(clientSize, reason);
});
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
@@ -739,6 +740,36 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void SizeToContent_Should_Not_Be_Lost_On_Scaling_Change()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var child = new Canvas
+ {
+ Width = 209,
+ Height = 117,
+ };
+
+ var target = new Window()
+ {
+ SizeToContent = SizeToContent.WidthAndHeight,
+ Content = child
+ };
+
+ Show(target);
+
+ // Size before and after DPI change is a real-world example, with size after DPI
+ // change coming from Win32 WM_DPICHANGED.
+ target.PlatformImpl.ScalingChanged(1.5);
+ target.PlatformImpl.Resized(
+ new Size(210.66666666666666, 118.66666666666667),
+ PlatformResizeReason.DpiChange);
+
+ Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
+ }
+ }
+
[Fact]
public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight()
{
@@ -791,8 +822,91 @@ namespace Avalonia.Controls.UnitTests
target.LayoutManager.ExecuteLayoutPass();
var windowImpl = Mock.Get(target.PlatformImpl);
- windowImpl.Verify(x => x.Resize(new Size(410, 800)));
+ windowImpl.Verify(x => x.Resize(new Size(410, 800), PlatformResizeReason.Application));
+ Assert.Equal(410, target.Width);
+ }
+ }
+
+
+ [Fact]
+ public void User_Resize_Of_Window_Width_Should_Reset_SizeToContent()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new Window()
+ {
+ SizeToContent = SizeToContent.WidthAndHeight,
+ Content = new Canvas
+ {
+ Width = 400,
+ Height = 800,
+ },
+ };
+
+ Show(target);
+ Assert.Equal(400, target.Width);
+ Assert.Equal(800, target.Height);
+
+ target.PlatformImpl.Resized(new Size(410, 800), PlatformResizeReason.User);
+
Assert.Equal(410, target.Width);
+ Assert.Equal(800, target.Height);
+ Assert.Equal(SizeToContent.Height, target.SizeToContent);
+ }
+ }
+
+ [Fact]
+ public void User_Resize_Of_Window_Height_Should_Reset_SizeToContent()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new Window()
+ {
+ SizeToContent = SizeToContent.WidthAndHeight,
+ Content = new Canvas
+ {
+ Width = 400,
+ Height = 800,
+ },
+ };
+
+ Show(target);
+ Assert.Equal(400, target.Width);
+ Assert.Equal(800, target.Height);
+
+ target.PlatformImpl.Resized(new Size(400, 810), PlatformResizeReason.User);
+
+ Assert.Equal(400, target.Width);
+ Assert.Equal(810, target.Height);
+ Assert.Equal(SizeToContent.Width, target.SizeToContent);
+ }
+ }
+
+ [Fact]
+ public void Window_Resize_Should_Not_Reset_SizeToContent_If_CanResize_False()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new Window()
+ {
+ SizeToContent = SizeToContent.WidthAndHeight,
+ CanResize = false,
+ Content = new Canvas
+ {
+ Width = 400,
+ Height = 800,
+ },
+ };
+
+ Show(target);
+ Assert.Equal(400, target.Width);
+ Assert.Equal(800, target.Height);
+
+ target.PlatformImpl.Resized(new Size(410, 810), PlatformResizeReason.Unspecified);
+
+ Assert.Equal(400, target.Width);
+ Assert.Equal(800, target.Height);
+ Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
index f72d6ba9c9..f9c85ee4ca 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Custom.cs
@@ -95,6 +95,7 @@ namespace Avalonia.Input.UnitTests
var root = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
target,
@@ -125,6 +126,7 @@ namespace Avalonia.Input.UnitTests
var root = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(current = new Button { Content = "Outside" }),
@@ -137,6 +139,36 @@ namespace Avalonia.Input.UnitTests
Assert.Same(next, result);
}
+ [Fact]
+ public void ShiftTab_Should_Navigate_Outside_When_Null_Returned_As_Next()
+ {
+ Button current;
+ Button next;
+ var target = new CustomNavigatingStackPanel
+ {
+ Children =
+ {
+ new Button { Content = "Button 1" },
+ (current = new Button { Content = "Button 2" }),
+ new Button { Content = "Button 3" },
+ },
+ };
+
+ var root = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ target,
+ (next = new Button { Content = "Outside" }),
+ }
+ };
+
+ var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous);
+
+ Assert.Same(next, result);
+ }
+
[Fact]
public void Tab_Should_Navigate_Outside_When_Null_Returned_As_Next()
{
@@ -154,6 +186,7 @@ namespace Avalonia.Input.UnitTests
var root = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
target,
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
index 1efbbed2e8..edcbf75a1d 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using Avalonia.Controls;
using Xunit;
@@ -13,6 +14,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -49,6 +51,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -85,6 +88,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -122,6 +126,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -165,6 +170,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -193,6 +199,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -222,6 +229,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -263,6 +271,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(next = new Button { Name = "Button1" }),
@@ -282,6 +291,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -324,6 +334,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -361,6 +372,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -398,6 +410,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -434,6 +447,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -471,6 +485,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -509,6 +524,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(container = new StackPanel
@@ -548,6 +564,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -586,6 +603,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(container = new StackPanel
@@ -625,6 +643,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -661,6 +680,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -697,6 +717,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -725,6 +746,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -767,6 +789,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -828,6 +851,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -865,6 +889,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -902,6 +927,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -938,6 +964,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -975,6 +1002,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -1013,6 +1041,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
(container = new StackPanel
@@ -1052,6 +1081,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children =
{
new StackPanel
@@ -1103,5 +1133,97 @@ namespace Avalonia.Input.UnitTests
Assert.Null(result);
}
+
+ [Fact]
+ public void Respects_TabIndex_Moving_Forwards()
+ {
+ Button start;
+
+ var top = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button1", TabIndex = 5 },
+ (start = new Button { Name = "Button2", TabIndex = 2 }),
+ new Button { Name = "Button3", TabIndex = 1 },
+ }
+ },
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button4", TabIndex = 3 },
+ new Button { Name = "Button5", TabIndex = 6 },
+ new Button { Name = "Button6", TabIndex = 4 },
+ }
+ },
+ }
+ };
+
+ var result = new List();
+ var current = (IInputElement)start;
+
+ do
+ {
+ result.Add(((IControl)current).Name);
+ current = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next);
+ } while (current is object && current != start);
+
+ Assert.Equal(new[]
+ {
+ "Button2", "Button4", "Button6", "Button1", "Button5", "Button3"
+ }, result);
+ }
+
+ [Fact]
+ public void Respects_TabIndex_Moving_Backwards()
+ {
+ Button start;
+
+ var top = new StackPanel
+ {
+ [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
+ Children =
+ {
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button1", TabIndex = 5 },
+ (start = new Button { Name = "Button2", TabIndex = 2 }),
+ new Button { Name = "Button3", TabIndex = 1 },
+ }
+ },
+ new StackPanel
+ {
+ Children =
+ {
+ new Button { Name = "Button4", TabIndex = 3 },
+ new Button { Name = "Button5", TabIndex = 6 },
+ new Button { Name = "Button6", TabIndex = 4 },
+ }
+ },
+ }
+ };
+
+ var result = new List();
+ var current = (IInputElement)start;
+
+ do
+ {
+ result.Add(((IControl)current).Name);
+ current = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous);
+ } while (current is object && current != start);
+
+ Assert.Equal(new[]
+ {
+ "Button2", "Button3", "Button5", "Button1", "Button6", "Button4"
+ }, result);
+ }
}
}
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index 8a24a8366f..bc003537f4 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -52,13 +52,14 @@ namespace Avalonia.UnitTests
windowImpl.Object.PositionChanged?.Invoke(x);
});
- windowImpl.Setup(x => x.Resize(It.IsAny())).Callback(x =>
+ windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny()))
+ .Callback((x, y) =>
{
clientSize = x.Constrain(s_screenSize);
- windowImpl.Object.Resized?.Invoke(clientSize);
+ windowImpl.Object.Resized?.Invoke(clientSize, y);
});
- windowImpl.Setup(x => x.Show(true)).Callback(() =>
+ windowImpl.Setup(x => x.Show(true, It.IsAny())).Callback(() =>
{
windowImpl.Object.Activated?.Invoke();
});
@@ -75,7 +76,7 @@ namespace Avalonia.UnitTests
{
clientSize = size.Constrain(s_screenSize);
popupImpl.Object.PositionChanged?.Invoke(pos);
- popupImpl.Object.Resized?.Invoke(clientSize);
+ popupImpl.Object.Resized?.Invoke(clientSize, PlatformResizeReason.Unspecified);
});
var positioner = new ManagedPopupPositioner(positionerHelper);
diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs
index b69bf990d9..4601dd7e5b 100644
--- a/tests/Avalonia.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.UnitTests/TestRoot.cs
@@ -21,6 +21,7 @@ namespace Avalonia.UnitTests
Renderer = Mock.Of();
LayoutManager = new LayoutManager(this);
IsVisible = true;
+ KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
public TestRoot(IControl child)
diff --git a/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png b/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png
index 535d411cc7..e2604d2836 100644
Binary files a/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png and b/tests/TestFiles/Skia/Controls/CustomRender/GeometryClip.expected.png differ
diff --git a/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png b/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png
index 5993088a33..543226ac53 100644
Binary files a/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png and b/tests/TestFiles/Skia/GeometryClipping/Geometry_Clip_Clips_Path.expected.png differ