diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 856d1f6566..77f23392e9 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -340,8 +340,6 @@ namespace Avalonia.Controls if (OwningGrid != null && OwningGrid.ColumnHeaders != null) { - args.Pointer.Capture(this); - _dragMode = DragMode.MouseDown; _frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth(); _lastMousePositionHeaders = this.Translate(OwningGrid.ColumnHeaders, mousePosition); @@ -413,8 +411,9 @@ namespace Avalonia.Controls } //TODO DragEvents - internal void OnMouseMove(ref bool handled, Point mousePosition, Point mousePositionHeaders) + internal void OnMouseMove(PointerEventArgs args, Point mousePosition, Point mousePositionHeaders) { + var handled = args.Handled; if (handled || OwningGrid == null || OwningGrid.ColumnHeaders == null) { return; @@ -438,7 +437,10 @@ namespace Avalonia.Controls } _lastMousePositionHeaders = mousePositionHeaders; - + + if (args.Pointer.Captured != this) + args.Pointer.Capture(this); + SetDragCursor(mousePosition); } @@ -506,8 +508,7 @@ namespace Avalonia.Controls Point mousePosition = e.GetPosition(this); Point mousePositionHeaders = e.GetPosition(OwningGrid.ColumnHeaders); - bool handled = false; - OnMouseMove(ref handled, mousePosition, mousePositionHeaders); + OnMouseMove(e, mousePosition, mousePositionHeaders); } /// diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index bdc68bee7e..7d50ef7d33 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -512,6 +512,13 @@ namespace Avalonia.Controls.Presenters var generator = Owner.ItemContainerGenerator; var newOffset = -1.0; + //better not trigger any container generation/recycle while or layout stuff + //before panel is attached/visible + if (!panel.IsAttachedToVisualTree) + { + return null; + } + if (!panel.IsMeasureValid && panel.PreviousMeasure.HasValue) { //before any kind of scrolling we need to make sure panel measure is valid diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index d8d3450c6f..457a9ea282 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -67,8 +67,6 @@ namespace Avalonia.Controls.Primitives { get { - if (IsPopup) - return null; var rv = FindLayer(); if (rv == null) { diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index cec5029c18..5c63546f5d 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -19,6 +19,7 @@ namespace Avalonia.Input private readonly Pointer _pointer; private bool _disposed; + private PixelPoint? _position; public MouseDevice(Pointer? pointer = null) { @@ -39,10 +40,11 @@ namespace Avalonia.Input /// /// Gets the mouse position, in screen coordinates. /// + [Obsolete("Use events instead")] public PixelPoint Position { - get; - protected set; + get => _position ?? new PixelPoint(-1, -1); + protected set => _position = value; } /// @@ -91,7 +93,16 @@ namespace Avalonia.Input public void SceneInvalidated(IInputRoot root, Rect rect) { - var clientPoint = root.PointToClient(Position); + // Pointer is outside of the target area + if (_position == null ) + { + if (root.PointerOverElement != null) + ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None); + return; + } + + + var clientPoint = root.PointToClient(_position.Value); if (rect.Contains(clientPoint)) { @@ -132,7 +143,7 @@ namespace Avalonia.Input if(mouse._disposed) return; - Position = e.Root.PointToScreen(e.Position); + _position = e.Root.PointToScreen(e.Position); var props = CreateProperties(e); var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers); switch (e.Type) @@ -176,6 +187,7 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); + _position = null; ClearPointerOver(this, timestamp, root, properties, inputModifiers); } diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 7fb44152c9..2d00d82a46 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -2,6 +2,8 @@ Compat issues with assembly Avalonia.Visuals: MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.Geometry.GetRenderBounds(Avalonia.Media.Pen)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Boolean Avalonia.Media.Geometry.StrokeContains(Avalonia.Media.Pen, Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract. @@ -34,4 +36,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. MembersMustExist : Member 'public Avalonia.Utilities.IRef Avalonia.Rendering.RenderLayer.Bitmap.get()' does not exist in the implementation but it does exist in the contract. -Total Issues: 35 +Total Issues: 37 diff --git a/src/Avalonia.Visuals/Media/Drawing.cs b/src/Avalonia.Visuals/Media/Drawing.cs index 6bc808e407..66d05e7e48 100644 --- a/src/Avalonia.Visuals/Media/Drawing.cs +++ b/src/Avalonia.Visuals/Media/Drawing.cs @@ -1,11 +1,19 @@ -using Avalonia.Platform; - -namespace Avalonia.Media +namespace Avalonia.Media { + /// + /// Abstract class that describes a 2-D drawing. + /// public abstract class Drawing : AvaloniaObject { + /// + /// Draws this drawing to the given . + /// + /// The drawing context. public abstract void Draw(DrawingContext context); + /// + /// Gets the drawing's bounding rectangle. + /// public abstract Rect GetBounds(); } } diff --git a/src/Avalonia.Visuals/Media/Geometry.cs b/src/Avalonia.Visuals/Media/Geometry.cs index 33aafb58fc..ccef6665a9 100644 --- a/src/Avalonia.Visuals/Media/Geometry.cs +++ b/src/Avalonia.Visuals/Media/Geometry.cs @@ -84,7 +84,7 @@ namespace Avalonia.Media /// /// The stroke thickness. /// The bounding rectangle. - public Rect GetRenderBounds(Pen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty; + public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty; /// /// Indicates whether the geometry's fill contains the specified point. @@ -102,7 +102,7 @@ namespace Avalonia.Media /// The pen to use. /// The point. /// true if the geometry contains the point; otherwise, false. - public bool StrokeContains(Pen pen, Point point) + public bool StrokeContains(IPen pen, Point point) { return PlatformImpl?.StrokeContains(pen, point) == true; } diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs index 4df3aa8ae2..e46e3a7d5f 100644 --- a/src/Avalonia.Visuals/Media/GeometryDrawing.cs +++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs @@ -1,12 +1,38 @@ -using Avalonia.Metadata; +using Avalonia.Media.Immutable; +using Avalonia.Metadata; namespace Avalonia.Media { + /// + /// Represents a drawing operation that combines + /// a geometry with and brush and/or pen to produce rendered content. + /// public class GeometryDrawing : Drawing { + // Adding the Pen's stroke thickness here could yield wrong results due to transforms. + private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUint32(), 0); + + /// + /// Defines the property. + /// public static readonly StyledProperty GeometryProperty = AvaloniaProperty.Register(nameof(Geometry)); + /// + /// Defines the property. + /// + public static readonly StyledProperty BrushProperty = + AvaloniaProperty.Register(nameof(Brush), Brushes.Transparent); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PenProperty = + AvaloniaProperty.Register(nameof(Pen)); + + /// + /// Gets or sets the that describes the shape of this . + /// [Content] public Geometry Geometry { @@ -14,18 +40,18 @@ namespace Avalonia.Media set => SetValue(GeometryProperty, value); } - public static readonly StyledProperty BrushProperty = - AvaloniaProperty.Register(nameof(Brush), Brushes.Transparent); - + /// + /// Gets or sets the used to fill the interior of the shape described by this . + /// public IBrush Brush { get => GetValue(BrushProperty); set => SetValue(BrushProperty, value); } - public static readonly StyledProperty PenProperty = - AvaloniaProperty.Register(nameof(Pen)); - + /// + /// Gets or sets the used to stroke this . + /// public IPen Pen { get => GetValue(PenProperty); @@ -42,9 +68,7 @@ namespace Avalonia.Media public override Rect GetBounds() { - // adding the Pen's stroke thickness here could yield wrong results due to transforms - var pen = new Pen(Brushes.Black, 0); - return Geometry?.GetRenderBounds(pen) ?? new Rect(); + return Geometry?.GetRenderBounds(s_boundsPen) ?? Rect.Empty; } } } diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 742973e0da..b3a24e6c37 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -13,7 +13,10 @@ namespace Avalonia.X11 { XiEventType.XI_Motion, XiEventType.XI_ButtonPress, - XiEventType.XI_ButtonRelease + XiEventType.XI_ButtonRelease, + XiEventType.XI_Leave, + XiEventType.XI_Enter, + }; private static readonly XiEventType[] MultiTouchEventTypes = new XiEventType[] @@ -162,7 +165,9 @@ namespace Avalonia.X11 | XEventMask.Button4MotionMask | XEventMask.Button5MotionMask | XEventMask.ButtonPressMask - | XEventMask.ButtonReleaseMask; + | XEventMask.ButtonReleaseMask + | XEventMask.LeaveWindowMask + | XEventMask.EnterWindowMask; } public void OnWindowDestroyed(IntPtr xid) => _clients.Remove(xid); @@ -175,14 +180,39 @@ namespace Avalonia.X11 _pointerDevice.Update(changed->Classes, changed->NumClasses); } - + if ((xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion) - || (xev->evtype>=XiEventType.XI_TouchBegin&&xev->evtype<=XiEventType.XI_TouchEnd)) + || (xev->evtype >= XiEventType.XI_TouchBegin && xev->evtype <= XiEventType.XI_TouchEnd)) { var dev = (XIDeviceEvent*)xev; if (_clients.TryGetValue(dev->EventWindow, out var client)) OnDeviceEvent(client, new ParsedDeviceEvent(dev)); } + + if (xev->evtype == XiEventType.XI_Leave || xev->evtype == XiEventType.XI_Enter) + { + var rev = (XIEnterLeaveEvent*)xev; + if (_clients.TryGetValue(rev->EventWindow, out var client)) + OnEnterLeaveEvent(client, ref *rev); + } + } + + void OnEnterLeaveEvent(IXI2Client client, ref XIEnterLeaveEvent ev) + { + if (ev.evtype == XiEventType.XI_Leave) + { + var buttons = ParsedDeviceEvent.ParseButtonState(ev.buttons.MaskLen, ev.buttons.Mask); + var detail = ev.detail; + if ((detail == XiEnterLeaveDetail.XINotifyNonlinearVirtual || + detail == XiEnterLeaveDetail.XINotifyNonlinear || + detail == XiEnterLeaveDetail.XINotifyVirtual) + && buttons == default) + { + client.ScheduleXI2Input(new RawPointerEventArgs(client.MouseDevice, (ulong)ev.time.ToInt64(), + client.InputRoot, + RawPointerEventType.LeaveWindow, new Point(ev.event_x, ev.event_y), buttons)); + } + } } void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev) @@ -284,6 +314,29 @@ namespace Avalonia.X11 public int Detail { get; set; } public bool Emulated { get; set; } public Dictionary Valuators { get; } + + public static RawInputModifiers ParseButtonState(int len, byte* buttons) + { + RawInputModifiers rv = default; + if (len > 0) + { + if (XIMaskIsSet(buttons, 1)) + rv |= RawInputModifiers.LeftMouseButton; + if (XIMaskIsSet(buttons, 2)) + rv |= RawInputModifiers.MiddleMouseButton; + if (XIMaskIsSet(buttons, 3)) + rv |= RawInputModifiers.RightMouseButton; + if (len > 1) + { + if (XIMaskIsSet(buttons, 8)) + rv |= RawInputModifiers.XButton1MouseButton; + if (XIMaskIsSet(buttons, 9)) + rv |= RawInputModifiers.XButton2MouseButton; + } + } + return rv; + } + public ParsedDeviceEvent(XIDeviceEvent* ev) { Type = ev->evtype; @@ -298,27 +351,16 @@ namespace Avalonia.X11 if (state.HasFlag(XModifierMask.Mod4Mask)) Modifiers |= RawInputModifiers.Meta; - if (ev->buttons.MaskLen > 0) - { - 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; - } + Modifiers = ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask); Valuators = new Dictionary(); Position = new Point(ev->event_x, ev->event_y); var values = ev->valuators.Values; - for (var c = 0; c < ev->valuators.MaskLen * 8; c++) - if (XIMaskIsSet(ev->valuators.Mask, c)) - Valuators[c] = *values++; + if(ev->valuators.Mask != null) + for (var c = 0; c < ev->valuators.MaskLen * 8; c++) + if (XIMaskIsSet(ev->valuators.Mask, c)) + Valuators[c] = *values++; + if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease) Button = ev->detail; Detail = ev->detail; diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs index 4675ef47f2..f4581a99ba 100644 --- a/src/Avalonia.X11/XIStructs.cs +++ b/src/Avalonia.X11/XIStructs.cs @@ -236,6 +236,34 @@ namespace Avalonia.X11 public XIModifierState mods; public XIModifierState group; } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct XIEnterLeaveEvent + { + public XEventName type; /* GenericEvent */ + public UIntPtr serial; /* # of last request processed by server */ + public Bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public int extension; /* XI extension offset */ + public XiEventType evtype; + public IntPtr time; + public int deviceid; + public int sourceid; + public XiEnterLeaveDetail detail; + public IntPtr RootWindow; + public IntPtr EventWindow; + public IntPtr ChildWindow; + public double root_x; + public double root_y; + public double event_x; + public double event_y; + public int mode; + public int focus; + public int same_screen; + public XIButtonState buttons; + public XIModifierState mods; + public XIModifierState group; + } [Flags] public enum XiDeviceEventFlags : int @@ -286,5 +314,18 @@ namespace Avalonia.X11 XI_BarrierLeave = 26, XI_LASTEVENT = XI_BarrierLeave, } - + + enum XiEnterLeaveDetail + { + XINotifyAncestor = 0, + XINotifyVirtual = 1, + XINotifyInferior = 2, + XINotifyNonlinear = 3, + XINotifyNonlinearVirtual = 4, + XINotifyPointer = 5, + XINotifyPointerRoot = 6, + XINotifyDetailNone = 7 + + } + }