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

feat(Touch): added touch events to tools #247

Merged

Conversation

Ouwen
Copy link
Contributor

@Ouwen Ouwen commented Oct 17, 2022

Adds the following cornerstone events:

TOUCH_START,
TOUCH_START_ACTIVATE,
MULTI_TOUCH_START,
MULTI_TOUCH_START_ACTIVATE,
TOUCH_DRAG,
MULTI_TOUCH_DRAG,
TOUCH_END

Currently only uses native browser events (touchstart, touchmove, touchend)

@netlify
Copy link

netlify bot commented Oct 17, 2022

Deploy Preview for cornerstone-3d-docs ready!

Name Link
🔨 Latest commit ec264ff
🔍 Latest deploy log https://app.netlify.com/sites/cornerstone-3d-docs/deploys/63d41aaedfdab00009c7464f
😎 Deploy Preview https://deploy-preview-247--cornerstone-3d-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@Ouwen Ouwen force-pushed the gradienthealth/added_touch_events branch 4 times, most recently from d3827d4 to f2fe364 Compare October 17, 2022 12:36
@Ouwen
Copy link
Contributor Author

Ouwen commented Oct 17, 2022

@sedghi let me know what you think

@Ouwen Ouwen force-pushed the gradienthealth/added_touch_events branch from f2fe364 to 0d58ee2 Compare October 19, 2022 01:25
@sedghi
Copy link
Member

sedghi commented Oct 19, 2022

This looks great at first sight. So are you plannig to uncommente the touch event listeners in annotation tools later? Like here https://github.com/cornerstonejs/cornerstone3D-beta/blob/main/packages/tools/src/tools/annotation/LengthTool.ts#L490

Also, are you saying we don't need the hammerJS anymore?

@Ouwen
Copy link
Contributor Author

Ouwen commented Oct 19, 2022

I think either hammerjs or contactjs will be needed to trigger the pinch/pan functionality

So I had some questions regarding hammerjs usage in the cornerstoneTools v2.

I noticed that hammerjs has a press and tap functionality, but cornerstone touch eventlistener seem to internally implement the press/tap. Was there a reason for this? cornerstonejs/cornerstoneTools#120

Last question in cornerstoneTools v2, there were some touch specific tool copies (pan vs panmultitouch) would it make more sense to have the pan tool have configuration which enables multitooltouch instead of these being two separate tools?

@sedghi
Copy link
Member

sedghi commented Oct 20, 2022

Hammer.js would add MULTI_TOUCH_PAN, PINCH, and ROTATE, PRESS (longer and tap)

@sedghi
Copy link
Member

sedghi commented Oct 20, 2022

Example to get shown need to be added here

@sedghi
Copy link
Member

sedghi commented Oct 20, 2022

In case of changing serve, we need to update the docs

@sedghi
Copy link
Member

sedghi commented Oct 25, 2022

Is this ready to go?

@Ouwen
Copy link
Contributor Author

Ouwen commented Oct 25, 2022

Not yet, am in the process of adding the multitouch and gestures. I think we can avoid hammerjs dependency as well

@Ouwen Ouwen force-pushed the gradienthealth/added_touch_events branch from 0d58ee2 to b14c070 Compare October 27, 2022 09:01
@Ouwen
Copy link
Contributor Author

Ouwen commented Oct 27, 2022

@sedghi this is almost ready for your review. This is becoming a larger change than I initially intended.

Highlights

  • We don't use the hammerjs dependency
  • Multi touch is "reduced" to single touch via taking means.
    • Distances are calculated as the mean distance of unique unordered points
    • In the case of a drag event where the user lifts a finger for example, the deltas of 3 last touch => 2 current touch are calculated as the mean point of the 3touch case minus the mean point of the 2touch case
    • Rotation is still todo, but following the case above we can take the mean of the 3 touch case and the mean of the 2 touch case. We can first translate the the 2 touch points by looking at the difference in mean points from 2points => 3points. Then for each matched point identifier we can calculate the theta and take the mean of all thetas.

TODO:

  • Since touch handles an array of touch points we have an array of IPoints[] as the currentPoints, lastPoints, and startPoints. This doesn't play nice with the mouse interface since IPoints is not an array.
  • I'll probably keep the IPoints[] under a different key (currentPointsList?). Then I'll reserve the original key with a datatype compatible with IPoints

@Ouwen Ouwen force-pushed the gradienthealth/added_touch_events branch 5 times, most recently from 9bac6d5 to a49735b Compare October 28, 2022 21:08
@Ouwen Ouwen force-pushed the gradienthealth/added_touch_events branch from a49735b to 7580eeb Compare November 6, 2022 00:35
@Ouwen Ouwen force-pushed the gradienthealth/added_touch_events branch from 7580eeb to 7219316 Compare November 19, 2022 04:42
/**
* The normalized mouse event detail
*/
type NormalizedTouchEventDetail = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't have anything different from the normalized mouse event detail - please re-use the existing type (you can rename it if you want, but only have one instance of it)

