@@ -436,52 +436,179 @@ void Path::addBubble (Rectangle<float> bodyArea, Rectangle<float> maximumArea, P
436436 // Clamp corner size to reasonable bounds
437437 cornerSize = jmin (cornerSize, bodyArea.getWidth () * 0 .5f , bodyArea.getHeight () * 0 .5f );
438438
439- // Determine which side of the body the arrow should be on
440- Point<float > bodyCenter = bodyArea.getCenter ();
439+ // Check if arrow tip is inside the body area - if so, draw no arrow
440+ if (bodyArea.contains (arrowTipPosition))
441+ {
442+ // Just draw a rounded rectangle
443+ addRoundedRectangle (bodyArea, cornerSize);
444+ return ;
445+ }
441446
442- // Calculate which edge the arrow should attach to
443- float leftDist = std::abs (arrowTipPosition.getX () - bodyArea.getX ());
444- float rightDist = std::abs (arrowTipPosition.getX () - bodyArea.getRight ());
445- float topDist = std::abs (arrowTipPosition.getY () - bodyArea.getY ());
446- float bottomDist = std::abs (arrowTipPosition.getY () - bodyArea.getBottom ());
447+ // Determine which side the arrow should be on based on tip position relative to rectangle
448+ enum ArrowSide { Left, Right, Top, Bottom } arrowSide;
449+ Point<float > arrowBase1, arrowBase2;
447450
448- float minDist = jmin (leftDist, rightDist, topDist, bottomDist);
451+ // Get rectangle center for direction calculation
452+ Point<float > rectCenter = bodyArea.getCenter ();
449453
450- // Start with the main body (rounded rectangle)
451- addRoundedRectangle (bodyArea, cornerSize);
454+ // Calculate relative position of arrow tip
455+ float deltaX = arrowTipPosition.getX () - rectCenter.getX ();
456+ float deltaY = arrowTipPosition.getY () - rectCenter.getY ();
452457
453- // Add arrow based on closest edge
454- Point<float > arrowBase1, arrowBase2;
458+ // Determine primary direction - use the larger absolute offset
459+ if (std::abs (deltaX) > std::abs (deltaY))
460+ {
461+ // Horizontal direction is dominant
462+ if (deltaX < 0 )
463+ {
464+ // Arrow tip is to the left of rectangle center
465+ arrowSide = Left;
466+ // Ensure arrow base doesn't overlap with corner radius
467+ float minY = bodyArea.getY () + cornerSize + arrowBaseWidth * 0 .5f ;
468+ float maxY = bodyArea.getBottom () - cornerSize - arrowBaseWidth * 0 .5f ;
469+ float arrowY = jlimit (minY, maxY, arrowTipPosition.getY ());
470+ // For left edge (going bottom to top in clockwise direction)
471+ arrowBase1 = Point<float > (bodyArea.getX (), arrowY + arrowBaseWidth * 0 .5f ); // bottom base point
472+ arrowBase2 = Point<float > (bodyArea.getX (), arrowY - arrowBaseWidth * 0 .5f ); // top base point
473+ }
474+ else
475+ {
476+ // Arrow tip is to the right of rectangle center
477+ arrowSide = Right;
478+ // Ensure arrow base doesn't overlap with corner radius
479+ float minY = bodyArea.getY () + cornerSize + arrowBaseWidth * 0 .5f ;
480+ float maxY = bodyArea.getBottom () - cornerSize - arrowBaseWidth * 0 .5f ;
481+ float arrowY = jlimit (minY, maxY, arrowTipPosition.getY ());
482+ // For right edge (going top to bottom in clockwise direction)
483+ arrowBase1 = Point<float > (bodyArea.getRight (), arrowY - arrowBaseWidth * 0 .5f ); // top base point
484+ arrowBase2 = Point<float > (bodyArea.getRight (), arrowY + arrowBaseWidth * 0 .5f ); // bottom base point
485+ }
486+ }
487+ else
488+ {
489+ // Vertical direction is dominant
490+ if (deltaY < 0 )
491+ {
492+ // Arrow tip is above rectangle center
493+ arrowSide = Top;
494+ // Ensure arrow base doesn't overlap with corner radius
495+ float minX = bodyArea.getX () + cornerSize + arrowBaseWidth * 0 .5f ;
496+ float maxX = bodyArea.getRight () - cornerSize - arrowBaseWidth * 0 .5f ;
497+ float arrowX = jlimit (minX, maxX, arrowTipPosition.getX ());
498+ // For top edge (going left to right in clockwise direction)
499+ arrowBase1 = Point<float > (arrowX - arrowBaseWidth * 0 .5f , bodyArea.getY ()); // left base point
500+ arrowBase2 = Point<float > (arrowX + arrowBaseWidth * 0 .5f , bodyArea.getY ()); // right base point
501+ }
502+ else
503+ {
504+ // Arrow tip is below rectangle center
505+ arrowSide = Bottom;
506+ // Ensure arrow base doesn't overlap with corner radius
507+ float minX = bodyArea.getX () + cornerSize + arrowBaseWidth * 0 .5f ;
508+ float maxX = bodyArea.getRight () - cornerSize - arrowBaseWidth * 0 .5f ;
509+ float arrowX = jlimit (minX, maxX, arrowTipPosition.getX ());
510+ // For bottom edge (going right to left in clockwise direction)
511+ arrowBase1 = Point<float > (arrowX + arrowBaseWidth * 0 .5f , bodyArea.getBottom ()); // right base point
512+ arrowBase2 = Point<float > (arrowX - arrowBaseWidth * 0 .5f , bodyArea.getBottom ()); // left base point
513+ }
514+ }
515+
516+ // Use the mathematically correct constant for circular arc approximation with cubic Bezier curves
517+ constexpr float kappa = 0 .5522847498f ;
518+
519+ float x = bodyArea.getX ();
520+ float y = bodyArea.getY ();
521+ float width = bodyArea.getWidth ();
522+ float height = bodyArea.getHeight ();
523+
524+ // Start drawing the integrated path clockwise from top-left
525+ moveTo (x + cornerSize, y);
526+
527+ // Top edge(left to right)
528+ if (arrowSide == Top)
529+ {
530+ lineTo (arrowBase1.getX (), arrowBase1.getY ());
531+ lineTo (arrowTipPosition.getX (), arrowTipPosition.getY ());
532+ lineTo (arrowBase2.getX (), arrowBase2.getY ());
533+ lineTo (x + width - cornerSize, y);
534+ }
535+ else
536+ {
537+ lineTo (x + width - cornerSize, y);
538+ }
539+
540+ // Top-right corner
541+ if (cornerSize > 0 .0f )
542+ {
543+ cubicTo (x + width - cornerSize + cornerSize * kappa, y,
544+ x + width, y + cornerSize - cornerSize * kappa,
545+ x + width, y + cornerSize);
546+ }
547+
548+ // Right edge (top to bottom)
549+ if (arrowSide == Right)
550+ {
551+ lineTo (arrowBase1.getX (), arrowBase1.getY ());
552+ lineTo (arrowTipPosition.getX (), arrowTipPosition.getY ());
553+ lineTo (arrowBase2.getX (), arrowBase2.getY ());
554+ lineTo (x + width, y + height - cornerSize);
555+ }
556+ else
557+ {
558+ lineTo (x + width, y + height - cornerSize);
559+ }
560+
561+ // Bottom-right corner
562+ if (cornerSize > 0 .0f )
563+ {
564+ cubicTo (x + width, y + height - cornerSize + cornerSize * kappa,
565+ x + width - cornerSize + cornerSize * kappa, y + height,
566+ x + width - cornerSize, y + height);
567+ }
568+
569+ // Bottom edge (right to left)
570+ if (arrowSide == Bottom)
571+ {
572+ lineTo (arrowBase1.getX (), arrowBase1.getY ());
573+ lineTo (arrowTipPosition.getX (), arrowTipPosition.getY ());
574+ lineTo (arrowBase2.getX (), arrowBase2.getY ());
575+ lineTo (x + cornerSize, y + height);
576+ }
577+ else
578+ {
579+ lineTo (x + cornerSize, y + height);
580+ }
455581
456- if (minDist == leftDist) // Arrow on left side
582+ // Bottom-left corner
583+ if (cornerSize > 0 .0f )
457584 {
458- float arrowY = jlimit (bodyArea. getY () + cornerSize, bodyArea. getBottom () - cornerSize, arrowTipPosition. getY ());
459- arrowBase1 = Point< float > (bodyArea. getX (), arrowY - arrowBaseWidth * 0 . 5f );
460- arrowBase2 = Point< float > (bodyArea. getX (), arrowY + arrowBaseWidth * 0 . 5f );
585+ cubicTo (x + cornerSize - cornerSize * kappa, y + height,
586+ x, y + height - cornerSize + cornerSize * kappa,
587+ x, y + height - cornerSize );
461588 }
462- else if (minDist == rightDist) // Arrow on right side
589+
590+ // Left edge (bottom to top)
591+ if (arrowSide == Left)
463592 {
464- float arrowY = jlimit (bodyArea.getY () + cornerSize, bodyArea.getBottom () - cornerSize, arrowTipPosition.getY ());
465- arrowBase1 = Point<float > (bodyArea.getRight (), arrowY - arrowBaseWidth * 0 .5f );
466- arrowBase2 = Point<float > (bodyArea.getRight (), arrowY + arrowBaseWidth * 0 .5f );
593+ lineTo (arrowBase1.getX (), arrowBase1.getY ());
594+ lineTo (arrowTipPosition.getX (), arrowTipPosition.getY ());
595+ lineTo (arrowBase2.getX (), arrowBase2.getY ());
596+ lineTo (x, y + cornerSize);
467597 }
468- else if (minDist == topDist) // Arrow on top side
598+ else
469599 {
470- float arrowX = jlimit (bodyArea.getX () + cornerSize, bodyArea.getRight () - cornerSize, arrowTipPosition.getX ());
471- arrowBase1 = Point<float > (arrowX - arrowBaseWidth * 0 .5f , bodyArea.getY ());
472- arrowBase2 = Point<float > (arrowX + arrowBaseWidth * 0 .5f , bodyArea.getY ());
600+ lineTo (x, y + cornerSize);
473601 }
474- else // Arrow on bottom side
602+
603+ // Top-left corner
604+ if (cornerSize > 0 .0f )
475605 {
476- float arrowX = jlimit (bodyArea. getX () + cornerSize, bodyArea. getRight () - cornerSize, arrowTipPosition. getX ());
477- arrowBase1 = Point< float > (arrowX - arrowBaseWidth * 0 . 5f , bodyArea. getBottom ());
478- arrowBase2 = Point< float > (arrowX + arrowBaseWidth * 0 . 5f , bodyArea. getBottom () );
606+ cubicTo (x, y + cornerSize - cornerSize * kappa,
607+ x + cornerSize - cornerSize * kappa, y,
608+ x + cornerSize, y );
479609 }
480610
481- // Add the arrow triangle
482- moveTo (arrowBase1);
483- lineTo (arrowTipPosition);
484- lineTo (arrowBase2);
611+ // Close the path
485612 close ();
486613}
487614
0 commit comments