Skip to content

Feat(canvas): Add temporary bbox move with C key hold - #9104

Merged
lstein merged 32 commits into
invoke-ai:mainfrom
DustyShoe:Feat(Canvas)/BBox-temp-move-on-C-hold
Aug 1, 2026
Merged

Feat(canvas): Add temporary bbox move with C key hold#9104
lstein merged 32 commits into
invoke-ai:mainfrom
DustyShoe:Feat(Canvas)/BBox-temp-move-on-C-hold

Conversation

@DustyShoe

Copy link
Copy Markdown
Collaborator

Summary

This PR adds bbox hotkey behavior for canvas. A short tap on C selects the bbox tool, and holding C temporarily activates bbox until the key is released.

This PR also moves temporary bbox hotkey handling into the shared canvas tool state so it works correctly with the existing temporary Space and Alt tool overrides. Persistent tool changes now update the base tool consistently, and focused tests were added for tap, hold, and nested temporary hotkey flows.

As a result it touches more files than it was originally expected.

Related Issues / Discussions

N/A

QA Instructions

  1. Open the canvas and select a non-bbox tool such as brush.
  2. Press and release C quickly. Verify that bbox becomes the selected tool.
  3. Select brush or another tool again, then hold C. Verify that bbox is active only while C is held and that releasing C returns to the previous tool.
  4. Hold C, then hold Space, release C, then release Space. Verify that the active tool returns to the original base tool and does not stay on bbox.
  5. Repeat step 4 with Alt instead of Space and verify the active tool still returns correctly.
  6. Click the bbox toolbar button and verify that it still performs a normal persistent tool switch.

Merge Plan

No special merge plan.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • ❗Changes to a redux slice have a corresponding migration
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

@github-actions github-actions Bot added the frontend PRs that change frontend files label May 1, 2026
@lstein lstein added the 6.14.0 label May 9, 2026
@lstein lstein moved this to 6.14.x Theme: LIBRARY UPDATES in Invoke - Community Roadmap May 9, 2026
@DustyShoe

Copy link
Copy Markdown
Collaborator Author

@joshistoast

Clearing the temporary bbox hotkey on window.blur changes more than the old view/color-picker quick-switch behavior did, because bbox is stateful.

If I drag the bbox and alt-tab away then back then this doesn't get cleaned up and is sorta stuck mid-interaction.

Good catch. I was only clearing the temporary bbox hotkey state on window.blur, but bbox can still be in the middle of a Konva drag/transform at that point.

I fixed this by explicitly stopping any active bbox interaction before clearing temporary tool overrides. CanvasToolModule.onWindowBlur() now calls bbox.stopInteraction(), and CanvasBboxToolModule.stopInteraction() uses Konva's stopDrag() / stopTransform() to cleanly end an in-flight bbox move/resize before the temporary bbox hotkey state is reset.

That should prevent the bbox from getting stuck half-active after alt-tab.

@DustyShoe
DustyShoe requested a review from JPPhoto as a code owner July 12, 2026 00:34
@DustyShoe
DustyShoe requested a review from joshistoast July 22, 2026 01:59

@joshistoast joshistoast left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. useEditImage still sets $tool directly — the selection gets silently discarded

    features/gallery/hooks/useEditImage.ts:48

    canvasManager.tool.$tool.set('brush');

    This is the only remaining external $tool.set in the codebase (verified by grep), and it's exactly the call the rest of the PR converted. Now that $tool is derived, writing to it directly leaves $baseTool stale — the next applyToolHotkeyStatehe brush away.

    Repro: Gallery → Edit image → canvas opens with brush active → tap and release Alt (or Space) → tool snaps back to move (the default $baseTool).

    Fix: canvasManager.tool.setBaseTool('brush').

    Worth also considering making $tool externally read-only (e.g. expose a readonly typed atom, or rename the writable one) so this class of bug can't reappear.

  2. pressSpaceKey() duplicates the pan branch that onKeyDown still runs

    konva/CanvasTool/CanvasToolModule.ts:262-270 vs :893-908

    pressSpaceKey ends with a lasso-pan/$cursorPos.set(null) branch, and onKeyDown immediately re-runs the same lasso check plus the two rect branches it doesn't know about.

    • stage.startDragging() is called twice for the lasso case. Harmless (it's idempotent — CanvasStageModule.ts:525 guards on getIsDragging()), but confusing.
    • Real behavior change: pressSpaceKey's else fires $cursorPos.set(null) for currentTool === 'rect', which the old code did not. The rect suspendable/polygon paths then call startDragging()tool.render() with a null cursor position.

    Fix: make pressSpaceKey a pure state transition (this.applyToolHotkeyState(pressSpaceInState(this.getToolHotkeyState()))) and leave all pan/cursor decisions in onKeyDown, which is the only place that sees the full branch set.

  3. bboxToolHotkey.ts is in hooks/ but contains no hooks

    features/controlLayers/hooks/bboxToolHotkey.ts

    It's a pure reducer over CanvasToolHotkeyState, imported by konva/CanvasTool/CanvasToolModule.ts. A konva module reaching into hooks/ is a layering smell, and there is already a home for exactly this: konva/CanvasTool/toolHotkeys.ts, with its colocated toolHotkeys.test.ts using the same relative-import style.

    Fix: move bboxToolHotkey.ts + .test.ts to konva/CanvasTool/. Consider merging into toolHotkeys.ts — it's the same concern, and the name bboxToolHotkey undersells a module that now owns Space and Alt too (CanvasToolHotkeyState is the honest name).

  4. Dead conditions in the lasso pointer-state check

    konva/CanvasTool/CanvasToolModule.ts:180-186

    previousTool !== 'colorPicker' &&
    tool !== 'colorPicker' &&
    (previousTool === 'lasso' || previousTool === 'view') &&
    (tool === 'lasso' || tool === 'view');

    The last two clauses already exclude colorPicker from both sides, so the first two can never be false. Drop them.

  5. Redundant aspect-ratio write in stopInteraction

    konva/CanvasTool/CanvasBboxToolModule.ts:293

    Konva's Transformer.stopTransform() fires transformend, and the existing onTransformEnd (:428) already sets $aspectRatioBuffer from the same proxyRect dimensions. The explicit write is a no-op. The stopDrag() half of the method is the part that matters — keep that.

  6. Minor

    • hooks/useCanvasSelectBboxToolHotkey.ts:346-ish — the event.repeat guard in the keyup handler is dead; keyup events never repeat.
    • applyToolHotkeyState (CanvasToolModule.ts:569-590) — the field-by-field bboxToolHotkeyStateChanged compare is unnecessary. Nanostores atoms already no-op on ===, and the reducers return the identical object reference when nothing changed, so a plain !== on the object works. Same for the three if (x.get() !== state.x) guards.
    • public/locales/en.json:709selectBboxTool.desc still reads "Select the bounding box tool." The behavior is now tap-to-select / hold-to-temporarily-use; the hotkeys modal should say so. The ToolBboxButton tooltip ((C)) could hint at it too.
    • Escape during a bbox hold does nothing (getToolToCancelOnEscape has no bbox case), so the hold only clears on keyup. Probably intentional — flagging in case it isn't.
    • Test coverage gap: setBaseToolInState while a bbox hold is active (does tapping a toolbar button mid-hold do the right thing?). The rest of the reducer coverage is solid.

@DustyShoe

Copy link
Copy Markdown
Collaborator Author

Thanks, addressed point-by-point:

  1. useEditImage now uses canvasManager.tool.setBaseTool('brush') instead of writing to .$tool directly, so it no longer leaves $baseTool stale after opening an image on canvas.

  2. pressSpaceKey() is now a pure state transition. The pan / cursor handling stays in onKeyDown(), which already has the full rect / lasso branch logic.

  3. The pure bbox hotkey state reducer was moved out of hooks/ and into konva/CanvasTool/toolHotkeys.ts, with the tests consolidated in konva/CanvasTool/toolHotkeys.test.ts. That keeps the state-machine colocated with the other canvas tool hotkey logic.

  4. Removed the dead colorPicker conditions from the lasso pointer-state preservation check.

  5. Removed the redundant aspect-ratio buffer write from stopInteraction(). transformend already updates the buffer via the existing onTransformEnd() path.

  6. Minor items:

  • Removed the dead keyup repeat guard in useCanvasSelectBboxToolHotkey.
  • Simplified the hotkey state application logic in CanvasToolModule.
  • Updated the selectBboxTool hotkey description to reflect tap-to-select / hold-to-temporarily-use behavior.
  • Added reducer coverage for changing the base tool while a bbox hold is active.

Two notes I did not change in this pass:

  • I did not make $tool externally read-only; I only fixed the remaining direct external write. I would propose that as a follow-up change in the future if needed.
  • I left Escape during a bbox hold unchanged. There is no separate bbox session to cancel in that case; releasing C is still the intended exit path.

@joshistoast
joshistoast self-requested a review July 28, 2026 03:31

@joshistoast joshistoast left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@lstein lstein left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lstein
lstein merged commit ae5694d into invoke-ai:main Aug 1, 2026
17 checks passed
@DustyShoe
DustyShoe deleted the Feat(Canvas)/BBox-temp-move-on-C-hold branch August 1, 2026 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.14.0 frontend PRs that change frontend files

Projects

Status: 6.14.x Theme: USER EXPERIENCE

Development

Successfully merging this pull request may close these issues.

3 participants