diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 44d5c239ef..897e70ff81 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.8.999 + 0.9.999 Copyright 2019 © The AvaloniaUI Project https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index f1c7664c3e..677fa2f4c5 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -92,9 +92,17 @@ enum AvnRawMouseEventType RightButtonUp, MiddleButtonDown, MiddleButtonUp, + XButton1Down, + XButton1Up, + XButton2Down, + XButton2Up, Move, Wheel, - NonClientLeftButtonDown + NonClientLeftButtonDown, + TouchBegin, + TouchUpdate, + TouchEnd, + TouchCancel }; enum AvnRawKeyEventType @@ -112,7 +120,9 @@ enum AvnInputModifiers Windows = 8, LeftMouseButton = 16, RightMouseButton = 32, - MiddleMouseButton = 64 + MiddleMouseButton = 64, + XButton1MouseButton = 128, + XButton2MouseButton = 256 }; enum AvnWindowState @@ -212,6 +222,10 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0; virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0; virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0; + virtual HRESULT ObtainNSWindowHandle(void** retOut) = 0; + virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0; + virtual HRESULT ObtainNSViewHandle(void** retOut) = 0; + virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0; virtual bool TryLock() = 0; virtual void Unlock() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 81855995b7..5c50aad4cc 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -1,16 +1,25 @@ #include "common.h" @interface AvnAppDelegate : NSObject @end + extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular; @implementation AvnAppDelegate - (void)applicationWillFinishLaunching:(NSNotification *)notification { - [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy]; + if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy) + { + for (NSRunningApplication * app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) { + [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + break; + } + + [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy]; + } } - (void)applicationDidFinishLaunching:(NSNotification *)notification { - [NSApp activateIgnoringOtherApps:true]; + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; } @end @@ -20,5 +29,4 @@ extern void InitializeAvnApp() NSApplication* app = [NSApplication sharedApplication]; id delegate = [AvnAppDelegate new]; [app setDelegate:delegate]; - } diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 0e85332555..c54829d750 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -83,6 +83,54 @@ public: [Window setContentView: View]; } + virtual HRESULT ObtainNSWindowHandle(void** ret) override + { + if (ret == nullptr) + { + return E_POINTER; + } + + *ret = (__bridge void*)Window; + + return S_OK; + } + + virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override + { + if (ret == nullptr) + { + return E_POINTER; + } + + *ret = (__bridge_retained void*)Window; + + return S_OK; + } + + virtual HRESULT ObtainNSViewHandle(void** ret) override + { + if (ret == nullptr) + { + return E_POINTER; + } + + *ret = (__bridge void*)View; + + return S_OK; + } + + virtual HRESULT ObtainNSViewHandleRetained(void** ret) override + { + if (ret == nullptr) + { + return E_POINTER; + } + + *ret = (__bridge_retained void*)View; + + return S_OK; + } + virtual AvnWindow* GetNSWindow() override { return Window; @@ -668,7 +716,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent AvnFramebuffer _swRenderedFrameBuffer; bool _queuedDisplayFromThread; NSTrackingArea* _area; - bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isMouseOver; + bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver; NSEvent* _lastMouseDownEvent; bool _lastKeyHandled; } @@ -691,6 +739,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { self = [super init]; [self setWantsBestResolutionOpenGLSurface:true]; + [self setWantsLayer:YES]; _parent = parent; _area = nullptr; return self; @@ -893,9 +942,23 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)otherMouseDown:(NSEvent *)event { - _isMiddlePressed = true; _lastMouseDownEvent = event; - [self mouseEvent:event withType:MiddleButtonDown]; + + switch(event.buttonNumber) + { + case 3: + _isMiddlePressed = true; + [self mouseEvent:event withType:MiddleButtonDown]; + break; + case 4: + _isXButton1Pressed = true; + [self mouseEvent:event withType:XButton1Down]; + break; + case 5: + _isXButton2Pressed = true; + [self mouseEvent:event withType:XButton2Down]; + break; + } } - (void)rightMouseDown:(NSEvent *)event @@ -913,8 +976,21 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)otherMouseUp:(NSEvent *)event { - _isMiddlePressed = false; - [self mouseEvent:event withType:MiddleButtonUp]; + switch(event.buttonNumber) + { + case 3: + _isMiddlePressed = false; + [self mouseEvent:event withType:MiddleButtonUp]; + break; + case 4: + _isXButton1Pressed = false; + [self mouseEvent:event withType:XButton1Up]; + break; + case 5: + _isXButton2Pressed = false; + [self mouseEvent:event withType:XButton2Up]; + break; + } } - (void)rightMouseUp:(NSEvent *)event @@ -1013,6 +1089,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent rv |= MiddleMouseButton; if (_isRightPressed) rv |= RightMouseButton; + if (_isXButton1Pressed) + rv |= XButton1MouseButton; + if (_isXButton2Pressed) + rv |= XButton2MouseButton; return (AvnInputModifiers)rv; } diff --git a/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs b/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs new file mode 100644 index 0000000000..837ea1abcd --- /dev/null +++ b/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs @@ -0,0 +1,15 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Platform +{ + public interface IMacOSTopLevelPlatformHandle + { + IntPtr NSView { get; } + IntPtr GetNSViewRetained(); + IntPtr NSWindow { get; } + IntPtr GetNSWindowRetained(); + } +} diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index bf22f0a08a..94c34b28d6 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -5,7 +5,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; @@ -324,20 +323,24 @@ namespace Avalonia.Controls return; } - var current = focus.Current - .GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(x => x.VisualParent == container); + IVisual current = focus.Current; - if (current != null) + while (current != null) { - var next = GetNextControl(container, direction.Value, current, false); - - if (next != null) + if (current.VisualParent == container && current is IInputElement inputElement) { - focus.Focus(next, NavigationMethod.Directional); - e.Handled = true; + IInputElement next = GetNextControl(container, direction.Value, inputElement, false); + + if (next != null) + { + focus.Focus(next, NavigationMethod.Directional); + e.Handled = true; + } + + break; } + + current = current.VisualParent; } } diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index aa91224572..1a9347e317 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -149,15 +149,9 @@ namespace Avalonia.Controls.Notifications /// The that will be the host. private void Install(Window host) { - var adornerLayer = host.GetVisualDescendants() - .OfType() - .FirstOrDefault() - ?.AdornerLayer; + var adornerLayer = host.FindDescendantOfType()?.AdornerLayer; - if (adornerLayer != null) - { - adornerLayer.Children.Add(this); - } + adornerLayer?.Children.Add(this); } } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 329b086a7c..a5bbcec186 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -13,7 +13,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Logging; -using Avalonia.Styling; using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives @@ -269,11 +268,20 @@ namespace Avalonia.Controls.Primitives /// The container or null if the event did not originate in a container. protected IControl GetContainerFromEventSource(IInteractive eventSource) { - var item = ((IVisual)eventSource).GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1); + var parent = (IVisual)eventSource; - return item; + while (parent != null) + { + if (parent is IControl control && control.LogicalParent == this + && ItemContainerGenerator?.IndexFromContainer(control) != -1) + { + return control; + } + + parent = parent.VisualParent; + } + + return null; } /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index a0df186eb7..5a8711a21c 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; using System.Reactive.Linq; -using Avalonia.Controls.Notifications; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; @@ -15,7 +13,6 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Utilities; -using Avalonia.VisualTree; using JetBrains.Annotations; namespace Avalonia.Controls @@ -302,10 +299,7 @@ namespace Avalonia.Controls /// The window scaling. protected virtual void HandleScalingChanged(double scaling) { - foreach (ILayoutable control in this.GetSelfAndVisualDescendants()) - { - control.InvalidateMeasure(); - } + LayoutHelper.InvalidateSelfAndChildrenMeasure(this); } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 1816a6c81d..f66a248aaf 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -336,7 +336,6 @@ namespace Avalonia.Controls if (close) { PlatformImpl?.Dispose(); - HandleClosed(); } } } diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs index 104ac9cb61..77902a7390 100644 --- a/src/Avalonia.Input/FocusManager.cs +++ b/src/Avalonia.Input/FocusManager.cs @@ -180,18 +180,18 @@ namespace Avalonia.Input if (sender == e.Source && ev.MouseButton == MouseButton.Left) { - var element = (ev.Pointer?.Captured as IInputElement) ?? (e.Source as IInputElement); + IVisual element = ev.Pointer?.Captured ?? e.Source as IInputElement; - if (element == null || !CanFocus(element)) + while (element != null) { - element = element.GetSelfAndVisualAncestors() - .OfType() - .FirstOrDefault(CanFocus); - } + if (element is IInputElement inputElement && CanFocus(inputElement)) + { + Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.InputModifiers); - if (element != null) - { - Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers); + break; + } + + element = element.VisualParent; } } } diff --git a/src/Avalonia.Input/IKeyboardDevice.cs b/src/Avalonia.Input/IKeyboardDevice.cs index 1a82f7d671..144979523f 100644 --- a/src/Avalonia.Input/IKeyboardDevice.cs +++ b/src/Avalonia.Input/IKeyboardDevice.cs @@ -48,6 +48,8 @@ namespace Avalonia.Input LeftMouseButton = 16, RightMouseButton = 32, MiddleMouseButton = 64, + XButton1MouseButton = 128, + XButton2MouseButton = 256, KeyboardMask = Alt | Control | Shift | Meta } diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 535b930f8b..1e2a621bd1 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -342,7 +342,7 @@ namespace Avalonia.Input } /// - /// Gets or sets a value indicating whether the control is focused. + /// Gets a value indicating whether the control is focused. /// public bool IsFocused { @@ -360,7 +360,7 @@ namespace Avalonia.Input } /// - /// Gets or sets a value indicating whether the pointer is currently over the control. + /// Gets a value indicating whether the pointer is currently over the control. /// public bool IsPointerOver { diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 4dcf0eee53..c74498171e 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -120,6 +120,10 @@ namespace Avalonia.Input rv++; if (props.IsRightButtonPressed) rv++; + if (props.IsXButton1Pressed) + rv++; + if (props.IsXButton2Pressed) + rv++; return rv; } @@ -142,6 +146,8 @@ namespace Avalonia.Input case RawPointerEventType.LeftButtonDown: case RawPointerEventType.RightButtonDown: case RawPointerEventType.MiddleButtonDown: + case RawPointerEventType.XButton1Down: + case RawPointerEventType.XButton2Down: if (ButtonCount(props) > 1) e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers); else @@ -151,6 +157,8 @@ namespace Avalonia.Input case RawPointerEventType.LeftButtonUp: case RawPointerEventType.RightButtonUp: case RawPointerEventType.MiddleButtonUp: + case RawPointerEventType.XButton1Up: + case RawPointerEventType.XButton2Up: if (ButtonCount(props) != 0) e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers); else @@ -186,12 +194,20 @@ namespace Avalonia.Input kind = PointerUpdateKind.MiddleButtonPressed; if (args.Type == RawPointerEventType.RightButtonDown) kind = PointerUpdateKind.RightButtonPressed; + if (args.Type == RawPointerEventType.XButton1Down) + kind = PointerUpdateKind.XButton1Pressed; + if (args.Type == RawPointerEventType.XButton2Down) + kind = PointerUpdateKind.XButton2Pressed; if (args.Type == RawPointerEventType.LeftButtonUp) kind = PointerUpdateKind.LeftButtonReleased; if (args.Type == RawPointerEventType.MiddleButtonUp) kind = PointerUpdateKind.MiddleButtonReleased; if (args.Type == RawPointerEventType.RightButtonUp) kind = PointerUpdateKind.RightButtonReleased; + if (args.Type == RawPointerEventType.XButton1Up) + kind = PointerUpdateKind.XButton1Released; + if (args.Type == RawPointerEventType.XButton2Up) + kind = PointerUpdateKind.XButton2Released; return new PointerPointProperties(args.InputModifiers, kind); } diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs index 819d231b31..00222e92cf 100644 --- a/src/Avalonia.Input/Pointer.cs +++ b/src/Avalonia.Input/Pointer.cs @@ -55,9 +55,11 @@ namespace Avalonia.Input Captured.DetachedFromVisualTree += OnCaptureDetached; } - IInputElement GetNextCapture(IVisual parent) => - parent as IInputElement ?? parent.GetVisualAncestors().OfType().FirstOrDefault(); - + IInputElement GetNextCapture(IVisual parent) + { + return parent as IInputElement ?? parent.FindAncestorOfType(); + } + private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e) { Capture(GetNextCapture(e.Parent)); diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs index 1068a0d4d4..96132fdc90 100644 --- a/src/Avalonia.Input/PointerPoint.cs +++ b/src/Avalonia.Input/PointerPoint.cs @@ -21,18 +21,24 @@ namespace Avalonia.Input public bool IsLeftButtonPressed { get; } public bool IsMiddleButtonPressed { get; } public bool IsRightButtonPressed { get; } + public bool IsXButton1Pressed { get; } + public bool IsXButton2Pressed { get; } + public PointerUpdateKind PointerUpdateKind { get; } + private PointerPointProperties() - { - + { } public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind) { PointerUpdateKind = kind; + IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton); IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton); IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton); + IsXButton1Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton1MouseButton); + IsXButton2Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton2MouseButton); // The underlying input source might be reporting the previous state, // so make sure that we reflect the current state @@ -49,6 +55,14 @@ namespace Avalonia.Input IsRightButtonPressed = true; if (kind == PointerUpdateKind.RightButtonReleased) IsRightButtonPressed = false; + if (kind == PointerUpdateKind.XButton1Pressed) + IsXButton1Pressed = true; + if (kind == PointerUpdateKind.XButton1Released) + IsXButton1Pressed = false; + if (kind == PointerUpdateKind.XButton2Pressed) + IsXButton2Pressed = true; + if (kind == PointerUpdateKind.XButton2Released) + IsXButton2Pressed = false; } public static PointerPointProperties None { get; } = new PointerPointProperties(); @@ -59,9 +73,13 @@ namespace Avalonia.Input LeftButtonPressed, MiddleButtonPressed, RightButtonPressed, + XButton1Pressed, + XButton2Pressed, LeftButtonReleased, MiddleButtonReleased, RightButtonReleased, + XButton1Released, + XButton2Released, Other } diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs index 56854c7d29..4b2c95943d 100644 --- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs @@ -14,6 +14,10 @@ namespace Avalonia.Input.Raw RightButtonUp, MiddleButtonDown, MiddleButtonUp, + XButton1Down, + XButton1Up, + XButton2Down, + XButton2Up, Move, Wheel, NonClientLeftButtonDown, diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs index cfb4b14b1c..c235bcd90f 100644 --- a/src/Avalonia.Layout/LayoutHelper.cs +++ b/src/Avalonia.Layout/LayoutHelper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Avalonia.VisualTree; namespace Avalonia.Layout { @@ -61,5 +62,31 @@ namespace Avalonia.Layout return availableSize; } + + /// + /// Invalidates measure for given control and all visual children recursively. + /// + public static void InvalidateSelfAndChildrenMeasure(ILayoutable control) + { + void InnerInvalidateMeasure(IVisual target) + { + if (target is ILayoutable targetLayoutable) + { + targetLayoutable.InvalidateMeasure(); + } + + var visualChildren = target.VisualChildren; + var visualChildrenCount = visualChildren.Count; + + for (int i = 0; i < visualChildrenCount; i++) + { + IVisual child = visualChildren[i]; + + InnerInvalidateMeasure(child); + } + } + + InnerInvalidateMeasure(control); + } } } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 4732808b91..9d0a5c57ee 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; using Avalonia.Logging; using Avalonia.VisualTree; @@ -694,12 +693,9 @@ namespace Avalonia.Layout } /// - protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent) + protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent) { - foreach (ILayoutable i in this.GetSelfAndVisualDescendants()) - { - i.InvalidateMeasure(); - } + LayoutHelper.InvalidateSelfAndChildrenMeasure(this); base.OnVisualParentChanged(oldParent, newParent); } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 8b397403ca..e72fefe3ce 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -16,6 +16,34 @@ using Avalonia.Threading; namespace Avalonia.Native { + public class MacOSTopLevelWindowHandle : IPlatformHandle, IMacOSTopLevelPlatformHandle + { + IAvnWindowBase _native; + + public MacOSTopLevelWindowHandle(IAvnWindowBase native) + { + _native = native; + } + + public IntPtr Handle => NSWindow; + + public string HandleDescriptor => "NSWindow"; + + public IntPtr NSView => _native.ObtainNSViewHandle(); + + public IntPtr NSWindow => _native.ObtainNSWindowHandle(); + + public IntPtr GetNSViewRetained() + { + return _native.ObtainNSViewHandleRetained(); + } + + public IntPtr GetNSWindowRetained() + { + return _native.ObtainNSWindowHandleRetained(); + } + } + public abstract class WindowBaseImpl : IWindowBaseImpl, IFramebufferPlatformSurface { @@ -45,6 +73,9 @@ namespace Avalonia.Native protected void Init(IAvnWindowBase window, IAvnScreens screens) { _native = window; + + Handle = new MacOSTopLevelWindowHandle(window); + _glSurface = new GlPlatformSurface(window); Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; @@ -349,6 +380,6 @@ namespace Avalonia.Native } - public IPlatformHandle Handle => new PlatformHandle(IntPtr.Zero, "NOT SUPPORTED"); + public IPlatformHandle Handle { get; private set; } } } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index cf3c4dc855..5e1bcde2f6 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -288,7 +288,7 @@ namespace Avalonia var list = new AvaloniaList { ResetBehavior = ResetBehavior.Remove, - Validate = ValidateLogicalChild + Validate = logical => ValidateLogicalChild(logical) }; list.CollectionChanged += LogicalChildrenCollectionChanged; _logicalChildren = list; diff --git a/src/Avalonia.Themes.Default/RadioButton.xaml b/src/Avalonia.Themes.Default/RadioButton.xaml index e7cbed8ec5..4cdb116cdd 100644 --- a/src/Avalonia.Themes.Default/RadioButton.xaml +++ b/src/Avalonia.Themes.Default/RadioButton.xaml @@ -13,7 +13,6 @@ Height="18" VerticalAlignment="Center"/> + - \ No newline at end of file + diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index f4306d3929..465b3a65be 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Specialized; using System.Linq; -using System.Reactive.Linq; using Avalonia.Collections; using Avalonia.Data; using Avalonia.Logging; @@ -121,7 +120,7 @@ namespace Avalonia { var visualChildren = new AvaloniaList(); visualChildren.ResetBehavior = ResetBehavior.Remove; - visualChildren.Validate = ValidateVisualChild; + visualChildren.Validate = visual => ValidateVisualChild(visual); visualChildren.CollectionChanged += VisualChildrenChanged; VisualChildren = visualChildren; } @@ -173,7 +172,22 @@ namespace Avalonia /// public bool IsEffectivelyVisible { - get { return this.GetSelfAndVisualAncestors().All(x => x.IsVisible); } + get + { + IVisual node = this; + + while (node != null) + { + if (!node.IsVisible) + { + return false; + } + + node = node.VisualParent; + } + + return true; + } } /// @@ -552,7 +566,7 @@ namespace Avalonia if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { - var root = this.GetVisualAncestors().OfType().FirstOrDefault(); + var root = this.FindAncestorOfType(); var e = new VisualTreeAttachmentEventArgs(_visualParent, root); OnAttachedToVisualTreeCore(e); } diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 567b676b1e..8e3c7e0765 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -14,7 +14,7 @@ namespace Avalonia.VisualTree public static class VisualExtensions { /// - /// Calculates the distance from a visual's . + /// Calculates the distance from a visual's ancestor. /// /// The visual. /// The ancestor visual. @@ -30,13 +30,39 @@ namespace Avalonia.VisualTree while (visual != null && visual != ancestor) { - ++result; visual = visual.VisualParent; + + result++; } return visual != null ? result : -1; } + /// + /// Calculates the distance from a visual's root. + /// + /// The visual. + /// + /// The number of steps from the visual to the root. + /// + public static int CalculateDistanceFromRoot(IVisual visual) + { + Contract.Requires(visual != null); + + var result = 0; + + visual = visual?.VisualParent; + + while (visual != null) + { + visual = visual.VisualParent; + + result++; + } + + return result; + } + /// /// Tries to get the first common ancestor of two visuals. /// @@ -47,8 +73,53 @@ namespace Avalonia.VisualTree { Contract.Requires(visual != null); - return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors()) - .FirstOrDefault(); + if (target is null) + { + return null; + } + + void GoUpwards(ref IVisual node, int count) + { + for (int i = 0; i < count; ++i) + { + node = node.VisualParent; + } + } + + // We want to find lowest node first, then make sure that both nodes are at the same height. + // By doing that we can sometimes find out that other node is our lowest common ancestor. + var firstHeight = CalculateDistanceFromRoot(visual); + var secondHeight = CalculateDistanceFromRoot(target); + + if (firstHeight > secondHeight) + { + GoUpwards(ref visual, firstHeight - secondHeight); + } + else + { + GoUpwards(ref target, secondHeight - firstHeight); + } + + if (visual == target) + { + return visual; + } + + while (visual != null && target != null) + { + IVisual firstParent = visual.VisualParent; + IVisual secondParent = target.VisualParent; + + if (firstParent == secondParent) + { + return firstParent; + } + + visual = visual.VisualParent; + target = target.VisualParent; + } + + return null; } /// @@ -69,6 +140,57 @@ namespace Avalonia.VisualTree } } + /// + /// Finds first ancestor of given type. + /// + /// Ancestor type. + /// The visual. + /// If given visual should be included in search. + /// First ancestor of given type. + public static T FindAncestorOfType(this IVisual visual, bool includeSelf = false) where T : class + { + if (visual is null) + { + return null; + } + + IVisual parent = includeSelf ? visual : visual.VisualParent; + + while (parent != null) + { + if (parent is T result) + { + return result; + } + + parent = parent.VisualParent; + } + + return null; + } + + /// + /// Finds first descendant of given type. + /// + /// Descendant type. + /// The visual. + /// If given visual should be included in search. + /// First descendant of given type. + public static T FindDescendantOfType(this IVisual visual, bool includeSelf = false) where T : class + { + if (visual is null) + { + return null; + } + + if (includeSelf && visual is T result) + { + return result; + } + + return FindDescendantOfTypeCore(visual); + } + /// /// Enumerates an and its ancestors in the visual tree. /// @@ -249,6 +371,31 @@ namespace Avalonia.VisualTree .Select(x => x.Element); } + private static T FindDescendantOfTypeCore(IVisual visual) where T : class + { + var visualChildren = visual.VisualChildren; + var visualChildrenCount = visualChildren.Count; + + for (var i = 0; i < visualChildrenCount; i++) + { + IVisual child = visualChildren[i]; + + if (child is T result) + { + return result; + } + + var childResult = FindDescendantOfTypeCore(child); + + if (!(childResult is null)) + { + return childResult; + } + } + + return null; + } + private class ZOrderElement : IComparable { public IVisual Element { get; set; } diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index cb9d3389e4..a431ffcc1a 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -79,7 +79,7 @@ namespace Avalonia.X11 atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE}) .ToArray(); XChangeProperty(_x11.Display, window, property, - target, 32, PropertyMode.Replace, atoms, atoms.Length); + _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); return property; } else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 32460fed86..6ff5b96f12 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -351,10 +351,17 @@ namespace Avalonia.X11 { if (ActivateTransientChildIfNeeded()) return; - if (ev.ButtonEvent.button < 4) - MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonDown - : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonDown - : RawPointerEventType.RightButtonDown, ref ev, ev.ButtonEvent.state); + if (ev.ButtonEvent.button < 4 || ev.ButtonEvent.button == 8 || ev.ButtonEvent.button == 9) + MouseEvent( + ev.ButtonEvent.button switch + { + 1 => RawPointerEventType.LeftButtonDown, + 2 => RawPointerEventType.MiddleButtonDown, + 3 => RawPointerEventType.RightButtonDown, + 8 => RawPointerEventType.XButton1Down, + 9 => RawPointerEventType.XButton2Down + }, + ref ev, ev.ButtonEvent.state); else { var delta = ev.ButtonEvent.button == 4 @@ -372,10 +379,17 @@ namespace Avalonia.X11 } else if (ev.type == XEventName.ButtonRelease) { - if (ev.ButtonEvent.button < 4) - MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonUp - : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonUp - : RawPointerEventType.RightButtonUp, ref ev, ev.ButtonEvent.state); + if (ev.ButtonEvent.button < 4 || ev.ButtonEvent.button == 8 || ev.ButtonEvent.button == 9) + MouseEvent( + ev.ButtonEvent.button switch + { + 1 => RawPointerEventType.LeftButtonUp, + 2 => RawPointerEventType.MiddleButtonUp, + 3 => RawPointerEventType.RightButtonUp, + 8 => RawPointerEventType.XButton1Up, + 9 => RawPointerEventType.XButton2Up + }, + ref ev, ev.ButtonEvent.state); } else if (ev.type == XEventName.ConfigureNotify) { @@ -586,8 +600,12 @@ namespace Avalonia.X11 rv |= RawInputModifiers.LeftMouseButton; if (state.HasFlag(XModifierMask.Button2Mask)) rv |= RawInputModifiers.RightMouseButton; - if (state.HasFlag(XModifierMask.Button2Mask)) + if (state.HasFlag(XModifierMask.Button3Mask)) rv |= RawInputModifiers.MiddleMouseButton; + if (state.HasFlag(XModifierMask.Button4Mask)) + rv |= RawInputModifiers.XButton1MouseButton; + if (state.HasFlag(XModifierMask.Button5Mask)) + rv |= RawInputModifiers.XButton2MouseButton; if (state.HasFlag(XModifierMask.ShiftMask)) rv |= RawInputModifiers.Shift; if (state.HasFlag(XModifierMask.ControlMask)) diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index e37ed39bee..ac14efe133 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -240,11 +240,15 @@ namespace Avalonia.X11 if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease) { var down = ev.Type == XiEventType.XI_ButtonPress; - var type = - ev.Button == 1 ? (down ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp) - : ev.Button == 2 ? (down ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp) - : ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp) - : (RawPointerEventType?)null; + var type = ev.Button switch + { + 1 => down ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp, + 2 => down ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp, + 3 => down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp, + 8 => down ? RawPointerEventType.XButton1Down : RawPointerEventType.XButton1Up, + 9 => down ? RawPointerEventType.XButton2Down : RawPointerEventType.XButton2Up, + _ => (RawPointerEventType?)null + }; if (type.HasValue) client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot, type.Value, ev.Position, ev.Modifiers)); @@ -283,12 +287,14 @@ namespace Avalonia.X11 var buttons = ev->buttons.Mask; if (XIMaskIsSet(buttons, 1)) Modifiers |= RawInputModifiers.LeftMouseButton; - if (XIMaskIsSet(buttons, 2)) Modifiers |= RawInputModifiers.MiddleMouseButton; - if (XIMaskIsSet(buttons, 3)) Modifiers |= RawInputModifiers.RightMouseButton; + if (XIMaskIsSet(buttons, 8)) + Modifiers |= RawInputModifiers.XButton1MouseButton; + if (XIMaskIsSet(buttons, 9)) + Modifiers |= RawInputModifiers.XButton2MouseButton; } Valuators = new Dictionary(); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 2c6425e26c..ed32382760 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1098,7 +1098,10 @@ namespace Avalonia.Win32.Interop [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target); - + + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern HRESULT RevokeDragDrop(IntPtr hwnd); + [DllImport("ole32.dll", EntryPoint = "OleInitialize")] public static extern HRESULT OleInitialize(IntPtr val); diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs index d454c797fa..c6e04a29b4 100644 --- a/src/Windows/Avalonia.Win32/OleContext.cs +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -7,9 +7,9 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32 { - class OleContext + internal class OleContext { - private static OleContext fCurrent; + private static OleContext s_current; internal static OleContext Current { @@ -18,13 +18,12 @@ namespace Avalonia.Win32 if (!IsValidOleThread()) return null; - if (fCurrent == null) - fCurrent = new OleContext(); - return fCurrent; + if (s_current == null) + s_current = new OleContext(); + return s_current; } } - private OleContext() { UnmanagedMethods.HRESULT res = UnmanagedMethods.OleInitialize(IntPtr.Zero); @@ -43,9 +42,21 @@ namespace Avalonia.Win32 internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target) { if (hwnd?.HandleDescriptor != "HWND" || target == null) + { return false; + } return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK; } + + internal bool UnregisterDragDrop(IPlatformHandle hwnd) + { + if (hwnd?.HandleDescriptor != "HWND") + { + return false; + } + + return UnmanagedMethods.RevokeDragDrop(hwnd.Handle) == UnmanagedMethods.HRESULT.S_OK; + } } } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index b17e0d6c09..37d047689c 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -6,7 +6,7 @@ using IDataObject = Avalonia.Input.IDataObject; namespace Avalonia.Win32 { - class OleDropTarget : IDropTarget + internal class OleDropTarget : IDropTarget { private readonly IInputRoot _target; private readonly ITopLevelImpl _tl; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9d35342fb9..0f5db58dfe 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -253,6 +253,11 @@ namespace Avalonia.Win32 public void Dispose() { + if (_dropTarget != null) + { + OleContext.Current?.UnregisterDragDrop(Handle); + _dropTarget = null; + } if (_hwnd != IntPtr.Zero) { UnmanagedMethods.DestroyWindow(_hwnd); @@ -540,34 +545,42 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN: - if(ShouldIgnoreTouchEmulatedMessage()) + case UnmanagedMethods.WindowsMessage.WM_XBUTTONDOWN: + if (ShouldIgnoreTouchEmulatedMessage()) break; e = new RawPointerEventArgs( _mouseDevice, timestamp, _owner, - msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN - ? RawPointerEventType.LeftButtonDown - : msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN - ? RawPointerEventType.RightButtonDown - : RawPointerEventType.MiddleButtonDown, + (UnmanagedMethods.WindowsMessage)msg switch + { + UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, + UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, + UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown, + UnmanagedMethods.WindowsMessage.WM_XBUTTONDOWN => + HighWord(ToInt32(wParam)) == 1 ? RawPointerEventType.XButton1Down : RawPointerEventType.XButton2Down + }, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP: case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP: - if(ShouldIgnoreTouchEmulatedMessage()) + case UnmanagedMethods.WindowsMessage.WM_XBUTTONUP: + if (ShouldIgnoreTouchEmulatedMessage()) break; e = new RawPointerEventArgs( _mouseDevice, timestamp, _owner, - msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP - ? RawPointerEventType.LeftButtonUp - : msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONUP - ? RawPointerEventType.RightButtonUp - : RawPointerEventType.MiddleButtonUp, + (UnmanagedMethods.WindowsMessage)msg switch + { + UnmanagedMethods.WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, + UnmanagedMethods.WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, + UnmanagedMethods.WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp, + UnmanagedMethods.WindowsMessage.WM_XBUTTONUP => + HighWord(ToInt32(wParam)) == 1 ? RawPointerEventType.XButton1Up : RawPointerEventType.XButton2Up, + }, DipFromLParam(lParam), GetMouseModifiers(wParam)); break; @@ -627,15 +640,19 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN: + case UnmanagedMethods.WindowsMessage.WM_NCXBUTTONDOWN: e = new RawPointerEventArgs( _mouseDevice, timestamp, _owner, - msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN - ? RawPointerEventType.NonClientLeftButtonDown - : msg == (int)UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN - ? RawPointerEventType.RightButtonDown - : RawPointerEventType.MiddleButtonDown, + (UnmanagedMethods.WindowsMessage)msg switch + { + UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType.NonClientLeftButtonDown, + UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown, + UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown, + UnmanagedMethods.WindowsMessage.WM_NCXBUTTONDOWN => + HighWord(ToInt32(wParam)) == 1 ? RawPointerEventType.XButton1Down : RawPointerEventType.XButton2Down, + }, PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); break; case WindowsMessage.WM_TOUCH: @@ -778,6 +795,10 @@ namespace Avalonia.Win32 modifiers |= RawInputModifiers.RightMouseButton; if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_MBUTTON)) modifiers |= RawInputModifiers.MiddleMouseButton; + if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_XBUTTON1)) + modifiers |= RawInputModifiers.XButton1MouseButton; + if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_XBUTTON2)) + modifiers |= RawInputModifiers.XButton2MouseButton; return modifiers; } @@ -1056,5 +1077,7 @@ namespace Avalonia.Win32 } } IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; + + private static int HighWord(int param) => param >> 16; } } diff --git a/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs b/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs new file mode 100644 index 0000000000..fc2380d670 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Traversal +{ + [MemoryDiagnoser] + public class VisualTreeTraversal + { + private readonly TestRoot _root; + private readonly List _controls = new List(); + private readonly List _shuffledControls; + + public VisualTreeTraversal() + { + var panel = new StackPanel(); + _root = new TestRoot { Child = panel, Renderer = new NullRenderer()}; + _controls.Add(panel); + _controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 4); + + var random = new Random(1); + + _shuffledControls = _controls.OrderBy(r => random.Next()).ToList(); + + _root.LayoutManager.ExecuteInitialLayoutPass(_root); + } + + [Benchmark] + public void FindAncestorOfType_Linq() + { + foreach (Control control in _controls) + { + control.GetSelfAndVisualAncestors() + .OfType() + .FirstOrDefault(); + } + } + + [Benchmark] + public void FindAncestorOfType_Optimized() + { + foreach (Control control in _controls) + { + control.FindAncestorOfType(); + } + } + + [Benchmark] + public void FindCommonVisualAncestor() + { + foreach (IVisual first in _controls) + { + foreach (Control second in _shuffledControls) + { + first.FindCommonVisualAncestor(second); + } + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index d87014f646..0508edd92f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -228,8 +228,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var windowImpl = Mock.Of(x => x.Scaling == 1); - var target = new Window(windowImpl); + var target = new Window(); target.Show(); target.Close(); diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index c33ec72141..a6701ef655 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -28,6 +28,10 @@ namespace Avalonia.UnitTests return CreatePopupMock().Object; }); + mock.Setup(x => x.Dispose()).Callback(() => + { + mock.Object.Closed?.Invoke(); + }); PixelPoint pos = default; mock.SetupGet(x => x.Position).Returns(() => pos); mock.Setup(x => x.Move(It.IsAny())).Callback(new Action(np => pos = np)); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs index a8d8c07d8b..781169cfa6 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs @@ -2,12 +2,184 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.UnitTests; +using Avalonia.VisualTree; using Xunit; namespace Avalonia.Visuals.UnitTests { public class VisualExtensionsTests { + [Fact] + public void FindAncestorOfType_Finds_Direct_Parent() + { + StackPanel target; + + var root = new TestRoot + { + Child = target = new StackPanel() + }; + + Assert.Equal(root, target.FindAncestorOfType()); + } + + [Fact] + public void FindAncestorOfType_Finds_Ancestor_Of_Nested_Child() + { + Button target; + + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + new StackPanel + { + Children = + { + (target = new Button()) + } + } + } + } + }; + + Assert.Equal(root, target.FindAncestorOfType()); + } + + [Fact] + public void FindDescendantOfType_Finds_Direct_Child() + { + StackPanel target; + + var root = new TestRoot + { + Child = target = new StackPanel() + }; + + Assert.Equal(target, root.FindDescendantOfType()); + } + + [Fact] + public void FindDescendantOfType_Finds_Nested_Child() + { + Button target; + + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + new StackPanel + { + Children = + { + (target = new Button()) + } + } + } + } + }; + + Assert.Equal(target, root.FindDescendantOfType