diff --git a/niivue/src/components/App.ts b/niivue/src/components/App.ts index 306a268..30226f2 100644 --- a/niivue/src/components/App.ts +++ b/niivue/src/components/App.ts @@ -25,10 +25,17 @@ export const App = () => { ` } + +export const enum SelectionMode { + NONE, + SINGLE, + MULTIPLE, +} + export interface AppProps { nvArray: Signal selection: Signal> - selectionMode: Signal + selectionMode: Signal hideUI: Signal sliceType: Signal location: Signal @@ -44,7 +51,7 @@ function useAppState(): AppProps { return { nvArray: useSignal([]), selection: useSignal>([]), - selectionMode: useSignal(0), + selectionMode: useSignal(SelectionMode.NONE), hideUI: useSignal(3), // 0: hide all, 1: show name, 2: hide overlay, 3: show-all sliceType: useSignal(SLICE_TYPE.MULTIPLANAR), // all views location: useSignal(''), diff --git a/niivue/src/components/Menu.ts b/niivue/src/components/Menu.ts index f78a2c4..dc6079c 100644 --- a/niivue/src/components/Menu.ts +++ b/niivue/src/components/Menu.ts @@ -1,10 +1,16 @@ import { html } from 'htm/preact' -import { AppProps } from './App' import { Signal, computed, effect, useSignal } from '@preact/signals' -import { addDcmFolderEvent, addImagesEvent, addOverlayEvent, openImageFromURL } from '../events' import { SLICE_TYPE } from '@niivue/niivue' +import { AppProps, SelectionMode } from './App' import { ScalingBox } from './ScalingBox' import { getMetadataString, getNumberOfPoints } from '../utility' +import { + addDcmFolderEvent, + addImagesEvent, + addOverlayEvent, + openImageFromURL, + ExtendedNiivue, +} from '../events' import { HeaderDialog, ImageSelect, @@ -14,7 +20,6 @@ import { ToggleEntry, toggle, } from './MenuElements' -import { ExtendedNiivue } from '../events' export const Menu = (props: AppProps) => { const { selection, selectionMode, nvArray, sliceType, hideUI } = props @@ -28,12 +33,14 @@ export const Menu = (props: AppProps) => { const crosshair = useSignal(true) const radiologicalConvention = useSignal(false) const colorbar = useSignal(false) + const selectionActive = useSignal(false) + const selectMultiple = useSignal(false) // Computed const isOverlay = computed(() => nvArraySelected.value[0]?.volumes?.length > 1) const multipleVolumes = computed(() => nvArray.value.length > 1) const nvArraySelected = computed(() => - selectionMode.value > 0 && selection.value.length > 0 + selectionMode.value != SelectionMode.NONE && selection.value.length > 0 ? nvArray.value.filter((_, i) => selection.value.includes(i)) : nvArray.value, ) @@ -55,7 +62,8 @@ export const Menu = (props: AppProps) => { }) // Effects that occur when state or computed changes - effect(() => ensureAlwaysSelectedAvailable(selection, nvArray, selectionMode)) + effect(() => applySelectionModeChange(selectionMode, selectionActive, selectMultiple)) + effect(() => ensureValidSelection(selection, nvArray, selectionMode)) effect(() => applyInterpolation(nvArray, interpolation)) effect(() => applyCrosshairWidth(nvArray, crosshair)) effect(() => applyRadiologicalConvention(nvArray, radiologicalConvention)) @@ -147,6 +155,7 @@ export const Menu = (props: AppProps) => { } const selectAll = () => { + selectMultiple.value = true selection.value = nvArray.value.map((_, i) => i) } @@ -219,7 +228,8 @@ export const Menu = (props: AppProps) => { console.log('Not implemented yet')} /> --> <${MenuEntry} label="Set Headers to 1" onClick=${setVoxelSize1AndOrigin0} /> - <${ImageSelect} label="Select" state=${selectionMode} visible=${multipleVolumes}> + <${ImageSelect} label="Select" state=${selectionActive} visible=${multipleVolumes}> + <${ToggleEntry} label="Multiple" state=${selectMultiple} /> <${MenuEntry} label="Select All" onClick=${selectAll} /> @@ -235,17 +245,26 @@ export const Menu = (props: AppProps) => { ` } -function ensureAlwaysSelectedAvailable( +function applySelectionModeChange( + selectionMode: Signal, + selectionActive: Signal, + selectMultiple: Signal, +) { + if (!selectionActive.value) selectionMode.value = SelectionMode.NONE + else if (selectMultiple.value) selectionMode.value = SelectionMode.MULTIPLE + else selectionMode.value = SelectionMode.SINGLE +} + +function ensureValidSelection( selection: Signal, nvArray: Signal, - selectionMode: Signal, + selectionMode: Signal, ) { - if (selection.value.length == 0 && nvArray.value.length > 0) { - if (selectionMode.value == 1) { - selection.value = [0] - } else { - selection.value = nvArray.value.map((_, i) => i) - } + if (nvArray.value.length == 0) return + else if (selectionMode.value == SelectionMode.SINGLE && selection.value.length != 1) + selection.value = [0] + else if (selectionMode.value == SelectionMode.MULTIPLE && selection.value.length == 0) { + selection.value = nvArray.value.map((_, i) => i) } } diff --git a/niivue/src/components/MenuElements.ts b/niivue/src/components/MenuElements.ts index 5d3f01d..88514bf 100644 --- a/niivue/src/components/MenuElements.ts +++ b/niivue/src/components/MenuElements.ts @@ -117,7 +117,10 @@ export const ImageSelect = ({ label, state, children, visible }: any) => { diff --git a/niivue/src/components/Volume.ts b/niivue/src/components/Volume.ts index bb76922..176bd6b 100644 --- a/niivue/src/components/Volume.ts +++ b/niivue/src/components/Volume.ts @@ -1,7 +1,7 @@ import { html } from 'htm/preact' import { NiiVueCanvas } from './NiiVueCanvas' import { computed, useSignal } from '@preact/signals' -import { AppProps } from './App' +import { AppProps, SelectionMode } from './App' import { ExtendedNiivue } from '../events' export interface VolumeProps { @@ -20,15 +20,17 @@ export const Volume = (props: AppProps & VolumeProps) => { const selected = computed(() => selection.value.includes(volumeIndex)) // it would maybe need a invisible box over the volume to prevent the click event, stopPropagation and preventDefault don't work - const selectClick = selectionMode.value - ? () => { - if (selected.value) { - selection.value = selection.value.filter((i) => i != volumeIndex) - } else { - selection.value = [...selection.value, volumeIndex] - } + const selectClick = () => { + if (selectionMode.value == SelectionMode.SINGLE) { + selection.value = [volumeIndex] + } else if (selectionMode.value == SelectionMode.MULTIPLE) { + if (selected.value) { + selection.value = selection.value.filter((i) => i != volumeIndex) + } else { + selection.value = [...selection.value, volumeIndex] } - : () => {} + } + } const nextVolume = () => { const currentVol = nv.volumes[0].frame4D