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:
1 parent
e6816f6de4
commit
691e26731f
10 files changed
+373
-33
No files matched your search
@@ -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(
|
||||
|
||||
@@ -17,9 +17,46 @@ interface HotkeyDefinitions {
|
||||
[key: string]: HotkeyDefinition;
|
||||
}
|
||||
|
||||
const MODIFIER_OPTIONS = [
|
||||
{ value: '16', label: 'Shift' },
|
||||
{ value: '17', label: 'Ctrl' },
|
||||
{ value: '18', label: 'Alt' },
|
||||
{ value: '91', label: 'Meta' },
|
||||
];
|
||||
|
||||
const DEFAULT_TOOL_BINDINGS_STORAGE_KEY = 'user-preferred-tool-bindings';
|
||||
|
||||
function getToolBindingsStorageKey(customizationService: any): string {
|
||||
const customizationValue = customizationService?.getCustomization(
|
||||
'ohif.userPreferences.toolBindingsStorageKey'
|
||||
);
|
||||
|
||||
return typeof customizationValue === 'string' && customizationValue.length > 0
|
||||
? customizationValue
|
||||
: DEFAULT_TOOL_BINDINGS_STORAGE_KEY;
|
||||
}
|
||||
|
||||
function getToolModifier(toolGroupService: any, toolGroupId: string, toolName: string): string | null {
|
||||
if (!toolGroupService) {
|
||||
return null;
|
||||
}
|
||||
const bindings = toolGroupService.getToolBindings(toolGroupId, toolName);
|
||||
if (!bindings?.length) {
|
||||
return null;
|
||||
}
|
||||
const modifierBinding = bindings.find(
|
||||
binding => binding.modifierKey != null && binding.numTouchPoints == null
|
||||
);
|
||||
|
||||
return modifierBinding?.modifierKey != null ? String(modifierBinding.modifierKey) : null;
|
||||
}
|
||||
|
||||
function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
const { hotkeysManager } = useSystem();
|
||||
const { hotkeysManager, servicesManager } = useSystem();
|
||||
const { t, i18n: i18nextInstance } = useTranslation('UserPreferencesModal');
|
||||
const toolGroupService = (servicesManager as any)?.services?.toolGroupService;
|
||||
const customizationService = (servicesManager as any)?.services?.customizationService;
|
||||
const toolBindingsStorageKey = getToolBindingsStorageKey(customizationService);
|
||||
|
||||
const { hotkeyDefinitions = {}, hotkeyDefaults = {} } = hotkeysManager;
|
||||
|
||||
@@ -51,9 +88,15 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
|
||||
const currentLanguage = currentLanguageFn();
|
||||
|
||||
const initialCrosshairModifier = useMemo(
|
||||
() => getToolModifier(toolGroupService, 'mpr', 'Crosshairs'),
|
||||
[toolGroupService]
|
||||
);
|
||||
|
||||
const [state, setState] = useState({
|
||||
hotkeyDefinitions: initialHotkeyDefinitions,
|
||||
languageValue: currentLanguage.value,
|
||||
crosshairModifier: initialCrosshairModifier,
|
||||
});
|
||||
|
||||
const onLanguageChangeHandler = (value: string) => {
|
||||
@@ -78,9 +121,11 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
...state,
|
||||
languageValue: defaultLanguage.value,
|
||||
hotkeyDefinitions: resolvedHotkeyDefaults,
|
||||
crosshairModifier: initialCrosshairModifier,
|
||||
}));
|
||||
|
||||
hotkeysManager.restoreDefaultBindings();
|
||||
localStorage.removeItem(toolBindingsStorageKey);
|
||||
};
|
||||
|
||||
const displayNames = React.useMemo(() => {
|
||||
@@ -165,6 +210,44 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
/>
|
||||
))}
|
||||
</UserPreferencesModal.HotkeysGrid>
|
||||
|
||||
{state.crosshairModifier != null && (
|
||||
<>
|
||||
<UserPreferencesModal.SubHeading>
|
||||
{t('ModifierKeys', { defaultValue: 'Modifier Keys' })}
|
||||
</UserPreferencesModal.SubHeading>
|
||||
<UserPreferencesModal.HotkeysGrid>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-foreground text-base">
|
||||
{t('CrosshairsModifier', { defaultValue: 'Crosshairs' })}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{t('PlusLeftClick', { defaultValue: 'Left Click +' })}
|
||||
</span>
|
||||
<Select
|
||||
value={state.crosshairModifier}
|
||||
onValueChange={val => setState(s => ({ ...s, crosshairModifier: val }))}
|
||||
>
|
||||
<SelectTrigger className="w-16">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{MODIFIER_OPTIONS.map(opt => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</UserPreferencesModal.HotkeysGrid>
|
||||
</>
|
||||
)}
|
||||
</UserPreferencesModal.Body>
|
||||
<FooterAction>
|
||||
<FooterAction.Left>
|
||||
@@ -191,6 +274,19 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
return; // Exit early since we're reloading
|
||||
}
|
||||
hotkeysManager.setHotkeys(state.hotkeyDefinitions);
|
||||
|
||||
if (toolGroupService && state.crosshairModifier != null) {
|
||||
const bindings = [
|
||||
{ mouseButton: 1, modifierKey: Number(state.crosshairModifier) },
|
||||
];
|
||||
toolGroupService.setToolBindings('mpr', 'Crosshairs', bindings);
|
||||
toolGroupService.applyToolBindings('mpr', 'Crosshairs');
|
||||
localStorage.setItem(
|
||||
toolBindingsStorageKey,
|
||||
JSON.stringify({ mpr: { Crosshairs: bindings } })
|
||||
);
|
||||
}
|
||||
|
||||
hotkeysModule.stopRecord();
|
||||
hotkeysModule.unpause();
|
||||
hide();
|
||||
@@ -206,4 +302,5 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
||||
|
||||
export default {
|
||||
'ohif.userPreferencesModal': UserPreferencesModalDefault,
|
||||
'ohif.userPreferences.toolBindingsStorageKey': DEFAULT_TOOL_BINDINGS_STORAGE_KEY,
|
||||
};
|
||||
Reference in new issue
Block a user