// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import './brush-toolbox-styles.scss'; import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { useDispatch, useSelector } from 'react-redux'; import Button from 'antd/lib/button'; import Icon, { VerticalAlignBottomOutlined } from '@ant-design/icons'; import InputNumber from 'antd/lib/input-number'; import Select from 'antd/lib/select'; import { getCore } from 'cvat-core-wrapper'; import { Canvas, CanvasMode } from 'cvat-canvas-wrapper'; import { BrushIcon, EraserIcon, PolygonMinusIcon, PolygonPlusIcon, PlusIcon, CheckIcon, MoveIcon, } from 'icons'; 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 useDraggable from './draggable-hoc'; const DraggableArea = (
); const MIN_BRUSH_SIZE = 1; function BrushTools(): React.ReactPortal | null { const dispatch = useDispatch(); const defaultLabelID = useSelector((state: CombinedState) => state.annotation.drawing.activeLabelID); 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 { visible } = config; const [editableState, setEditableState] = useState(null); const [currentTool, setCurrentTool] = useState<'brush' | 'eraser' | 'polygon-plus' | 'polygon-minus'>('brush'); const [brushForm, setBrushForm] = useState<'circle' | 'square'>('circle'); const [[top, left], setTopLeft] = useState([0, 0]); const [brushSize, setBrushSize] = useState(10); const [removeUnderlyingPixels, setRemoveUnderlyingPixels] = useState(false); const dragBar = useDraggable( (): number[] => { const [element] = window.document.getElementsByClassName('cvat-brush-tools-toolbox'); if (element) { const { offsetTop, offsetLeft } = element as HTMLDivElement; return [offsetTop, offsetLeft]; } return [0, 0]; }, (newTop, newLeft) => setTopLeft([newTop, newLeft]), DraggableArea, ); useEffect(() => { const label = labels.find((_label: any) => _label.id === defaultLabelID); getCore().config.removeUnderlyingMaskPixels = removeUnderlyingPixels; if (visible && label && canvasInstance instanceof Canvas) { const onUpdateConfiguration = ({ brushTool }: any): void => { if (brushTool?.size) { setBrushSize(Math.max(MIN_BRUSH_SIZE, brushTool.size)); } }; if (canvasInstance.mode() === CanvasMode.DRAW) { canvasInstance.draw({ enabled: true, shapeType: ShapeType.MASK, crosshair: false, brushTool: { type: currentTool, size: brushSize, form: brushForm, color: label.color, }, onUpdateConfiguration, }); } else if (canvasInstance.mode() === CanvasMode.EDIT && editableState) { canvasInstance.edit({ enabled: true, state: editableState, brushTool: { type: currentTool, size: brushSize, form: brushForm, color: label.color, }, onUpdateConfiguration, }); } } }, [currentTool, brushSize, brushForm, visible, defaultLabelID, editableState]); useEffect(() => { const canvasContainer = window.document.getElementsByClassName('cvat-canvas-container')[0]; if (canvasContainer) { const { offsetTop, offsetLeft } = canvasContainer.parentElement as HTMLElement; setTopLeft([offsetTop, offsetLeft]); } }, []); useEffect(() => { const hideToolset = (): void => { if (visible) { dispatch(updateCanvasBrushTools({ visible: false })); } }; const showToolset = (e: Event): void => { const evt = e as CustomEvent; if (evt.detail?.state?.shapeType === ShapeType.MASK || (evt.detail?.drawData?.shapeType === ShapeType.MASK && !evt.detail?.drawData?.initialState)) { dispatch(updateCanvasBrushTools({ visible: true })); } }; const updateEditableState = (e: Event): void => { const evt = e as CustomEvent; if (evt.type === 'canvas.editstart' && evt.detail.state) { setEditableState(evt.detail.state); } else if (editableState) { setEditableState(null); } }; if (canvasInstance instanceof Canvas) { canvasInstance.html().addEventListener('canvas.drawn', hideToolset); canvasInstance.html().addEventListener('canvas.canceled', hideToolset); canvasInstance.html().addEventListener('canvas.canceled', updateEditableState); canvasInstance.html().addEventListener('canvas.drawstart', showToolset); canvasInstance.html().addEventListener('canvas.editstart', showToolset); canvasInstance.html().addEventListener('canvas.editstart', updateEditableState); canvasInstance.html().addEventListener('canvas.editdone', updateEditableState); } return () => { if (canvasInstance instanceof Canvas) { canvasInstance.html().removeEventListener('canvas.drawn', hideToolset); canvasInstance.html().removeEventListener('canvas.canceled', hideToolset); canvasInstance.html().removeEventListener('canvas.canceled', updateEditableState); canvasInstance.html().removeEventListener('canvas.drawstart', showToolset); canvasInstance.html().removeEventListener('canvas.editstart', showToolset); canvasInstance.html().removeEventListener('canvas.editstart', updateEditableState); canvasInstance.html().removeEventListener('canvas.editdone', updateEditableState); } }; }, [visible, editableState]); if (!labels.length) { return null; } return ReactDOM.createPortal((
), window.document.body); } export default React.memo(BrushTools);