-
Notifications
You must be signed in to change notification settings - Fork 327
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
Merged
sedghi
merged 9 commits into
cornerstonejs:main
from
jbocce:fix/#375-double-click-detection
Jan 26, 2023
Merged
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
2dd1088
fix(mouseDownListener): Double click detection (#375)
jbocce 1ae84b5
Fixing syntax error in example-info.json.
jbocce 1e9850a
PR feedback: CS3D should not suppress a double click it has detected in
jbocce b26eab3
PR feedback - using a drag tolerance to help determine if a double cl…
jbocce 0c22936
PR updates:
jbocce d25fc52
Added a TODO for the double click tolerance for touch devices.
jbocce c69e1ec
PR feedback - restored adding and removing the double click ignore li…
jbocce fa2f2d7
Adding the ignore double click listener back on the element.
jbocce 2af6f67
Adding and removing the ignore double click listener in the tools init
jbocce File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
249 changes: 249 additions & 0 deletions
249
packages/tools/examples/doubleClickWithStackAnnotationTools/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
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'; | ||
|
||
// It is best to listen for the browser double click event on an ancestor of the viewport | ||
// element instead of the viewport element itself. This is so that in case CS3D needs to | ||
// handle the double click first (e.g. edit arrow annotation) and stop its propagation. | ||
content.addEventListener('dblclick', () => { | ||
toggleCanvasSize(); | ||
|
||
browserDoubleClickEventStatus.style.visibility = ''; | ||
const renderEngine = getRenderingEngine(renderingEngineId); | ||
renderEngine.resize(true); | ||
|
||
statusDiv.style.backgroundColor = '#00ff00'; | ||
}); | ||
|
||
element.addEventListener(Events.MOUSE_DOWN, () => { | ||
browserDoubleClickEventStatus.style.visibility = 'hidden'; | ||
statusDiv.style.backgroundColor = null; | ||
}); | ||
|
||
content.appendChild(element); | ||
|
||
// double click status info elements | ||
const statusDiv = document.createElement('div'); | ||
statusDiv.style.width = element.style.width; | ||
|
||
content.append(statusDiv); | ||
|
||
const browserDoubleClickEventStatus = document.createElement('p'); | ||
browserDoubleClickEventStatus.style.visibility = 'hidden'; | ||
browserDoubleClickEventStatus.innerText = | ||
"Browser 'dblclick' event detected on a viewport element ancestor."; | ||
statusDiv.append(browserDoubleClickEventStatus); | ||
|
||
// 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 just below the viewport. | ||
Note that a double click is permitted during any phase of annotation creation and it does not change the state of the annotation. | ||
Double clicking an arrow annotation to edit its text stops the event from bubbling up.`; | ||
|
||
instructionsDiv.append(instructions); | ||
|
||
// 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; | ||
} | ||
|
||
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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,14 @@ import mouseMoveListener from './mouseMoveListener'; | |
*/ | ||
function disable(element: HTMLDivElement): void { | ||
element.removeEventListener('dblclick', mouseDoubleClickListener); | ||
|
||
// A separate double click listener for the element. Separate because... | ||
// - it listens on the capture phase (and not the typical bubble phase) | ||
// - the data used to ignore the double click is private to mouseDoubleClickIgnoreListener | ||
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 +39,10 @@ function enable(element: HTMLDivElement): void { | |
disable(element); | ||
|
||
element.addEventListener('dblclick', mouseDoubleClickListener); | ||
element.addEventListener('dblclick', mouseDoubleClickIgnoreListener, { | ||
capture: true, | ||
}); | ||
|
||
element.addEventListener('mousedown', mouseDownListener); | ||
element.addEventListener('mousemove', mouseMoveListener); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.