feat(ui-next): Adds toggle state for ToolButton and Crosshair example (#5914)

Co-authored-by: sedghi <ar.sedghi@gmail.com>
This commit is contained in:
Dan Rukasandsedghi authored and GitHub committed 2026-04-30 10:13:58 -04:00
1 parent e6816f6de4
commit 691e26731f
10 files changed
+373 -33

No files matched your search

+54 -24
View File
@@ -145,7 +145,21 @@ function commandsModule({
function _getActiveViewportToolGroupId() {
const viewport = _getActiveViewportEnabledElement();
return toolGroupService.getToolGroupForViewport(viewport.id);
const toolGroup = viewport && toolGroupService.getToolGroupForViewport(viewport.id);
return toolGroup?.id;
}
function _usesPrimaryActivation(bindings) {
if (!bindings?.length) {
return true;
}
return bindings.some(
binding =>
binding.mouseButton === Enums.MouseBindings.Primary &&
binding.modifierKey == null &&
binding.numTouchPoints == null
);
}
function _getActiveSegmentationInfo() {
@@ -1017,32 +1031,48 @@ function commandsModule({
toolIsEnabled ? toolGroup.setToolDisabled(toolName) : toolGroup.setToolEnabled(toolName);
},
toggleActiveDisabledToolbar({ value, itemId, toolGroupId }) {
toggleActiveDisabledToolbar({ value, itemId, toolGroupId, toolGroupIds }) {
const toolName = itemId || value;
toolGroupId = toolGroupId ?? _getActiveViewportToolGroupId();
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
return;
}
const resolvedToolGroupIds = toolGroupIds?.length
? toolGroupIds
: [toolGroupId ?? _getActiveViewportToolGroupId()];
const toolIsActive = [
Enums.ToolModes.Active,
Enums.ToolModes.Enabled,
Enums.ToolModes.Passive,
].includes(toolGroup.getToolOptions(toolName).mode);
toolIsActive
? toolGroup.setToolDisabled(toolName)
: actions.setToolActive({ toolName, toolGroupId });
// we should set the previously active tool to active after we set the
// current tool disabled
if (toolIsActive) {
const prevToolName = toolGroup.getPrevActivePrimaryToolName();
if (prevToolName !== toolName) {
actions.setToolActive({ toolName: prevToolName, toolGroupId });
resolvedToolGroupIds.forEach(toolGroupId => {
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
return;
}
}
const toolIsActive = [
Enums.ToolModes.Active,
Enums.ToolModes.Enabled,
Enums.ToolModes.Passive,
].includes(toolGroup.getToolOptions(toolName).mode);
if (toolIsActive) {
toolGroup.setToolDisabled(toolName);
const bindings = toolGroupService.getToolBindings(toolGroupId, toolName);
if (_usesPrimaryActivation(bindings)) {
// we should set the previously active tool to active after we set the
// current tool disabled
const prevToolName = toolGroup.getPrevActivePrimaryToolName();
if (prevToolName !== toolName) {
actions.setToolActive({ toolName: prevToolName, toolGroupId });
}
}
return;
}
const bindings = toolGroupService.getToolBindings(toolGroupId, toolName);
if (_usesPrimaryActivation(bindings)) {
actions.setToolActive({ toolName, toolGroupId, bindings });
} else {
toolGroup.setToolActive(toolName, { bindings });
}
});
},
setToolActiveToolbar: ({ value, itemId, toolName, toolGroupIds = [], bindings }) => {
// Sometimes it is passed as value (tools with options), sometimes as itemId (toolbar buttons)
@@ -429,6 +429,40 @@ export default function getToolbarModule({ servicesManager, extensionManager }:
};
},
},
{
name: 'evaluate.cornerstoneTool.toggleWithModifier',
evaluate: ({ viewportId, button, disabledText, toggledOnIcon, defaultIcon }) => {
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup) {
return;
}
const toolName = toolbarService.getToolNameForButton(button);
if (!toolGroup.hasTool(toolName)) {
return getDisabledState(disabledText);
}
const { mode } = toolGroup.getToolOptions(toolName) ?? {};
const isToggled =
mode === Enums.ToolModes.Passive ||
mode === Enums.ToolModes.Active ||
mode === Enums.ToolModes.Enabled;
const toolBindings = toolGroupService.getToolBindings(toolGroup.id, toolName);
const hasModifierKey =
toolBindings?.some(binding => binding.modifierKey != null) ?? false;
return {
disabled: false,
isActive: false,
isToggled,
icon:
isToggled && hasModifierKey && toggledOnIcon
? toggledOnIcon
: defaultIcon ?? button.props.icon,
};
},
},
{
name: 'evaluate.action',
evaluate: () => {
@@ -36,7 +36,9 @@ export default class ToolGroupService {
cornerstoneViewportService: any;
viewportGridService: any;
uiNotificationService: any;
customizationService: any;
private toolGroupIds: Set<string> = new Set();
private toolBindingsMap: Map<string, Map<string, Array<Record<string, unknown>>>> = new Map();
/**
* Service-specific
*/
@@ -44,11 +46,17 @@ export default class ToolGroupService {
EVENTS: { [key: string]: string };
constructor(servicesManager: AppTypes.ServicesManager) {
const { cornerstoneViewportService, viewportGridService, uiNotificationService } =
const {
cornerstoneViewportService,
viewportGridService,
uiNotificationService,
customizationService,
} =
servicesManager.services;
this.cornerstoneViewportService = cornerstoneViewportService;
this.viewportGridService = viewportGridService;
this.uiNotificationService = uiNotificationService;
this.customizationService = customizationService;
this.listeners = {};
this.EVENTS = EVENTS;
Object.assign(this, pubSubServiceInterface);
@@ -122,6 +130,7 @@ export default class ToolGroupService {
public destroy(): void {
ToolGroupManager.destroy();
this.toolGroupIds = new Set();
this.toolBindingsMap.clear();
eventTarget.removeEventListener(Enums.Events.TOOL_ACTIVATED, this._onToolActivated);
}
@@ -198,6 +207,7 @@ export default class ToolGroupService {
// this.changeConfigurationIfNecessary(toolGroup, volumeId);
this._addTools(toolGroup, tools, configs);
this._setToolsMode(toolGroup, tools);
this._loadPersistedBindings(toolGroupId);
}
public createToolGroupAndAddTools(toolGroupId: string, tools: Array<Tool>): Types.IToolGroup {
@@ -242,29 +252,92 @@ export default class ToolGroupService {
return this.getToolGroup(toolGroupId)?.getActivePrimaryMouseButtonTool();
}
public getToolBindings(
toolGroupId: string,
toolName: string
): Array<Record<string, unknown>> | undefined {
return this.toolBindingsMap.get(toolGroupId)?.get(toolName);
}
public setToolBindings(
toolGroupId: string,
toolName: string,
bindings: Array<Record<string, unknown>>
): void {
if (!this.toolBindingsMap.has(toolGroupId)) {
this.toolBindingsMap.set(toolGroupId, new Map());
}
this.toolBindingsMap.get(toolGroupId).set(toolName, bindings);
}
public applyToolBindings(toolGroupId: string, toolName: string): void {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
if (!toolGroup || !toolGroup.hasTool(toolName)) {
return;
}
const bindings = this.getToolBindings(toolGroupId, toolName);
if (!bindings) {
return;
}
const { mode } = toolGroup.getToolOptions(toolName);
if (
mode === Enums.ToolModes.Active ||
mode === Enums.ToolModes.Passive ||
mode === Enums.ToolModes.Enabled
) {
toolGroup.setToolActive(toolName, { bindings });
}
}
public getAllToolBindings(): Array<{
toolGroupId: string;
toolName: string;
bindings: Array<Record<string, unknown>>;
}> {
const result = [];
for (const [toolGroupId, toolMap] of this.toolBindingsMap) {
for (const [toolName, bindings] of toolMap) {
result.push({ toolGroupId, toolName, bindings });
}
}
return result;
}
private _setToolsMode(toolGroup, tools) {
const { active, passive, enabled, disabled } = tools;
if (active) {
active.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
}
toolGroup.setToolActive(toolName, { bindings });
});
}
if (passive) {
passive.forEach(({ toolName }) => {
passive.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
}
toolGroup.setToolPassive(toolName);
});
}
if (enabled) {
enabled.forEach(({ toolName }) => {
enabled.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
}
toolGroup.setToolEnabled(toolName);
});
}
if (disabled) {
disabled.forEach(({ toolName }) => {
disabled.forEach(({ toolName, bindings }) => {
if (bindings) {
this.setToolBindings(toolGroup.id, toolName, bindings);
}
toolGroup.setToolDisabled(toolName);
});
}
@@ -300,6 +373,35 @@ export default class ToolGroupService {
}
}
private _loadPersistedBindings(toolGroupId: string): void {
try {
const stored = localStorage.getItem(this._getToolBindingsStorageKey());
if (!stored) {
return;
}
const parsed = JSON.parse(stored);
const toolGroupBindings = parsed[toolGroupId];
if (!toolGroupBindings) {
return;
}
for (const [toolName, bindings] of Object.entries(toolGroupBindings)) {
this.setToolBindings(toolGroupId, toolName, bindings as Array<Record<string, unknown>>);
}
} catch {
// ignore corrupt localStorage
}
}
private _getToolBindingsStorageKey(): string {
const customizationValue = this.customizationService?.getCustomization(
'ohif.userPreferences.toolBindingsStorageKey'
);
return typeof customizationValue === 'string' && customizationValue.length > 0
? customizationValue
: 'user-preferred-tool-bindings';
}
private _onToolActivated = (evt: Types.EventTypes.ToolActivatedEventType) => {
const { toolGroupId, toolName, toolBindingsOptions } = evt.detail;
const isPrimaryTool = toolBindingsOptions.bindings?.some(