diff --git a/extensions/vtk/src/commandsModule.js b/extensions/vtk/src/commandsModule.js index 677a50fc1..d57156712 100644 --- a/extensions/vtk/src/commandsModule.js +++ b/extensions/vtk/src/commandsModule.js @@ -18,6 +18,7 @@ const { BlendMode } = Constants; // TODO: Put this somewhere else let apis = {}; let currentSlabThickness = 0.1; +let defaultSlabThickness = 0.1; function getCrosshairCallbackForIndex(index) { return ({ worldPos }) => { @@ -117,6 +118,7 @@ function switchMPRInteractors(api, istyle) { let currentSlabThickness; if (currentIStyle.getSlabThickness && istyle.getSlabThickness) { currentSlabThickness = currentIStyle.getSlabThickness(); + defaultSlabThickness = currentSlabThickness; } renderWindow.getInteractor().setInteractorStyle(istyle); @@ -182,7 +184,7 @@ const actions = { switchMPRInteractors(api, istyle); }); }, - setSlabThickness: slabThickness => { + setSlabThickness: ({ slabThickness }) => { currentSlabThickness = slabThickness; apis.forEach(api => { @@ -191,15 +193,6 @@ const actions = { if (istyle.setSlabThickness) { istyle.setSlabThickness(currentSlabThickness); - - // TODO: Do this inside the interactors in a setSlabThickness function instead - const renderer = api.genericRenderWindow.getRenderer(); - const camera = renderer.getActiveCamera(); - const dist = camera.getDistance(); - const near = dist - currentSlabThickness / 2; - const far = dist + currentSlabThickness / 2; - - camera.setClippingRange(near, far); } renderWindow.render(); @@ -220,6 +213,32 @@ const actions = { renderWindow.render(); }); }, + setBlendModeToComposite: () => { + apis.forEach(api => { + const renderWindow = api.genericRenderWindow.getRenderWindow(); + const istyle = renderWindow.getInteractor().getInteractorStyle(); + + const mapper = api.volumes[0].getMapper(); + if (mapper.setBlendModeToComposite) { + mapper.setBlendModeToComposite(); + } + + if (istyle.setSlabThickness) { + istyle.setSlabThickness(defaultSlabThickness); + } + renderWindow.render(); + }); + }, + setBlendModeToMaximumIntensity: () => { + apis.forEach(api => { + const renderWindow = api.genericRenderWindow.getRenderWindow(); + const mapper = api.volumes[0].getMapper(); + if (mapper.setBlendModeToMaximumIntensity) { + mapper.setBlendModeToMaximumIntensity(); + } + renderWindow.render(); + }); + }, setBlendMode: ({ blendMode }) => { apis.forEach(api => { const renderWindow = api.genericRenderWindow.getRenderWindow(); @@ -338,12 +357,12 @@ const definitions = { options: {}, }, setBlendModeToComposite: { - commandFn: actions.setBlendMode, + commandFn: actions.setBlendModeToComposite, storeContexts: [], options: { blendMode: BlendMode.COMPOSITE_BLEND }, }, setBlendModeToMaximumIntensity: { - commandFn: actions.setBlendMode, + commandFn: actions.setBlendModeToMaximumIntensity, storeContexts: [], options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND }, }, diff --git a/extensions/vtk/src/toolbarComponents/SlabThicknessToolbarComponent.js b/extensions/vtk/src/toolbarComponents/SlabThicknessToolbarComponent.js new file mode 100644 index 000000000..50327f3e4 --- /dev/null +++ b/extensions/vtk/src/toolbarComponents/SlabThicknessToolbarComponent.js @@ -0,0 +1,207 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { Range, Checkbox, Select } from '@ohif/ui'; + +import './slab-thickness-toolbar-button.styl'; + +const SLIDER = { + MIN: 0.1, + MAX: 1000, + STEP: 0.1, +}; + +const ToolbarLabel = props => { + const { label } = props; + return
{label}
; +}; + +const ToolbarSlider = props => { + const { value, min, max, onChange } = props; + return ( +
+ + +
+ ); +}; + +const _getSelectOptions = button => { + return button.operationButtons.map(button => { + return { + key: button.label, + value: button.id, + }; + }); +}; + +const _getClassNames = (isActive, className) => { + return classnames('toolbar-button', 'slab-thickness', className, { + active: isActive, + }); +}; + +const _applySlabThickness = ( + value, + modeChecked, + toolbarClickCallback, + button +) => { + if (!modeChecked || !toolbarClickCallback) { + return; + } + + const { actionButton } = button; + + const generateOperation = (operation, value) => { + // Combine slider value into slider operation + const generatedOperation = { ...operation }; + generatedOperation.commandOptions = { + ...operation.commandOptions, + slabThickness: value, + }; + + return generatedOperation; + }; + + const operation = generateOperation(actionButton, value); + toolbarClickCallback(operation, event); +}; + +const _applyModeOperation = ( + operation, + modeChecked, + toolbarClickCallback, + button +) => { + // in case modeChecked has not being triggered by user yet + if (typeof modeChecked !== 'boolean') { + return; + } + + const { deactivateButton } = button; + + const _operation = modeChecked ? operation : deactivateButton; + if (toolbarClickCallback && _operation) { + toolbarClickCallback(_operation); + } +}; + +const _getInitialState = currentSelectedOption => { + return { + value: SLIDER.MIN, + sliderMin: SLIDER.MIN, + sliderMax: SLIDER.MAX, + modeChecked: undefined, + operation: currentSelectedOption, + }; +}; + +const INITIAL_OPTION_INDEX = 0; +const _getInitialtSelectedOption = (button = {}) => { + return ( + button.operationButtons && button.operationButtons[INITIAL_OPTION_INDEX] + ); +}; + +function SlabThicknessToolbarComponent({ + parentContext, + toolbarClickCallback, + button, + activeButtons, + isActive, + className, +}) { + const currentSelectedOption = _getInitialtSelectedOption(button); + const [state, setState] = useState(_getInitialState(currentSelectedOption)); + const { label, operationButtons } = button; + const _className = _getClassNames(isActive, className); + const selectOptions = _getSelectOptions(button); + function onChangeSelect(selectedValue) { + // find select value + const operation = operationButtons.find( + button => button.id === selectedValue + ); + + if (operation === state.operation) { + return; + } + + setState({ ...state, operation }); + } + + function onChangeCheckbox(checked) { + setState({ ...state, modeChecked: checked }); + } + + function onChangeSlider(event) { + const value = Number(event.target.value); + + if (value !== state.value) { + setState({ ...state, value, modeChecked: true }); + } + } + + useEffect(() => { + _applyModeOperation( + state.operation, + state.modeChecked, + toolbarClickCallback, + button + ); + }, [state.modeChecked, state.operation]); + + useEffect(() => { + _applySlabThickness( + state.value, + state.modeChecked, + toolbarClickCallback, + button + ); + }, [state.operation, state.modeChecked, state.value]); + + return ( +
+
+ + +
+
+ + +
+
+ ); +} + +SlabThicknessToolbarComponent.propTypes = { + parentContext: PropTypes.object.isRequired, + toolbarClickCallback: PropTypes.func.isRequired, + button: PropTypes.object.isRequired, + activeButtons: PropTypes.array.isRequired, + isActive: PropTypes.bool, + className: PropTypes.string, +}; + +export default SlabThicknessToolbarComponent; diff --git a/extensions/vtk/src/toolbarComponents/slab-thickness-toolbar-button.styl b/extensions/vtk/src/toolbarComponents/slab-thickness-toolbar-button.styl new file mode 100644 index 000000000..c8441e89f --- /dev/null +++ b/extensions/vtk/src/toolbarComponents/slab-thickness-toolbar-button.styl @@ -0,0 +1,55 @@ +.slab-thickness + display: flex; + flex-direction: row; + align-items: flex-end; + height: 60px; + margin-top: -12px; + &:hover + color: var(--default-color); + .container + padding-right: 10px; + height: inherit; + display: flex; + flex-direction: column; + justify-content: space-between; + .toolbar-slider-container + width: 80px; + margin: 0px 2px 2px 2px; + font-size: 12px; + &:hover + color: var(--hover-color); + input + height: 20px; + padding-top: 6px; + &::-moz-range-thumb + height: 16px; + margin-top: -7px; + &::-webkit-slider-thumb + height: 16px; + margin-top: -7px; + .controller + width: 62px; + .ohif-check-container + margin-bottom: 5px; + padding-left: 18px; + .ohif-check-label + text-transform: capitalize; + &:hover + color: var(--hover-color); + .ohif-checkbox + border-radius: 3px; + background-color: white; + width: 12px; + height: 12px; + top: 22%; + &.ohif-checked:after + background-color: var(--active-color); + top: 17%; + left: 17%; + .select-ohif + color: var(--default-color); + border-color: var(--default-color); + background-color: black; + background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'); + &:hover + color: var(--hover-color); diff --git a/extensions/vtk/src/toolbarModule.js b/extensions/vtk/src/toolbarModule.js index 9f2832047..805644dd2 100644 --- a/extensions/vtk/src/toolbarModule.js +++ b/extensions/vtk/src/toolbarModule.js @@ -1,3 +1,5 @@ +import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent'; + const TOOLBAR_BUTTON_TYPES = { COMMAND: 'command', SET_TOOL_ACTIVE: 'setToolActive', @@ -31,6 +33,7 @@ const definitions = [ commandName: 'enableRotateTool', commandOptions: {}, }, + /* { id: 'setBlendModeToComposite', label: 'Disable MIP', @@ -68,6 +71,50 @@ const definitions = [ commandName: 'decreaseSlabThickness', commandOptions: {}, }, + */ + { + id: 'changeSlabThickness', + label: 'Slab Thickness', + icon: 'soft-tissue', + CustomComponent: SlabThicknessToolbarComponent, + commandName: 'setSlabThickness', + actionButton: { + id: 'setSlabThickness', + label: 'slider', + type: TOOLBAR_BUTTON_TYPES.COMMAND, + commandName: 'setSlabThickness', + commandOptions: {}, + }, + deactivateButton: { + id: 'setBlendModeToComposite', + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setBlendModeToComposite', + commandOptions: {}, + }, + operationButtons: [ + { + id: 'setBlendModeToMaximumIntensity', + label: 'MIP', + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setBlendModeToMaximumIntensity', + commandOptions: {}, + }, + { + id: 'setBlendModeToMinimumIntensity', + label: 'MinIP', + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setBlendModeToMinimumIntensity', + commandOptions: {}, + }, + { + id: 'setBlendModeToAverageIntensity', + label: 'AvgIP', + type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, + commandName: 'setBlendModeToAverageIntensity', + commandOptions: {}, + }, + ], + }, ]; export default { diff --git a/platform/ui/src/components/checkbox/checkbox.js b/platform/ui/src/components/checkbox/checkbox.js index 5f1f16ea3..0315bab02 100644 --- a/platform/ui/src/components/checkbox/checkbox.js +++ b/platform/ui/src/components/checkbox/checkbox.js @@ -6,15 +6,29 @@ export class Checkbox extends Component { static propTypes = { label: PropTypes.string.isRequired, checked: PropTypes.bool, + onChange: PropTypes.func, }; constructor(props) { super(props); - this.state = { checked: props.checked, label: props.label }; + this.state = { checked: !!props.checked, label: props.label }; } handleChange(e) { - this.setState({ checked: e.target.checked }); + const checked = e.target.checked; + this.setState({ checked }); + if (this.props.onChange) this.props.onChange(checked); + } + + componentDidUpdate(props) { + const { checked = false, label } = props; + + if (this.state.checked !== checked || this.state.label !== label) { + this.setState({ + checked, + label, + }); + } } render() { @@ -32,9 +46,7 @@ export class Checkbox extends Component { { - this.handleChange(e); - }} + onChange={this.handleChange.bind(this)} /> {checkbox} {this.state.label} diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js index bc9beb74a..34b3d421e 100644 --- a/platform/ui/src/components/index.js +++ b/platform/ui/src/components/index.js @@ -13,6 +13,7 @@ import { UserPreferencesModal, } from './userPreferencesModal'; +import { Checkbox } from './checkbox'; import { CineDialog } from './cineDialog'; import { QuickSwitch } from './quickSwitch'; import { RoundedButtonGroup } from './roundedButtonGroup'; @@ -23,6 +24,7 @@ import { ToolbarSection } from './toolbarSection'; import { Tooltip } from './tooltip'; export { + Checkbox, CineDialog, ExampleDropTarget, LayoutButton, diff --git a/platform/ui/src/elements/form/Range.css b/platform/ui/src/elements/form/Range.css index 434a8486f..3bdaa94c2 100644 --- a/platform/ui/src/elements/form/Range.css +++ b/platform/ui/src/elements/form/Range.css @@ -2,6 +2,7 @@ margin: 0; width: 100%; -webkit-appearance: none; + background-color: transparent; } .range:focus { diff --git a/platform/ui/src/elements/form/Range.js b/platform/ui/src/elements/form/Range.js index a8647a8fa..479668431 100644 --- a/platform/ui/src/elements/form/Range.js +++ b/platform/ui/src/elements/form/Range.js @@ -11,7 +11,7 @@ class Range extends React.Component { handleChange = event => { this.setState({ value: event.target.value }); - if (this.props.onChange) this.props.onChange(); + if (this.props.onChange) this.props.onChange(event); }; render() { @@ -21,6 +21,7 @@ class Range extends React.Component { value={this.state.value} min={this.props.min} max={this.props.max} + step={this.props.step || 1} onChange={this.handleChange} id={this.props.id} className="range" diff --git a/platform/ui/src/elements/form/Select.css b/platform/ui/src/elements/form/Select.css index 0ea43c5a9..3f420fd39 100644 --- a/platform/ui/src/elements/form/Select.css +++ b/platform/ui/src/elements/form/Select.css @@ -34,10 +34,6 @@ } .select-ohif:focus { - border-color: #aaa; - box-shadow: 0 0 1px 3px rgba(59, 153, 252, 0.7); - box-shadow: 0 0 0 3px -moz-mac-focusring; - color: #222; outline: none; } diff --git a/platform/ui/src/elements/form/Select.js b/platform/ui/src/elements/form/Select.js index 5652f9a8c..fe8a79dbb 100644 --- a/platform/ui/src/elements/form/Select.js +++ b/platform/ui/src/elements/form/Select.js @@ -11,8 +11,9 @@ class Select extends Component { } handleChange = event => { - this.setState({ value: event.target.value }); - if (this.props.onChange) this.props.onChange(); + const value = event.target.value; + this.setState({ value }); + if (this.props.onChange) this.props.onChange(value); }; render() { diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js index 90a121e9a..ba4d32550 100644 --- a/platform/ui/src/index.js +++ b/platform/ui/src/index.js @@ -1,4 +1,5 @@ import { + Checkbox, CineDialog, ExampleDropTarget, LayoutButton, @@ -25,7 +26,7 @@ import { import { ICONS, Icon } from './elements'; // Alias this for now as not all dependents are using strict versioning -import { DropdownMenu as Dropdown } from './elements/form'; +import { DropdownMenu as Dropdown, Range, Select } from './elements/form'; import ExpandableToolMenu from './viewer/ExpandableToolMenu.js'; import LayoutManager from './LayoutChooser/LayoutManager.js'; import LayoutPanelDropTarget from './LayoutChooser/LayoutPanelDropTarget.js'; @@ -38,6 +39,7 @@ import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js'; export { ICONS, // + Checkbox, CineDialog, Dropdown, ExpandableToolMenu, @@ -53,8 +55,10 @@ export { OverlayTrigger, PlayClipButton, QuickSwitch, + Range, RoundedButtonGroup, ScrollableArea, + Select, SelectTree, SimpleDialog, StudyBrowser, diff --git a/platform/ui/src/viewer/ExpandableToolMenu.js b/platform/ui/src/viewer/ExpandableToolMenu.js index 0934e212f..e7df9cc34 100644 --- a/platform/ui/src/viewer/ExpandableToolMenu.js +++ b/platform/ui/src/viewer/ExpandableToolMenu.js @@ -108,16 +108,8 @@ class ExpandableToolMenu extends React.Component { }; render() { - return ( - + const getToolBarButtonComponent = () => { + return ( + ); + }; + + const toolbarComponent = getToolBarButtonComponent(); + + return ( + + {toolbarComponent} ); } diff --git a/platform/viewer/src/connectedComponents/ToolbarRow.js b/platform/viewer/src/connectedComponents/ToolbarRow.js index 1612f44e0..ef5705d0c 100644 --- a/platform/viewer/src/connectedComponents/ToolbarRow.js +++ b/platform/viewer/src/connectedComponents/ToolbarRow.js @@ -229,6 +229,7 @@ function _getButtonComponents(toolbarButtons, activeButtons) { }); } + /** * A handy way for us to handle different button types. IE. firing commands for * buttons, or initiation built in behavior.