// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

part of dart.ui;

/// How the pointer has changed since the last report.
enum PointerChange {
  /// The input from the pointer is no longer directed towards this receiver.
  cancel,

  /// The device has started tracking the pointer.
  ///
  /// For example, the pointer might be hovering above the device, having not yet
  /// made contact with the surface of the device.
  add,

  /// The device is no longer tracking the pointer.
  ///
  /// For example, the pointer might have drifted out of the device's hover
  /// detection range or might have been disconnected from the system entirely.
  remove,

  /// The pointer has moved with respect to the device while not in contact with
  /// the device.
  hover,

  /// The pointer has made contact with the device.
  down,

  /// The pointer has moved with respect to the device while in contact with the
  /// device.
  move,

  /// The pointer has stopped making contact with the device.
  up,

  /// A pan/zoom has started on this pointer.
  ///
  /// This type of event will always have kind [PointerDeviceKind.trackpad].
  panZoomStart,

  /// The pan/zoom on this pointer has updated.
  ///
  /// This type of event will always have kind [PointerDeviceKind.trackpad].
  panZoomUpdate,

  /// The pan/zoom on this pointer has ended.
  ///
  /// This type of event will always have kind [PointerDeviceKind.trackpad].
  panZoomEnd,
}

/// The kind of pointer device.
enum PointerDeviceKind {
  /// A touch-based pointer device.
  ///
  /// The most common case is a touch screen.
  ///
  /// When the user is operating with a trackpad on iOS, clicking will also
  /// dispatch events with kind [touch] if
  /// `UIApplicationSupportsIndirectInputEvents` is not present in `Info.plist`
  /// or returns NO.
  ///
  /// See also:
  ///
  ///  * [UIApplicationSupportsIndirectInputEvents](https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationsupportsindirectinputevents?language=objc).
  touch,

  /// A mouse-based pointer device.
  ///
  /// The most common case is a mouse on the desktop or Web.
  ///
  /// When the user is operating with a trackpad on iOS, moving the pointing
  /// cursor will also dispatch events with kind [mouse], and clicking will
  /// dispatch events with kind [mouse] if
  /// `UIApplicationSupportsIndirectInputEvents` is not present in `Info.plist`
  /// or returns NO.
  ///
  /// See also:
  ///
  ///  * [UIApplicationSupportsIndirectInputEvents](https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationsupportsindirectinputevents?language=objc).
  mouse,

  /// A pointer device with a stylus.
  stylus,

  /// A pointer device with a stylus that has been inverted.
  invertedStylus,

  /// Gestures from a trackpad.
  ///
  /// A trackpad here is defined as a touch-based pointer device with an
  /// indirect surface (the user operates the screen by touching something that
  /// is not the screen).
  ///
  /// When the user makes zoom, pan, scroll or rotate gestures with a physical
  /// trackpad, supporting platforms dispatch events with kind [trackpad].
  ///
  /// Events with kind [trackpad] can only have a [PointerChange] of `add`,
  /// `remove`, and pan-zoom related values.
  ///
  /// Some platforms don't support (or don't fully support) trackpad
  /// gestures, and might convert trackpad gestures into fake pointer events
  /// that simulate dragging. These events typically have kind [touch] or
  /// [mouse] instead of [trackpad]. This includes (but is not limited to) Web,
  /// and iOS when `UIApplicationSupportsIndirectInputEvents` isn't present in
  /// `Info.plist` or returns NO.
  ///
  /// Moving the pointing cursor or clicking with a trackpad typically triggers
  /// [touch] or [mouse] events, but never triggers [trackpad] events.
  ///
  /// See also:
  ///
  ///  * [UIApplicationSupportsIndirectInputEvents](https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationsupportsindirectinputevents?language=objc).
  trackpad,

  /// An unknown pointer device.
  unknown
}

/// The kind of pointer signal event.
enum PointerSignalKind {
  /// The event is not associated with a pointer signal.
  none,

  /// A pointer-generated scroll (e.g., mouse wheel or trackpad scroll).
  scroll,

  /// A pointer-generated scroll-inertia cancel.
  scrollInertiaCancel,

