Browse Source
* Use relative position of view for drag and drop * Add Drag And Drop Test * Add AutomationPropertiespull/16076/merge
committed by
GitHub
5 changed files with 287 additions and 3 deletions
@ -0,0 +1,62 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="IntegrationTestApp.Pages.DragDropPage"> |
|||
<DockPanel> |
|||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="4"> |
|||
<Button Name="ResetDragDrop" Click="ResetDragDrop_Click" Margin="0,0,8,0">Reset</Button> |
|||
<TextBlock VerticalAlignment="Center">Drop Position: </TextBlock> |
|||
<TextBlock Name="DropPosition" VerticalAlignment="Center" Margin="4,0"/> |
|||
<TextBlock VerticalAlignment="Center" Margin="8,0,0,0">Status: </TextBlock> |
|||
<TextBlock Name="DragDropStatus" VerticalAlignment="Center" Margin="4,0"/> |
|||
</StackPanel> |
|||
|
|||
<!-- Use a Grid with row definitions to create offset from window origin --> |
|||
<!-- The top spacer ensures the drag/drop controls are NOT at (0,0) --> |
|||
<Grid RowDefinitions="100,*"> |
|||
<!-- Spacer row to offset controls from window origin --> |
|||
<Border Grid.Row="0" Background="LightGray"> |
|||
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" |
|||
Text="Spacer (100px) - Controls below are offset from window origin"/> |
|||
</Border> |
|||
|
|||
<!-- Drag and Drop test area --> |
|||
<Grid Grid.Row="1" ColumnDefinitions="*,*" Margin="20"> |
|||
<!-- Drag Source --> |
|||
<Border Name="DragSource" |
|||
Grid.Column="0" |
|||
Background="CornflowerBlue" |
|||
Margin="10" |
|||
CornerRadius="8" |
|||
AutomationProperties.AccessibilityView="Content" |
|||
PointerPressed="DragSource_PointerPressed"> |
|||
<TextBlock HorizontalAlignment="Center" |
|||
VerticalAlignment="Center" |
|||
Text="Drag Source" |
|||
Foreground="White" |
|||
FontWeight="Bold"/> |
|||
</Border> |
|||
|
|||
<!-- Drop Target --> |
|||
<Border Name="DropTarget" |
|||
Grid.Column="1" |
|||
Background="LightGreen" |
|||
Margin="10" |
|||
CornerRadius="8" |
|||
AutomationProperties.AccessibilityView="Content" |
|||
DragDrop.AllowDrop="True"> |
|||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<TextBlock Text="Drop Target" |
|||
HorizontalAlignment="Center" |
|||
FontWeight="Bold"/> |
|||
<TextBlock Name="DropTargetText" |
|||
HorizontalAlignment="Center" |
|||
Text="Drop items here"/> |
|||
</StackPanel> |
|||
</Border> |
|||
</Grid> |
|||
</Grid> |
|||
</DockPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,103 @@ |
|||
using System; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Platform; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace IntegrationTestApp.Pages; |
|||
|
|||
public partial class DragDropPage : UserControl |
|||
{ |
|||
public DragDropPage() |
|||
{ |
|||
InitializeComponent(); |
|||
|
|||
// Set up drag-drop event handlers
|
|||
AddHandler(DragDrop.DragOverEvent, DropTarget_DragOver); |
|||
AddHandler(DragDrop.DropEvent, DropTarget_Drop); |
|||
} |
|||
|
|||
private async void DragSource_PointerPressed(object? sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) |
|||
{ |
|||
var dragData = new DataTransfer(); |
|||
dragData.Add(DataTransferItem.CreateText("TestDragData")); |
|||
|
|||
DragDropStatus.Text = "Dragging..."; |
|||
|
|||
var result = await DragDrop.DoDragDropAsync(e, dragData, DragDropEffects.Copy | DragDropEffects.Move); |
|||
|
|||
DragDropStatus.Text = result switch |
|||
{ |
|||
DragDropEffects.Copy => "Copied", |
|||
DragDropEffects.Move => "Moved", |
|||
DragDropEffects.None => "Cancelled", |
|||
_ => $"Result: {result}" |
|||
}; |
|||
} |
|||
} |
|||
|
|||
private void DropTarget_DragOver(object? sender, DragEventArgs e) |
|||
{ |
|||
// Only handle events for the drop target
|
|||
if (e.Source != DropTarget && !IsChildOf(e.Source as Visual, DropTarget)) |
|||
return; |
|||
|
|||
e.DragEffects = DragDropEffects.Copy; |
|||
|
|||
// Get the position relative to the drop target
|
|||
var position = e.GetPosition(DropTarget); |
|||
DropPosition.Text = $"DragOver: ({position.X:F0}, {position.Y:F0})"; |
|||
} |
|||
|
|||
private void DropTarget_Drop(object? sender, DragEventArgs e) |
|||
{ |
|||
// Only handle events for the drop target
|
|||
if (e.Source != DropTarget && !IsChildOf(e.Source as Visual, DropTarget)) |
|||
return; |
|||
|
|||
// Get the position relative to the drop target
|
|||
var position = e.GetPosition(DropTarget); |
|||
DropPosition.Text = $"Drop: ({position.X:F0}, {position.Y:F0})"; |
|||
|
|||
// Check if the position is within reasonable bounds of the drop target
|
|||
var bounds = DropTarget.Bounds; |
|||
var isWithinBounds = position.X >= 0 && position.X <= bounds.Width && |
|||
position.Y >= 0 && position.Y <= bounds.Height; |
|||
|
|||
var text = e.DataTransfer.TryGetText(); |
|||
if (text != null) |
|||
{ |
|||
DropTargetText.Text = isWithinBounds |
|||
? $"Dropped: {text} at ({position.X:F0}, {position.Y:F0})" |
|||
: $"ERROR: Position out of bounds! ({position.X:F0}, {position.Y:F0})"; |
|||
DragDropStatus.Text = isWithinBounds ? "Drop OK" : "Drop position ERROR"; |
|||
} |
|||
|
|||
e.DragEffects = DragDropEffects.Copy; |
|||
} |
|||
|
|||
private static bool IsChildOf(Visual? child, Visual? parent) |
|||
{ |
|||
if (child == null || parent == null) |
|||
return false; |
|||
|
|||
var current = child.Parent as Visual; |
|||
while (current != null) |
|||
{ |
|||
if (current == parent) |
|||
return true; |
|||
current = current.Parent as Visual; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private void ResetDragDrop_Click(object? sender, RoutedEventArgs e) |
|||
{ |
|||
DropPosition.Text = string.Empty; |
|||
DragDropStatus.Text = string.Empty; |
|||
DropTargetText.Text = "Drop items here"; |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using OpenQA.Selenium.Appium; |
|||
using OpenQA.Selenium.Interactions; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.IntegrationTests.Appium; |
|||
|
|||
[Collection("Default")] |
|||
public class DragDropTests : TestBase |
|||
{ |
|||
public DragDropTests(DefaultAppFixture fixture) |
|||
: base(fixture, "DragDrop") |
|||
{ |
|||
var reset = Session.FindElementByAccessibilityId("ResetDragDrop"); |
|||
reset.Click(); |
|||
} |
|||
|
|||
[PlatformFact(TestPlatforms.MacOS)] |
|||
public void DragDrop_Coordinates_Correct_When_Controls_Offset_From_Origin() |
|||
{ |
|||
// This test verifies the fix for drag-drop coordinate calculation when
|
|||
// controls are positioned away from the window origin.
|
|||
// Issue: In embedded views or when controls have margin/offset from origin,
|
|||
// the drag-drop coordinates were incorrectly calculated relative to the
|
|||
// window rather than the view.
|
|||
|
|||
var dragSource = Session.FindElementByAccessibilityId("DragSource"); |
|||
var dropTarget = Session.FindElementByAccessibilityId("DropTarget"); |
|||
var dropPosition = Session.FindElementByAccessibilityId("DropPosition"); |
|||
var dragDropStatus = Session.FindElementByAccessibilityId("DragDropStatus"); |
|||
|
|||
// Perform drag from source to target
|
|||
new Actions(Session) |
|||
.MoveToElement(dragSource) |
|||
.ClickAndHold() |
|||
.MoveToElement(dropTarget) |
|||
.Release() |
|||
.Perform(); |
|||
|
|||
Thread.Sleep(500); // Allow UI to update
|
|||
|
|||
// Verify the drop was successful
|
|||
var status = dragDropStatus.Text; |
|||
Assert.True(status == "Drop OK" || status == "Copied", |
|||
$"Expected drop to succeed, but status was: {status}"); |
|||
|
|||
// Verify the drop position is within the target bounds
|
|||
// If the coordinate calculation bug exists, the position would be
|
|||
// offset by the spacer/margin and would show negative coordinates
|
|||
// or coordinates outside the target bounds
|
|||
var positionText = dropPosition.Text; |
|||
Assert.StartsWith("Drop:", positionText, StringComparison.Ordinal); |
|||
|
|||
// The DropTargetText should not contain "ERROR"
|
|||
var dropTargetText = Session.FindElementByAccessibilityId("DropTargetText"); |
|||
Assert.DoesNotContain("ERROR", dropTargetText.Text); |
|||
} |
|||
|
|||
[PlatformFact(TestPlatforms.MacOS)] |
|||
public void DragDrop_Position_Updates_During_DragOver() |
|||
{ |
|||
// Verifies that position is correctly reported during drag-over events
|
|||
var dragSource = Session.FindElementByAccessibilityId("DragSource"); |
|||
var dropTarget = Session.FindElementByAccessibilityId("DropTarget"); |
|||
var dropPosition = Session.FindElementByAccessibilityId("DropPosition"); |
|||
|
|||
// Start drag and move over target, then release
|
|||
var device = new PointerInputDevice(PointerKind.Mouse); |
|||
var builder = new ActionBuilder(); |
|||
|
|||
// Move to drag source and start drag
|
|||
builder.AddAction(device.CreatePointerMove(dragSource, 0, 0, TimeSpan.FromMilliseconds(100))); |
|||
builder.AddAction(device.CreatePointerDown(MouseButton.Left)); |
|||
|
|||
// Move to drop target (this triggers DragOver events)
|
|||
builder.AddAction(device.CreatePointerMove(dropTarget, 0, 0, TimeSpan.FromMilliseconds(200))); |
|||
|
|||
// Pause to allow DragOver events to be processed
|
|||
builder.AddAction(device.CreatePause(TimeSpan.FromMilliseconds(200))); |
|||
|
|||
// Release at current position (completes the drag)
|
|||
builder.AddAction(device.CreatePointerUp(MouseButton.Left)); |
|||
|
|||
Session.PerformActions(builder.ToActionSequenceList()); |
|||
|
|||
Thread.Sleep(200); |
|||
|
|||
// Check that position was recorded (either DragOver or Drop)
|
|||
var positionText = dropPosition.Text; |
|||
|
|||
// Position should have been updated during drag-over or drop
|
|||
Assert.True(positionText.Contains("DragOver:") || positionText.Contains("Drop:"), |
|||
$"Expected position to be updated during drag, but got: {positionText}"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DragDrop_Can_Be_Cancelled() |
|||
{ |
|||
var dragSource = Session.FindElementByAccessibilityId("DragSource"); |
|||
var dragDropStatus = Session.FindElementByAccessibilityId("DragDropStatus"); |
|||
|
|||
// Start drag but release outside any drop target
|
|||
new Actions(Session) |
|||
.MoveToElement(dragSource) |
|||
.ClickAndHold() |
|||
.MoveByOffset(-200, 0) // Move away from drop target
|
|||
.Release() |
|||
.Perform(); |
|||
|
|||
Thread.Sleep(500); |
|||
|
|||
// Status should indicate cancelled
|
|||
var status = dragDropStatus.Text; |
|||
Assert.Equal("Cancelled", status); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue