Browse Source

Merge branch 'master' into feature/UniformDrawRectangle

pull/3307/head
Benedikt Stebner 7 years ago
committed by GitHub
parent
commit
6b3060e6d6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/SharedVersion.props
  2. 18
      native/Avalonia.Native/inc/avalonia-native.h
  3. 14
      native/Avalonia.Native/src/OSX/app.mm
  4. 90
      native/Avalonia.Native/src/OSX/window.mm
  5. 15
      src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs
  6. 25
      src/Avalonia.Controls/ItemsControl.cs
  7. 10
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  8. 18
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  9. 8
      src/Avalonia.Controls/TopLevel.cs
  10. 1
      src/Avalonia.Controls/Window.cs
  11. 18
      src/Avalonia.Input/FocusManager.cs
  12. 2
      src/Avalonia.Input/IKeyboardDevice.cs
  13. 4
      src/Avalonia.Input/InputElement.cs
  14. 16
      src/Avalonia.Input/MouseDevice.cs
  15. 8
      src/Avalonia.Input/Pointer.cs
  16. 22
      src/Avalonia.Input/PointerPoint.cs
  17. 4
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  18. 27
      src/Avalonia.Layout/LayoutHelper.cs
  19. 8
      src/Avalonia.Layout/Layoutable.cs
  20. 33
      src/Avalonia.Native/WindowImplBase.cs
  21. 2
      src/Avalonia.Styling/StyledElement.cs
  22. 5
      src/Avalonia.Themes.Default/RadioButton.xaml
  23. 22
      src/Avalonia.Visuals/Visual.cs
  24. 155
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  25. 2
      src/Avalonia.X11/X11Clipboard.cs
  26. 36
      src/Avalonia.X11/X11Window.cs
  27. 20
      src/Avalonia.X11/XI2Manager.cs
  28. 5
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  29. 23
      src/Windows/Avalonia.Win32/OleContext.cs
  30. 2
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  31. 57
      src/Windows/Avalonia.Win32/WindowImpl.cs
  32. 64
      tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs
  33. 3
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  34. 4
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs
  35. 172
      tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.8.999</Version>
<Version>0.9.999</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

18
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;
};

14
native/Avalonia.Native/src/OSX/app.mm

@ -1,16 +1,25 @@
#include "common.h"
@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
@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];
}

90
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;
}

15
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();
}
}

25
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<IInputElement>()
.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;
}
}

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

@ -149,15 +149,9 @@ namespace Avalonia.Controls.Notifications
/// <param name="host">The <see cref="Window"/> that will be the host.</param>
private void Install(Window host)
{
var adornerLayer = host.GetVisualDescendants()
.OfType<VisualLayerManager>()
.FirstOrDefault()
?.AdornerLayer;
var adornerLayer = host.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
if (adornerLayer != null)
{
adornerLayer.Children.Add(this);
}
adornerLayer?.Children.Add(this);
}
}
}

18
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
/// <returns>The container or null if the event did not originate in a container.</returns>
protected IControl GetContainerFromEventSource(IInteractive eventSource)
{
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
.OfType<IControl>()
.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;
}
/// <inheritdoc/>

8
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
/// <param name="scaling">The window scaling.</param>
protected virtual void HandleScalingChanged(double scaling)
{
foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
{
control.InvalidateMeasure();
}
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
}
/// <inheritdoc/>

1
src/Avalonia.Controls/Window.cs

@ -336,7 +336,6 @@ namespace Avalonia.Controls
if (close)
{
PlatformImpl?.Dispose();
HandleClosed();
}
}
}

18
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<IInputElement>()
.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;
}
}
}

2
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
}

4
src/Avalonia.Input/InputElement.cs