  /// A pointer-generated scale event (e.g. trackpad pinch).
  scale,

  /// An unknown pointer signal kind.
  unknown
}

/// A function that implements the [PointerData.respond] method.
typedef PointerDataRespondCallback = void Function({bool allowPlatformDefault});

/// Information about the state of a pointer.
class PointerData {
  /// Creates an object that represents the state of a pointer.
  const PointerData({
    this.viewId = 0,
    this.embedderId = 0,
    this.timeStamp = Duration.zero,
    this.change = PointerChange.cancel,
    this.kind = PointerDeviceKind.touch,
    this.signalKind,
    this.device = 0,
    this.pointerIdentifier = 0,
    this.physicalX = 0.0,
    this.physicalY = 0.0,
    this.physicalDeltaX = 0.0,
    this.physicalDeltaY = 0.0,
    this.buttons = 0,
    this.obscured = false,
    this.synthesized = false,
    this.pressure = 0.0,
    this.pressureMin = 0.0,
    this.pressureMax = 0.0,
    this.distance = 0.0,
    this.distanceMax = 0.0,
    this.size = 0.0,
    this.radiusMajor = 0.0,
    this.radiusMinor = 0.0,
    this.radiusMin = 0.0,
    this.radiusMax = 0.0,
    this.orientation = 0.0,
    this.tilt = 0.0,
    this.platformData = 0,
    this.scrollDeltaX = 0.0,
    this.scrollDeltaY = 0.0,
    this.panX = 0.0,
    this.panY = 0.0,
    this.panDeltaX = 0.0,
    this.panDeltaY = 0.0,
    this.scale = 0.0,
    this.rotation = 0.0,
    PointerDataRespondCallback? onRespond,
  }) : _onRespond = onRespond;

  /// The ID of the [FlutterView] this [PointerEvent] originated from.
  final int viewId;

  /// Unique identifier that ties the [PointerEvent] to the embedder
  /// event that created it.
  /// it.
  ///
  /// No two pointer events can have the same [embedderId]. This is different
  /// from [pointerIdentifier] - used for hit-testing, whereas [embedderId] is
  /// used to identify the platform event.
  final int embedderId;

  /// Time of event dispatch, relative to an arbitrary timeline.
  final Duration timeStamp;

  /// How the pointer has changed since the last report.
  final PointerChange change;

  /// The kind of input device for which the event was generated.
  final PointerDeviceKind kind;

  /// The kind of signal for a pointer signal event.
  final PointerSignalKind? signalKind;

  /// Unique identifier for the pointing device, reused across interactions.
  final int device;

  /// Unique identifier for the pointer.
  ///
  /// This field changes for each new pointer down event. Framework uses this
  /// identifier to determine hit test result.
  final int pointerIdentifier;

  /// X coordinate of the position of the pointer, in physical pixels in the
  /// global coordinate space.
  final double physicalX;

  /// Y coordinate of the position of the pointer, in physical pixels in the
  /// global coordinate space.
  final double physicalY;

  /// The distance of pointer movement on X coordinate in physical pixels.
  final double physicalDeltaX;

  /// The distance of pointer movement on Y coordinate in physical pixels.
  final double physicalDeltaY;

  /// Bit field using the *Button constants (primaryMouseButton,
  /// secondaryStylusButton, etc). For example, if this has the value 6 and the
  /// [kind] is [PointerDeviceKind.invertedStylus], then this indicates an
  /// upside-down stylus with both its primary and secondary buttons pressed.
  final int buttons;

  /// Set if an application from a different security domain is in any way
  /// obscuring this application's window. (Aspirational; not currently
  /// implemented.)
  final bool obscured;

  /// Set if this pointer data was synthesized by pointer data packet converter.
  /// pointer data packet converter will synthesize additional pointer datas if
  /// the input sequence of pointer data is illegal.
  ///
  /// For example, a down pointer data will be synthesized if the converter receives
  /// a move pointer data while the pointer is not previously down.
  final bool synthesized;

