import { Box2d, RotateCorner, TLEmbedShape, TLSelectionForegroundComponent, TLTextShape, getCursor, toDomPrecision, track, useEditor, useSelectionEvents, useTransform, useValue, } from '@bigbluebutton/editor' import classNames from 'classnames' import { useRef } from 'react' import { useReadonly } from '../ui/hooks/useReadonly' import { TldrawCropHandles } from './TldrawCropHandles' /** @public */ export const TldrawSelectionForeground: TLSelectionForegroundComponent = track( function TldrawSelectionForeground({ bounds, rotation }: { bounds: Box2d; rotation: number }) { const editor = useEditor() const rSvg = useRef(null) const isReadonlyMode = useReadonly() const topEvents = useSelectionEvents('top') const rightEvents = useSelectionEvents('right') const bottomEvents = useSelectionEvents('bottom') const leftEvents = useSelectionEvents('left') const topLeftEvents = useSelectionEvents('top_left') const topRightEvents = useSelectionEvents('top_right') const bottomRightEvents = useSelectionEvents('bottom_right') const bottomLeftEvents = useSelectionEvents('bottom_left') const isDefaultCursor = !editor.getIsMenuOpen() && editor.getInstanceState().cursor.type === 'default' const isCoarsePointer = editor.getInstanceState().isCoarsePointer const shapes = editor.getSelectedShapes() const onlyShape = editor.getOnlySelectedShape() const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape) // if all shapes have an expandBy for the selection outline, we can expand by the l const expandOutlineBy = onlyShape ? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape) : 0 useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.getSelectionRotation(), { x: -expandOutlineBy, y: -expandOutlineBy, }) if (!bounds) return null bounds = bounds.clone().expandBy(expandOutlineBy).zeroFix() const zoom = editor.getZoomLevel() const isChangingStyle = editor.getInstanceState().isChangingStyle const width = bounds.width const height = bounds.height const size = 8 / zoom const isTinyX = width < size * 2 const isTinyY = height < size * 2 const isSmallX = width < size * 4 const isSmallY = height < size * 4 const isSmallCropX = width < size * 5 const isSmallCropY = height < size * 5 const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1 const targetSize = (6 / zoom) * mobileHandleMultiplier const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75) const targetSizeY = (isSmallY ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75) const showSelectionBounds = (onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) && !isChangingStyle let shouldDisplayBox = (showSelectionBounds && editor.isInAny( 'select.idle', 'select.brushing', 'select.scribble_brushing', 'select.pointing_canvas', 'select.pointing_selection', 'select.pointing_shape', 'select.crop.idle', 'select.crop.pointing_crop', 'select.pointing_resize_handle', 'select.pointing_crop_handle' )) || (showSelectionBounds && editor.isIn('select.resizing') && onlyShape && editor.isShapeOfType(onlyShape, 'text')) if (onlyShape && shouldDisplayBox) { if (editor.environment.isFirefox && editor.isShapeOfType(onlyShape, 'embed')) { shouldDisplayBox = false } } const showCropHandles = editor.isInAny( 'select.pointing_crop_handle', 'select.crop.idle', 'select.crop.pointing_crop' ) && !isChangingStyle && !isReadonlyMode const shouldDisplayControls = editor.isInAny( 'select.idle', 'select.pointing_selection', 'select.pointing_shape', 'select.crop.idle' ) && !isChangingStyle && !isReadonlyMode const showCornerRotateHandles = !isCoarsePointer && !(isTinyX || isTinyY) && (shouldDisplayControls || showCropHandles) && (onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) && !isLockedShape const showMobileRotateHandle = isCoarsePointer && (!isSmallX || !isSmallY) && (shouldDisplayControls || showCropHandles) && (onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) && !isLockedShape const showResizeHandles = shouldDisplayControls && (onlyShape ? editor.getShapeUtil(onlyShape).canResize(onlyShape) && !editor.getShapeUtil(onlyShape).hideResizeHandles(onlyShape) : true) && !showCropHandles && !isLockedShape const hideAlternateCornerHandles = isTinyX || isTinyY const showOnlyOneHandle = isTinyX && isTinyY const hideAlternateCropHandles = isSmallCropX || isSmallCropY const showHandles = showResizeHandles || showCropHandles const hideRotateCornerHandles = !showCornerRotateHandles const hideMobileRotateHandle = !shouldDisplayControls || !showMobileRotateHandle const hideTopLeftCorner = !shouldDisplayControls || !showHandles const hideTopRightCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles const hideBottomLeftCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles const hideBottomRightCorner = !shouldDisplayControls || !showHandles || (showOnlyOneHandle && !showCropHandles) let hideEdgeTargetsDueToCoarsePointer = isCoarsePointer if ( hideEdgeTargetsDueToCoarsePointer && shapes.every((shape) => editor.getShapeUtil(shape).isAspectRatioLocked(shape)) ) { hideEdgeTargetsDueToCoarsePointer = false } // If we're showing crop handles, then show the edges too. // If we're showing resize handles, then show the edges only // if we're not hiding them for some other reason let hideEdgeTargets = true if (showCropHandles) { hideEdgeTargets = hideAlternateCropHandles } else if (showResizeHandles) { hideEdgeTargets = hideAlternateCornerHandles || showOnlyOneHandle || hideEdgeTargetsDueToCoarsePointer } const textHandleHeight = Math.min(24 / zoom, height - targetSizeY * 3) const showTextResizeHandles = shouldDisplayControls && isCoarsePointer && onlyShape && editor.isShapeOfType(onlyShape, 'text') && textHandleHeight * zoom >= 4 return ( {shouldDisplayBox && ( )} {/* Targets */} {/* Corner Targets */} {/* Resize Handles */} {showResizeHandles && ( <> )} {showTextResizeHandles && ( <> )} {/* Crop Handles */} {showCropHandles && ( )} ) } ) export const RotateCornerHandle = function RotateCornerHandle({ cx, cy, targetSize, corner, cursor, isHidden, 'data-testid': testId, }: { cx: number cy: number targetSize: number corner: RotateCorner cursor?: string isHidden: boolean 'data-testid'?: string }) { const events = useSelectionEvents(corner) return ( ) } const SQUARE_ROOT_PI = Math.sqrt(Math.PI) export const MobileRotateHandle = function RotateHandle({ cx, cy, size, isHidden, 'data-testid': testId, }: { cx: number cy: number size: number isHidden: boolean 'data-testid'?: string }) { const events = useSelectionEvents('mobile_rotate') const editor = useEditor() const zoom = useValue('zoom level', () => editor.getZoomLevel(), [editor]) const bgRadius = Math.max(14 * (1 / zoom), 20 / Math.max(1, zoom)) return ( ) }