Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: Add DisplacementEvent to fix delta coordinate transformations for drag events #2871

Merged
merged 13 commits into from
Nov 30, 2023
6 changes: 3 additions & 3 deletions doc/flame/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ void update(double dt) {

### Querying components at a specific point on the screen

The method `componentsAtPoint()` allows you to check which components were rendered at some point
The method `componentsAtVector2()` allows you to check which components were rendered at some point
on the screen. The returned value is an iterable of components, but you can also obtain the
coordinates of the initial point in each component's local coordinate space by providing a writable
`List<Vector2>` as a second parameter.
Expand All @@ -347,11 +347,11 @@ This method can only return components that implement the method `containsLocalP
implementation. However, if you're defining a custom class that derives from `Component`, you'd have
to implement the `containsLocalPoint()` method yourself.

Here is an example of how `componentsAtPoint()` can be used:
Here is an example of how `componentsAtVector2()` can be used:

```dart
void onDragUpdate(DragUpdateInfo info) {
game.componentsAtPoint(info.widget).forEach((component) {
game.componentsAtVector2(info.widget).forEach((component) {
if (component is DropTarget) {
component.highlight();
}
Expand Down
4 changes: 2 additions & 2 deletions doc/flame/examples/lib/drag_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class DragTarget extends PositionComponent with DragCallbacks {

@override
void onDragUpdate(DragUpdateEvent event) {
_trails[event.pointerId]!.addPoint(event.localPosition);
_trails[event.pointerId]!.addPoint(event.localEndPosition);
}

@override
Expand Down Expand Up @@ -240,6 +240,6 @@ class Star extends PositionComponent with DragCallbacks {

@override
void onDragUpdate(DragUpdateEvent event) {
position += event.delta;
position += event.localDelta;
}
}
5 changes: 2 additions & 3 deletions doc/tutorials/klondike/app/lib/step4/components/card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,7 @@ class Card extends PositionComponent with DragCallbacks {
if (!_isDragging) {
return;
}
final cameraZoom = findGame()!.camera.viewfinder.zoom;
final delta = event.delta / cameraZoom;
final delta = event.localDelta;
position.add(delta);
attachedCards.forEach((card) => card.position.add(delta));
}
Expand All @@ -255,7 +254,7 @@ class Card extends PositionComponent with DragCallbacks {
}
_isDragging = false;
final dropPiles = parent!
.componentsAtPoint(position + size / 2)
.componentsAtVector2(position + size / 2)
.whereType<Pile>()
.toList();
if (dropPiles.isNotEmpty) {
Expand Down
5 changes: 2 additions & 3 deletions doc/tutorials/klondike/app/lib/step5/components/card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,7 @@ class Card extends PositionComponent
if (!_isDragging) {
return;
}
final cameraZoom = findGame()!.camera.viewfinder.zoom;
final delta = event.delta / cameraZoom;
final delta = event.localDelta;
position.add(delta);
attachedCards.forEach((card) => card.position.add(delta));
}
Expand All @@ -280,7 +279,7 @@ class Card extends PositionComponent
_isDragging = false;
// Find out what is under the center-point of this card when it is dropped.
final dropPiles = parent!
.componentsAtPoint(position + size / 2)
.componentsAtVector2(position + size / 2)
.whereType<Pile>()
.toList();
if (dropPiles.isNotEmpty) {
Expand Down
32 changes: 9 additions & 23 deletions doc/tutorials/klondike/step4.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,19 +502,13 @@ the card, so that it is rendered above all others. Without this, the card would
During the drag, the `onDragUpdate` event will be called continuously. Using this callback we will
be updating the position of the card so that it follows the movement of the finger (or the mouse).
The `event` object passed to this callback contains the most recent coordinate of the point of
touch, and also the `delta` property -- which is the displacement vector since the previous call of
`onDragUpdate`. The only problem is that this delta is measured in screen pixels, whereas we want
it to be in game world units. The conversion between the two is given by the camera zoom level, so
we will add an extra method to determine the zoom level:
touch, and also the `localDelta` property -- which is the displacement vector since the previous
call of `onDragUpdate`, considering the camera zoom.

```dart
@override
void onDragUpdate(DragUpdateEvent event) {
final cameraZoom = (findGame()! as FlameGame)
.camera
.viewfinder
.zoom;
position += event.delta / cameraZoom;
position += event.delta;
}
```

Expand Down Expand Up @@ -606,11 +600,7 @@ to `false`:
if (!isDragged) {
return;
}
final cameraZoom = (findGame()! as FlameGame)
.camera
.viewfinder
.zoom;
position += event.delta / cameraZoom;
position += event.delta;
}

@override
Expand All @@ -627,7 +617,7 @@ so let's work on that.

At this point what we want to do is to figure out where the dragged card is being dropped. More
specifically, we want to know into which *pile* it is being dropped. This can be achieved by using
the `componentsAtPoint()` API, which allows you to query which components are located at a given
the `componentsAtVector2()` API, which allows you to query which components are located at a given
position on the screen.

Thus, my first attempt at revising the `onDragEnd` callback looks like this:
Expand All @@ -640,7 +630,7 @@ Thus, my first attempt at revising the `onDragEnd` callback looks like this:
}
super.onDragEnd(event);
final dropPiles = parent!
.componentsAtPoint(position + size / 2)
.componentsAtVector2(position + size / 2)
.whereType<Pile>()
.toList();
if (dropPiles.isNotEmpty) {
Expand Down Expand Up @@ -810,7 +800,7 @@ Now, putting this all together, the `Card`'s `onDragEnd` method will look like t
}
super.onDragEnd(event);
final dropPiles = parent!
.componentsAtPoint(position + size / 2)
.componentsAtVector2(position + size / 2)
.whereType<Pile>()
.toList();
if (dropPiles.isNotEmpty) {
Expand Down Expand Up @@ -940,11 +930,7 @@ the `onDragUpdate` method:
if (!isDragged) {
return;
}
final cameraZoom = (findGame()! as FlameGame)
.camera
.viewfinder
.zoom;
final delta = event.delta / cameraZoom;
final delta = event.delta;
position.add(delta);
attachedCards.forEach((card) => card.position.add(delta));
}
Expand Down Expand Up @@ -976,7 +962,7 @@ attached cards into the pile, and the same when it comes to returning the cards
}
super.onDragEnd(event);
final dropPiles = parent!
.componentsAtPoint(position + size / 2)
.componentsAtVector2(position + size / 2)
.whereType<Pile>()
.toList();
if (dropPiles.isNotEmpty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class DraggableBall extends Ball with DragCallbacks {

@override
void onDragUpdate(DragUpdateEvent event) {
body.applyLinearImpulse(event.delta * 1000);
body.applyLinearImpulse(event.localDelta * 1000);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class MouseJointWorld extends Forge2DWorld

@override
void onDragUpdate(DragUpdateEvent info) {
mouseJoint?.setTarget(info.localPosition);
mouseJoint?.setTarget(info.localEndPosition);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@ class DraggableBox extends Box with DragCallbacks {

@override
bool onDragUpdate(DragUpdateEvent info) {
final target = info.localPosition;
if (target.isNaN) {
return false;
}
final target = info.localEndPosition;
final mouseJointDef = MouseJointDef()
..maxForce = body.mass * 300
..dampingRatio = 0
Expand Down
3 changes: 1 addition & 2 deletions examples/lib/stories/input/drag_callbacks_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class DraggableEmber extends Ember with DragCallbacks {

@override
void onDragUpdate(DragUpdateEvent event) {
event.continuePropagation = true;
return;
position += event.localDelta;
}
}
46 changes: 34 additions & 12 deletions packages/flame/lib/src/camera/camera_component.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame/src/camera/behaviors/bounded_position_behavior.dart';
import 'package:flame/src/camera/behaviors/follow_behavior.dart';
Expand All @@ -6,9 +7,6 @@ import 'package:flame/src/camera/viewfinder.dart';
import 'package:flame/src/camera/viewport.dart';
import 'package:flame/src/camera/viewports/fixed_resolution_viewport.dart';
import 'package:flame/src/camera/viewports/max_viewport.dart';
import 'package:flame/src/camera/world.dart';
import 'package:flame/src/components/core/component.dart';
import 'package:flame/src/components/position_component.dart';
import 'package:flame/src/effects/controllers/effect_controller.dart';
import 'package:flame/src/effects/move_by_effect.dart';
import 'package:flame/src/effects/move_effect.dart';
Expand Down Expand Up @@ -226,19 +224,43 @@ class CameraComponent extends Component {
final _viewportPoint = Vector2.zero();

@override
Iterable<Component> componentsAtPoint(
Vector2 point, [
List<Vector2>? nestedPoints,
]) sync* {
final viewportPoint = viewport.globalToLocal(point, output: _viewportPoint);
yield* viewport.componentsAtPoint(viewportPoint, nestedPoints);
Iterable<Component> componentsAtLocation<T>(
T locationContext,
List<T>? nestedContexts,
T? Function(CoordinateTransform, T) transformContext,
bool Function(Component, T) checkContains,
) sync* {
final viewportPoint = transformContext(viewport, locationContext);
if (viewportPoint == null) {
return;
}

yield* viewport.componentsAtLocation(
viewportPoint,
nestedContexts,
transformContext,
checkContains,
);
if ((world?.isMounted ?? false) &&
currentCameras.length < maxCamerasDepth) {
if (viewport.containsLocalPoint(_viewportPoint)) {
currentCameras.add(this);
final worldPoint = viewfinder.transform.globalToLocal(_viewportPoint);
yield* viewfinder.componentsAtPoint(worldPoint, nestedPoints);
yield* world!.componentsAtPoint(worldPoint, nestedPoints);
final worldPoint = transformContext(viewfinder, viewportPoint);
if (worldPoint == null) {
return;
}
yield* viewfinder.componentsAtLocation(
worldPoint,
nestedContexts,
transformContext,
checkContains,
);
yield* world!.componentsAtLocation(
worldPoint,
nestedContexts,
transformContext,
checkContains,
);
currentCameras.removeLast();
}
}
Expand Down
42 changes: 32 additions & 10 deletions packages/flame/lib/src/components/core/component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ import 'package:meta/meta.dart';
/// [update]d, and then all the components will be [render]ed.
///
/// You may also need to override [containsLocalPoint] if the component needs to
/// respond to tap events or similar; the [componentsAtPoint] may also need to
/// be overridden if you have reimplemented [renderTree].
/// respond to tap events or similar; the [componentsAtLocation] may also need
/// to be overridden if you have reimplemented [renderTree].
class Component {
Component({
Iterable<Component>? children,
Expand Down Expand Up @@ -700,31 +700,53 @@ class Component {
/// If your component overrides [renderTree], then it almost certainly needs
/// to override this method as well, so that this method can find all rendered
/// components wherever they are.
Iterable<Component> componentsAtPoint(
Iterable<Component> componentsAtVector2(
Vector2 point, [
List<Vector2>? nestedPoints,
]) sync* {
nestedPoints?.add(point);
]) {
return componentsAtLocation<Vector2>(
point,
nestedPoints,
(transform, point) => transform.parentToLocal(point),
(component, point) => component.containsLocalPoint(point),
);
}

Iterable<Component> componentsAtLocation<T>(
T locationContext,
List<T>? nestedContexts,
T? Function(CoordinateTransform, T) transformContext,
bool Function(Component, T) checkContains,
) sync* {
nestedContexts?.add(locationContext);
if (_children != null) {
for (final child in _children!.reversed()) {
if (child is IgnoreEvents && child.ignoreEvents) {
continue;
}
Vector2? childPoint = point;
T? childPoint = locationContext;
if (child is CoordinateTransform) {
childPoint = (child as CoordinateTransform).parentToLocal(point);
childPoint = transformContext(
child as CoordinateTransform,
locationContext,
);
}
if (childPoint != null) {
yield* child.componentsAtPoint(childPoint, nestedPoints);
yield* child.componentsAtLocation(
childPoint,
nestedContexts,
transformContext,
checkContains,
);
}
}
}
final shouldIgnoreEvents =
this is IgnoreEvents && (this as IgnoreEvents).ignoreEvents;
if (containsLocalPoint(point) && !shouldIgnoreEvents) {
if (checkContains(this, locationContext) && !shouldIgnoreEvents) {
yield this;
}
nestedPoints?.removeLast();
nestedContexts?.removeLast();
}

//#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class JoystickComponent extends HudMarginComponent with DragCallbacks {

@override
bool onDragUpdate(DragUpdateEvent event) {
_unscaledDelta.add(event.delta);
_unscaledDelta.add(event.localDelta);
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'package:vector_math/vector_math_64.dart';
/// transformation, that is, the transform applies to all children of the
/// component equally. If that is not the case (for example, the component does
/// different transformations for some of its children), then that component
/// must implement [Component.componentsAtPoint] method instead.
/// must implement [Component.componentsAtLocation] method instead.
///
/// The two methods of this interface convert between the parent's coordinate
/// space and the local coordinates. The methods may also return `null`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flame/src/components/core/component.dart';
/// This mixin allows a component and all it's descendants to ignore events.
///
/// Do note that this will also ignore the component and its descendants in
/// calls to [Component.componentsAtPoint].
/// calls to [Component.componentsAtLocation].
///
/// If you want to dynamically use this mixin, you can add it and set
/// [ignoreEvents] true or false at runtime.
Expand Down
Loading