Skip to content

Commit 98fe216

Browse files
committed
Fix bubble
1 parent f9da470 commit 98fe216

File tree

1 file changed

+159
-32
lines changed

1 file changed

+159
-32
lines changed

modules/yup_graphics/primitives/yup_Path.cpp

Lines changed: 159 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)