From 12dac6de85fea4fbdd470ade8798c69eff4fe7cf Mon Sep 17 00:00:00 2001 From: Kirill Lakhov Date: Fri, 11 Oct 2024 15:00:04 +0300 Subject: [PATCH] Add shortcuts to brush tools (#8519) ### Motivation and context The pr adds shortcuts to actions in brush tool panel (in mask editing mode) ![image](https://github.com/user-attachments/assets/a2559e58-250c-4726-a057-7ebe09f2050a) ### How has this been tested? ### Checklist - [x] I submit my changes into the `develop` branch - [x] I have created a changelog fragment - [ ] I have updated the documentation accordingly - [x] I have added tests to cover my changes - ~~[ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))~~ - [x] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning)) ### License - [x] I submit _my code changes_ under the same [MIT License]( https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. ## Summary by CodeRabbit - **New Features** - Introduced keyboard shortcuts for various brush tools, enhancing user efficiency. - Added tooltips displaying keyboard shortcuts for better usability. - Implemented a `LabelSelector` component for improved label selection. - **Improvements** - Updated sorting logic for shortcut settings to organize items by their defined weights. - **Refactor** - Enhanced state management and control flow for brush tool interactions. - Added a new optional `weight` property to the key mapping interface for better customization. --- ...20241009_101726_klakhov_brush_shortcuts.md | 4 + cvat-ui/package.json | 2 +- .../canvas/views/canvas2d/brush-tools.tsx | 184 +++++++++++++----- .../settings-modal/shortcut-settings.tsx | 2 +- cvat-ui/src/utils/mousetrap-react.tsx | 1 + tests/cypress/e2e/features/masks_basics.js | 25 +++ 6 files changed, 162 insertions(+), 56 deletions(-) create mode 100644 changelog.d/20241009_101726_klakhov_brush_shortcuts.md diff --git a/changelog.d/20241009_101726_klakhov_brush_shortcuts.md b/changelog.d/20241009_101726_klakhov_brush_shortcuts.md new file mode 100644 index 000000000000..8d70aac199be --- /dev/null +++ b/changelog.d/20241009_101726_klakhov_brush_shortcuts.md @@ -0,0 +1,4 @@ +### Added + +- Keyboard shortcuts for **brush**, **eraser**, **polygon** and **polygon remove** tools on masks drawing toolbox + () diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 34a288252d25..81b392eb7e54 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.66.0", + "version": "1.66.1", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx index 3e365cdb3605..6c140438c20e 100644 --- a/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/views/canvas2d/brush-tools.tsx @@ -24,6 +24,10 @@ import CVATTooltip from 'components/common/cvat-tooltip'; import { CombinedState, ObjectType, ShapeType } from 'reducers'; import LabelSelector from 'components/label-selector/label-selector'; import { rememberObject, updateCanvasBrushTools } from 'actions/annotation-actions'; +import { ShortcutScope } from 'utils/enums'; +import GlobalHotKeys from 'utils/mousetrap-react'; +import { subKeyMap } from 'utils/component-subkeymap'; +import { registerComponentShortcuts } from 'actions/shortcuts-actions'; import useDraggable from './draggable-hoc'; const DraggableArea = ( @@ -32,6 +36,38 @@ const DraggableArea = ( ); +const componentShortcuts = { + ACTIVATE_BRUSH_TOOL_STANDARD_CONTROLS: { + name: 'Brush tool', + description: 'Activate brush tool on masks drawing toolbox', + sequences: ['shift+1'], + scope: ShortcutScope.STANDARD_WORKSPACE_CONTROLS, + displayWeight: 10, + }, + ACTIVATE_ERASER_TOOL_STANDARD_CONTROLS: { + name: 'Eraser tool', + description: 'Activate eraser tool on masks drawing toolbox', + sequences: ['shift+2'], + scope: ShortcutScope.STANDARD_WORKSPACE_CONTROLS, + displayWeight: 15, + }, + ACTIVATE_POLYGON_TOOL_STANDARD_CONTROLS: { + name: 'Polygon tool', + description: 'Activate polygon tool on masks drawing toolbox', + sequences: ['shift+3'], + scope: ShortcutScope.STANDARD_WORKSPACE_CONTROLS, + displayWeight: 20, + }, + ACTIVATE_POLYGON_REMOVE_TOOL_STANDARD_CONTROLS: { + name: 'Polygon remove tool', + description: 'Activate polygon remove tool on masks drawing toolbox', + sequences: ['shift+4'], + scope: ShortcutScope.STANDARD_WORKSPACE_CONTROLS, + displayWeight: 25, + }, +}; +registerComponentShortcuts(componentShortcuts); + const MIN_BRUSH_SIZE = 1; function BrushTools(): React.ReactPortal | null { const dispatch = useDispatch(); @@ -39,6 +75,7 @@ function BrushTools(): React.ReactPortal | null { const config = useSelector((state: CombinedState) => state.annotation.canvas.brushTools); const canvasInstance = useSelector((state: CombinedState) => state.annotation.canvas.instance); const labels = useSelector((state: CombinedState) => state.annotation.job.labels); + const { keyMap, normalizedKeyMap } = useSelector((state: CombinedState) => state.shortcuts); const { visible } = config; const [editableState, setEditableState] = useState(null); @@ -53,6 +90,26 @@ function BrushTools(): React.ReactPortal | null { 'polygon-minus': false, }); + const setBrushTool = useCallback(() => setCurrentTool('brush'), [setCurrentTool]); + const setEraserTool = useCallback(() => { + if (!blockedTools.eraser) { + setCurrentTool('eraser'); + } + }, [setCurrentTool, blockedTools.eraser]); + const setPolygonTool = useCallback(() => setCurrentTool('polygon-plus'), [setCurrentTool]); + const setPolygonRemoveTool = useCallback(() => { + if (!blockedTools['polygon-minus']) { + setCurrentTool('polygon-minus'); + } + }, [setCurrentTool, blockedTools['polygon-minus']]); + + const handlers: Record void> = { + ACTIVATE_BRUSH_TOOL_STANDARD_CONTROLS: setBrushTool, + ACTIVATE_ERASER_TOOL_STANDARD_CONTROLS: setEraserTool, + ACTIVATE_POLYGON_TOOL_STANDARD_CONTROLS: setPolygonTool, + ACTIVATE_POLYGON_REMOVE_TOOL_STANDARD_CONTROLS: setPolygonRemoveTool, + }; + const [removeUnderlyingPixels, setRemoveUnderlyingPixels] = useState(false); const dragBar = useDraggable( (): number[] => { @@ -99,7 +156,7 @@ function BrushTools(): React.ReactPortal | null { type: currentTool, size: brushSize, form: brushForm, - color: label.color, + color: label.color as string, onBlockUpdated, }, onUpdateConfiguration, @@ -112,7 +169,7 @@ function BrushTools(): React.ReactPortal | null { type: currentTool, size: brushSize, form: brushForm, - color: label.color, + color: label.color as string, onBlockUpdated, }, onUpdateConfiguration, @@ -202,68 +259,86 @@ function BrushTools(): React.ReactPortal | null { return ReactDOM.createPortal((
-