|
24 | 24 | #include "horizontalspacing.h" |
25 | 25 |
|
26 | 26 | #include "dom/barline.h" |
| 27 | +#include "dom/beam.h" |
27 | 28 | #include "dom/chord.h" |
28 | 29 | #include "dom/engravingitem.h" |
29 | 30 | #include "dom/glissando.h" |
|
33 | 34 | #include "dom/score.h" |
34 | 35 | #include "dom/stemslash.h" |
35 | 36 | #include "dom/staff.h" |
| 37 | +#include "dom/stem.h" |
36 | 38 | #include "dom/system.h" |
37 | 39 | #include "dom/tie.h" |
38 | 40 | #include "dom/timesig.h" |
@@ -239,7 +241,7 @@ void HorizontalSpacing::spaceMeasureGroup(const std::vector<Measure*>& measureGr |
239 | 241 | ctx.xCur = lastMeas->x() + lastMeas->width(); |
240 | 242 | } |
241 | 243 |
|
242 | | -std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*> segList, int startSegIdx, |
| 244 | +std::vector<HorizontalSpacing::SegmentPosition> HorizontalSpacing::spaceSegments(const std::vector<Segment*>& segList, int startSegIdx, |
243 | 245 | HorizontalSpacingContext& ctx) |
244 | 246 | { |
245 | 247 | std::vector<SegmentPosition> placedSegments; |
@@ -614,6 +616,54 @@ void HorizontalSpacing::moveRightAlignedSegments(std::vector<SegmentPosition>& p |
614 | 616 | } |
615 | 617 | } |
616 | 618 |
|
| 619 | +void HorizontalSpacing::checkCollisionsWithCrossStaffStems(const Segment* thisSeg, const Segment* nextSeg, staff_idx_t staffIdx, |
| 620 | + double& curMinDist) |
| 621 | +{ |
| 622 | + std::vector<ChordRest*> itemsToCheck; |
| 623 | + itemsToCheck.reserve(VOICES); |
| 624 | + |
| 625 | + for (EngravingItem* el : nextSeg->elist()) { |
| 626 | + if (el && el->vStaffIdx() == staffIdx) { |
| 627 | + ChordRest* cr = toChordRest(el); |
| 628 | + if (cr->beam() && cr->beam()->cross()) { |
| 629 | + itemsToCheck.push_back(toChordRest(el)); |
| 630 | + } |
| 631 | + } |
| 632 | + } |
| 633 | + |
| 634 | + for (ChordRest* cr : itemsToCheck) { |
| 635 | + Chord* potentialCollidingChord = nullptr; |
| 636 | + for (ChordRest* beamElement : cr->beam()->elements()) { |
| 637 | + if (beamElement->isChord() && beamElement->parent() == thisSeg) { |
| 638 | + potentialCollidingChord = toChord(beamElement); |
| 639 | + break; |
| 640 | + } |
| 641 | + } |
| 642 | + |
| 643 | + if (!potentialCollidingChord || potentialCollidingChord->up() != cr->up() || !potentialCollidingChord->stem()) { |
| 644 | + return; |
| 645 | + } |
| 646 | + |
| 647 | + bool checkStemCollision = (cr->up() && potentialCollidingChord->vStaffIdx() > cr->vStaffIdx()) |
| 648 | + || (!cr->up() && potentialCollidingChord->vStaffIdx() < cr->vStaffIdx()); |
| 649 | + if (!checkStemCollision) { |
| 650 | + return; |
| 651 | + } |
| 652 | + |
| 653 | + Stem* stem = potentialCollidingChord->stem(); |
| 654 | + Shape prevStemShape = stem->shape().translated(stem->pos() + potentialCollidingChord->pos()); |
| 655 | + // We don't know where this stem is vertically, but we know from the beam arrangement that |
| 656 | + // it's going to be crossed anyway, so we can space to it as if it had infinite height. |
| 657 | + prevStemShape.adjust(0.0, -100000, 0.0, 100000); |
| 658 | + |
| 659 | + const Shape& staffShape = nextSeg->staffShape(staffIdx); |
| 660 | + |
| 661 | + double minDist = minHorizontalDistance(prevStemShape, staffShape, stem->spatium()); |
| 662 | + |
| 663 | + curMinDist = std::max(curMinDist, minDist); |
| 664 | + } |
| 665 | +} |
| 666 | + |
617 | 667 | double HorizontalSpacing::chordRestSegmentNaturalWidth(Segment* segment, HorizontalSpacingContext& ctx) |
618 | 668 | { |
619 | 669 | double durationStretch = computeSegmentDurationStretch(segment, segment->prev(SegmentType::ChordRest)); |
@@ -725,37 +775,76 @@ bool HorizontalSpacing::needsCueSizeSpacing(const Segment* segment) |
725 | 775 |
|
726 | 776 | void HorizontalSpacing::applyCrossBeamSpacingCorrection(Segment* thisSeg, Segment* nextSeg, double& width) |
727 | 777 | { |
728 | | - Measure* m = thisSeg->measure(); |
729 | | - CrossBeamType crossBeamType = computeCrossBeamType(thisSeg, nextSeg); |
| 778 | + CrossBeamSpacing crossBeamSpacing = computeCrossBeamSpacing(thisSeg, nextSeg); |
| 779 | + |
| 780 | + const Score* score = thisSeg->score(); |
| 781 | + const PaddingTable& paddingTable = score->paddingTable(); |
| 782 | + const MStyle& style = score->style(); |
| 783 | + |
| 784 | + double displacement = score->noteHeadWidth() - style.styleMM(Sid::stemWidth); |
730 | 785 |
|
731 | | - double displacement = m->score()->noteHeadWidth() - m->score()->style().styleMM(Sid::stemWidth); |
732 | | - if (crossBeamType.upDown && crossBeamType.canBeAdjusted) { |
| 786 | + if (crossBeamSpacing.upDown && crossBeamSpacing.canBeAdjusted) { |
733 | 787 | thisSeg->addWidthOffset(displacement); |
734 | 788 | width += displacement; |
735 | | - } else if (crossBeamType.downUp && crossBeamType.canBeAdjusted) { |
| 789 | + } else if (crossBeamSpacing.downUp && crossBeamSpacing.canBeAdjusted) { |
736 | 790 | thisSeg->addWidthOffset(-displacement); |
737 | 791 | width -= displacement; |
738 | 792 | } |
739 | 793 |
|
740 | | - if (crossBeamType.upDown) { |
741 | | - if (crossBeamType.hasOpposingBeamlets) { |
742 | | - double minBeamletClearance = m->style().styleMM(Sid::beamMinLen) * 2.0 |
743 | | - + m->score()->paddingTable().at(ElementType::BEAM).at(ElementType::BEAM); |
| 794 | + if (crossBeamSpacing.upDown) { |
| 795 | + if (crossBeamSpacing.hasOpposingBeamlets) { |
| 796 | + double minBeamletClearance = style.styleMM(Sid::beamMinLen) * 2.0 + paddingTable.at(ElementType::BEAM).at(ElementType::BEAM); |
744 | 797 | width = std::max(width, displacement + minBeamletClearance); |
745 | 798 | } else { |
746 | 799 | width = std::max(width, 2 * displacement); |
747 | 800 | } |
748 | 801 | } |
| 802 | + |
| 803 | + if (crossBeamSpacing.preventCrossStaffKerning) { |
| 804 | + double padding = crossBeamSpacing.ensureMinStemDistance ? paddingTable.at(ElementType::STEM).at(ElementType::STEM) |
| 805 | + : style.styleMM(Sid::minNoteDistance); |
| 806 | + width = std::max(width, score->noteHeadWidth() + padding); |
| 807 | + } else if (crossBeamSpacing.ensureMinStemDistance) { |
| 808 | + width = std::max(width, score->paddingTable().at(ElementType::STEM).at(ElementType::STEM)); |
| 809 | + } |
749 | 810 | } |
750 | 811 |
|
751 | | -HorizontalSpacing::CrossBeamType HorizontalSpacing::computeCrossBeamType(Segment* thisSeg, Segment* nextSeg) |
| 812 | +HorizontalSpacing::CrossBeamSpacing HorizontalSpacing::computeCrossBeamSpacing(Segment* thisSeg, Segment* nextSeg) |
752 | 813 | { |
753 | | - CrossBeamType crossBeamType; |
| 814 | + CrossBeamSpacing crossBeamType; |
754 | 815 |
|
755 | 816 | if (!thisSeg->isChordRestType() || !nextSeg || !nextSeg->isChordRestType()) { |
756 | 817 | return crossBeamType; |
757 | 818 | } |
758 | 819 |
|
| 820 | + bool preventCrossStaffKerning = false; |
| 821 | + bool ensureMinStemDistance = false; |
| 822 | + for (EngravingItem* e : thisSeg->elist()) { |
| 823 | + if (!e || !e->isChord() || !e->visible() || !e->staff()->visible()) { |
| 824 | + continue; |
| 825 | + } |
| 826 | + |
| 827 | + Chord* thisChord = toChord(e); |
| 828 | + ChordRest* nextCR = toChordRest(nextSeg->element(thisChord->track())); |
| 829 | + Chord* nextChord = nextCR && nextCR->isChord() ? toChord(nextCR) : nullptr; |
| 830 | + if (!nextChord) { |
| 831 | + continue; |
| 832 | + } |
| 833 | + |
| 834 | + int thisStaffMove = thisChord->staffMove(); |
| 835 | + int nextStaffMove = nextChord->staffMove(); |
| 836 | + if (thisStaffMove == nextStaffMove) { |
| 837 | + continue; |
| 838 | + } |
| 839 | + |
| 840 | + preventCrossStaffKerning = thisStaffMove > nextStaffMove; |
| 841 | + ensureMinStemDistance = (thisStaffMove > nextStaffMove && thisChord->up() && !nextChord->up()) |
| 842 | + || (thisStaffMove < nextStaffMove && thisChord->up() == nextChord->up()); |
| 843 | + } |
| 844 | + |
| 845 | + crossBeamType.preventCrossStaffKerning = preventCrossStaffKerning; |
| 846 | + crossBeamType.ensureMinStemDistance = ensureMinStemDistance; |
| 847 | + |
759 | 848 | bool upDown = false; |
760 | 849 | bool downUp = false; |
761 | 850 | bool canBeAdjusted = true; |
@@ -1115,6 +1204,10 @@ double HorizontalSpacing::minHorizontalDistance(const Segment* f, const Segment* |
1115 | 1204 | d = std::max(d, f->staffShape(staffIdx).right()); |
1116 | 1205 | } |
1117 | 1206 |
|
| 1207 | + if (f->isChordRestType() && ns->isChordRestType()) { |
| 1208 | + checkCollisionsWithCrossStaffStems(f, ns, staffIdx, d); |
| 1209 | + } |
| 1210 | + |
1118 | 1211 | ww = std::max(ww, d); |
1119 | 1212 | } |
1120 | 1213 | double w = std::max(ww, 0.0); // non-negative |
@@ -1354,6 +1447,10 @@ void HorizontalSpacing::computeLyricsPadding(const Lyrics* lyrics1, const Engrav |
1354 | 1447 |
|
1355 | 1448 | KerningType HorizontalSpacing::computeKerning(const EngravingItem* item1, const EngravingItem* item2) |
1356 | 1449 | { |
| 1450 | + if (ignoreItems(item1, item2)) { |
| 1451 | + return KerningType::ALLOW_COLLISION; |
| 1452 | + } |
| 1453 | + |
1357 | 1454 | if (isSameVoiceKerningLimited(item1) && isSameVoiceKerningLimited(item2) && item1->track() == item2->track()) { |
1358 | 1455 | return KerningType::NON_KERNING; |
1359 | 1456 | } |
@@ -1420,6 +1517,16 @@ bool HorizontalSpacing::isAlwaysKernable(const EngravingItem* item) |
1420 | 1517 | return item->isTextBase() || item->isChordLine() || item->isParenthesis(); |
1421 | 1518 | } |
1422 | 1519 |
|
| 1520 | +bool HorizontalSpacing::ignoreItems(const EngravingItem* item1, const EngravingItem* item2) |
| 1521 | +{ |
| 1522 | + if (item1->isRest() && toRest(item1)->isFullMeasureRest()) { |
| 1523 | + // Full-measure rest must ignore cross-stave notes |
| 1524 | + return item1->staffIdx() != item2->staffIdx(); |
| 1525 | + } |
| 1526 | + |
| 1527 | + return false; |
| 1528 | +} |
| 1529 | + |
1423 | 1530 | KerningType HorizontalSpacing::doComputeKerningType(const EngravingItem* item1, const EngravingItem* item2) |
1424 | 1531 | { |
1425 | 1532 | ElementType type1 = item1->type(); |
|
0 commit comments