@ -342,7 +342,7 @@ namespace Avalonia.Input
}
/// <summary>
/// Gets or sets a value indicating whether the control is focused.
/// Gets a value indicating whether the control is focused.
/// </summary>
public bool IsFocused
{
@ -360,7 +360,7 @@ namespace Avalonia.Input
}
/// <summary>
/// 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.
/// </summary>
public bool IsPointerOver
{

16
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);
}

8
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<IInputElement>().FirstOrDefault();
IInputElement GetNextCapture(IVisual parent)
{
return parent as IInputElement ?? parent.FindAncestorOfType<IInputElement>();
}
private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
{
Capture(GetNextCapture(e.Parent));

22
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
}

4
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

@ -14,6 +14,10 @@ namespace Avalonia.Input.Raw
RightButtonUp,
MiddleButtonDown,
MiddleButtonUp,
XButton1Down,
XButton1Up,
XButton2Down,
XButton2Up,
Move,
Wheel,
NonClientLeftButtonDown,

27
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;
}
/// <summary>
/// Invalidates measure for given control and all visual children recursively.
/// </summary>
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);
}
}
}

8
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
}
/// <inheritdoc/>
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);
}

33
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; }
}
}

2
src/Avalonia.Styling/StyledElement.cs

@ -288,7 +288,7 @@ namespace Avalonia
var list = new AvaloniaList<ILogical>
{
ResetBehavior = ResetBehavior.Remove,
Validate = ValidateLogicalChild
Validate = logical => ValidateLogicalChild(logical)
};
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;

5
src/Avalonia.Themes.Default/RadioButton.xaml

@ -13,7 +13,6 @@
Height="18"
VerticalAlignment="Center"/>
<Ellipse Name="checkMark"
Fill="{DynamicResource HighlightBrush}"
Width="10"
Height="10"
Stretch="Uniform"
@ -38,10 +37,12 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="RadioButton:pointerover /template/ Ellipse#border">
<Setter Property="Stroke" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style>
<Style Selector="RadioButton /template/ Ellipse#checkMark">
<Setter Property="Fill" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="RadioButton /template/ Ellipse#indeterminateMark">
@ -56,4 +57,4 @@
<Style Selector="RadioButton:disabled /template/ Ellipse#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
</Styles>
</Styles>

22
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<IVisual>();
visualChildren.ResetBehavior = ResetBehavior.Remove;
visualChildren.Validate = ValidateVisualChild;
visualChildren.Validate = visual => ValidateVisualChild(visual);
visualChildren.CollectionChanged += VisualChildrenChanged;
VisualChildren = visualChildren;
}
@ -173,7 +172,22 @@ namespace Avalonia
/// </summary>
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;
}
}
/// <summary>
@ -552,7 +566,7 @@ namespace Avalonia
if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true)
{
var root = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
var root = this.FindAncestorOfType<IRenderRoot>();
var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
OnAttachedToVisualTreeCore(e);
}

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

@ -14,7 +14,7 @@ namespace Avalonia.VisualTree
public static class VisualExtensions
{
/// <summary>
/// Calculates the distance from a visual's <see cref="IRenderRoot"/>.
/// Calculates the distance from a visual's ancestor.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="ancestor">The ancestor visual.</param>
@ -30,13 +30,39 @@ namespace Avalonia.VisualTree
while (visual != null && visual != ancestor)
{
++result;
visual = visual.VisualParent;
result++;
}
return visual != null ? result : -1;
}
/// <summary>
/// Calculates the distance from a visual's root.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>
/// The number of steps from the visual to the root.
/// </returns>
public static int CalculateDistanceFromRoot(IVisual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
var result = 0;
visual = visual?.VisualParent;
while (visual != null)
{
visual = visual.VisualParent;
result++;
}
return result;
}
/// <summary>
/// Tries to get the first common ancestor of two visuals.
/// </summary>
@ -47,8 +73,53 @@ namespace Avalonia.VisualTree
{
Contract.Requires<ArgumentNullException>(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;
}
/// <summary>
@ -69,6 +140,57 @@ namespace Avalonia.VisualTree
}
}
/// <summary>
/// Finds first ancestor of given type.
/// </summary>
/// <typeparam name="T">Ancestor type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First ancestor of given type.</returns>
public static T FindAncestorOfType<T>(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;
}
/// <summary>
/// Finds first descendant of given type.
/// </summary>
/// <typeparam name="T">Descendant type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First descendant of given type.</returns>
public static T FindDescendantOfType<T>(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<T>(visual);
}
/// <summary>
/// Enumerates an <see cref="IVisual"/> and its ancestors in the visual tree.
/// </summary>
@ -249,6 +371,31 @@ namespace Avalonia.VisualTree
.Select(x => x.Element);
}
private static T FindDescendantOfTypeCore<T>(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<T>(child);
if (!(childResult is null))
{
return childResult;
}
}
return null;
}
private class ZOrderElement : IComparable<ZOrderElement>
{
public IVisual Element { get; set; }

2
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)

