diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 70a4b621b5f..e4e02843cd7 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -20,6 +20,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Datasets can now be renamed and can have duplicate names. [#8075](https://github.com/scalableminds/webknossos/pull/8075) - Improved the default colors for skeleton trees. [#8228](https://github.com/scalableminds/webknossos/pull/8228) - Allowed to train an AI model using differently sized bounding boxes. We recommend all bounding boxes to have equal dimensions or to have dimensions which are multiples of the smallest bounding box. [#8222](https://github.com/scalableminds/webknossos/pull/8222) +- Within the bounding box tool, the cursor updates immediately after pressing `ctrl`, indicating that a bounding box can be moved instead of resized. [#8253](https://github.com/scalableminds/webknossos/pull/8253) ### Fixed - Fixed that listing datasets with the `api/datasets` route without compression failed due to missing permissions regarding public datasets. [#8249](https://github.com/scalableminds/webknossos/pull/8249) diff --git a/frontend/javascripts/libs/input.ts b/frontend/javascripts/libs/input.ts index 19e952ffe9c..cd3d385e518 100644 --- a/frontend/javascripts/libs/input.ts +++ b/frontend/javascripts/libs/input.ts @@ -84,6 +84,7 @@ export class InputKeyboardNoLoop { supportInputElements?: boolean; }, extendedCommands?: KeyBindingMap, + keyUpBindings?: KeyBindingMap, ) { if (options) { this.supportInputElements = options.supportInputElements || this.supportInputElements; @@ -100,16 +101,17 @@ export class InputKeyboardNoLoop { document.addEventListener("keydown", this.preventBrowserSearchbarShortcut); this.attach(EXTENDED_COMMAND_KEYS, this.toggleExtendedMode); // Add empty callback in extended mode to deactivate the extended mode via the same EXTENDED_COMMAND_KEYS. - this.attach(EXTENDED_COMMAND_KEYS, _.noop, true); + this.attach(EXTENDED_COMMAND_KEYS, _.noop, _.noop, true); for (const key of Object.keys(extendedCommands)) { const callback = extendedCommands[key]; - this.attach(key, callback, true); + this.attach(key, callback, _.noop, true); } } for (const key of Object.keys(initialBindings)) { const callback = initialBindings[key]; - this.attach(key, callback); + const keyUpCallback = keyUpBindings != null ? keyUpBindings[key] : _.noop; + this.attach(key, callback, keyUpCallback); } } @@ -141,7 +143,12 @@ export class InputKeyboardNoLoop { } } - attach(key: KeyboardKey, callback: KeyboardHandler, isExtendedCommand: boolean = false) { + attach( + key: KeyboardKey, + keyDownCallback: KeyboardHandler, + keyUpCallback: KeyboardHandler = _.noop, + isExtendedCommand: boolean = false, + ) { const binding = [ key, (event: KeyboardEvent) => { @@ -163,13 +170,15 @@ export class InputKeyboardNoLoop { } if (!event.repeat) { - callback(event); + keyDownCallback(event); } else { event.preventDefault(); event.stopPropagation(); } }, - _.noop, + (event: KeyboardEvent) => { + keyUpCallback(event); + }, ]; if (isExtendedCommand) { KeyboardJS.withContext("extended", () => { diff --git a/frontend/javascripts/oxalis/controller/combinations/bounding_box_handlers.ts b/frontend/javascripts/oxalis/controller/combinations/bounding_box_handlers.ts index 10f39ed3aea..8bba7bd64c9 100644 --- a/frontend/javascripts/oxalis/controller/combinations/bounding_box_handlers.ts +++ b/frontend/javascripts/oxalis/controller/combinations/bounding_box_handlers.ts @@ -265,7 +265,7 @@ export function createBoundingBoxAndGetEdges( } export const highlightAndSetCursorOnHoveredBoundingBox = _.throttle( - (position: Point2, planeId: OrthoView, event: MouseEvent) => { + (position: Point2, planeId: OrthoView, event: MouseEvent | KeyboardEvent) => { const hoveredEdgesInfo = getClosestHoveredBoundingBox(position, planeId); // Access the parent element as that is where the cursor style property is set const inputCatcher = document.getElementById(`inputcatcher_${planeId}`)?.parentElement; diff --git a/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx b/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx index 0158940dd1d..99dfb486eea 100644 --- a/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx +++ b/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.tsx @@ -66,6 +66,7 @@ import { import { showToastWarningForLargestSegmentIdMissing } from "oxalis/view/largest_segment_id_modal"; import { getDefaultBrushSizes } from "oxalis/view/action-bar/toolbar_view"; import { userSettings } from "types/schemas/user_settings.schema"; +import { highlightAndSetCursorOnHoveredBoundingBox } from "../combinations/bounding_box_handlers"; function ensureNonConflictingHandlers( skeletonControls: Record, @@ -183,12 +184,29 @@ class BoundingBoxKeybindings { static getKeyboardControls() { return { c: () => Store.dispatch(addUserBoundingBoxAction()), + meta: BoundingBoxKeybindings.createKeyDownAndUpHandler(), + ctrl: BoundingBoxKeybindings.createKeyDownAndUpHandler(), }; } + static handleUpdateCursor = (event: KeyboardEvent) => { + const { viewModeData, temporaryConfiguration } = Store.getState(); + const { mousePosition } = temporaryConfiguration; + if (mousePosition == null) return; + highlightAndSetCursorOnHoveredBoundingBox( + { x: mousePosition[0], y: mousePosition[1] }, + viewModeData.plane.activeViewport, + event, + ); + }; + static getExtendedKeyboardControls() { return { x: () => setTool(AnnotationToolEnum.BOUNDING_BOX) }; } + + static createKeyDownAndUpHandler() { + return (event: KeyboardEvent) => BoundingBoxKeybindings.handleUpdateCursor(event); + } } function createDelayAwareMoveHandler(multiplier: number) { @@ -364,6 +382,7 @@ class PlaneController extends React.PureComponent { }); const { baseControls: notLoopedKeyboardControls, + keyUpControls, extendedControls: extendedNotLoopedKeyboardControls, } = this.getNotLoopedKeyboardControls(); const loopedKeyboardControls = this.getLoopedKeyboardControls(); @@ -397,6 +416,7 @@ class PlaneController extends React.PureComponent { notLoopedKeyboardControls, {}, extendedNotLoopedKeyboardControls, + keyUpControls, ); this.storePropertyUnsubscribers.push( listenToStoreProperty( @@ -501,7 +521,11 @@ class PlaneController extends React.PureComponent { this.props.tracing.volumes.length > 0 ? VolumeKeybindings.getKeyboardControls() : emptyDefaultHandler; - const { c: boundingBoxCHandler } = BoundingBoxKeybindings.getKeyboardControls(); + const { + c: boundingBoxCHandler, + meta: boundingBoxMetaHandler, + ctrl: boundingBoxCtrlHandler, + } = BoundingBoxKeybindings.getKeyboardControls(); ensureNonConflictingHandlers(skeletonControls, volumeControls); const extendedSkeletonControls = this.props.tracing.skeleton != null ? SkeletonKeybindings.getExtendedKeyboardControls() : {}; @@ -524,6 +548,12 @@ class PlaneController extends React.PureComponent { volumeCHandler, boundingBoxCHandler, ), + ctrl: this.createToolDependentKeyboardHandler(null, null, boundingBoxCtrlHandler), + meta: this.createToolDependentKeyboardHandler(null, null, boundingBoxMetaHandler), + }, + keyUpControls: { + ctrl: this.createToolDependentKeyboardHandler(null, null, boundingBoxCtrlHandler), + meta: this.createToolDependentKeyboardHandler(null, null, boundingBoxMetaHandler), }, extendedControls, };