-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow moving layers using the keyboard arrow keys #7202
base: main
Are you sure you want to change the base?
Changes from all commits
31dead0
9f05d95
483ceb2
574e390
52d3461
e596c2e
5e6bd2e
96442f9
696cc37
5a03121
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { $focusedRegion } from 'common/hooks/focus'; | ||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; | ||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; | ||
import type { CanvasToolModule } from 'features/controlLayers/konva/CanvasTool/CanvasToolModule'; | ||
|
@@ -20,12 +21,34 @@ export class CanvasMoveToolModule extends CanvasModuleBase { | |
this.manager = this.parent.manager; | ||
this.path = this.manager.buildPath(this); | ||
this.log = this.manager.buildLogger(this); | ||
|
||
this.log.debug('Creating module'); | ||
} | ||
|
||
/** | ||
* This is a noop. Entity transformers handle cursor style when the move tool is active. | ||
*/ | ||
syncCursorStyle = noop; | ||
|
||
onKeyDown = (e: KeyboardEvent) => { | ||
// Support moving via arrow keys | ||
const OFFSET = 1; // How much to move, in px | ||
const offsets: Record<string, { x: number; y: number }> = { | ||
ArrowLeft: { x: -OFFSET, y: 0 }, | ||
ArrowRight: { x: OFFSET, y: 0 }, | ||
ArrowUp: { x: 0, y: -OFFSET }, | ||
ArrowDown: { x: 0, y: OFFSET }, | ||
}; | ||
Comment on lines
+34
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move these constants outside the callback, either to class property or outside the class, so we don't need to recreate them every keydown |
||
const { key } = e; | ||
const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter(); | ||
const { x: offsetX = 0, y: offsetY = 0 } = offsets[key] || {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's clearer to do this: const offsets = KEY_TO_OFFSETS[e.key]
if (!offsets) {
return
}
// ...
selectedEntity.transformer.nudgePosition(offset.x, offset.y); |
||
|
||
if ( | ||
!(selectedEntity && selectedEntity.$isInteractable.get() && $focusedRegion.get() === 'canvas') || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Getting the selected entity is the most expensive part check, so we can be a bit more verbose to improve perf when checking if we should bail early. For example: if ($focusedRegion.get() !== 'canvas') {
return;
}
const offsets = KEY_TO_OFFSETS[e.key];
if (!offsets) {
return;
}
const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter();
if (!selectedEntity || !selectedEntity.$isInteractable.get()) {
return;
} Also, I forgot to mention we should also bail if |
||
(offsetX === 0 && offsetY === 0) | ||
) { | ||
return; // Early return if no entity is selected or it is disabled or canvas is not focused | ||
} | ||
|
||
selectedEntity.transformer.nudgePosition(offsetX, offsetY); | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't work quite how we need it to, because an entity's objects are positioned relative to the entity's position. For example:
Here, the entity position is
0,0
, but the objects - represented by the interaction proxy rect - are at a different position.As a result, when we use do a single-pixel nudge, we offset the entity by the object position + the single-pixel nudge offsets.
Screen.Recording.2024-10-29.at.1.24.59.pm.mov
Instead of handling this with logic in the transformer class, I think it's best to create a redux action specifically for moving an entity by a certain amount. Proposal:
entityMoved
redux action toentityMovedTo
. That's the one here: https://github.com/hippalectryon-0/InvokeAI/blob/keyboard_move/invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts#L1170-L1180entityMovedBy
redux action which accepts aoffset: Coordinate
.CanvasStateApiModule
to dispatchentityMovedBy
action. Could be called something likemoveEntityBy
.CanvasEntityTransformer.nudgePosition
to call this new method without doing any konva stuff.*CanvasEntityTransformer.onDragEnd
.Optionally, you could do something like this to immediately move the entity and its objects before waiting for the redux round-trip (untested):