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

Both scaleable and draggable chart implemented #1728

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 131 additions & 10 deletions lib/src/chart/base/base_chart/fl_touch_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ abstract class FlTouchEvent {
/// That's why this field is nullable
Offset? get localPosition => null;

/// Represents the count of pointers that is in contact with the screen
///
/// Default is 0, nothing is in contact with the screen
int get pointerCount => 0;

/// excludes exit or up events to show interactions on charts
bool get isInterestedForInteractions {
final isLinux = defaultTargetPlatform == TargetPlatform.linux;
Expand All @@ -38,22 +43,76 @@ abstract class FlTouchEvent {
}
}

/// When a pointers has contacted the screen and might begin to move
class FlScaleStartEvent extends FlTouchEvent {
const FlScaleStartEvent(this.details);

/// Contains information of happened touch gesture
final ScaleStartDetails details;

/// Represents the position of happened touch/pointer events and count of pointers
@override
Offset get localPosition => details.focalPoint;

/// Represents the count of pointers
@override
int get pointerCount => details.pointerCount;
}

/// When a pointers that is in contact with the screen and has moved again.
class FlScaleUpdateEvent extends FlTouchEvent {
const FlScaleUpdateEvent(this.details);

/// Contains information of happened touch gesture
final ScaleUpdateDetails details;

/// Represents the position of happened touch/pointer events and count of pointers
@override
Offset get localPosition => details.focalPoint;

/// Represents the count of pointers
@override
int get pointerCount => details.pointerCount;
}

/// When a pointers has stopped contacting the screen and the scale gesture has ended.
class FlScaleEndEvent extends FlTouchEvent {
const FlScaleEndEvent(this.details);

final ScaleEndDetails details;

@override
Offset get localPosition => Offset.zero;

/// Represents the count of pointers
///
/// This type of event can no longer have any pointers in contact with the screen
@override
int get pointerCount => 0;
}

/// When a pointer has contacted the screen and might begin to move
/// Uses [ScaleStartDetails] to provide the position of the touch.
///
/// The [details] object provides the position of the touch.
/// Inspired from [GestureDragDownCallback]
class FlPanDownEvent extends FlTouchEvent {
const FlPanDownEvent(this.details);

/// Contains information of happened touch gesture
final DragDownDetails details;
final ScaleStartDetails details;

/// Represents the position of happened touch/pointer event
/// Represents the position of happened touch/pointer events and count of pointers
@override
Offset get localPosition => details.localPosition;
Offset get localPosition => details.focalPoint;

/// Represents the count of pointers
@override
int get pointerCount => details.pointerCount;
}

/// When a pointer has contacted the screen and has begun to move.
/// Uses [ScaleStartDetails] to provide the position of the touch.
///
/// The [details] object provides the position of the touch when it first
/// touched the surface.
Expand All @@ -63,15 +122,19 @@ class FlPanStartEvent extends FlTouchEvent {
const FlPanStartEvent(this.details);

/// Contains information of happened touch gesture
final DragStartDetails details;
final ScaleStartDetails details;

/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;
Offset get localPosition => details.localFocalPoint;

/// Represents the count of pointers
@override
int get pointerCount => details.pointerCount;
}

/// When a pointer that is in contact with the screen and moving
/// has moved again.
/// When a pointer that is in contact with the screen and has moved again.
/// Uses [ScaleStartDetails] to provide the position of the touch.
///
/// The [details] object provides the position of the touch and the distance it
/// has traveled since the last update.
Expand All @@ -80,11 +143,15 @@ class FlPanUpdateEvent extends FlTouchEvent {
const FlPanUpdateEvent(this.details);

/// Contains information of happened touch gesture
final DragUpdateDetails details;
final ScaleUpdateDetails details;

/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;
Offset get localPosition => details.localFocalPoint;

/// Represents the count of pointers
@override
int get pointerCount => details.pointerCount;
}

/// When the pointer that previously triggered a [FlPanStartEvent] did not complete.
Expand All @@ -103,7 +170,13 @@ class FlPanEndEvent extends FlTouchEvent {
const FlPanEndEvent(this.details);

/// Contains information of happened touch gesture
final DragEndDetails details;
final ScaleEndDetails details;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 0;
}

/// When a pointer that might cause a tap has contacted the
Expand All @@ -121,6 +194,12 @@ class FlTapDownEvent extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 1;
}

/// When the pointer that previously triggered a [FlTapDownEvent] will not end up causing a tap.
Expand All @@ -144,6 +223,12 @@ class FlTapUpEvent extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 0;
}

/// Called When a pointer has remained in contact with the screen at the
Expand All @@ -161,6 +246,12 @@ class FlLongPressStart extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 1;
}

/// When a pointer is moving after being held in contact at the same
Expand All @@ -179,6 +270,12 @@ class FlLongPressMoveUpdate extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 1;
}

/// When a pointer stops contacting the screen after a long press
Expand All @@ -197,6 +294,12 @@ class FlLongPressEnd extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => details.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 0;
}

/// The pointer has moved with respect to the device while the pointer is or is
Expand All @@ -214,6 +317,12 @@ class FlPointerEnterEvent extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => event.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 1;
}

/// The pointer has moved with respect to the device while the pointer is not
Expand All @@ -231,6 +340,12 @@ class FlPointerHoverEvent extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => event.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 1;
}

