Files
paint-ui/hooks/use-keyboard-shortcuts.ts
Sebastian Krüger 7f4d574c64 feat(tools): implement advanced brush tools - Clone Stamp, Smudge, and Dodge/Burn
Added three professional-grade image manipulation tools to complete Feature 4:

Clone Stamp Tool (Shortcut: 8)
- Sample from source location with Alt+Click
- Paint sampled content to destination
- Maintains relative offset for natural cloning
- Supports soft/hard brush with hardness setting

Smudge Tool (Shortcut: 9)
- Creates realistic paint-smearing effects
- Progressively blends colors for natural smudging
- Uses flow setting to control smudge strength
- Soft brush falloff for smooth blending

Dodge/Burn Tool (Shortcut: 0)
- Dodge mode: Lightens image areas (default)
- Burn mode: Darkens image areas (Alt key)
- Professional photography exposure adjustment
- Respects hardness setting for precise control

All tools:
- Fully integrated with tool palette and keyboard shortcuts
- Support smooth interpolation for fluid strokes
- Use existing tool settings (size, opacity, hardness, flow, spacing)
- Lazy-loaded via code splitting system
- Icons from Lucide React (Stamp, Droplet, Sun)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 16:27:02 +01:00

171 lines
4.3 KiB
TypeScript

import { useEffect } from 'react';
import { useHistoryStore } from '@/store/history-store';
import { useToolStore, useLayerStore, useTextStore } from '@/store';
import { duplicateLayerWithHistory, deleteLayerWithHistory } from '@/lib/layer-operations';
import type { ToolType } from '@/types';
/**
* Keyboard shortcuts configuration
*/
interface KeyboardShortcut {
key: string;
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
meta?: boolean;
handler: () => void;
description: string;
}
/**
* Tool shortcuts mapping
*/
const TOOL_SHORTCUTS: Record<string, ToolType> = {
'1': 'pencil',
'2': 'brush',
'3': 'eraser',
'4': 'fill',
'5': 'eyedropper',
'6': 'text',
'7': 'select',
'8': 'clone',
'9': 'smudge',
'0': 'dodge',
};
/**
* Hook to manage keyboard shortcuts
*/
export function useKeyboardShortcuts() {
const { undo, redo, canUndo, canRedo } = useHistoryStore();
const { setActiveTool } = useToolStore();
const { layers, activeLayerId, setActiveLayer } = useLayerStore();
const { isOnCanvasEditorActive } = useTextStore();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Check if we're in an input field or text editor is active
const target = e.target as HTMLElement;
if (
isOnCanvasEditorActive ||
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.isContentEditable
) {
return;
}
// Tool selection shortcuts: 1-7
if (TOOL_SHORTCUTS[e.key]) {
e.preventDefault();
setActiveTool(TOOL_SHORTCUTS[e.key]);
return;
}
// Layer navigation: Arrow Up/Down
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault();
// Sort layers by order (highest first, same as UI)
const sortedLayers = [...layers].sort((a, b) => b.order - a.order);
const currentIndex = sortedLayers.findIndex((l) => l.id === activeLayerId);
if (currentIndex === -1) return;
if (e.key === 'ArrowUp' && currentIndex > 0) {
setActiveLayer(sortedLayers[currentIndex - 1].id);
} else if (e.key === 'ArrowDown' && currentIndex < sortedLayers.length - 1) {
setActiveLayer(sortedLayers[currentIndex + 1].id);
}
return;
}
// Delete layer: Delete or Backspace (without modifier keys)
if ((e.key === 'Delete' || e.key === 'Backspace') && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
e.preventDefault();
if (activeLayerId && layers.length > 1) {
if (confirm('Delete this layer?')) {
deleteLayerWithHistory(activeLayerId);
}
}
return;
}
// Duplicate layer: Ctrl+D
if ((e.ctrlKey || e.metaKey) && e.key === 'd' && !e.shiftKey) {
e.preventDefault();
if (activeLayerId) {
duplicateLayerWithHistory(activeLayerId);
}
return;
}
// Undo: Ctrl+Z (but not Ctrl+Shift+Z)
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
if (canUndo()) {
undo();
}
return;
}
// Redo: Ctrl+Shift+Z or Ctrl+Y
if (
((e.ctrlKey || e.metaKey) && e.key === 'z' && e.shiftKey) ||
((e.ctrlKey || e.metaKey) && e.key === 'y')
) {
e.preventDefault();
if (canRedo()) {
redo();
}
return;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [
undo,
redo,
canUndo,
canRedo,
setActiveTool,
layers,
activeLayerId,
setActiveLayer,
isOnCanvasEditorActive,
]);
}
/**
* Get keyboard shortcut display string
*/
export function getShortcutDisplay(shortcut: {
key: string;
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
meta?: boolean;
}): string {
const parts: string[] = [];
const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/.test(navigator.platform);
if (shortcut.ctrl || shortcut.meta) {
parts.push(isMac ? '⌘' : 'Ctrl');
}
if (shortcut.shift) {
parts.push(isMac ? '⇧' : 'Shift');
}
if (shortcut.alt) {
parts.push(isMac ? '⌥' : 'Alt');
}
parts.push(shortcut.key.toUpperCase());
return parts.join(isMac ? '' : '+');
}