packageDocumentation module import "./DrawingNavigationAid.scss" ; import classnames from "classnames" ; import * as React from "react" ; import * as ReactDOM from "react-dom" ; import { Constant , Geometry , Matrix3d , Point2d , Point3d , Point4d , Vector3d } from "@itwin/core-geometry" ; import { IModelApp , IModelConnection , ScreenViewport , ViewManager , Viewport , ViewState } from "@itwin/core-frontend" ; import { SpecialKey } from "@itwin/appui-abstract" ; import { CommonProps } from "@itwin/core-react" ; import { UiIModelComponents } from "../UiIModelComponents" ; import { ViewportComponentEvents , ViewRotationChangeEventArgs } from "../viewport/ViewportComponentEvents" ; internal export enum MapMode { Opened = "map-opened" , Closed = "map-closed" } public export interface DrawingNavigationAidProps extends CommonProps { iModelConnection : IModelConnection ; viewport ? : Viewport ; internal animationTime ? : number ; internal openSize ? : Vector3d ; internal closeSize ? : Vector3d ; internal initialMapMode ? : MapMode ; internal onAnimationEnd ? : ( ) => void ; internal initialRotateMinimapWithView ? : boolean ; internal initialView ? : ViewState ; internal viewManagerOverride ? : ViewManager ; internal screenViewportOverride ? : typeof ScreenViewport ; } internal interface DrawingNavigationAidState { startOrigin : Point3d ; origin : Point3d ; extents : Vector3d ; startRotation : Matrix3d ; rotation : Matrix3d ; startMapOrigin : Point3d ; mapOrigin : Point3d ; startMapExtents : Vector3d ; mapExtents : Vector3d ; mouseStart : Point2d ; panningDirection : Vector3d ; startDrawingZoom : number ; drawingZoom : number ; mode : MapMode ; animation : number ; isMoving : boolean ; isPanning : boolean ; viewId : string ; view : ViewState | undefined ; rotateMinimapWithView : boolean ; } public export class DrawingNavigationAid extends React . Component < DrawingNavigationAidProps , DrawingNavigationAidState > { private _rootElement = React . createRef < HTMLDivElement > ( ) ; private _viewContainerElement = React . createRef < HTMLDivElement > ( ) ; private _viewElement = React . createRef < HTMLDivElement > ( ) ; private _rootOffset : DOMRect = DOMRect . fromRect ( { x : 0 , y : 0 , width : 0 , height : 0 } ) ; private _viewport : Viewport | undefined ; private _animationFrame : any ; private _mounted : boolean = false ; internal public override readonly state : Readonly < DrawingNavigationAidState > ; constructor ( props ) { super ( props ) ; const mode = props . initialMapMode || MapMode . Closed ; const mapExtents = mode === MapMode . Closed ? this . _getClosedMapSize ( ) : this . _getOpenedMapSize ( ) ; const startMapExtents = mapExtents ; this . state = { startOrigin : Point3d . createZero ( ) , origin : Point3d . createZero ( ) , extents : Vector3d . create ( 1 , 1 , 1 ) , startRotation : Matrix3d . createIdentity ( ) , rotation : Matrix3d . createIdentity ( ) , startMapOrigin : Point3d . createZero ( ) , mapOrigin : Point3d . createZero ( ) , startMapExtents , mapExtents , mouseStart : Point2d . createZero ( ) , panningDirection : Vector3d . createZero ( ) , startDrawingZoom : 1.0 , drawingZoom : 1.0 , mode , animation : 1.0 , viewId : props . initialView !== undefined ? props . initialView . id : "" , view : props . initialView , isMoving : false , isPanning : false , rotateMinimapWithView : props . initialRotateMinimapWithView !== undefined ? props . initialRotateMinimapWithView : false } ; } internal public override render ( ) { const { startOrigin , origin , extents , startRotation , rotation , startMapOrigin , mapOrigin , startMapExtents , mapExtents , startDrawingZoom , drawingZoom , animation , viewId , mode , rotateMinimapWithView , } = this . state ; const a = DrawingNavigationAid . _animationFn ( animation ) ; const rot = Matrix3d . createFromQuaternion ( Point4d . interpolateQuaternions ( startRotation . toQuaternion ( ) , a , rotation . toQuaternion ( ) ) ) ; const or = startOrigin . interpolate ( a , origin ) ; const map = startMapOrigin . interpolate ( a , mapOrigin ) ; const sz = startMapExtents . interpolate ( a , mapExtents ) ; const dz = Geometry . interpolate ( startDrawingZoom , a , drawingZoom ) ; const rootStyle = { width : sz . x , height : sz . y , ... this . props . style } ; const is3D = this . _isViewport3D ( ) ; const tOr = rotateMinimapWithView || is3D ? rotation . multiplyVector ( Vector3d . createFrom ( or ) ) : Vector3d . createFrom ( or ) ; const tMap = rotateMinimapWithView || is3D ? rotation . multiplyVector ( Vector3d . createFrom ( map ) ) : Vector3d . createFrom ( map ) ; const tPosX = ( tOr . x - extents . x / 2 - tMap . x ) * dz + sz . x / 2 ; const tPosY = ( - tOr . y - extents . y / 2 + tMap . y ) * dz + sz . y / 2 ; const viewWindowStyle = { transform : `translate(${ tPosX }px, ${ tPosY }px)` , height : extents . y * dz , width : extents . x * dz } ; if ( ! rotateMinimapWithView && ! is3D ) viewWindowStyle . transform = "matrix3d(" + `${ rot . at ( 0 , 0 ) }, ${ rot . at ( 1 , 0 ) }, ${ rot . at ( 2 , 0 ) }, 0, ` + `${ rot . at ( 0 , 1 ) }, ${ rot . at ( 1 , 1 ) }, ${ rot . at ( 2 , 1 ) }, 0, ` + `${ rot . at ( 0 , 2 ) }, ${ rot . at ( 1 , 2 ) }, ${ rot . at ( 2 , 2 ) }, 0, ` + `${ tPosX }, ${ tPosY }, 0, 1)` ; const isAnimating = animation !== 1 && ! startMapExtents . isExactEqual ( mapExtents ) ; if ( mode === MapMode . Opened || isAnimating ) { rootStyle . position = "fixed" ; rootStyle . right = window . innerWidth - this . _rootOffset . right ; rootStyle . top = this . _rootOffset . top ; } const halfExtents = extents . scale ( .5 ) ; const offset = rotateMinimapWithView || is3D ? Vector3d . createZero ( ) : rotation . multiplyTransposeVector ( halfExtents ) . minus ( halfExtents ) ; const unrotateLabel = UiIModelComponents . translate ( "drawing.unrotate" ) ; const e = sz . scale ( 1 / dz ) ; const halfMapExtents = e . scale ( .5 ) ; const mapOffset = rotateMinimapWithView || is3D ? rotation . multiplyTransposeVector ( halfMapExtents ) : halfMapExtents ; const viewManager = this . props . viewManagerOverride ? this . props . viewManagerOverride : IModelApp . viewManager ; const screenViewport = this . props . screenViewportOverride ? this . props . screenViewportOverride : ScreenViewport ; const nodes = ( < div className = { classnames ( "components-drawing-navigation-aid" , this . props . className ) } data - testid = "components-drawing-navigation-aid" ref = { this . _rootElement } onWheel = { this . _handleWheel } onMouseDown = { this . _handleDrawingMouseDown } style = { rootStyle } role = "presentation" > < DrawingNavigationCanvas view = { this . state . view } viewManagerOverride = { viewManager } screenViewportOverride = { screenViewport } viewId = { viewId } origin = { map . plus ( offset ) . minus ( mapOffset ) } extents = { e } zoom = { dz } rotation = { rotateMinimapWithView || is3D ? rotation : Matrix3d . createIdentity ( ) } /> < div className = "drawing-container" data - testid = "drawing-container" ref = { this . _viewContainerElement } > < div className = "drawing-view-window" data - testid = "drawing-view-window" onMouseDown = { this . _handleWindowMouseDown } onTouchStart = { this . _handleWindowTouchStart } style = { viewWindowStyle } ref = { this . _viewElement } role = "presentation" / > { ! is3D && < div className = { classnames ( "toggle-rotate-style" , "icon" , "icon-rotate-left" , { checked : rotateMinimapWithView } ) } data - testid = "toggle-rotate-style" style = { mode === MapMode . Closed && ! isAnimating ? { bottom : 2 , left : 2 } : { } } title = { UiIModelComponents . translate ( "drawing.rotateStyle" ) } onClick = { this . _toggleRotationMode } role = "presentation" / > } { ! rot . isIdentity && < div className = "unrotate-button" data - testid = "drawing-unrotate-button" style = { mode === MapMode . Closed && ! isAnimating ? { top : 2 } : { } } onClick = { this . _handleUnrotate } role = "button" tabIndex = { - 1 } > { unrotateLabel } < /div>} { mode === MapMode . Opened && ! isAnimating && < > < div className = "close" data - testid = "drawing-close-button" onClick = { this . _closeLargeMap } role = "button" tabIndex = { - 1 } > < div className = "close-icon icon icon-sort-up" / > < /div> < div className = "zoom" > < div className = "zoom-button icon icon-add" data - testid = "drawing-zoom-in-button" onClick = { this . _handleZoomIn } role = "button" tabIndex = { - 1 } /> < div className = "zoom-button icon icon-remove-2" data - testid = "drawing-zoom-out-button" onClick = { this . _handleZoomOut } role = "button" tabIndex = { - 1 } /> < /div> < />} < /div> < /div>); if ( mode === MapMode . Closed && ( animation === 1.0 || startMapExtents . isExactEqual ( mapExtents ) ) ) { return nodes ; } else { let drawingPortal = document . querySelector ( "#components-drawing-portal" ) ; if ( ! drawingPortal ) { drawingPortal = document . createElement ( "div" ) ; drawingPortal . id = "components-drawing-portal" ; document . body . appendChild ( drawingPortal ) ; } return ReactDOM . createPortal ( nodes , drawingPortal ) ; } } private _isViewport3D = ( ) => { return this . state . view !== undefined && this . state . view . is3d ( ) ; } ; private _lastTime : number | undefined ; private static _animationFn = ( t ) => t < .5 ? 2 * t ** 2 : - 1 + ( 4 - 2 * t ) * t ; private _animation = ( ) => { const t = Date . now ( ) ; const delta = this . _lastTime ? t - this . _lastTime : 16 ; this . _lastTime = t ; if ( this . state . animation !== 1 ) { const animation = Math . min ( 1 , this . state . animation + delta / ( 1000 * ( this . props . animationTime || 0.4 ) ) ) ; this . _updateFrustum ( ) ; if ( this . _mounted ) { this . setState ( { animation } , ( ) => { this . _animationFrame = setTimeout ( this . _animation , 16.667 ) ; } ) ; } } else { this . _lastTime = undefined ; this . _animationEnd ( ) ; } } ; private _animationEnd = ( ) => { const hasViewportMoved = ! this . state . origin . isAlmostEqual ( this . state . startOrigin ) || ! this . state . rotation . isAlmostEqual ( this . state . startRotation ) ; const startMapOrigin = Point3d . createFrom ( this . state . mapOrigin ) ; const startMapExtents = Vector3d . createFrom ( this . state . mapExtents ) ; const startDrawingZoom = this . state . drawingZoom ; const startOrigin = this . state . origin ; const startRotation = this . state . rotation ; if ( this . _mounted ) { this . setState ( { startMapOrigin , startDrawingZoom , startOrigin , startRotation , startMapExtents } , ( ) => { if ( this . props . onAnimationEnd ) this . props . onAnimationEnd ( ) ; if ( hasViewportMoved ) this . _updateFrustum ( true ) ; } ) ; } } ; private _lastPanTime : number | undefined ; private _panAnimation = ( ) => { const t = Date . now ( ) ; if ( this . _lastPanTime === undefined ) this . _lastPanTime = t - 16.667 ; const delta = t - this . _lastPanTime ; this . _lastPanTime = t ; if ( ! this . state . panningDirection . isAlmostZero && this . state . isMoving ) { const { panningDirection } = this . state ; const offset = panningDirection . scale ( delta / 1000 / this . state . drawingZoom ) ; const mapOrigin = this . state . mapOrigin . plus ( offset ) ; const origin = this . state . origin . plus ( offset ) ; this . _updateFrustum ( ) ; if ( this . _mounted ) { this . setState ( { mapOrigin , origin , panningDirection } , ( ) => { this . _animationFrame = setTimeout ( this . _panAnimation , 16.667 ) ; } ) ; } } else { this . _lastPanTime = undefined ; this . _updateFrustum ( true ) ; } } ; public override componentDidMount ( ) { this . _mounted = true ; ViewportComponentEvents . onViewRotationChangeEvent . addListener ( this . _handleViewRotationChangeEvent ) ; window . addEventListener ( "mousedown" , this . _handleMouseDown as any ) ; window . addEventListener ( "mouseup" , this . _handleMouseDragEnd as any ) ; window . addEventListener ( "mousemove" , this . _handleMouseDrag as any ) ; window . addEventListener ( "keyup" , this . _handleKeyUp as any ) ; } public override componentWillUnmount ( ) { ViewportComponentEvents . onViewRotationChangeEvent . removeListener ( this . _handleViewRotationChangeEvent ) ; window . removeEventListener ( "mousedown" , this . _handleMouseDown as any ) ; window . removeEventListener ( "mouseup" , this . _handleMouseDragEnd as any ) ; window . removeEventListener ( "mousemove" , this . _handleMouseDrag as any ) ; window . removeEventListener ( "keyup" , this . _handleKeyUp as any ) ; clearTimeout ( this . _animationFrame ) ; if ( this . props . onAnimationEnd ) this . props . onAnimationEnd ( ) ; const rt = document . getElementById ( "components-drawing-portal" ) as HTMLDivElement ; if ( rt && rt . parentElement !== null && rt . children . length === 0 ) { rt . parentElement . removeChild ( rt ) ; } this . _mounted = false ; } private _handleViewRotationChangeEvent = ( args ) => { if ( ! this . state . isMoving && ! this . state . isPanning && this . props . viewport === args . viewport ) { const extents = args . viewport . view . getExtents ( ) . clone ( ) ; const rotation = args . viewport . view . getRotation ( ) . clone ( ) ; const halfExtents = extents . scale ( .5 ) ; const offset = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? rotation . multiplyTransposeVector ( halfExtents ) : halfExtents ; const origin = args . viewport . view . getOrigin ( ) . plus ( offset ) ; const deltaZoom = ( this . state . extents . x - extents . x ) / this . state . extents . x ; let drawingZoom = this . state . drawingZoom / ( 1 - deltaZoom ) ; const deltaOrigin = origin . minus ( this . state . origin ) ; let mapOrigin = this . state . mapOrigin . plus ( deltaOrigin ) ; mapOrigin = mapOrigin . plus ( Vector3d . createFrom ( origin . minus ( mapOrigin ) ) . scale ( deltaZoom ) ) ; if ( this . state . viewId !== args . viewport . view . id ) { const rect = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? extents : DrawingNavigationAid . findRotatedWindowDimensions ( extents , rotation ) ; const maxRectDim = Math . max ( rect . y , rect . x ) ; const maxExtentDim = Math . max ( this . state . mapExtents . x , this . state . mapExtents . y ) ; drawingZoom = maxExtentDim / ( 3 * maxRectDim ) ; mapOrigin = origin . clone ( ) ; this . _viewport = args . viewport ; this . setState ( { viewId : args . viewport . view . id , view : args . viewport . view . clone ( ) } ) ; } if ( this . _mounted ) { this . setState ( { startOrigin : origin , origin , extents , startRotation : rotation , rotation , startMapOrigin : mapOrigin , mapOrigin , startDrawingZoom : drawingZoom , drawingZoom } ) ; } } } ; private _updateFrustum = ( complete = false ) => { const halfExtents = this . state . extents . scale ( .5 ) ; const offset = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? this . state . rotation . multiplyTransposeVector ( halfExtents ) : halfExtents ; const origin = this . state . origin . minus ( offset ) ; ViewportComponentEvents . setDrawingViewportState ( origin , this . state . rotation , complete ) ; } ; private _toggleRotationMode = ( ) => { const rotateMinimapWithView = ! this . state . rotateMinimapWithView ; this . setState ( { rotateMinimapWithView } , ( ) => { if ( this . _viewport ) this . _handleViewRotationChangeEvent ( { viewport : this . _viewport } ) ; } ) ; } ; private _handleKeyUp = ( event ) => { if ( event . key === SpecialKey . Escape && this . state . mode === MapMode . Opened ) { this . _closeLargeMap ( ) ; } } ; private _lastClientXY : Point2d = Point2d . createZero ( ) ; private _processWindowDrag ( movement ) { const vect = Vector3d . create ( movement . x / this . state . drawingZoom , - movement . y / this . state . drawingZoom , 0 ) ; const offset = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? this . state . rotation . multiplyTransposeVector ( vect ) : vect ; const origin = this . state . origin . plus ( offset ) ; const panningDirection = this . _getPanVector ( ) ; const wasAlmostZero = this . state . panningDirection . isAlmostZero ; this . setState ( { startOrigin : origin , origin , panningDirection , isPanning : false } , ( ) => { this . _updateFrustum ( ) ; if ( wasAlmostZero && ! this . state . panningDirection . isAlmostZero ) { this . _animationFrame = setTimeout ( this . _panAnimation , 16.667 ) ; } } ) ; } private _processWindowEndDrag ( ) { this . setState ( { isMoving : false } ) ; this . _updateFrustum ( true ) ; if ( this . state . mode === MapMode . Closed ) { setTimeout ( ( ) => { const startMapOrigin = this . state . mapOrigin ; const mapOrigin = this . state . origin . clone ( ) ; if ( this . _mounted ) { this . setState ( { startMapOrigin , mapOrigin , animation : 0 } , ( ) => { this . _animationFrame = setTimeout ( this . _animation , 16.667 ) ; } ) ; } } , 0 ) ; } } private _handleMouseDown = ( event ) => { const mouseStart = Point2d . create ( event . clientX , event . clientY ) ; this . setState ( { mouseStart } ) ; this . _lastClientXY = Point2d . create ( event . clientX , event . clientY ) ; } ; private _handleDrawingMouseDown = ( event ) => { if ( this . state . mode === MapMode . Opened ) { event . preventDefault ( ) ; this . setState ( { isPanning : true } ) ; } } ; private _handleWindowMouseDown = ( event ) => { event . preventDefault ( ) ; this . setState ( { isMoving : true } ) ; } ; private _handleMouseDrag = ( event ) => { const mouse = Point2d . create ( event . clientX , event . clientY ) ; const movement = mouse . minus ( this . _lastClientXY ) ; if ( this . state . isMoving ) { this . _processWindowDrag ( movement ) ; } else { if ( this . state . isPanning && this . state . mode === MapMode . Opened ) { const vect = Vector3d . create ( movement . x / this . state . drawingZoom , - movement . y / this . state . drawingZoom , 0 ) ; const offset = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? this . state . rotation . multiplyTransposeVector ( vect ) : vect ; const mapOrigin = this . state . mapOrigin . minus ( offset ) ; this . setState ( { startMapOrigin : mapOrigin , mapOrigin } ) ; } } this . _lastClientXY = mouse ; } ; private _handleMouseDragEnd = ( event ) => { const mouse = Point2d . create ( event . clientX , event . clientY ) ; if ( mouse . isAlmostEqual ( this . state . mouseStart ) && this . state . mode === MapMode . Closed && ( event . target === this . _viewContainerElement . current || event . target === this . _viewElement . current ) ) { this . _openLargeMap ( ) ; } else if ( this . state . isMoving ) { this . _processWindowEndDrag ( ) ; } else if ( this . state . isPanning ) { if ( this . state . mode === MapMode . Opened ) { event . stopPropagation ( ) ; this . setState ( { isPanning : false } ) ; } } else if ( ( ! ( event . target instanceof Node ) || this . _rootElement . current && ! this . _rootElement . current . contains ( event . target ) ) && mouse . isAlmostEqual ( this . state . mouseStart ) ) { this . _closeLargeMap ( ) ; } this . _lastClientXY = Point2d . create ( event . clientX , event . clientY ) ; } ; private _handleWindowTouchStart = ( event ) => { if ( 1 !== event . targetTouches . length ) return ; window . addEventListener ( "touchmove" , this . _onTouchMove , false ) ; window . addEventListener ( "touchend" , this . _onTouchEnd , false ) ; const mouseStart = Point2d . create ( event . targetTouches [ 0 ] . clientX , event . targetTouches [ 0 ] . clientY ) ; this . setState ( { mouseStart } ) ; this . _lastClientXY = mouseStart . clone ( ) ; this . setState ( { isMoving : true } ) ; } ; private _onTouchMove = ( event ) => { if ( 1 !== event . targetTouches . length ) return ; const mouse = Point2d . create ( event . targetTouches [ 0 ] . clientX , event . targetTouches [ 0 ] . clientY ) ; const movement = mouse . minus ( this . _lastClientXY ) ; this . _processWindowDrag ( movement ) ; this . _lastClientXY = mouse ; } ; private _onTouchEnd = ( event ) => { if ( 0 !== event . targetTouches . length ) return ; this . _processWindowEndDrag ( ) ; if ( 0 !== event . changedTouches . length ) this . _lastClientXY = Point2d . create ( event . changedTouches [ 0 ] . clientX , event . changedTouches [ 0 ] . clientY ) ; window . removeEventListener ( "touchmove" , this . _onTouchMove ) ; window . removeEventListener ( "touchend" , this . _onTouchEnd ) ; } ; public static getDefaultClosedMapSize = ( ) : Vector3d => { return Vector3d . create ( 96 , 96 ) ; } ; public static getDefaultOpenedMapSize = ( paddingX = 0 , paddingY = 0 ) : Vector3d => { return Vector3d . create ( window . innerWidth / 3 - 2 * ( window . innerWidth - paddingX ) , window . innerHeight / 3 - 2 * paddingY ) ; } ; private _getClosedMapSize = ( ) : Vector3d => { if ( this . props . closeSize === undefined ) return DrawingNavigationAid . getDefaultClosedMapSize ( ) ; return this . props . closeSize ; } ; private _getOpenedMapSize = ( ) : Vector3d => { if ( this . props . openSize === undefined ) return DrawingNavigationAid . getDefaultOpenedMapSize ( this . _rootOffset . right , this . _rootOffset . top ) ; return this . props . openSize ; } ; private _openLargeMap = ( ) => { if ( this . state . mode === MapMode . Closed && this . state . animation === 1.0 ) { if ( this . _rootElement . current ) { const rect = this . _rootElement . current . getBoundingClientRect ( ) ; this . _rootOffset = rect ; } const startMapExtents = Vector3d . createFrom ( this . state . mapExtents ) ; const mapExtents = this . _getOpenedMapSize ( ) ; this . setState ( { mode : MapMode . Opened , isMoving : false , isPanning : false , mapExtents , startMapExtents , animation : 0 } , ( ) => { this . _animationFrame = setTimeout ( this . _animation ) ; } ) ; } } ; private _closeLargeMap = ( ) => { if ( this . state . mode === MapMode . Opened && this . state . animation === 1.0 ) { const startMapOrigin = this . state . mapOrigin ; const mapOrigin = this . state . origin . clone ( ) ; const startMapExtents = Vector3d . createFrom ( this . state . mapExtents ) ; const mapExtents = this . _getClosedMapSize ( ) ; const rect = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? this . state . extents : DrawingNavigationAid . findRotatedWindowDimensions ( this . state . extents , this . state . rotation ) ; const maxRectDim = Math . max ( rect . y , rect . x ) ; const maxExtentDim = Math . max ( mapExtents . x , mapExtents . y ) ; const drawingZoom = maxExtentDim / ( 3 * maxRectDim ) ; const startDrawingZoom = this . state . drawingZoom ; this . setState ( { mode : MapMode . Closed , startMapOrigin , mapOrigin , startDrawingZoom , drawingZoom , isMoving : false , isPanning : false , startMapExtents , mapExtents , panningDirection : Vector3d . createZero ( ) , animation : 0 } , ( ) => { this . _animationFrame = setTimeout ( this . _animation , 16.667 ) ; } ) ; } } ; private _getPanVector = ( ) => { const is3D = this . _isViewport3D ( ) ; const rect = this . state . rotateMinimapWithView || is3D ? this . state . extents : DrawingNavigationAid . findRotatedWindowDimensions ( this . state . extents , this . state . rotation ) ; const mapExtents = this . state . mapExtents . scale ( 1 / this . state . drawingZoom ) ; const maxMagnitude = 80 ; const magnitudeTravel = 30 ; const diff = Vector3d . createFrom ( this . state . origin . minus ( this . state . mapOrigin ) ) ; const diffVector = this . state . rotateMinimapWithView || is3D ? this . state . rotation . multiplyVector ( diff ) : diff ; const magnitudeVector = Vector3d . create ( Math . max ( 0 , Math . abs ( diffVector . x ) - ( mapExtents . x - rect . x ) / 2 ) , Math . max ( 0 , Math . abs ( diffVector . y ) - ( mapExtents . y - rect . y ) / 2 ) ) ; if ( magnitudeVector . isAlmostZero ) return Vector3d . createZero ( ) ; const magnitude = Math . min ( magnitudeVector . magnitude ( ) * this . state . drawingZoom , magnitudeTravel ) / magnitudeTravel * maxMagnitude ; const vect = Vector3d . createFrom ( this . state . origin . minus ( this . state . mapOrigin ) ) ; const norm = vect . normalize ( ) ; return norm ! . scale ( magnitude ) ; } ; private _handleWheel = ( event ) => { if ( this . state . mode === MapMode . Opened && this . state . animation === 1 ) { const { mapOrigin , drawingZoom } = this . state ; const mapSize = this . _getOpenedMapSize ( ) ; const mouseX = event . clientX - this . _rootOffset . right + mapSize . x / 2 ; const mouseY = event . clientY - this . _rootOffset . top - mapSize . y / 2 ; const is3D = this . _isViewport3D ( ) ; if ( event . deltaY < 0 ) { const zoom = drawingZoom * 1.1 ; const vect = Vector3d . create ( mouseX * 0.1 / zoom , - mouseY * 0.1 / zoom , 0 ) ; const offset = this . state . rotateMinimapWithView || is3D ? this . state . rotation . multiplyTransposeVector ( vect ) : vect ; const m = mapOrigin . plus ( offset ) ; this . setState ( { mapOrigin : m , drawingZoom : zoom } ) ; } if ( event . deltaY > 0 ) { const zoom = drawingZoom / 1.1 ; const vect = Vector3d . create ( mouseX * 0.1 / drawingZoom , - mouseY * 0.1 / drawingZoom , 0 ) ; const offset = this . state . rotateMinimapWithView || is3D ? this . state . rotation . multiplyTransposeVector ( vect ) : vect ; const m = mapOrigin . minus ( offset ) ; this . setState ( { mapOrigin : m , drawingZoom : zoom } ) ; } } } ; private _handleZoomIn = ( ) => { const zoom = this . state . drawingZoom * 1.4 ; this . setState ( { drawingZoom : zoom } ) ; } ; private _handleZoomOut = ( ) => { const zoom = this . state . drawingZoom / 1.4 ; this . setState ( { drawingZoom : zoom } ) ; } ; private _handleUnrotate = ( ) => { const startRotation = this . state . rotation ; const halfExtents = this . state . extents . scale ( .5 ) ; const offset = this . state . rotateMinimapWithView || this . _isViewport3D ( ) ? Vector3d . createZero ( ) : this . state . rotation . multiplyTransposeVector ( halfExtents ) . minus ( halfExtents ) ; const startOrigin = this . state . origin ; const origin = this . state . origin . plus ( offset ) ; const startMapOrigin = this . state . mapOrigin ; const mapOrigin = this . state . mapOrigin . plus ( offset ) ; this . setState ( { animation : 0 , startOrigin , origin , startMapOrigin , mapOrigin , startRotation , rotation : Matrix3d . createIdentity ( ) } , ( ) => { this . _animationFrame = setTimeout ( this . _animation , 16.667 ) ; } ) ; } ; public static findRotatedWindowDimensions = ( extents , rotation ) => { const cos = rotation . at ( 0 , 0 ) ; const sin = rotation . at ( 1 , 0 ) ; const cosWidth = cos * extents . x ; const sinHeight = sin * extents . y ; const sinWidth = cos * extents . x ; const cosHeight = sin * extents . y ; const a = Math . atan2 ( sin , cos ) ; if ( a % Math . PI >= 0 && a % Math . PI < Math . PI / 2 ) { const y1 = - cosHeight - sinWidth ; const x2 = cosWidth + sinHeight ; const y3 = cosHeight + sinWidth ; const x4 = - cosWidth - sinHeight ; return Vector3d . create ( Math . abs ( x2 - x4 ) / 2 , Math . abs ( y1 - y3 ) / 2 ) ; } else { const x1 = - cosWidth + sinHeight ; const y2 = - cosHeight + sinWidth ; const x3 = cosWidth - sinHeight ; const y4 = cosHeight - sinWidth ; return Vector3d . create ( Math . abs ( x1 - x3 ) / 2 , Math . abs ( y2 - y4 ) / 2 ) ; } } ; } internal export interface DrawingNavigationCanvasProps { view : ViewState | undefined ; viewId ? : string ; origin : Point3d ; extents : Vector3d ; zoom : number ; rotation : Matrix3d ; viewManagerOverride ? : ViewManager ; screenViewportOverride ? : typeof ScreenViewport ; canvasSizeOverride ? : boolean ; } internal export class DrawingNavigationCanvas extends React . Component < DrawingNavigationCanvasProps > { private _canvasElement = React . createRef < HTMLDivElement > ( ) ; private _vp ? : ScreenViewport ; public override render ( ) { return ( < div className = "drawing-canvas" data - testid = "drawing-canvas" ref = { this . _canvasElement } > < /div>); } public override componentDidMount ( ) { if ( this . _canvasElement . current && this . props . view !== undefined ) { const viewManager = this . props . viewManagerOverride ? this . props . viewManagerOverride : IModelApp . viewManager ; const screenViewport = this . props . screenViewportOverride ? this . props . screenViewportOverride : ScreenViewport ; const previousView = viewManager . selectedView ; this . _vp = screenViewport . create ( this . _canvasElement . current , this . props . view ) ; this . customizeViewport ( this . _vp ) ; viewManager . addViewport ( this . _vp ) ; void viewManager . setSelectedView ( previousView ) ; this . _update ( ) ; } } private customizeViewport ( vp ) { if ( vp . logo . style ) vp . logo . style . display = "none" ; if ( vp . viewFlags . acsTriad ) vp . viewFlags = vp . viewFlags . with ( "acsTriad" , false ) ; } private _update = ( ) => { if ( this . _vp ) { const max = Math . max ( this . props . extents . x , this . props . extents . y ) ; this . _vp . view . extentLimits = { max , min : Constant . oneMillimeter } ; this . _vp . view . setOrigin ( this . props . origin ) ; this . _vp . view . setRotation ( this . props . rotation ) ; this . _vp . view . setExtents ( this . props . extents ) ; this . _vp . applyViewState ( this . _vp . view ) ; } } ; public override componentDidUpdate ( oldProps ) { const viewManager = this . props . viewManagerOverride ? this . props . viewManagerOverride : IModelApp . viewManager ; if ( this . props . view !== undefined ) { if ( oldProps . view !== this . props . view ) { const screenViewport = this . props . screenViewportOverride ? this . props . screenViewportOverride : ScreenViewport ; if ( oldProps !== undefined && oldProps . viewId !== "" && oldProps . viewId !== this . props . viewId && this . _vp ) { viewManager . dropViewport ( this . _vp , true ) ; } if ( this . _canvasElement . current && ( this . props . canvasSizeOverride || ( this . _canvasElement . current . clientWidth !== 0 && this . _canvasElement . current . clientHeight !== 0 ) ) ) { const previousView = viewManager . selectedView ; this . _vp = screenViewport . create ( this . _canvasElement . current , this . props . view . clone ( ) ) ; this . customizeViewport ( this . _vp ) ; viewManager . addViewport ( this . _vp ) ; void viewManager . setSelectedView ( previousView ) ; this . _update ( ) ; } } else if ( ! this . props . origin . isExactEqual ( oldProps . origin ) || ! this . props . extents . isExactEqual ( oldProps . extents ) || this . props . zoom !== oldProps . zoom || ! this . props . rotation . isExactEqual ( oldProps . rotation ) ) { this . _update ( ) ; } } } public override componentWillUnmount ( ) { if ( this . _vp ) { const viewManager = this . props . viewManagerOverride ? this . props . viewManagerOverride : IModelApp . viewManager ; viewManager . dropViewport ( this . _vp , true ) ; } } }