/// The pointer has moved with respect to the device while the pointer is or is
Expand All @@ -246,4 +361,10 @@ class FlPointerExitEvent extends FlTouchEvent {
/// Represents the position of happened touch/pointer event
@override
Offset get localPosition => event.localPosition;

/// Represents the count of pointers
///
/// This type of event can only have one pointer that is contact with the screen
@override
int get pointerCount => 0;
}
62 changes: 42 additions & 20 deletions lib/src/chart/base/base_chart/render_base_chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ abstract class RenderBaseChart<R extends BaseTouchResponse> extends RenderBox

late bool _validForMouseTracker;

/// Recognizes pan gestures, such as onDown, onStart, onUpdate, onCancel, ...
late PanGestureRecognizer _panGestureRecognizer;
// Instead of drag we use scale which also includes pan gestures
// Recognizes pan gestures, such as onDown, onStart, onUpdate, onCancel, ...
// and detects scale gestures scroll, pinch in out, such as onStart, onUpdate, onEnd, ...
late ScaleGestureRecognizer _scaleGestureRecognizer;

/// Recognizes tap gestures, such as onTapDown, onTapCancel and onTapUp
late TapGestureRecognizer _tapGestureRecognizer;
Expand All @@ -50,22 +52,20 @@ abstract class RenderBaseChart<R extends BaseTouchResponse> extends RenderBox

/// Initializes our recognizers and implement their callbacks.
void initGestureRecognizers() {
_panGestureRecognizer = PanGestureRecognizer();
_panGestureRecognizer
..onDown = (dragDownDetails) {
_notifyTouchEvent(FlPanDownEvent(dragDownDetails));
_scaleGestureRecognizer = ScaleGestureRecognizer();
_scaleGestureRecognizer
..onStart = (details) {
_notifyScaleEvent(FlScaleStartEvent(details));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it get called for the second finger (when you already have your first finger, and try to add the second finger to do the scale gesture)?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I think we should have something like fingerIndex or something like that

_notifyTouchEvent(FlPanDownEvent(details));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay to call both FlPanDown and FlPanStart at the same time?

Copy link
Contributor Author

@adamsocrat adamsocrat Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, drag down details and scalestart details will be different and be a breaking change. Thinking of another solution

_notifyTouchEvent(FlPanStartEvent(details));
}
..onStart = (dragStartDetails) {
_notifyTouchEvent(FlPanStartEvent(dragStartDetails));
..onUpdate = (details) {
_notifyScaleEvent(FlScaleUpdateEvent(details));
_notifyTouchEvent(FlPanUpdateEvent(details));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also for here, (something like fingerIndex)

}
..onUpdate = (dragUpdateDetails) {
_notifyTouchEvent(FlPanUpdateEvent(dragUpdateDetails));
}
..onCancel = () {
_notifyTouchEvent(const FlPanCancelEvent());
}
..onEnd = (dragEndDetails) {
_notifyTouchEvent(FlPanEndEvent(dragEndDetails));
..onEnd = (details) {
_notifyScaleEvent(FlScaleEndEvent(details));
_notifyTouchEvent(FlPanEndEvent(details));
};

_tapGestureRecognizer = TapGestureRecognizer();
Expand Down Expand Up @@ -124,7 +124,7 @@ abstract class RenderBaseChart<R extends BaseTouchResponse> extends RenderBox
if (event is PointerDownEvent) {
_longPressGestureRecognizer.addPointer(event);
_tapGestureRecognizer.addPointer(event);
_panGestureRecognizer.addPointer(event);
_scaleGestureRecognizer.addPointer(event);
} else if (event is PointerHoverEvent) {
_notifyTouchEvent(FlPointerHoverEvent(event));
}
Expand All @@ -140,7 +140,7 @@ abstract class RenderBaseChart<R extends BaseTouchResponse> extends RenderBox
PointerExitEventListener? get onExit =>
(event) => _notifyTouchEvent(FlPointerExitEvent(event));

/// Invokes the [_touchCallback] to notify listeners of this [FlTouchEvent]
/// Invokes the [_touchCallback] to notify listeners of this [FlTouchEvent] if the pointer is one or zero(exit events).
///
/// We get a [BaseTouchResponse] using [getResponseAtLocation] for events which contains a localPosition.
/// Then we invoke [_touchCallback] using the [event] and [response].
Expand All @@ -149,11 +149,16 @@ abstract class RenderBaseChart<R extends BaseTouchResponse> extends RenderBox
return;
}
final localPosition = event.localPosition;
final pointerCount = event.pointerCount;

R? response;
if (localPosition != null) {
if (localPosition != null && pointerCount == 1) {
response = getResponseAtLocation(localPosition);
}
_touchCallback!(event, response);

if (pointerCount <= 1) {
_touchCallback!(event, response);
}

if (_mouseCursorResolver == null) {
_latestMouseCursor = MouseCursor.defer;
Expand All @@ -162,6 +167,23 @@ abstract class RenderBaseChart<R extends BaseTouchResponse> extends RenderBox
}
}

/// Invokes the [_touchCallback] to notify listeners of this [FlTouchEvent]
///
/// We don't get a response for these events because they are scale events (scroll, pinch.etc)
/// We just invoke [_touchCallback] using the [event].
void _notifyScaleEvent(FlTouchEvent event) {
if (_touchCallback == null) {
return;
}

final pointerCount = event.pointerCount;

R? response;
if (pointerCount > 1) {
_touchCallback!(event, response);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just set a null as the second parameter (instead of having a nullable R?, which is always null)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

}
}

/// Represents the mouse cursor style when hovers on our chart
/// In the future we can change it runtime, for example we can turn it to
/// [SystemMouseCursors.click] when mouse hovers a specific point of our chart.
Expand Down