1+ import Konva from "konva" ;
12import { getRoot , isAlive , types } from "mobx-state-tree" ;
23import { useContext } from "react" ;
34import { Rect } from "react-konva" ;
@@ -54,6 +55,7 @@ const RectRegionAbsoluteCoordsDEV3793 = types
5455 self . updateAppearenceFromState ( ) ;
5556 } ,
5657 setPosition ( x , y , width , height , rotation ) {
58+ [ x , y , width , height , rotation ] = self . beforeSetPosition ( x , y , width , height , rotation ) ;
5759 self . x = x ;
5860 self . y = y ;
5961 self . width = width ;
@@ -333,6 +335,34 @@ const Model = types
333335 self . rotation = ( rotation + 360 ) % 360 ;
334336 } ,
335337
338+ beforeSetPosition ( x , y , width , height , rotation ) {
339+ // Konva flipping fix
340+ if ( height < 0 ) {
341+ let flippedBack ;
342+ // If height is negative, it means it was flipped. We need to correct it
343+ // Negative height also means that it was changed.
344+ // In that case the difference between rotation and current rotation may be only 0 (or 360) and 180 degrees.
345+ // However, as it's not an integer value, we had to check the difference to be sure,
346+ // so 90 and 270 degrees are the safest values to check
347+ const deltaRotation = Math . abs ( rotation - self . rotation ) % 360 ;
348+ if ( deltaRotation > 90 && deltaRotation < 270 ) {
349+ // when rotation changes involved, it's a horizontal flip
350+ flippedBack = self . flipBack ( { x, y, width, height, rotation } , true ) ;
351+ } else {
352+ // vertical flip
353+ flippedBack = self . flipBack ( { x, y, width, height, rotation } ) ;
354+ }
355+ [ x , y , width , height , rotation ] = [
356+ flippedBack . x ,
357+ flippedBack . y ,
358+ flippedBack . width ,
359+ flippedBack . height ,
360+ flippedBack . rotation ,
361+ ] ;
362+ }
363+ return [ x , y , width , height , rotation ] ;
364+ } ,
365+
336366 /**
337367 * Bounding Box set position on canvas
338368 * @param {number } x
@@ -342,6 +372,7 @@ const Model = types
342372 * @param {number } rotation
343373 */
344374 setPosition ( x , y , width , height , rotation ) {
375+ [ x , y , width , height , rotation ] = self . beforeSetPosition ( x , y , width , height , rotation ) ;
345376 const internalX = self . parent . canvasToInternalX ( x ) ;
346377 const internalY = self . parent . canvasToInternalY ( y ) ;
347378 const internalWidth = self . parent . canvasToInternalX ( width ) ;
@@ -362,8 +393,14 @@ const Model = types
362393 } ) ;
363394
364395 // Calculate snapped dimensions
365- const snappedWidth = bottomRightPoint . x - topLeftPoint . x ;
366- const snappedHeight = bottomRightPoint . y - topLeftPoint . y ;
396+ let snappedWidth = bottomRightPoint . x - topLeftPoint . x ;
397+ let snappedHeight = bottomRightPoint . y - topLeftPoint . y ;
398+
399+ // Ensure at least 1 pixel in size after snapping
400+ const minPixelWidth = self . parent ?. zoomedPixelSize ?. x ?? 1 ;
401+ const minPixelHeight = self . parent ?. zoomedPixelSize ?. y ?? 1 ;
402+ if ( snappedWidth < minPixelWidth ) snappedWidth = minPixelWidth ;
403+ if ( snappedHeight < minPixelHeight ) snappedHeight = minPixelHeight ;
367404
368405 self . setPositionInternal ( topLeftPoint . x , topLeftPoint . y , snappedWidth , snappedHeight , rotation ) ;
369406 } else {
@@ -395,27 +432,39 @@ const Model = types
395432 * - when the region is flipped horizontally with no rotation, we fix the rotation back to 0.
396433 * - when the region is flipped vertically, rotation is still 0, we just flip the height.
397434 */
398- flipRegion ( ) {
399- const height = - self . height ;
400-
401- // the most common case, when the region is flipped horizontally with no rotation,
402- // for this case we are fixing rotation back to 0, that's more intuitive for the user.
403- if ( self . rotation === 180 ) {
404- self . height = height ;
405- self . x -= self . width ;
406- self . rotation = 0 ;
435+ flipBack ( attrs , isHorizontalFlip = false ) {
436+ // To make it calculable, we need to avoid relative coordinates
437+ let { x, y, width, height, rotation } = attrs ;
438+ const radiansRotation = ( rotation * Math . PI ) / 180 ;
439+ const transform = new Konva . Transform ( ) ;
440+ transform . rotate ( radiansRotation ) ;
441+ let targetCorner ;
442+
443+ if ( isHorizontalFlip ) {
444+ // When it flips horizontally, it turns the height negative and rotates the region by 180°.
445+ // In general, we want to return a top-right corner to the top-left corner and rotate back
446+ targetCorner = {
447+ x : width ,
448+ y : 0 ,
449+ } ;
450+ rotation = ( rotation + 180 ) % 360 ;
407451 } else {
408- // we need to invert the height and swap top-left and bottom-left corners, but with respect to rotation.
409- // we'll use tranform from Konva.js to not fight aspect ratio and rotation.
410- // transform is calculated in canvas coords, so we need to convert coords back and forth.
411- const transform = self . shapeRef . getAbsoluteTransform ( ) ;
412- // bottom-left corner; it's "above" the top-left corner because of inverted height
413- const { x, y } = transform . point ( { x : 0 , y : - self . parent . internalToCanvasY ( height ) } ) ;
414-
415- self . height = height ;
416- self . x = self . parent . canvasToInternalX ( x ) ;
417- self . y = self . parent . canvasToInternalY ( y ) ;
452+ // In a vertical flipping case it affects only the height.
453+ // It means that we want to return a bottom-left corner to the top-left corner
454+ targetCorner = {
455+ x : 0 ,
456+ y : height ,
457+ } ;
418458 }
459+ const offset = transform . point ( targetCorner ) ;
460+
461+ return {
462+ x : x + offset . x ,
463+ y : y + offset . y ,
464+ width,
465+ height : - height ,
466+ rotation,
467+ } ;
419468 } ,
420469
421470 /**
@@ -492,6 +541,7 @@ const HtxRectangleView = ({ item, setShapeRef }) => {
492541 } ;
493542 eventHandlers . onTransformEnd = ( e ) => {
494543 const t = e . target ;
544+ const isFlipped = t . getAttr ( "scaleY" ) < 0 ;
495545
496546 item . setPosition (
497547 t . getAttr ( "x" ) ,
@@ -516,6 +566,12 @@ const HtxRectangleView = ({ item, setShapeRef }) => {
516566 } ) ;
517567 }
518568
569+ if ( isFlipped ) {
570+ // Somehow react-konva caches rotation, most probably as a controllable state,
571+ // so we need to set it manually if it able to be reverted to the previous value
572+ t . setAttr ( "rotation" , item . rotation ) ;
573+ }
574+
519575 item . notifyDrawingFinished ( ) ;
520576 } ;
521577
0 commit comments