-
Notifications
You must be signed in to change notification settings - Fork 298
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(doubleClick): Add Double click detection (#375) #382
Changes from 2 commits
2dd1088
1ae84b5
1e9850a
b26eab3
0c22936
d25fc52
c69e1ec
fa2f2d7
2af6f67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
import { | ||
RenderingEngine, | ||
Types, | ||
Enums, | ||
getRenderingEngine, | ||
} from '@cornerstonejs/core'; | ||
import { | ||
initDemo, | ||
createImageIdsAndCacheMetaData, | ||
setTitleAndDescription, | ||
addDropdownToToolbar, | ||
} from '../../../../utils/demo/helpers'; | ||
import * as cornerstoneTools from '@cornerstonejs/tools'; | ||
|
||
// This is for debugging purposes | ||
console.warn( | ||
'Click on index.ts to open source code for this example --------->' | ||
); | ||
|
||
const { | ||
LengthTool, | ||
ProbeTool, | ||
RectangleROITool, | ||
EllipticalROITool, | ||
BidirectionalTool, | ||
AngleTool, | ||
ToolGroupManager, | ||
ArrowAnnotateTool, | ||
Enums: csToolsEnums, | ||
} = cornerstoneTools; | ||
|
||
const { ViewportType } = Enums; | ||
const { Events } = cornerstoneTools.Enums; | ||
const { MouseBindings } = csToolsEnums; | ||
|
||
const renderingEngineId = 'myRenderingEngine'; | ||
const viewportId = 'CT_STACK'; | ||
|
||
document.documentElement.style.userSelect = 'none'; | ||
|
||
// ======== Set up page ======== // | ||
setTitleAndDescription( | ||
'Double Click With Stack Annotation Tools', | ||
'Double click detection before/during/after using annotation tools on a stack viewport.' | ||
); | ||
|
||
const content = document.getElementById('content'); | ||
const element = document.createElement('div'); | ||
|
||
// Disable right click context menu so we can have right click tools | ||
element.oncontextmenu = (e) => e.preventDefault(); | ||
|
||
element.id = 'cornerstone-element'; | ||
|
||
// Listen for the browser double click event in an ancestor of the viewport element for best results. | ||
// Listening for the browser double click event on the viewport element itself, could result | ||
// in receiving a 'dblclick' event when cornerstone is trying to suppress it. | ||
content.addEventListener('dblclick', () => { | ||
browserDoubleClickEventStatus.innerText = | ||
"Browser 'dblclick' event detected on an ancestor of the viewport element."; | ||
toggleCanvasSize(); | ||
|
||
const renderEngine = getRenderingEngine(renderingEngineId); | ||
renderEngine.resize(true); | ||
|
||
statusDiv.style.backgroundColor = '#00ff00'; | ||
}); | ||
|
||
element.addEventListener(Events.MOUSE_DOUBLE_CLICK, () => { | ||
cs3dDoubleClickEventStatus.innerText = `'${Events.MOUSE_DOUBLE_CLICK}' event detected on the viewport element.`; | ||
statusDiv.style.backgroundColor = null; | ||
}); | ||
|
||
element.addEventListener(Events.MOUSE_DOWN, () => { | ||
browserDoubleClickEventStatus.innerText = ''; | ||
cs3dDoubleClickEventStatus.innerText = ''; | ||
}); | ||
|
||
content.appendChild(element); | ||
|
||
// instruction elements | ||
const instructionsDiv = document.createElement('div'); | ||
instructionsDiv.style.width = element.style.width; | ||
|
||
content.append(instructionsDiv); | ||
|
||
let instructions = document.createElement('p'); | ||
instructions.innerText = `Select a tool from the drop down above the viewport. | ||
Left Click to use the selected tool. | ||
Try double clicking at any point before/during/after use. | ||
`; | ||
|
||
instructionsDiv.append(instructions); | ||
|
||
instructions = document.createElement('p'); | ||
instructions.innerText = `When a double click is detected, the viewport size changes and an info message is displayed below. | ||
Note that a double click during tool usage/interaction is suppressed`; | ||
|
||
instructionsDiv.append(instructions); | ||
|
||
// double click status info elements | ||
const statusDiv = document.createElement('div'); | ||
statusDiv.style.width = element.style.width; | ||
|
||
content.append(statusDiv); | ||
|
||
const browserDoubleClickEventStatus = document.createElement('p'); | ||
statusDiv.append(browserDoubleClickEventStatus); | ||
|
||
const cs3dDoubleClickEventStatus = document.createElement('p'); | ||
statusDiv.append(cs3dDoubleClickEventStatus); | ||
|
||
// canvas sizing | ||
const canvasSizes = ['500px', '750px']; | ||
|
||
function toggleCanvasSize() { | ||
const canvasSize = canvasSizes.shift(); | ||
canvasSizes.push(canvasSize); | ||
|
||
element.style.width = canvasSize; | ||
element.style.height = canvasSize; | ||
|
||
statusDiv.style.width = canvasSize; | ||
|
||
instructionsDiv.style.width = canvasSize; | ||
} | ||
|
||
toggleCanvasSize(); | ||
|
||
// ============================= // | ||
|
||
const toolGroupId = 'STACK_TOOL_GROUP_ID'; | ||
|
||
const toolsNames = [ | ||
LengthTool.toolName, | ||
ProbeTool.toolName, | ||
RectangleROITool.toolName, | ||
EllipticalROITool.toolName, | ||
BidirectionalTool.toolName, | ||
AngleTool.toolName, | ||
ArrowAnnotateTool.toolName, | ||
]; | ||
let selectedToolName = toolsNames[0]; | ||
|
||
addDropdownToToolbar({ | ||
options: { values: toolsNames, defaultValue: selectedToolName }, | ||
onSelectedValueChange: (newSelectedToolNameAsStringOrNumber) => { | ||
const newSelectedToolName = String(newSelectedToolNameAsStringOrNumber); | ||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); | ||
|
||
// Set the new tool active | ||
toolGroup.setToolActive(newSelectedToolName, { | ||
bindings: [ | ||
{ | ||
mouseButton: MouseBindings.Primary, // Left Click | ||
}, | ||
], | ||
}); | ||
|
||
// Set the old tool passive | ||
toolGroup.setToolPassive(selectedToolName); | ||
|
||
selectedToolName = <string>newSelectedToolName; | ||
}, | ||
}); | ||
|
||
/** | ||
* Runs the demo | ||
*/ | ||
async function run() { | ||
// Init Cornerstone and related libraries | ||
await initDemo(); | ||
|
||
// Add tools to Cornerstone3D | ||
cornerstoneTools.addTool(LengthTool); | ||
cornerstoneTools.addTool(ProbeTool); | ||
cornerstoneTools.addTool(RectangleROITool); | ||
cornerstoneTools.addTool(EllipticalROITool); | ||
cornerstoneTools.addTool(BidirectionalTool); | ||
cornerstoneTools.addTool(AngleTool); | ||
cornerstoneTools.addTool(ArrowAnnotateTool); | ||
|
||
// Define a tool group, which defines how mouse events map to tool commands for | ||
// Any viewport using the group | ||
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); | ||
|
||
// Add the tools to the tool group | ||
toolGroup.addTool(LengthTool.toolName); | ||
toolGroup.addTool(ProbeTool.toolName); | ||
toolGroup.addTool(RectangleROITool.toolName); | ||
toolGroup.addTool(EllipticalROITool.toolName); | ||
toolGroup.addTool(BidirectionalTool.toolName); | ||
toolGroup.addTool(AngleTool.toolName); | ||
toolGroup.addTool(ArrowAnnotateTool.toolName); | ||
|
||
// Set the initial state of the tools, here we set one tool active on left click. | ||
// This means left click will draw that tool. | ||
toolGroup.setToolActive(LengthTool.toolName, { | ||
bindings: [ | ||
{ | ||
mouseButton: MouseBindings.Primary, // Left Click | ||
}, | ||
], | ||
}); | ||
// We set all the other tools passive here, this means that any state is rendered, and editable | ||
// But aren't actively being drawn (see the toolModes example for information) | ||
toolGroup.setToolPassive(ProbeTool.toolName); | ||
toolGroup.setToolPassive(RectangleROITool.toolName); | ||
toolGroup.setToolPassive(EllipticalROITool.toolName); | ||
toolGroup.setToolPassive(BidirectionalTool.toolName); | ||
toolGroup.setToolPassive(AngleTool.toolName); | ||
toolGroup.setToolPassive(ArrowAnnotateTool.toolName); | ||
|
||
// Get Cornerstone imageIds and fetch metadata into RAM | ||
const imageIds = await createImageIdsAndCacheMetaData({ | ||
StudyInstanceUID: | ||
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463', | ||
SeriesInstanceUID: | ||
'1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561', | ||
wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb', | ||
}); | ||
|
||
// Instantiate a rendering engine | ||
const renderingEngine = new RenderingEngine(renderingEngineId); | ||
|
||
// Create a stack viewport | ||
const viewportInput = { | ||
viewportId, | ||
type: ViewportType.STACK, | ||
element, | ||
defaultOptions: { | ||
background: <Types.Point3>[0.2, 0, 0.2], | ||
}, | ||
}; | ||
|
||
renderingEngine.enableElement(viewportInput); | ||
|
||
// Set the tool group on the viewport | ||
toolGroup.addViewport(viewportId, renderingEngineId); | ||
|
||
// Get the stack viewport that was created | ||
const viewport = <Types.IStackViewport>( | ||
renderingEngine.getViewport(viewportId) | ||
); | ||
|
||
// Define a stack containing a single image | ||
const stack = [imageIds[0]]; | ||
|
||
// Set the stack on the viewport | ||
viewport.setStack(stack); | ||
|
||
// Render the image | ||
viewport.render(); | ||
} | ||
|
||
run(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { state } from '../../store'; | ||
|
||
/** | ||
* @function suppressEventForToolInteraction This is used as a generic event handler for tool events | ||
* on viewports that should be suppressed when the tool in being interacted with. | ||
* | ||
* @param evt the event to possibly suppress | ||
*/ | ||
export default function suppressEventForToolInteraction(evt) { | ||
if (state.isInteractingWithTool) { | ||
// Allow no further siblings or ancestors (on bubble phase) or descendants (on capture phase) | ||
// to handle this event. | ||
evt.stopImmediatePropagation(); | ||
evt.preventDefault(); | ||
return false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
import mouseDoubleClickListener from './mouseDoubleClickListener'; | ||
import mouseDownListener from './mouseDownListener'; | ||
import mouseDownListener, { | ||
mouseDoubleClickIgnoreListener, | ||
} from './mouseDownListener'; | ||
import mouseMoveListener from './mouseMoveListener'; | ||
|
||
/** | ||
|
@@ -12,6 +14,9 @@ import mouseMoveListener from './mouseMoveListener'; | |
*/ | ||
function disable(element: HTMLDivElement): void { | ||
element.removeEventListener('dblclick', mouseDoubleClickListener); | ||
element.removeEventListener('dblclick', mouseDoubleClickIgnoreListener, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This adds the listener for every enabled viewport. It is a bit of a waste since only one (global) listener is required. |
||
capture: true, // capture phase is the best way to ignore double clicks | ||
}); | ||
element.removeEventListener('mousedown', mouseDownListener); | ||
element.removeEventListener('mousemove', mouseMoveListener); | ||
} | ||
|
@@ -29,6 +34,10 @@ function enable(element: HTMLDivElement): void { | |
disable(element); | ||
|
||
element.addEventListener('dblclick', mouseDoubleClickListener); | ||
element.addEventListener('dblclick', mouseDoubleClickIgnoreListener, { | ||
capture: true, // capture phase is the best way to ignore double clicks | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there any reason we are adding again the dblclick listener? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes there are two reasons...
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then can we put them as inline comment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most certainly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I will move this logic code out of this index.ts and put it in mouseDownListener because none of it needs to be exposed outside that module. |
||
|
||
element.addEventListener('mousedown', mouseDownListener); | ||
element.addEventListener('mousemove', mouseMoveListener); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,7 +35,21 @@ function mouseDoubleClickListener(evt: MouseEvent): void { | |
deltaPoints, | ||
}; | ||
|
||
triggerEvent(element, Events.MOUSE_DOUBLE_CLICK, eventDetail); | ||
const preventDefault = !triggerEvent( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we rename this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or even better There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that |
||
element, | ||
Events.MOUSE_DOUBLE_CLICK, | ||
eventDetail | ||
); | ||
|
||
if (preventDefault) { | ||
// The Events.MOUSE_DOUBLE_CLICK was handled or cancelled, thus nobody else should handle this 'dblclick' event. | ||
|
||
// Use stopImmediatePropagation to lessen the possibility that a third party 'dblclick' | ||
// listener receives this event. However, there still is no guarantee | ||
// that any third party listener has not already handled the event. | ||
evt.stopImmediatePropagation(); | ||
evt.preventDefault(); | ||
} | ||
} | ||
|
||
export default mouseDoubleClickListener; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new example for double click.