Skip to content

Commit

Permalink
Merge pull request #1020 from silx-kit/interaction-conflict
Browse files Browse the repository at this point in the history
Handle interaction key conflicts by setting them in VisCanvas
  • Loading branch information
loichuder authored Mar 21, 2022
2 parents 5493f69 + e00005c commit ac4e87b
Show file tree
Hide file tree
Showing 21 changed files with 157 additions and 50 deletions.
3 changes: 2 additions & 1 deletion apps/storybook/src/Pan.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const Template: Story<TemplateProps> = (args) => {
<VisCanvas
abscissaConfig={{ visDomain: [-10, 0], showGrid: true }}
ordinateConfig={{ visDomain: [50, 100], showGrid: true }}
interactionKeys={{ Pan: modifierKey || true, Zoom: true }}
>
<Pan disabled={disabled} modifierKey={modifierKey} />
<Pan disabled={disabled} />
<Zoom />
<ResetZoomButton />
</VisCanvas>
Expand Down
7 changes: 6 additions & 1 deletion apps/storybook/src/SelectToZoom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ const Template: Story<TemplateProps> = (args) => {
<VisCanvas
abscissaConfig={{ visDomain: [-5, 5], showGrid: true }}
ordinateConfig={{ visDomain: [-0.5, 1.5], showGrid: true }}
interactionKeys={{
Pan: true,
Zoom: true,
SelectToZoom: modifierKey,
}}
>
<Pan />
<Zoom />
<SelectToZoom modifierKey={modifierKey} />
<SelectToZoom />
<ResetZoomButton />
<GaussianCurve />
</VisCanvas>
Expand Down
12 changes: 10 additions & 2 deletions apps/storybook/src/Selection.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,18 @@ const Template: Story<TemplateProps> = (args) => {
}
abscissaConfig={{ visDomain: [-10, 0], showGrid: true }}
ordinateConfig={{ visDomain: [50, 100], showGrid: true }}
interactionKeys={{
Pan: true,
Zoom: true,
Selection: modifierKey || true,
}}
>
<Pan disabled={disablePan} />
<Zoom disabled={disableZoom} />
<ResetZoomButton />
<SelectionTool
onSelectionChange={setActiveSelection}
onSelectionEnd={() => setActiveSelection(undefined)}
modifierKey={modifierKey}
>
{(selection) => <SelectionComponent {...selection} {...svgProps} />}
</SelectionTool>
Expand Down Expand Up @@ -132,6 +136,11 @@ export const PersistSelection: Story<TemplateProps> = (args) => {
}
abscissaConfig={{ visDomain: [-10, 0], showGrid: true }}
ordinateConfig={{ visDomain: [50, 100], showGrid: true }}
interactionKeys={{
Pan: true,
Zoom: true,
Selection: modifierKey || true,
}}
>
<Pan disabled={disablePan} />
<Zoom disabled={disableZoom} />
Expand All @@ -140,7 +149,6 @@ export const PersistSelection: Story<TemplateProps> = (args) => {
setPersistedSelection(undefined);
}}
onSelectionEnd={setPersistedSelection}
modifierKey={modifierKey}
>
{(selection) => <SelectionComponent {...selection} {...svgProps} />}
</SelectionTool>
Expand Down
3 changes: 2 additions & 1 deletion apps/storybook/src/XZoom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const Template: Story<TemplateProps> = (args) => {
<VisCanvas
abscissaConfig={{ visDomain: [-10, 0], showGrid: true }}
ordinateConfig={{ visDomain: [50, 100], showGrid: true }}
interactionKeys={{ Pan: true, XAxisZoom: modifierKey || true }}
>
<Pan />
<XAxisZoom disabled={disabled} modifierKey={modifierKey} />
<XAxisZoom disabled={disabled} />
<ResetZoomButton />
</VisCanvas>
);
Expand Down
3 changes: 2 additions & 1 deletion apps/storybook/src/YZoom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const Template: Story<TemplateProps> = (args) => {
<VisCanvas
abscissaConfig={{ visDomain: [-10, 0], showGrid: true }}
ordinateConfig={{ visDomain: [50, 100], showGrid: true }}
interactionKeys={{ Pan: true, YAxisZoom: modifierKey || true }}
>
<Pan />
<YAxisZoom disabled={disabled} modifierKey={modifierKey} />
<YAxisZoom disabled={disabled} />
<ResetZoomButton />
</VisCanvas>
);
Expand Down
3 changes: 2 additions & 1 deletion apps/storybook/src/Zoom.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ const Template: Story<TemplateProps> = (args) => {
<VisCanvas
abscissaConfig={{ visDomain: [-10, 0], showGrid: true }}
ordinateConfig={{ visDomain: [50, 100], showGrid: true }}
interactionKeys={{ Pan: true, Zoom: modifierKey || true }}
>
<Pan />
<Zoom disabled={disabled} modifierKey={modifierKey} />
<Zoom disabled={disabled} />
<ResetZoomButton />
</VisCanvas>
);
Expand Down
13 changes: 6 additions & 7 deletions packages/lib/src/interactions/Pan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { useThree } from '@react-three/fiber';
import { useRef, useCallback } from 'react';
import type { Vector3 } from 'three';

import { useAxisSystemContext } from '../vis/shared/AxisSystemContext';
import { useCanvasEvents, useMoveCameraTo } from './hooks';
import type { CanvasEvent, ModifierKey } from './models';
import { checkModifierKey } from './utils';
import type { CanvasEvent } from './models';

interface Props {
disabled?: boolean;
modifierKey?: ModifierKey;
}

function Pan(props: Props) {
const { disabled, modifierKey } = props;
const { disabled } = props;
const { shouldInteract } = useAxisSystemContext();

const camera = useThree((state) => state.camera);

Expand All @@ -29,13 +29,12 @@ function Pan(props: Props) {
return;
}

const isPanAllowed = checkModifierKey(modifierKey, sourceEvent);
if (isPanAllowed) {
if (shouldInteract('Pan', sourceEvent)) {
(target as Element).setPointerCapture(pointerId); // https://stackoverflow.com/q/28900077/758806
startOffsetPosition.current = unprojectedPoint.clone();
}
},
[disabled, modifierKey]
[shouldInteract, disabled]
);

const onPointerUp = useCallback((evt: CanvasEvent<PointerEvent>) => {
Expand Down
7 changes: 3 additions & 4 deletions packages/lib/src/interactions/SelectToZoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { useAxisSystemContext } from '../vis/shared/AxisSystemContext';
import SelectionRect from './SelectionRect';
import SelectionTool from './SelectionTool';
import { useMoveCameraTo } from './hooks';
import type { ModifierKey, Selection } from './models';
import type { Selection } from './models';
import { getRatioEndPoint } from './utils';

interface Props {
modifierKey?: ModifierKey;
keepRatio?: boolean;
}

function SelectToZoom(props: Props) {
const { modifierKey = 'Control', keepRatio } = props;
const { keepRatio } = props;

const { dataToWorld } = useAxisSystemContext();
const moveCameraTo = useMoveCameraTo();
Expand Down Expand Up @@ -53,7 +52,7 @@ function SelectToZoom(props: Props) {
};

return (
<SelectionTool modifierKey={modifierKey} onSelectionEnd={onSelectionEnd}>
<SelectionTool onSelectionEnd={onSelectionEnd} id="SelectToZoom">
{({ startPoint, endPoint }) => (
<>
<SelectionRect
Expand Down
39 changes: 25 additions & 14 deletions packages/lib/src/interactions/SelectionTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import type { Vector2 } from 'three';

import { useAxisSystemContext } from '../vis/shared/AxisSystemContext';
import { useCanvasEvents } from './hooks';
import type { CanvasEvent, ModifierKey, Selection } from './models';
import { boundPointToFOV, checkModifierKey } from './utils';
import type { CanvasEvent, Selection } from './models';
import { boundPointToFOV } from './utils';

interface Props {
onSelectionStart?: () => void;
onSelectionChange?: (points: Selection) => void;
onSelectionEnd?: (points: Selection) => void;
modifierKey?: ModifierKey;
id?: string;
children: (points: Selection) => ReactElement;
}

Expand All @@ -24,11 +24,12 @@ function SelectionTool(props: Props) {
onSelectionStart,
onSelectionChange,
onSelectionEnd,
modifierKey,
id = 'Selection',
} = props;

const camera = useThree((state) => state.camera);
const { worldToData } = useAxisSystemContext();
const { worldToData, shouldInteract, getModifierKey } =
useAxisSystemContext();

const [startPoint, setStartPoint] = useState<Vector2>();
const [endPoint, setEndPoint] = useRafState<Vector2 | undefined>(undefined);
Expand All @@ -37,7 +38,7 @@ function SelectionTool(props: Props) {
const onPointerDown = useCallback(
(evt: CanvasEvent<PointerEvent>) => {
const { unprojectedPoint, sourceEvent } = evt;
if (!checkModifierKey(modifierKey, sourceEvent)) {
if (!shouldInteract(id, sourceEvent)) {
return;
}

Expand All @@ -49,7 +50,7 @@ function SelectionTool(props: Props) {
onSelectionStart();
}
},
[modifierKey, onSelectionStart, worldToData]
[id, onSelectionStart, shouldInteract, worldToData]
);

const onPointerMove = useCallback(
Expand All @@ -62,20 +63,21 @@ function SelectionTool(props: Props) {
const point = worldToData(boundPointToFOV(unprojectedPoint, camera));
setEndPoint(point);

if (onSelectionChange && checkModifierKey(modifierKey, sourceEvent)) {
if (onSelectionChange && shouldInteract(id, sourceEvent)) {
onSelectionChange({
startPoint,
endPoint: point,
});
}
},
[
camera,
modifierKey,
startPoint,
onSelectionChange,
setEndPoint,
worldToData,
camera,
setEndPoint,
onSelectionChange,
shouldInteract,
id,
]
);

Expand All @@ -92,18 +94,27 @@ function SelectionTool(props: Props) {
setStartPoint(undefined);
setEndPoint(undefined);

if (onSelectionEnd && checkModifierKey(modifierKey, sourceEvent)) {
if (onSelectionEnd && shouldInteract(id, sourceEvent)) {
onSelectionEnd({
startPoint,
endPoint: worldToData(boundPointToFOV(unprojectedPoint, camera)),
});
}
},
[camera, modifierKey, startPoint, onSelectionEnd, setEndPoint, worldToData]
[
startPoint,
setEndPoint,
onSelectionEnd,
shouldInteract,
id,
worldToData,
camera,
]
);

useCanvasEvents({ onPointerDown, onPointerMove, onPointerUp });

const modifierKey = getModifierKey(id);
useKeyboardEvent(modifierKey, () => toggleVisible(), [], { event: 'keyup' });
useKeyboardEvent(
modifierKey,
Expand Down
9 changes: 5 additions & 4 deletions packages/lib/src/interactions/XAxisZoom.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { useAxisSystemContext } from '../vis/shared/AxisSystemContext';
import { useCanvasEvents, useZoomOnWheel } from './hooks';
import type { ModifierKey } from './models';

interface Props {
disabled?: boolean;
modifierKey?: ModifierKey;
}

function XAxisZoom(props: Props) {
const { disabled, modifierKey = 'Alt' } = props;
const { disabled } = props;

const { shouldInteract } = useAxisSystemContext();

const isZoomAllowed = (sourceEvent: WheelEvent) => ({
x: sourceEvent.getModifierState(modifierKey),
x: shouldInteract('XAxisZoom', sourceEvent),
y: false,
});

Expand Down
8 changes: 4 additions & 4 deletions packages/lib/src/interactions/YAxisZoom.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { useAxisSystemContext } from '../vis/shared/AxisSystemContext';
import { useCanvasEvents, useZoomOnWheel } from './hooks';
import type { ModifierKey } from './models';

interface Props {
disabled?: boolean;
modifierKey?: ModifierKey;
}

function YAxisZoom(props: Props) {
const { disabled, modifierKey = 'Shift' } = props;
const { disabled } = props;
const { shouldInteract } = useAxisSystemContext();

const isZoomAllowed = (sourceEvent: WheelEvent) => ({
x: false,
y: sourceEvent.getModifierState(modifierKey),
y: shouldInteract('YAxisZoom', sourceEvent),
});

useCanvasEvents({ onWheel: useZoomOnWheel(isZoomAllowed, disabled) });
Expand Down
9 changes: 4 additions & 5 deletions packages/lib/src/interactions/Zoom.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { useAxisSystemContext } from '../vis/shared/AxisSystemContext';
import { useCanvasEvents, useZoomOnWheel } from './hooks';
import type { ModifierKey } from './models';
import { checkModifierKey } from './utils';

interface Props {
disabled?: boolean;
modifierKey?: ModifierKey;
}

function Zoom(props: Props) {
const { disabled, modifierKey } = props;
const { disabled } = props;
const { shouldInteract } = useAxisSystemContext();

const isZoomAllowed = (sourceEvent: WheelEvent) => {
const shouldZoom = checkModifierKey(modifierKey, sourceEvent);
const shouldZoom = shouldInteract('Zoom', sourceEvent);

return { x: shouldZoom, y: shouldZoom };
};
Expand Down
4 changes: 3 additions & 1 deletion packages/lib/src/interactions/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface Selection {
endPoint: Vector2;
}

export interface CanvasEvent<T extends PointerEvent | WheelEvent> {
export interface CanvasEvent<T extends MouseEvent> {
unprojectedPoint: Vector3;
sourceEvent: T;
}
Expand All @@ -23,3 +23,5 @@ export interface Interaction {
shortcut: string;
description: string;
}

export type InteractionKeys = Record<string, ModifierKey | true>;
10 changes: 8 additions & 2 deletions packages/lib/src/vis/heatmap/HeatmapVis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ import {
import type { NdArray } from 'ndarray';
import type { ReactElement, ReactNode } from 'react';

import { XAxisZoom, YAxisZoom } from '../..';
import Pan from '../../interactions/Pan';
import ResetZoomButton from '../../interactions/ResetZoomButton';
import SelectToZoom from '../../interactions/SelectToZoom';
import XAxisZoom from '../../interactions/XAxisZoom';
import YAxisZoom from '../../interactions/YAxisZoom';
import Zoom from '../../interactions/Zoom';
import { useAxisDomain, useValueToIndexScale } from '../hooks';
import type { AxisParams, VisScaleType } from '../models';
import TooltipMesh from '../shared/TooltipMesh';
import VisCanvas from '../shared/VisCanvas';
import { DEFAULT_DOMAIN, formatNumType } from '../utils';
import {
DEFAULT_DOMAIN,
DEFAULT_INTERACTIONS_KEYS,
formatNumType,
} from '../utils';
import ColorBar from './ColorBar';
import HeatmapMesh from './HeatmapMesh';
import styles from './HeatmapVis.module.css';
Expand Down Expand Up @@ -103,6 +108,7 @@ function HeatmapVis(props: Props) {
label: ordinateLabel,
flip: flipYAxis,
}}
interactionKeys={DEFAULT_INTERACTIONS_KEYS}
>
<Pan />
<Zoom />
Expand Down
Loading

0 comments on commit ac4e87b

Please sign in to comment.