blob: 62a846ffa80ab183947181ac118c4b69446e1805 [file] [log] [blame] [edit]
// <copyright file="PointerInputDevice.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>
using OpenQA.Selenium.Internal;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace OpenQA.Selenium.Interactions;
/// <summary>
/// Represents the origin of the coordinates for mouse movement.
/// </summary>
public enum CoordinateOrigin
{
/// <summary>
/// The coordinate origin is the origin of the view port of the browser.
/// </summary>
Viewport,
/// <summary>
/// The origin of the movement is the most recent pointer location.
/// </summary>
Pointer,
/// <summary>
/// The origin of the movement is the center of the element specified.
/// </summary>
Element
}
/// <summary>
/// Specifies the type of pointer a pointer device represents.
/// </summary>
public enum PointerKind
{
/// <summary>
/// The pointer device is a mouse.
/// </summary>
Mouse,
/// <summary>
/// The pointer device is a pen or stylus.
/// </summary>
Pen,
/// <summary>
/// The pointer device is a touch screen device.
/// </summary>
Touch
}
/// <summary>
/// Specifies the button used during a pointer down or up action.
/// </summary>
public enum MouseButton
{
/// <summary>
/// This button is used for signifying touch actions.
/// </summary>
Touch = 0,
/// <summary>
/// The button used is the primary button.
/// </summary>
Left = 0,
/// <summary>
/// The button used is the middle button or mouse wheel.
/// </summary>
Middle = 1,
/// <summary>
/// The button used is the secondary button.
/// </summary>
Right = 2,
/// <summary>
/// The X1 button used for navigating back.
/// </summary>
Back = 3,
/// <summary>
/// The X2 button used for navigating forward.
/// </summary>
Forward = 4,
}
/// <summary>
/// Represents a pointer input device, such as a stylus, mouse, or finger on a touch screen.
/// </summary>
public class PointerInputDevice : InputDevice
{
private readonly PointerKind pointerKind;
/// <summary>
/// Initializes a new instance of the <see cref="PointerInputDevice"/> class.
/// </summary>
public PointerInputDevice()
: this(PointerKind.Mouse)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PointerInputDevice"/> class.
/// </summary>
/// <param name="pointerKind">The kind of pointer represented by this input device.</param>
public PointerInputDevice(PointerKind pointerKind)
: this(pointerKind, Guid.NewGuid().ToString())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PointerInputDevice"/> class.
/// </summary>
/// <param name="pointerKind">The kind of pointer represented by this input device.</param>
/// <param name="deviceName">The unique name for this input device.</param>
/// <exception cref="ArgumentException">If <paramref name="deviceName"/> is <see langword="null"/> or <see cref="string.Empty"/>.</exception>
public PointerInputDevice(PointerKind pointerKind, string deviceName)
: base(deviceName)
{
this.pointerKind = pointerKind;
}
/// <summary>
/// Gets the type of device for this input device.
/// </summary>
public override InputDeviceKind DeviceKind => InputDeviceKind.Pointer;
/// <summary>
/// Returns a value for this input device that can be transmitted across the wire to a remote end.
/// </summary>
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> representing this action.</returns>
public override Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn = new Dictionary<string, object>();
toReturn["type"] = "pointer";
toReturn["id"] = this.DeviceName;
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["pointerType"] = this.pointerKind.ToString().ToLowerInvariant();
toReturn["parameters"] = parameters;
return toReturn;
}
/// <summary>
/// Creates a pointer down action.
/// </summary>
/// <param name="button">The button of the pointer that should be pressed.</param>
/// <returns>The action representing the pointer down gesture.</returns>
public Interaction CreatePointerDown(MouseButton button)
{
return CreatePointerDown(button, new PointerEventProperties());
}
/// <summary>
/// Creates a pointer down action.
/// </summary>
/// <remarks>
/// MouseButton value applies to Pen types for primary, secondary and erase functionality (0, 2, and 5 respectively)
/// </remarks>
/// <param name="button">The button of the pointer that should be pressed.</param>
/// <param name="properties">The specifications for the pointer event interaction</param>
/// <returns>The action representing the pointer down gesture.</returns>
public Interaction CreatePointerDown(MouseButton button, PointerEventProperties properties)
{
return new PointerDownInteraction(this, button, properties);
}
/// <summary>
/// Creates a pointer up action.
/// </summary>
/// <param name="button">The button of the pointer that should be released.</param>
/// <returns>The action representing the pointer up gesture.</returns>
public Interaction CreatePointerUp(MouseButton button)
{
return CreatePointerUp(button, new PointerEventProperties());
}
/// <summary>
/// Creates a pointer down action.
/// </summary>
/// <remarks>
/// MouseButton value applies to Pen types for primary, secondary and erase functionality (0, 2, and 5 respectively)
/// </remarks>
/// <param name="button">The button of the pointer that should be pressed.</param>
/// <param name="properties">The specifications for the pointer event interaction</param>
/// <returns>The action representing the pointer down gesture.</returns>
public Interaction CreatePointerUp(MouseButton button, PointerEventProperties properties)
{
return new PointerUpInteraction(this, button, properties);
}
/// <summary>
/// Creates a pointer move action to a specific element.
/// </summary>
/// <param name="target">The <see cref="IWebElement"/> used as the target for the move.</param>
/// <param name="xOffset">The horizontal offset from the origin of the move.</param>
/// <param name="yOffset">The vertical offset from the origin of the move.</param>
/// <param name="duration">The length of time the move gesture takes to complete.</param>
/// <returns>The action representing the pointer move gesture.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="target"/> is <see langword="null"/>.</exception>
public Interaction CreatePointerMove(IWebElement target, int xOffset, int yOffset, TimeSpan duration)
{
return CreatePointerMove(target, xOffset, yOffset, duration, new PointerEventProperties());
}
/// <summary>
/// Creates a pointer move action to a specific element.
/// </summary>
/// <param name="target">The <see cref="IWebElement"/> used as the target for the move.</param>
/// <param name="xOffset">The horizontal offset from the origin of the move.</param>
/// <param name="yOffset">The vertical offset from the origin of the move.</param>
/// <param name="duration">The length of time the move gesture takes to complete.</param>
/// <param name="properties">The specifications for the pointer event interaction</param>
/// <returns>The action representing the pointer move gesture.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="target"/> is <see langword="null"/>.</exception>
public Interaction CreatePointerMove(IWebElement target, int xOffset, int yOffset, TimeSpan duration, PointerEventProperties properties)
{
return new PointerMoveInteraction(this, target, CoordinateOrigin.Element, xOffset, yOffset, duration, properties);
}
/// <summary>
/// Creates a pointer move action to an absolute coordinate.
/// </summary>
/// <param name="origin">The origin of coordinates for the move. Values can be relative to
/// the view port origin, or the most recent pointer position.</param>
/// <param name="xOffset">The horizontal offset from the origin of the move.</param>
/// <param name="yOffset">The vertical offset from the origin of the move.</param>
/// <param name="duration">The length of time the move gesture takes to complete.</param>
/// <returns>The action representing the pointer move gesture.</returns>
/// <exception cref="ArgumentException">Thrown when passing CoordinateOrigin.Element into origin.
/// Users should us the other CreatePointerMove overload to move to a specific element.</exception>
public Interaction CreatePointerMove(CoordinateOrigin origin, int xOffset, int yOffset, TimeSpan duration)
{
return CreatePointerMove(origin, xOffset, yOffset, duration, new PointerEventProperties());
}
/// <summary>
/// Creates a pointer move action to an absolute coordinate.
/// </summary>
/// <param name="origin">The origin of coordinates for the move. Values can be relative to
/// the view port origin, or the most recent pointer position.</param>
/// <param name="xOffset">The horizontal offset from the origin of the move.</param>
/// <param name="yOffset">The vertical offset from the origin of the move.</param>
/// <param name="duration">The length of time the move gesture takes to complete.</param>
/// <param name="properties">The object containing additional properties for this pointer move operation.</param>
/// <returns>The action representing the pointer move gesture.</returns>
/// <exception cref="ArgumentException">Thrown when passing CoordinateOrigin.Element into origin.
/// Users should use the other CreatePointerMove overload to move to a specific element.</exception>
public Interaction CreatePointerMove(CoordinateOrigin origin, int xOffset, int yOffset, TimeSpan duration, PointerEventProperties properties)
{
if (origin == CoordinateOrigin.Element)
{
throw new ArgumentException("Using a value of CoordinateOrigin.Element without an element is not supported.", nameof(origin));
}
return new PointerMoveInteraction(this, null, origin, xOffset, yOffset, duration, properties);
}
/// <summary>
/// Creates a pointer cancel action.
/// </summary>
/// <returns>The action representing the pointer cancel gesture.</returns>
public Interaction CreatePointerCancel()
{
return new PointerCancelInteraction(this);
}
/// <summary>
/// A class representing the properties of a pointer event.
/// </summary>
public class PointerEventProperties
{
/// <summary>
/// Gets or sets the width (magnitude on x-axis) in pixels of the contact geometry of the pointer.
/// </summary>
public double? Width { get; set; }
/// <summary>
/// Gets or sets the height (magnitude on y-axis) in pixels of the contact geometry of the pointer.
/// </summary>
public double? Height { get; set; }
/// <summary>
/// Gets or sets the normalized pressure of the pointer input.
/// </summary>
/// <remarks>
/// 0 and 1 represent the minimum and maximum pressure the hardware is capable of detecting, respectively.
/// </remarks>
public double? Pressure { get; set; }
/// <summary>
/// Gets or sets the normalized tangential pressure (also known as barrel pressure) of the pointer input.
/// </summary>
/// <remarks>
/// Valid values are between -1 and 1 with 0 being the neutral position of the control.
/// Some hardware may only support positive values between 0 and 1.
/// </remarks>
public double? TangentialPressure { get; set; }
/// <summary>
/// Gets or sets the plane angle in degrees between the Y-Z plane and the plane containing
/// both the transducer (e.g. pen stylus) axis and the Y axis..
/// </summary>
/// <remarks>
/// Valid values are between -90 and 90. A positive value is to the right.
/// </remarks>
public int? TiltX { get; set; }
/// <summary>
/// Gets or sets the plane angle in degrees between the X-Z plane and the plane containing
/// both the transducer (e.g. pen stylus) axis and the X axis..
/// </summary>
/// <remarks>
/// Valid values are between -90 and 90. A positive value is toward the user.
/// </remarks>
public int? TiltY { get; set; }
/// <summary>
/// Gets or sets the clockwise rotation in degrees of a transducer (e.g. stylus) around its own major axis
/// </summary>
/// <remarks>
/// Valid values are between 0 and 359.
/// </remarks>
public int? Twist { get; set; }
/// <summary>
/// Gets or sets the altitude in radians of the transducer (e.g. pen/stylus)
/// </summary>
/// <remarks>
/// Valid values are between 0 and π/2, where 0 is parallel to the surface (X-Y plane),
/// and π/2 is perpendicular to the surface.
/// </remarks>
public double? AltitudeAngle { get; set; }
/// <summary>
/// Gets or sets the azimuth angle (in radians) of the transducer (e.g. pen/stylus)
/// </summary>
/// <remarks>
/// Valid values are between 0 and 2Ï€,
/// where 0 represents a transducer whose cap is pointing in the direction of increasing X values,
/// and the values progressively increase when going clockwise.
/// </remarks>
public double? AzimuthAngle { get; set; }
/// <summary>
/// Serializes the properties of this input device as a dictionary.
/// </summary>
/// <returns>The dictionary containing the properties of this device.</returns>
public Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn = new Dictionary<string, object>();
if (this.Width.HasValue)
{
toReturn["width"] = this.Width.Value;
}
if (this.Height.HasValue)
{
toReturn["height"] = this.Height.Value;
}
if (this.Pressure.HasValue)
{
toReturn["pressure"] = this.Pressure.Value;
}
if (this.TangentialPressure.HasValue)
{
toReturn["tangentialPressure"] = this.TangentialPressure.Value;
}
if (this.TiltX.HasValue)
{
toReturn["tiltX"] = this.TiltX.Value;
}
if (this.TiltY.HasValue)
{
toReturn["tiltY"] = this.TiltY.Value;
}
if (this.Twist.HasValue)
{
toReturn["twist"] = this.Twist.Value;
}
if (this.AltitudeAngle.HasValue)
{
toReturn["altitudeAngle"] = this.AltitudeAngle.Value;
}
if (this.AzimuthAngle.HasValue)
{
toReturn["azimuthAngle"] = this.AzimuthAngle.Value;
}
return toReturn;
}
}
private class PointerDownInteraction : Interaction
{
private readonly MouseButton button;
private readonly PointerEventProperties eventProperties;
public PointerDownInteraction(InputDevice sourceDevice, MouseButton button, PointerEventProperties properties)
: base(sourceDevice)
{
this.button = button;
this.eventProperties = properties;
}
public override Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn;
if (eventProperties is null)
{
toReturn = new Dictionary<string, object>();
}
else
{
toReturn = eventProperties.ToDictionary();
}
toReturn["type"] = "pointerDown";
toReturn["button"] = Convert.ToInt32(this.button, CultureInfo.InvariantCulture);
return toReturn;
}
public override string ToString()
{
return "Pointer down";
}
}
private class PointerUpInteraction : Interaction
{
private readonly MouseButton button;
private readonly PointerEventProperties eventProperties;
public PointerUpInteraction(InputDevice sourceDevice, MouseButton button, PointerEventProperties properties)
: base(sourceDevice)
{
this.button = button;
this.eventProperties = properties;
}
public override Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn;
if (eventProperties is null)
{
toReturn = new Dictionary<string, object>();
}
else
{
toReturn = eventProperties.ToDictionary();
}
toReturn["type"] = "pointerUp";
toReturn["button"] = Convert.ToInt32(this.button, CultureInfo.InvariantCulture);
return toReturn;
}
public override string ToString()
{
return "Pointer up";
}
}
private class PointerCancelInteraction : Interaction
{
public PointerCancelInteraction(InputDevice sourceDevice)
: base(sourceDevice)
{
}
public override Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn = new Dictionary<string, object>();
toReturn["type"] = "pointerCancel";
return toReturn;
}
public override string ToString()
{
return "Pointer cancel";
}
}
private class PointerMoveInteraction : Interaction
{
private readonly IWebElement? target;
private readonly int x = 0;
private readonly int y = 0;
private TimeSpan duration = TimeSpan.MinValue;
private readonly CoordinateOrigin origin = CoordinateOrigin.Pointer;
private readonly PointerEventProperties eventProperties;
public PointerMoveInteraction(InputDevice sourceDevice, IWebElement? target, CoordinateOrigin origin, int x, int y, TimeSpan duration, PointerEventProperties properties)
: base(sourceDevice)
{
if (target != null)
{
this.target = target;
this.origin = CoordinateOrigin.Element;
}
else
{
if (this.origin != CoordinateOrigin.Element)
{
this.origin = origin;
}
}
if (duration != TimeSpan.MinValue)
{
this.duration = duration;
}
this.x = x;
this.y = y;
this.eventProperties = properties;
}
public override Dictionary<string, object> ToDictionary()
{
Dictionary<string, object> toReturn;
if (eventProperties == null)
{
toReturn = new Dictionary<string, object>();
}
else
{
toReturn = eventProperties.ToDictionary();
}
toReturn["type"] = "pointerMove";
if (this.duration != TimeSpan.MinValue)
{
toReturn["duration"] = Convert.ToInt64(this.duration.TotalMilliseconds);
}
if (this.target != null)
{
toReturn["origin"] = this.ConvertElement();
}
else
{
toReturn["origin"] = this.origin.ToString().ToLowerInvariant();
}
toReturn["x"] = this.x;
toReturn["y"] = this.y;
return toReturn;
}
public override string ToString()
{
string originDescription = this.origin.ToString();
if (this.origin == CoordinateOrigin.Element)
{
originDescription = this.target?.ToString() ?? "<null>";
}
return string.Format(CultureInfo.InvariantCulture, "Pointer move [origin: {0}, x offset: {1}, y offset: {2}, duration: {3}ms]", originDescription, this.x, this.y, this.duration.TotalMilliseconds);
}
private Dictionary<string, object> ConvertElement()
{
IWebDriverObjectReference? elementReference = this.target as IWebDriverObjectReference;
if (elementReference == null)
{
IWrapsElement? elementWrapper = this.target as IWrapsElement;
if (elementWrapper != null)
{
elementReference = elementWrapper.WrappedElement as IWebDriverObjectReference;
}
}
if (elementReference == null)
{
throw new ArgumentException("Target element cannot be converted to IWebElementReference");
}
Dictionary<string, object> elementDictionary = elementReference.ToDictionary();
return elementDictionary;
}
}
}