diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 92e4afdca8..603308ef9a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,7 +52,7 @@ jobs: sdk: 'macosx10.14' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' - xcodeVersion: 'default' # Options: 8, 9, default, specifyPath + xcodeVersion: '10' # Options: 8, 9, default, specifyPath args: '-derivedDataPath ./' - task: CmdLine@2 diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs new file mode 100644 index 0000000000..a8306c2d69 --- /dev/null +++ b/src/Avalonia.Base/EnumExtensions.cs @@ -0,0 +1,23 @@ +// 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; +using System.Runtime.CompilerServices; + +namespace Avalonia +{ + /// + /// Provides extension methods for enums. + /// + public static class EnumExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool HasFlagCustom(this T value, T flag) where T : unmanaged, Enum + { + var intValue = *(int*)&value; + var intFlag = *(int*)&flag; + + return (intValue & intFlag) == intFlag; + } + } +} diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 7c88401615..d06a71a9f8 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -42,7 +42,7 @@ namespace Avalonia.Controls /// static WrapPanel() { - AffectsMeasure(OrientationProperty); + AffectsMeasure(OrientationProperty, ItemWidthProperty, ItemHeightProperty); } /// diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index c84596b913..4dcf0eee53 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -14,13 +14,14 @@ namespace Avalonia.Input /// /// Represents a mouse device. /// - public class MouseDevice : IMouseDevice + public class MouseDevice : IMouseDevice, IDisposable { private int _clickCount; private Rect _lastClickRect; private ulong _lastClickTime; private readonly Pointer _pointer; + private bool _disposed; public MouseDevice(Pointer pointer = null) { @@ -126,7 +127,9 @@ namespace Avalonia.Input { Contract.Requires(e != null); - var mouse = (IMouseDevice)e.Device; + var mouse = (MouseDevice)e.Device; + if(mouse._disposed) + return; Position = e.Root.PointToScreen(e.Position); var props = CreateProperties(e); @@ -441,5 +444,11 @@ namespace Avalonia.Input el = (IInputElement)el.VisualParent; } } + + public void Dispose() + { + _disposed = true; + _pointer?.Dispose(); + } } } diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs index e9f3c02b7f..1068a0d4d4 100644 --- a/src/Avalonia.Input/PointerPoint.cs +++ b/src/Avalonia.Input/PointerPoint.cs @@ -1,3 +1,6 @@ +// 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. + namespace Avalonia.Input { public sealed class PointerPoint @@ -27,9 +30,9 @@ namespace Avalonia.Input public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind) { PointerUpdateKind = kind; - IsLeftButtonPressed = modifiers.HasFlag(RawInputModifiers.LeftMouseButton); - IsMiddleButtonPressed = modifiers.HasFlag(RawInputModifiers.MiddleMouseButton); - IsRightButtonPressed = modifiers.HasFlag(RawInputModifiers.RightMouseButton); + IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton); + IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton); + IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton); // The underlying input source might be reporting the previous state, // so make sure that we reflect the current state diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index b231c9fff4..d6ad836f37 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Avalonia.Input.Raw; @@ -11,10 +12,11 @@ namespace Avalonia.Input /// This class is supposed to be used on per-toplevel basis, don't use a shared one /// /// - public class TouchDevice : IInputDevice + public class TouchDevice : IInputDevice, IDisposable { - Dictionary _pointers = new Dictionary(); - + private readonly Dictionary _pointers = new Dictionary(); + private bool _disposed; + KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) => (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask); @@ -28,6 +30,8 @@ namespace Avalonia.Input public void ProcessRawEvent(RawInputEventArgs ev) { + if(_disposed) + return; var args = (RawTouchEventArgs)ev; if (!_pointers.TryGetValue(args.TouchPointId, out var pointer)) { @@ -82,6 +86,17 @@ namespace Avalonia.Input } + + public void Dispose() + { + if(_disposed) + return; + var values = _pointers.Values.ToList(); + _pointers.Clear(); + _disposed = true; + foreach (var p in values) + p.Dispose(); + } } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index ddb71b61bb..fab3ce36b8 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -95,7 +95,6 @@ namespace Avalonia.Native .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) - .Bind().ToConstant(MouseDevice) .Bind().ToConstant(this) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index fe7458d583..7f1fab4b1c 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native private object _syncRoot = new object(); private bool _deferredRendering = false; private bool _gpu = false; - private readonly IMouseDevice _mouse; + private readonly MouseDevice _mouse; private readonly IKeyboardDevice _keyboard; private readonly IStandardCursorFactory _cursorFactory; private Size _savedLogicalSize; @@ -38,7 +38,7 @@ namespace Avalonia.Native _deferredRendering = opts.UseDeferredRendering; _keyboard = AvaloniaLocator.Current.GetService(); - _mouse = AvaloniaLocator.Current.GetService(); + _mouse = new MouseDevice(); _cursorFactory = AvaloniaLocator.Current.GetService(); } @@ -142,6 +142,7 @@ namespace Avalonia.Native { n?.Dispose(); } + _parent._mouse.Dispose(); } void IAvnWindowBaseEvents.Activated() => _parent.Activated?.Invoke(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 558e96e132..1689f2e964 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -51,7 +51,7 @@ namespace Avalonia.Rendering.SceneGraph UpdateSize(scene); } - if (visual.VisualRoot != null) + if (visual.VisualRoot == scene.Root.Visual) { if (node?.Parent != null && visual.VisualParent != null && diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index d7a7bb97fd..6ba562bb69 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -19,9 +19,7 @@ namespace Avalonia.X11 class AvaloniaX11Platform : IWindowingPlatform { private Lazy _keyboardDevice = new Lazy(() => new KeyboardDevice()); - private Lazy _mouseDevice = new Lazy(() => new MouseDevice()); public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; - public MouseDevice MouseDevice => _mouseDevice.Value; public Dictionary> Windows = new Dictionary>(); public XI2Manager XI2; public X11Info Info { get; private set; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 17471fad10..32460fed86 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -32,7 +32,8 @@ namespace Avalonia.X11 private PixelPoint? _configurePoint; private bool _triggeredExpose; private IInputRoot _inputRoot; - private readonly IMouseDevice _mouse; + private readonly MouseDevice _mouse; + private readonly TouchDevice _touch; private readonly IKeyboardDevice _keyboard; private PixelPoint? _position; private PixelSize _realSize; @@ -57,7 +58,8 @@ namespace Avalonia.X11 _platform = platform; _popup = popupParent != null; _x11 = platform.Info; - _mouse = platform.MouseDevice; + _mouse = new MouseDevice(); + _touch = new TouchDevice(); _keyboard = platform.KeyboardDevice; var glfeature = AvaloniaLocator.Current.GetService(); @@ -702,6 +704,8 @@ namespace Avalonia.X11 _platform.XI2?.OnWindowDestroyed(_handle); _handle = IntPtr.Zero; Closed?.Invoke(); + _mouse.Dispose(); + _touch.Dispose(); } if (_useRenderWindow && _renderHandle != IntPtr.Zero) @@ -830,6 +834,8 @@ namespace Avalonia.X11 } public IMouseDevice MouseDevice => _mouse; + public TouchDevice TouchDevice => _touch; + public IPopupImpl CreatePopup() => _platform.Options.OverlayPopups ? null : new X11Window(_platform, this); diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 6989d6d26d..e37ed39bee 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -92,8 +92,6 @@ namespace Avalonia.X11 private PointerDeviceInfo _pointerDevice; private AvaloniaX11Platform _platform; - private readonly TouchDevice _touchDevice = new TouchDevice(); - public bool Init(AvaloniaX11Platform platform) { @@ -198,7 +196,7 @@ namespace Avalonia.X11 (ev.Type == XiEventType.XI_TouchUpdate ? RawPointerEventType.TouchUpdate : RawPointerEventType.TouchEnd); - client.ScheduleInput(new RawTouchEventArgs(_touchDevice, + client.ScheduleInput(new RawTouchEventArgs(client.TouchDevice, ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail)); return; } @@ -232,10 +230,10 @@ namespace Avalonia.X11 } if (scrollDelta != default) - client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp, + client.ScheduleInput(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot, ev.Position, scrollDelta, ev.Modifiers)); if (_pointerDevice.HasMotion(ev)) - client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, + client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot, RawPointerEventType.Move, ev.Position, ev.Modifiers)); } @@ -248,7 +246,7 @@ namespace Avalonia.X11 : ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp) : (RawPointerEventType?)null; if (type.HasValue) - client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, + client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot, type.Value, ev.Position, ev.Modifiers)); } @@ -310,5 +308,7 @@ namespace Avalonia.X11 { IInputRoot InputRoot { get; } void ScheduleInput(RawInputEventArgs args); + IMouseDevice MouseDevice { get; } + TouchDevice TouchDevice { get; } } } diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index eb7b65cdce..a5e56b1d7a 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -99,9 +99,26 @@ namespace Avalonia.Skia public TextHitTestResult HitTestPoint(Point point) { float y = (float)point.Y; - var line = _skiaLines.Find(l => l.Top <= y && (l.Top + l.Height) > y); - if (!line.Equals(default(AvaloniaFormattedTextLine))) + AvaloniaFormattedTextLine line = default; + + float nextTop = 0; + + foreach(var currentLine in _skiaLines) + { + if(currentLine.Top <= y) + { + line = currentLine; + nextTop = currentLine.Top + currentLine.Height; + } + else + { + nextTop = currentLine.Top; + break; + } + } + + if (!line.Equals(default)) { var rects = GetRects(); @@ -127,12 +144,15 @@ namespace Avalonia.Skia line.Length : (line.Length - 1); } - return new TextHitTestResult + if (y < nextTop) { - IsInside = false, - TextPosition = line.Start + offset, - IsTrailing = Text.Length == (line.Start + offset + 1) - }; + return new TextHitTestResult + { + IsInside = false, + TextPosition = line.Start + offset, + IsTrailing = Text.Length == (line.Start + offset + 1) + }; + } } bool end = point.X > _bounds.Width || point.Y > _lines.Sum(l => l.Height); diff --git a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs index e7c379ad89..8f060b1b81 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs @@ -11,19 +11,11 @@ namespace Avalonia.Win32.Input { class WindowsMouseDevice : MouseDevice { - public static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice(); - public WindowsMouseDevice() : base(new WindowsMousePointer()) { } - public WindowImpl CurrentWindow - { - get; - set; - } - class WindowsMousePointer : Pointer { public WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 04a9303d53..2ae200a179 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -30,6 +30,7 @@ namespace Avalonia.Win32 private IntPtr _hwnd; private bool _multitouch; private TouchDevice _touchDevice = new TouchDevice(); + private MouseDevice _mouseDevice = new WindowsMouseDevice(); private IInputRoot _owner; private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock(); private bool _trackingMouse; @@ -205,7 +206,7 @@ namespace Avalonia.Win32 } } - public IMouseDevice MouseDevice => WindowsMouseDevice.Instance; + public IMouseDevice MouseDevice => _mouseDevice; public WindowState WindowState { @@ -333,7 +334,7 @@ namespace Avalonia.Win32 public void BeginMoveDrag(PointerPressedEventArgs e) { - WindowsMouseDevice.Instance.Capture(null); + _mouseDevice.Capture(null); UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)UnmanagedMethods.HitTestValues.HTCAPTION), IntPtr.Zero); e.Pointer.Capture(null); @@ -356,7 +357,7 @@ namespace Avalonia.Win32 #if USE_MANAGED_DRAG _managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position)); #else - WindowsMouseDevice.Instance.Capture(null); + _mouseDevice.Capture(null); UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, new IntPtr((int)EdgeDic[edge]), IntPtr.Zero); #endif @@ -437,9 +438,7 @@ namespace Avalonia.Win32 uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime()); RawInputEventArgs e = null; - - WindowsMouseDevice.Instance.CurrentWindow = this; - + switch ((UnmanagedMethods.WindowsMessage)msg) { case UnmanagedMethods.WindowsMessage.WM_ACTIVATE: @@ -485,6 +484,8 @@ namespace Avalonia.Win32 _parent._disabledBy.Remove(this); _parent.UpdateEnabled(); } + _mouseDevice.Dispose(); + _touchDevice?.Dispose(); //Free other resources Dispose(); return IntPtr.Zero; @@ -542,7 +543,7 @@ namespace Avalonia.Win32 if(ShouldIgnoreTouchEmulatedMessage()) break; e = new RawPointerEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN @@ -559,7 +560,7 @@ namespace Avalonia.Win32 if(ShouldIgnoreTouchEmulatedMessage()) break; e = new RawPointerEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP @@ -587,7 +588,7 @@ namespace Avalonia.Win32 } e = new RawPointerEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, RawPointerEventType.Move, @@ -597,7 +598,7 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_MOUSEWHEEL: e = new RawMouseWheelEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, PointToClient(PointFromLParam(lParam)), @@ -606,7 +607,7 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_MOUSEHWHEEL: e = new RawMouseWheelEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, PointToClient(PointFromLParam(lParam)), @@ -616,7 +617,7 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE: _trackingMouse = false; e = new RawPointerEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, RawPointerEventType.LeaveWindow, @@ -627,7 +628,7 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN: case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN: e = new RawPointerEventArgs( - WindowsMouseDevice.Instance, + _mouseDevice, timestamp, _owner, msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN @@ -649,9 +650,9 @@ namespace Avalonia.Win32 { Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, _owner, - touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_UP) ? + touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ? RawPointerEventType.TouchEnd : - touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_DOWN) ? + touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ? RawPointerEventType.TouchBegin : RawPointerEventType.TouchUpdate, PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), @@ -771,11 +772,11 @@ namespace Avalonia.Win32 { var keys = (UnmanagedMethods.ModifierKeys)ToInt32(wParam); var modifiers = WindowsKeyboardDevice.Instance.Modifiers; - if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_LBUTTON)) + if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_LBUTTON)) modifiers |= RawInputModifiers.LeftMouseButton; - if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_RBUTTON)) + if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_RBUTTON)) modifiers |= RawInputModifiers.RightMouseButton; - if (keys.HasFlag(UnmanagedMethods.ModifierKeys.MK_MBUTTON)) + if (keys.HasFlagCustom(UnmanagedMethods.ModifierKeys.MK_MBUTTON)) modifiers |= RawInputModifiers.MiddleMouseButton; return modifiers; } @@ -785,7 +786,7 @@ namespace Avalonia.Win32 // Ensure that the delegate doesn't get garbage collected by storing it as a field. _wndProcDelegate = new UnmanagedMethods.WndProc(WndProc); - _className = "Avalonia-" + Guid.NewGuid(); + _className = $"Avalonia-{Guid.NewGuid().ToString()}"; UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { diff --git a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs index fd93df46b8..82f94b4f3c 100644 --- a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs @@ -12,14 +12,14 @@ namespace Avalonia.Controls.UnitTests public void Lays_Out_Horizontally_On_Separate_Lines() { var target = new WrapPanel() - { - Width = 100, - Children = + { + Width = 100, + Children = { new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 }, } - }; + }; target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); @@ -33,14 +33,14 @@ namespace Avalonia.Controls.UnitTests public void Lays_Out_Horizontally_On_A_Single_Line() { var target = new WrapPanel() - { - Width = 200, - Children = + { + Width = 200, + Children = { new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 }, } - }; + }; target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); @@ -54,15 +54,15 @@ namespace Avalonia.Controls.UnitTests public void Lays_Out_Vertically_Children_On_A_Single_Line() { var target = new WrapPanel() - { - Orientation = Orientation.Vertical, - Height = 120, - Children = + { + Orientation = Orientation.Vertical, + Height = 120, + Children = { new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 }, } - }; + }; target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); @@ -76,15 +76,15 @@ namespace Avalonia.Controls.UnitTests public void Lays_Out_Vertically_On_Separate_Lines() { var target = new WrapPanel() - { - Orientation = Orientation.Vertical, - Height = 60, - Children = + { + Orientation = Orientation.Vertical, + Height = 60, + Children = { new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 }, } - }; + }; target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); @@ -98,17 +98,17 @@ namespace Avalonia.Controls.UnitTests public void Applies_ItemWidth_And_ItemHeight_Properties() { var target = new WrapPanel() - { - Orientation = Orientation.Horizontal, - Width = 50, - ItemWidth = 20, - ItemHeight = 15, - Children = + { + Orientation = Orientation.Horizontal, + Width = 50, + ItemWidth = 20, + ItemHeight = 15, + Children = { new Border(), new Border(), } - }; + }; target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); @@ -117,5 +117,33 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 20, 15), target.Children[0].Bounds); Assert.Equal(new Rect(20, 0, 20, 15), target.Children[1].Bounds); } + + [Fact] + void ItemWidth_Trigger_InvalidateMeasure() + { + var target = new WrapPanel(); + + target.Measure(new Size(10, 10)); + + Assert.True(target.IsMeasureValid); + + target.ItemWidth = 1; + + Assert.False(target.IsMeasureValid); + } + + [Fact] + void ItemHeight_Trigger_InvalidateMeasure() + { + var target = new WrapPanel(); + + target.Measure(new Size(10, 10)); + + Assert.True(target.IsMeasureValid); + + target.ItemHeight = 1; + + Assert.False(target.IsMeasureValid); + } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index b4743e900d..6063a382a0 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -369,6 +369,81 @@ namespace Avalonia.Visuals.UnitTests.Rendering } + [Fact] + public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent_And_New_Root() + { + var dispatcher = new ImmediateDispatcher(); + var loop = new Mock(); + + Decorator moveFrom; + Decorator moveTo; + Canvas moveMe; + + var root = new TestRoot + { + Child = new StackPanel + { + Children = + { + (moveFrom = new Decorator + { + Child = moveMe = new Canvas(), + }) + } + } + }; + + var otherRoot = new TestRoot + { + Child = new StackPanel + { + Children = + { + (moveTo = new Decorator()) + } + } + }; + + var sceneBuilder = new SceneBuilder(); + var target = new DeferredRenderer( + root, + loop.Object, + sceneBuilder: sceneBuilder, + dispatcher: dispatcher); + + var otherSceneBuilder = new SceneBuilder(); + var otherTarget = new DeferredRenderer( + otherRoot, + loop.Object, + sceneBuilder: otherSceneBuilder, + dispatcher: dispatcher); + + root.Renderer = target; + otherRoot.Renderer = otherTarget; + + target.Start(); + otherTarget.Start(); + + RunFrame(target); + RunFrame(otherTarget); + + moveFrom.Child = null; + moveTo.Child = moveMe; + + RunFrame(target); + RunFrame(otherTarget); + + var scene = target.UnitTestScene(); + var otherScene = otherTarget.UnitTestScene(); + + var moveFromNode = (VisualNode)scene.FindNode(moveFrom); + var moveToNode = (VisualNode)otherScene.FindNode(moveTo); + + Assert.Empty(moveFromNode.Children); + Assert.Equal(1, moveToNode.Children.Count); + Assert.Same(moveMe, moveToNode.Children[0].Visual); + } + [Fact] public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity() {