  /// The pressure of the touch as a number ranging from 0.0, indicating a touch
  /// with no discernible pressure, to 1.0, indicating a touch with "normal"
  /// pressure, and possibly beyond, indicating a stronger touch. For devices
  /// that do not detect pressure (e.g. mice), returns 1.0.
  final double pressure;

  /// The minimum value that [pressure] can return for this pointer. For devices
  /// that do not detect pressure (e.g. mice), returns 1.0. This will always be
  /// a number less than or equal to 1.0.
  final double pressureMin;

  /// The maximum value that [pressure] can return for this pointer. For devices
  /// that do not detect pressure (e.g. mice), returns 1.0. This will always be
  /// a greater than or equal to 1.0.
  final double pressureMax;

  /// The distance of the detected object from the input surface (e.g. the
  /// distance of a stylus or finger from a touch screen), in arbitrary units on
  /// an arbitrary (not necessarily linear) scale. If the pointer is down, this
  /// is 0.0 by definition.
  final double distance;

  /// The maximum value that a distance can return for this pointer. If this
  /// input device cannot detect "hover touch" input events, then this will be
  /// 0.0.
  final double distanceMax;

  /// The area of the screen being pressed, scaled to a value between 0 and 1.
  /// The value of size can be used to determine fat touch events. This value
  /// is only set on Android, and is a device specific approximation within
  /// the range of detectable values. So, for example, the value of 0.1 could
  /// mean a touch with the tip of the finger, 0.2 a touch with full finger,
  /// and 0.3 the full palm.
  final double size;

  /// The radius of the contact ellipse along the major axis, in logical pixels.
  final double radiusMajor;

  /// The radius of the contact ellipse along the minor axis, in logical pixels.
  final double radiusMinor;

  /// The minimum value that could be reported for radiusMajor and radiusMinor
  /// for this pointer, in logical pixels.
  final double radiusMin;

  /// The minimum value that could be reported for radiusMajor and radiusMinor
  /// for this pointer, in logical pixels.
  final double radiusMax;

  /// For PointerDeviceKind.touch events:
  ///
  /// The angle of the contact ellipse, in radius in the range:
  ///
  ///    -pi/2 < orientation <= pi/2
  ///
  /// ...giving the angle of the major axis of the ellipse with the y-axis
  /// (negative angles indicating an orientation along the top-left /
  /// bottom-right diagonal, positive angles indicating an orientation along the
  /// top-right / bottom-left diagonal, and zero indicating an orientation
  /// parallel with the y-axis).
  ///
  /// For PointerDeviceKind.stylus and PointerDeviceKind.invertedStylus events:
  ///
  /// The angle of the stylus, in radians in the range:
  ///
  ///    -pi < orientation <= pi
  ///
  /// ...giving the angle of the axis of the stylus projected onto the input
  /// surface, relative to the positive y-axis of that surface (thus 0.0
  /// indicates the stylus, if projected onto that surface, would go from the
  /// contact point vertically up in the positive y-axis direction, pi would
  /// indicate that the stylus would go down in the negative y-axis direction;
  /// pi/4 would indicate that the stylus goes up and to the right, -pi/2 would
  /// indicate that the stylus goes to the left, etc).
  final double orientation;

  /// For PointerDeviceKind.stylus and PointerDeviceKind.invertedStylus events:
  ///
  /// The angle of the stylus, in radians in the range:
  ///
  ///    0 <= tilt <= pi/2
  ///
  /// ...giving the angle of the axis of the stylus, relative to the axis
  /// perpendicular to the input surface (thus 0.0 indicates the stylus is
  /// orthogonal to the plane of the input surface, while pi/2 indicates that
  /// the stylus is flat on that surface).
  final double tilt;

  /// Opaque platform-specific data associated with the event.
  final int platformData;

  /// For events with signalKind of PointerSignalKind.scroll:
  ///
  /// The amount to scroll in the x direction, in physical pixels.
  final double scrollDeltaX;

  /// For events with signalKind of PointerSignalKind.scroll:
  ///
  /// The amount to scroll in the y direction, in physical pixels.
  final double scrollDeltaY;

  /// For events with change of PointerChange.panZoomUpdate:
  ///
  /// The current panning magnitude of the pan/zoom in the x direction, in
  /// physical pixels.
  final double panX;

  /// For events with change of PointerChange.panZoomUpdate:
  ///
  /// The current panning magnitude of the pan/zoom in the y direction, in
  /// physical pixels.
  final double panY;

  /// For events with change of PointerChange.panZoomUpdate:
  ///
  /// The difference in panning of the pan/zoom in the x direction since the
  /// latest panZoomUpdate event, in physical pixels.
  final double panDeltaX;

  /// For events with change of PointerChange.panZoomUpdate:
  ///
  /// The difference in panning of the pan/zoom in the y direction since the
  /// last panZoomUpdate event, in physical pixels.
  final double panDeltaY;

  /// For events with change of PointerChange.panZoomUpdate:
  ///
  /// The current scale of the pan/zoom (unitless), with 1.0 as the initial scale.
  final double scale;

  /// For events with change of PointerChange.panZoomUpdate:
  ///
  /// The current angle of the pan/zoom in radians, with 0.0 as the initial angle.
  final double rotation;

  // An optional function that allows the framework to respond to the event
  // that triggered this PointerData instance.
  final PointerDataRespondCallback? _onRespond;

  /// Method that the framework/app can call to respond to the native event
  /// that triggered this [PointerData].
  ///
  /// The parameter [allowPlatformDefault] allows the platform to perform the
  /// default action associated with the native event when it's set to `true`.
  ///
  /// This method can be called any number of times, but once `allowPlatformDefault`
  /// is set to `true`, it can't be set to `false` again.
  ///
  /// If `allowPlatformDefault` is never set to `true`, the Flutter engine will
  /// consume the event, so it won't be seen by the platform. In the web, this
  /// means that `preventDefault` will be called in the DOM event that triggered
  /// the `PointerData`. See [Event: preventDefault() method in MDN][EpDmiMDN].
  ///
  /// The implementation of this method is configured through the `onRespond`
  /// parameter of the [PointerData] constructor.
  ///
  /// See also [PointerDataRespondCallback].
  ///
  /// [EpDmiMDN]: https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
  void respond({required bool allowPlatformDefault}) {
    if (_onRespond != null) {
      _onRespond(allowPlatformDefault: allowPlatformDefault);
    }
  }

  @override
  String toString() => 'PointerData(viewId: $viewId, x: $physicalX, y: $physicalY)';

  /// Returns a complete textual description of the information in this object.
  String toStringFull() {
    return '$runtimeType('
             'embedderId: $embedderId, '
             'timeStamp: $timeStamp, '
             'change: $change, '
             'kind: $kind, '
             'signalKind: $signalKind, '
             'device: $device, '
             'pointerIdentifier: $pointerIdentifier, '
             'physicalX: $physicalX, '
             'physicalY: $physicalY, '
             'physicalDeltaX: $physicalDeltaX, '
             'physicalDeltaY: $physicalDeltaY, '
             'buttons: $buttons, '
             'synthesized: $synthesized, '
             'pressure: $pressure, '
             'pressureMin: $pressureMin, '
             'pressureMax: $pressureMax, '
             'distance: $distance, '
             'distanceMax: $distanceMax, '
             'size: $size, '
             'radiusMajor: $radiusMajor, '
             'radiusMinor: $radiusMinor, '
             'radiusMin: $radiusMin, '
             'radiusMax: $radiusMax, '
             'orientation: $orientation, '
             'tilt: $tilt, '
             'platformData: $platformData, '
             'scrollDeltaX: $scrollDeltaX, '
             'scrollDeltaY: $scrollDeltaY, '
             'panX: $panX, '
             'panY: $panY, '
             'panDeltaX: $panDeltaX, '
             'panDeltaY: $panDeltaY, '
             'scale: $scale, '
             'rotation: $rotation, '
             'viewId: $viewId'
           ')';
  }
}

/// A sequence of reports about the state of pointers.
class PointerDataPacket {
  /// Creates a packet of pointer data reports.
  const PointerDataPacket({ this.data = const <PointerData>[] });

  /// Data about the individual pointers in this packet.
  ///
  /// This list might contain multiple pieces of data about the same pointer.
  final List<PointerData> data;
}