36
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))

20
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<int, double>();

5
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);

23
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;
}
}
}

2
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;

57
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;
}
}

64
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<Control> _controls = new List<Control>();
private readonly List<Control> _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<TestRoot>()
.FirstOrDefault();
}
}
[Benchmark]
public void FindAncestorOfType_Optimized()
{
foreach (Control control in _controls)
{
control.FindAncestorOfType<TestRoot>();
}
}
[Benchmark]
public void FindCommonVisualAncestor()
{
foreach (IVisual first in _controls)
{
foreach (Control second in _shuffledControls)
{
first.FindCommonVisualAncestor(second);
}
}
}
}
}

3
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -228,8 +228,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = Mock.Of<IWindowImpl>(x => x.Scaling == 1);
var target = new Window(windowImpl);
var target = new Window();
target.Show();
target.Close();

4
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<PixelPoint>())).Callback(new Action<PixelPoint>(np => pos = np));

172
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<TestRoot>());
}
[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<TestRoot>());
}
[Fact]
public void FindDescendantOfType_Finds_Direct_Child()
{
StackPanel target;
var root = new TestRoot
{
Child = target = new StackPanel()
};
Assert.Equal(target, root.FindDescendantOfType<StackPanel>());
}
[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<Button>());
}
[Fact]
public void FindCommonVisualAncestor_First_Is_Parent_Of_Second()
{
Control left, right;
var root = new TestRoot
{
Child = left = new Decorator
{
Child = right = new Decorator()
}
};
var ancestor = left.FindCommonVisualAncestor(right);
Assert.Equal(left, ancestor);
ancestor = right.FindCommonVisualAncestor(left);
Assert.Equal(left, ancestor);
}
[Fact]
public void FindCommonVisualAncestor_Two_Subtrees_Uniform_Height()
{
Control left, right;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new Decorator
{
Child = new Decorator
{
Child = left = new Decorator()
}
},
new Decorator
{
Child = new Decorator
{
Child = right = new Decorator()
}
}
}
}
};
var ancestor = left.FindCommonVisualAncestor(right);
Assert.Equal(root.Child, ancestor);
ancestor = right.FindCommonVisualAncestor(left);
Assert.Equal(root.Child, ancestor);
}
[Fact]
public void FindCommonVisualAncestor_Two_Subtrees_NonUniform_Height()
{
Control left, right;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new Decorator
{
Child = new Decorator
{
Child = left = new Decorator()
}
},
new Decorator
{
Child = new Decorator
{
Child = new Decorator
{
Child = right = new Decorator()
}
}
}
}
}
};
var ancestor = left.FindCommonVisualAncestor(right);
Assert.Equal(root.Child, ancestor);
ancestor = right.FindCommonVisualAncestor(left);
Assert.Equal(root.Child, ancestor);
}
[Fact]
public void TranslatePoint_Should_Respect_RenderTransforms()
{

Loading…
Cancel
Save