/**
* EventDetail for touchstart event
*/
type TouchStartEventDetail = NormalizedTouchEventDetail & {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please start with the MouseStartEventDetail, and then add specific behaviour that identifies this as a touch event. That allows leaving most of the tools classes alone, and only tweaking them if needed. There should/must be an identifier that identifies this as a touch event, and probably something that identifies what "drag" type to listen to.

*/
type TouchStartEventDetail = NormalizedTouchEventDetail & {
/** The starting points of the touch event. */
startPoints: ITouchPoints;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let the values that can be inheritted from MouseStartEventDetail be inheritted, and only add the extras as required.

@@ -101,6 +103,10 @@ class MagnifyTool extends BaseTool {
return true;
};

preTouchStartCallback = (evt: EventTypes.TouchStartActivateEventType) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You won't need many, if any of this type of change if the preMouseDownCallback just takes a mouse down activate event type, and can check if it is a touch event sub-type.

@diegohennrich diegohennrich force-pushed the gradienthealth/added_touch_events branch 2 times, most recently from 64e932c to 78287da Compare January 24, 2023 21:47
@diegohennrich
Copy link
Contributor

Could you please review it? @Ouwen @sedghi

Copy link
Contributor Author

@Ouwen Ouwen left a comment

Choose a reason for hiding this comment

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

I did a pretty thorough review for each file. I want to be mindful that this is a very large PR and would prefer it to go in before other tool related PRs if possible @sedghi. Otherwise subsequent changes will need to rebased and force push might sneak diffs.

These were the main changes I found, but many of them are cosmetic which might be better handled in another (smaller) PR.

evt: EventTypes.MouseDownActivateEventType,
interactionType: string
evt: EventTypes.InteractionEventType,
interactionType = 'mouse'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think this is used in this function. Minor nit that might be better to do in a separate PR.

@@ -469,7 +469,7 @@ class CrosshairsTool extends AnnotationTool {
}

handleSelectedCallback = (
evt: EventTypes.MouseDownEventType,
evt: EventTypes.InteractionEventType,
annotation: Annotation,
handle: ToolHandle,
interactionType = 'mouse'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here

doubleClickCallback = (evt: EventTypes.MouseUpEventType) => {
touchTapCallback = (evt: EventTypes.TouchTapEventType) => {
if (evt.detail.taps == 2) {
this.doubleClickCallback(evt as unknown as EventTypes.MouseUpEventType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this can pass evt without as unknown as EventTypes... with the InteractionEventType change we made

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I think we need to use the specific TouchTapEventDetail here because we don't have the "taps" parameter from InteractionEventType. So "TouchTapEventDetail" is inherited from the interaction.

type TouchTapEventDetail = NormalizedInteractionEventDetail &
  TouchCustomEventDetail & {
    currentPointsList: ITouchPoints[];
    currentPoints: ITouchPoints;
    taps: number; <----
  };

Copy link
Contributor

Choose a reason for hiding this comment

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

As we discussed yesterday, this is a special case where we need to use the taps parameter and we don't have it inherited from the InteractionType. So, make sense to use the TouchTapEventDetail at this specific point.

@@ -51,7 +51,7 @@ class DragProbeTool extends ProbeTool {
}

postMouseDownCallback = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needs a postTouchDownCallback exposed to have touch events dispatched to this tool.

Copy link
Contributor

Choose a reason for hiding this comment

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

"postTouchDownCallback" or "postTouchStartCallback"?

I believe we're using the "postTouchStartCallback", see below:

packages/tools/src/eventDispatchers/touchEventHandler/touchstart.ts

if (activeTool && typeof activeTool.postTouchStartCallback === 'function') {
    const consumedEvent = activeTool.postTouchStartCallback(evt);

    if (consumedEvent) {
      // If the tool claims it consumed the event, prevent further checks.
      return;
    }
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should be postTouchStart callback you are correct. Rather than copying the mouse function you can just call the postMouseDownCallback function inside of postTouchStart callback

@@ -172,7 +172,8 @@ abstract class AnnotationTool extends AnnotationDisplayTool {
element: HTMLDivElement,
annotation: Annotation,
canvasCoords: Types.Point2,
proximity: number
proximity: number,
interactionType = 'mouse'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be removed

// element.removeEventListener(Events.TOUCH_END, this._mouseUpCallback)
// element.removeEventListener(Events.TOUCH_DRAG, this._mouseDragCallback)
// element.removeEventListener(Events.TOUCH_END, this._endCallback)
// element.removeEventListener(Events.TOUCH_DRAG, this._dragCallback)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wrongly commented out same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

also removeEventListener to the Events.TOUCH_TAP event

// element.addEventListener(Events.TOUCH_END, this._mouseUpCallback)
// element.addEventListener(Events.TOUCH_DRAG, this._mouseDragCallback)
// element.addEventListener(Events.TOUCH_END, this._endCallback)
// element.addEventListener(Events.TOUCH_DRAG, this._dragCallback)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should be enabled and include

element.addEventListener(Events.TOUCH_TAP, this._endCallback)

// element.removeEventListener(Events.TOUCH_END, this._mouseUpCallback)
// element.removeEventListener(Events.TOUCH_DRAG, this._mouseDragCallback)
// element.removeEventListener(Events.TOUCH_END, this._endCallback)
// element.removeEventListener(Events.TOUCH_DRAG, this._dragCallback)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here
This should be enabled and include

element. removeEventListener(Events.TOUCH_TAP, this._endCallback)

// element.addEventListener(Events.TOUCH_END, this._mouseUpCallback)
// element.addEventListener(Events.TOUCH_DRAG, this._mouseDragCallback)
// element.addEventListener(Events.TOUCH_END, this._endCallback)
// element.addEventListener(Events.TOUCH_DRAG, this._dragCallback)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as other segmentation tools.

// element.removeEventListener(Events.TOUCH_END, this._mouseUpCallback)
// element.removeEventListener(Events.TOUCH_DRAG, this._mouseDragCallback)
// element.removeEventListener(Events.TOUCH_END, this._endCallback)
// element.removeEventListener(Events.TOUCH_DRAG, this._dragCallback)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as other segmentation tools

@Ouwen
Copy link
Contributor Author

Ouwen commented Jan 25, 2023

@sedghi lmk if there are any outstanding changes required aside from the above... thanks again for taking the time to review this large diff which pokes other existing files.

@sedghi
Copy link
Member

sedghi commented Jan 25, 2023

@Ouwen probably yes, but I think I would like to do one last review. Just let me know when it is ready for it

@diegohennrich
Copy link
Contributor

Could you review it, please? @Ouwen @sedghi

}
};

doubleClickCallback = (evt: EventTypes.InteractionEventType): void => {
doubleClickCallback = (evt: EventTypes.TouchTapEventType): void => {
Copy link
Contributor Author

@Ouwen Ouwen Jan 25, 2023

Choose a reason for hiding this comment

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

should stay InteractionEventType since taps are not used

@@ -405,11 +405,11 @@ class ArrowAnnotateTool extends AnnotationTool {

touchTapCallback = (evt: EventTypes.TouchTapEventType) => {
if (evt.detail.taps == 2) {
this.doubleClickCallback(evt as unknown as EventTypes.MouseUpEventType);
this.doubleClickCallback(evt as EventTypes.TouchTapEventType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should just be evt

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should be evt since touchTapCallback = (evt: EventTypes.TouchTapEventType) => {

@@ -321,8 +321,8 @@ class RectangleScissorsTool extends BaseTool {
element.removeEventListener(Events.MOUSE_CLICK, this._endCallback);
element.removeEventListener(Events.TOUCH_TAP, this._endCallback);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

let's move the touch_tap back down to after line 325 to match mouse formatting

@diegohennrich
Copy link
Contributor

I think everything was done. Could you review it, please? @Ouwen @sedghi

@Ouwen
Copy link
Contributor Author

Ouwen commented Jan 26, 2023

@sedghi, I went through another pass at it, I think it is ready for review

Comment on lines +152 to +162
toolGroup.setToolActive(WindowLevelTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary, // special condition for one finger touch
},
],
});
```

The `MouseBindings.Primary` is a special binding type which will
automatically bind single finger touch.
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand, why the API here is different, is this supposed to do a mouse binding or touch bindings? If it is touch, I expected the following for consistensy

toolGroup.setToolActive(WindowLevelTool.toolName, {
  bindings: [{ numTouchPoints: 1 }],
});

Comment on lines +91 to +97
toolGroup.setToolActive(WindowLevelTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary, // Left Click
},
],
});
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Member

Choose a reason for hiding this comment

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

I remember you said this

Yeah numTouchPoints = 1 is a special case where we also treat it as a "Primary Mouse" event. This way existing modes which just bind a primary mouse get the free touch win without additional explicit config.

But don't think we should treat the numPoint ===1 special, why not for the others? why just 1 ?

Copy link
Member

@sedghi sedghi left a comment

Choose a reason for hiding this comment

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

The work done on this PR is truly exceptional and greatly appreciated. Kudos to the Gradient Health team as always 🎉 💯

@sedghi sedghi changed the title feat: added touch events to @cornerstonejs/tools feat(Touch): added touch events to tools Jan 27, 2023
@sedghi sedghi merged commit e35f963 into cornerstonejs:main Jan 27, 2023
@Ouwen Ouwen deleted the gradienthealth/added_touch_events branch February 7, 2023 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants