diff --git a/src/components/image-editor/OptionsBar.tsx b/src/components/image-editor/OptionsBar.tsx
index a22723d..152694e 100644
--- a/src/components/image-editor/OptionsBar.tsx
+++ b/src/components/image-editor/OptionsBar.tsx
@@ -171,6 +171,18 @@ export function OptionsBar({
)
}
+ if (tool === 'hand') {
+ return (
+
+
+
+ {t('pages.imageEditor.handToolHint')}
+
+
+
+ )
+ }
+
if (tool === 'text') {
return (
diff --git a/src/components/image-editor/ToolsPalette.tsx b/src/components/image-editor/ToolsPalette.tsx
index 9bb91a8..6e84716 100644
--- a/src/components/image-editor/ToolsPalette.tsx
+++ b/src/components/image-editor/ToolsPalette.tsx
@@ -160,7 +160,6 @@ const GROUPS: ToolDef[][] = [
icon: ,
labelKey: 'pages.imageEditor.tool.hand',
shortcut: 'H',
- stub: true,
},
{ id: 'zoom', icon: , labelKey: 'pages.imageEditor.tool.zoom', shortcut: 'Z' },
],
diff --git a/src/components/image-editor/tool-meta.ts b/src/components/image-editor/tool-meta.ts
index 8d865c0..172b3c0 100644
--- a/src/components/image-editor/tool-meta.ts
+++ b/src/components/image-editor/tool-meta.ts
@@ -21,7 +21,6 @@ export const STUB_TOOLS: ReadonlySet = new Set([
'dodge',
'pen',
'arrowPath',
- 'hand',
'rotateView',
'frame',
'note',
diff --git a/src/i18n/en.json b/src/i18n/en.json
index c1ad936..cb77c6a 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -521,6 +521,7 @@
"moveToolHint": "Move (V): drag layers; Cmd/Ctrl+J duplicate; Delete remove.",
"zoomToolHint": "Zoom (Z): click to zoom in 2×, Alt-click to zoom out. Cmd/Ctrl+wheel zooms at cursor.",
"eyedropperHint": "Eyedropper (I): click anywhere on the canvas to set the foreground color.",
+ "handToolHint": "Hand (H): drag the canvas to pan. Hold Space to pan with any other tool.",
"textToolHint": "Type (T): click on the canvas to add text.",
"toolStubHint": "{{tool}} is a placeholder — the palette button is here for parity with Photoshop, but the tool isn't implemented yet.",
"toolStubToast": "{{tool}} is not yet implemented.",
diff --git a/src/i18n/zh-CN.json b/src/i18n/zh-CN.json
index 30ff37d..2e7dda9 100644
--- a/src/i18n/zh-CN.json
+++ b/src/i18n/zh-CN.json
@@ -521,6 +521,7 @@
"moveToolHint": "移动 (V):拖动图层;Cmd/Ctrl+J 复制;Delete 删除。",
"zoomToolHint": "缩放 (Z):单击放大 2×,Alt 单击缩小。Cmd/Ctrl+滚轮在光标位置缩放。",
"eyedropperHint": "吸管 (I):在画布上单击以拾取前景色。",
+ "handToolHint": "抓手 (H):拖动画布以平移。任何工具下按住空格也可临时平移。",
"textToolHint": "文字 (T):在画布上单击以新增文字。",
"toolStubHint": "{{tool}} 仅作占位 —— 工具栏按钮是为了对齐 Photoshop 而保留,但功能尚未实现。",
"toolStubToast": "{{tool}} 暂未实现。",
diff --git a/src/pages/ImageEditor.tsx b/src/pages/ImageEditor.tsx
index 86e93fc..7c6833c 100644
--- a/src/pages/ImageEditor.tsx
+++ b/src/pages/ImageEditor.tsx
@@ -89,10 +89,14 @@ export function ImageEditorPage() {
const moveLayerRef = useRef<(d: 'forward' | 'backward' | 'front' | 'back') => void>(() => {})
const deleteLayerRef = useRef<() => void>(() => {})
- // Zoom + pan + Space-held pan mode.
+ // Zoom + pan. Workspace enters pan mode when the user holds Space OR when
+ // the Hand tool is active — both treat the canvas as drag-to-pan instead
+ // of drag-to-draw.
const [zoom, setZoom] = useState(1)
const [pan, setPan] = useState({ x: 0, y: 0 })
- const [panMode, setPanMode] = useState(false)
+ const [spaceHeld, setSpaceHeld] = useState(false)
+ const setPanMode = setSpaceHeld
+ const panMode = spaceHeld || tool === 'hand'
const ZOOM_MIN = 0.1
const ZOOM_MAX = 8
@@ -229,7 +233,7 @@ export function ImageEditorPage() {
window.removeEventListener('keydown', onKeyDown)
window.removeEventListener('keyup', onKeyUp)
}
- }, [focused, zoomIn, zoomOut, zoomReset, swapColors, resetColors, selectedLayerId, trySetTool])
+ }, [focused, zoomIn, zoomOut, zoomReset, swapColors, resetColors, selectedLayerId, trySetTool, setPanMode])
// ── Layer state helpers ──────────────────────────────────────────────────
const setLayers = useCallback(