LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - chordbracketlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 15.8 % 133 21
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "chordbracketlayout.h"
      24              : 
      25              : #include <algorithm>
      26              : #include <vector>
      27              : 
      28              : #include "dom/chord.h"
      29              : #include "dom/chordbracket.h"
      30              : #include "dom/engravingitem.h"
      31              : #include "dom/note.h"
      32              : #include "dom/part.h"
      33              : #include "dom/segment.h"
      34              : 
      35              : #include "chordlayout.h"
      36              : #include "horizontalspacing.h"
      37              : #include "tlayout.h"
      38              : 
      39              : using namespace muse;
      40              : using namespace mu::engraving;
      41              : using namespace mu::engraving::rendering::score;
      42              : 
      43              : //---------------------------------------------------------
      44              : //   chordBracketsInSegment
      45              : //---------------------------------------------------------
      46              : 
      47      1573724 : std::vector<ChordBracket*> ChordBracketLayout::chordBracketsInSegment(const Segment* segment)
      48              : {
      49      1573724 :     std::vector<ChordBracket*> result;
      50              : 
      51      1573724 :     if (!segment) {
      52            0 :         return result;
      53              :     }
      54              : 
      55     15613920 :     for (EngravingItem* item : segment->elist()) {
      56     14040196 :         if (!item || !item->isChord()) {
      57     12935604 :             continue;
      58              :         }
      59              : 
      60      1107103 :         for (EngravingItem* child : toChord(item)->el()) {
      61         2511 :             if (child && child->isChordBracket()) {
      62            0 :                 result.push_back(toChordBracket(child));
      63              :             }
      64              :         }
      65              :     }
      66              : 
      67      1573724 :     return result;
      68            0 : }
      69              : 
      70              : //---------------------------------------------------------
      71              : //   bracketVisualSpan
      72              : //---------------------------------------------------------
      73              : 
      74            0 : ChordBracketLayout::BracketVisualSpan ChordBracketLayout::bracketVisualSpan(const ChordBracket* bracket)
      75              : {
      76            0 :     const Chord* startChord = bracket->chord();
      77            0 :     EngravingItem* endItem = startChord->segment()->element(bracket->endTrack());
      78            0 :     const Chord* endChord = endItem && endItem->isChord() ? toChord(endItem) : startChord;
      79              : 
      80            0 :     const auto topY = [](const Chord* chord) {
      81            0 :         const Note* note = chord->upNote();
      82            0 :         return chord->pos().y() + note->pos().y() + note->ldata()->bbox().top();
      83              :     };
      84              : 
      85            0 :     const auto bottomY = [](const Chord* chord) {
      86            0 :         const Note* note = chord->downNote();
      87            0 :         return chord->pos().y() + note->pos().y() + note->ldata()->bbox().bottom();
      88              :     };
      89              : 
      90            0 :     const staff_idx_t startStaff = startChord->vStaffIdx();
      91            0 :     const staff_idx_t endStaff = endChord->vStaffIdx();
      92              : 
      93            0 :     const double spatium = bracket->spatium();
      94            0 :     const double lineWidth = bracket->style().styleAbsolute(Sid::chordBracketLineWidth);
      95              : 
      96              :     // Match TLayout::layoutChordBracket() padding.
      97            0 :     const double topPadding = bracket->userLen1() + 0.25 * spatium + 0.5 * lineWidth;
      98            0 :     const double bottomPadding = bracket->userLen2() + 0.5 * spatium + 0.5 * lineWidth;
      99              : 
     100            0 :     BracketVisualSpan result;
     101            0 :     result.topStaff = std::min(startStaff, endStaff);
     102            0 :     result.bottomStaff = std::max(startStaff, endStaff);
     103              : 
     104            0 :     if (startStaff == endStaff) {
     105            0 :         result.topY = std::min(topY(startChord), topY(endChord)) - topPadding;
     106            0 :         result.bottomY = std::max(bottomY(startChord), bottomY(endChord)) + bottomPadding;
     107              : 
     108              :         // Enforce the minimum length using endpoint coordinates.
     109            0 :         const double minLen = 2 * spatium;
     110            0 :         const double diff = minLen - std::abs(result.bottomY - result.topY);
     111              : 
     112            0 :         if (diff > 0.0) {
     113            0 :             if (bracket->hookPos() == DirectionV::DOWN) {
     114            0 :                 result.topY -= diff;
     115            0 :             } else if (bracket->hookPos() == DirectionV::AUTO) {
     116            0 :                 result.topY -= 0.5 * diff;
     117            0 :                 result.bottomY += 0.5 * diff;
     118              :             } else {
     119            0 :                 result.bottomY += diff;
     120              :             }
     121              :         }
     122              :     } else {
     123            0 :         const Chord* upperAnchor = startStaff < endStaff ? startChord : endChord;
     124            0 :         const Chord* lowerAnchor = startStaff < endStaff ? endChord : startChord;
     125              : 
     126            0 :         result.topY = topY(upperAnchor) - topPadding;
     127            0 :         result.bottomY = bottomY(lowerAnchor) + bottomPadding;
     128              :     }
     129              : 
     130            0 :     result.topY += bracket->offset().y();
     131            0 :     result.bottomY += bracket->offset().y();
     132              : 
     133            0 :     return result;
     134              : }
     135              : 
     136              : //---------------------------------------------------------
     137              : //   bracketCollisionShape
     138              : //---------------------------------------------------------
     139              : 
     140            0 : Shape ChordBracketLayout::bracketCollisionShape(const ChordBracket* bracket, const BracketVisualSpan& span, staff_idx_t staffIdx,
     141              :                                                 double x)
     142              : {
     143            0 :     if (staffIdx < span.topStaff || staffIdx > span.bottomStaff) {
     144            0 :         return Shape();
     145              :     }
     146              : 
     147            0 :     double top = span.topY;
     148            0 :     double bottom = span.bottomY;
     149              : 
     150              :     // Final staff distances are not known during horizontal spacing.
     151              :     // Approximate the bracket per staff: extend it toward the other endpoint on the endpoint staves
     152              :     // and across the full height of intermediate staves.
     153              :     static constexpr double CROSS_STAFF_COLLISION_EXTENT = 100000.0;
     154              : 
     155            0 :     if (span.topStaff != span.bottomStaff) {
     156            0 :         top = staffIdx == span.topStaff ? span.topY : -CROSS_STAFF_COLLISION_EXTENT;
     157            0 :         bottom = staffIdx == span.bottomStaff ? span.bottomY : CROSS_STAFF_COLLISION_EXTENT;
     158              :     }
     159              : 
     160            0 :     if (bottom < top) {
     161            0 :         std::swap(top, bottom);
     162              :     }
     163              : 
     164            0 :     return Shape(RectF(x, top, bracket->width(), bottom - top), bracket);
     165              : }
     166              : 
     167              : //---------------------------------------------------------
     168              : //   layoutHorizontal
     169              : //---------------------------------------------------------
     170            0 : void ChordBracketLayout::layoutHorizontal(ChordBracket* bracket)
     171              : {
     172            0 :     Chord* owner = bracket->chord();
     173            0 :     Segment* segment = owner->segment();
     174            0 :     const TrackRange range = bracket->part()->trackRange();
     175            0 :     const BracketVisualSpan span = bracketVisualSpan(bracket);
     176              : 
     177            0 :     double requiredDistance = 0.0;
     178            0 :     const double baseX = owner->pos().x() + bracket->offset().x();
     179              : 
     180            0 :     for (track_idx_t track = range.startTrack; track < range.endTrack; ++track) {
     181            0 :         EngravingItem* item = segment->element(track);
     182            0 :         if (!item || !item->isChord()) {
     183            0 :             continue;
     184              :         }
     185              : 
     186            0 :         Chord* chord = toChord(item);
     187            0 :         Shape chordShape = chord->shape();
     188            0 :         chordShape.removeTypes({ ElementType::CHORD_BRACKET });
     189            0 :         chordShape.translate(chord->pos());
     190            0 :         if (chordShape.empty()) {
     191            0 :             continue;
     192              :         }
     193              : 
     194            0 :         Shape bracketShape = bracketCollisionShape(bracket, span, chord->vStaffIdx(), baseX);
     195            0 :         if (bracketShape.empty()) {
     196            0 :             continue;
     197              :         }
     198              : 
     199            0 :         const double distance = bracket->rightSide()
     200            0 :                                 ? HorizontalSpacing::minHorizontalDistance(chordShape, bracketShape, bracket->spatium())
     201            0 :                                 : HorizontalSpacing::minHorizontalDistance(bracketShape, chordShape, bracket->spatium());
     202            0 :         requiredDistance = std::max(requiredDistance, distance);
     203            0 :     }
     204              : 
     205            0 :     bracket->mutldata()->setPosX(bracket->rightSide() ? requiredDistance : -requiredDistance);
     206            0 : }
     207              : 
     208              : //---------------------------------------------------------
     209              : //   layoutSegment
     210              : //---------------------------------------------------------
     211              : 
     212       130198 : void ChordBracketLayout::layoutSegment(Segment* segment, LayoutContext& ctx)
     213              : {
     214       130198 :     if (!segment) {
     215            0 :         return;
     216              :     }
     217              : 
     218       130198 :     for (ChordBracket* bracket : chordBracketsInSegment(segment)) {
     219            0 :         Chord* owner = bracket->chord();
     220            0 :         if (!owner || owner->segment() != segment || !bracket->part() || owner->onTabStaff()) {
     221            0 :             continue;
     222              :         }
     223              : 
     224              :         // Initialize the bracket's width, vertical geometry, and Shape.
     225              :         // Replace the chord-local horizontal position with the segment-level result.
     226            0 :         TLayout::layoutItem(bracket, ctx);
     227            0 :         layoutHorizontal(bracket);
     228              : 
     229              :         // Rebuild the owner Shape after moving the bracket.
     230            0 :         ChordLayout::fillShape(owner, owner->mutldata());
     231       130198 :     }
     232              : }
     233              : 
     234              : //---------------------------------------------------------
     235              : //   updateHorizontalSpacing
     236              : //---------------------------------------------------------
     237              : 
     238       721763 : void ChordBracketLayout::updateHorizontalSpacing(const Segment* firstSegment, const Segment* secondSegment,
     239              :                                                  staff_idx_t staffIdx, double squeezeFactor, double& minDistance)
     240              : {
     241       721763 :     if (!firstSegment || !secondSegment) {
     242            0 :         return;
     243              :     }
     244              : 
     245      1443526 :     auto updateForBrackets = [&](const Segment* segment, const Shape& otherShape, bool rightSide) {
     246      1443526 :         for (ChordBracket* bracket : chordBracketsInSegment(segment)) {
     247            0 :             const Chord* owner = bracket->chord();
     248            0 :             if (!owner || owner->onTabStaff() || !bracket->visible() || !bracket->addToSkyline() || bracket->rightSide() != rightSide) {
     249            0 :                 continue;
     250              :             }
     251              : 
     252            0 :             const BracketVisualSpan span = bracketVisualSpan(bracket);
     253              : 
     254            0 :             if (span.topStaff == span.bottomStaff) {
     255            0 :                 continue;
     256              :             }
     257              : 
     258            0 :             const double x = owner->pos().x() + bracket->pos().x();
     259            0 :             const Shape bracketShape = bracketCollisionShape(bracket, span, staffIdx, x);
     260              : 
     261            0 :             if (bracketShape.empty()) {
     262            0 :                 continue;
     263              :             }
     264              : 
     265              :             const double distance = rightSide
     266            0 :                                     ? HorizontalSpacing::minHorizontalDistance(bracketShape, otherShape, bracket->spatium(), squeezeFactor)
     267            0 :                                     : HorizontalSpacing::minHorizontalDistance(otherShape, bracketShape, bracket->spatium(), squeezeFactor);
     268              : 
     269            0 :             minDistance = std::max(minDistance, distance);
     270      1443526 :         }
     271      1443526 :     };
     272              : 
     273              :     // Check the brackets facing the gap between the two Segments:
     274              :     // left-side brackets on the second Segment and right-side brackets on the first.
     275       721763 :     updateForBrackets(secondSegment, firstSegment->staffShape(staffIdx), /*rightSide=*/ false);
     276       721763 :     updateForBrackets(firstSegment, secondSegment->staffShape(staffIdx), /*rightSide=*/ true);
     277              : }
     278              : 
     279              : //---------------------------------------------------------
     280              : //   updateVerticalGeometry
     281              : //---------------------------------------------------------
     282              : 
     283            0 : void ChordBracketLayout::updateVerticalGeometry(ChordBracket* bracket, LayoutContext& ctx)
     284              : {
     285            0 :     if (!bracket || !bracket->chord() || !bracket->chord()->segment()) {
     286            0 :         return;
     287              :     }
     288              : 
     289              :     // Re-layout the final height without changing the horizontal position.
     290            0 :     const double x = bracket->ldata()->pos().x();
     291            0 :     TLayout::layoutItem(bracket, ctx);
     292            0 :     bracket->mutldata()->setPosX(x);
     293              : 
     294              :     // Refresh cached shapes after restoring the segment-level position.
     295            0 :     Chord* chord = bracket->chord();
     296            0 :     Segment* segment = chord->segment();
     297            0 :     ChordLayout::fillShape(chord, chord->mutldata());
     298              : 
     299            0 :     const BracketVisualSpan span = bracketVisualSpan(bracket);
     300            0 :     for (staff_idx_t staffIdx = span.topStaff; staffIdx <= span.bottomStaff; ++staffIdx) {
     301            0 :         segment->createShape(staffIdx);
     302              :     }
     303              : }
        

Generated by: LCOV version 2.0-1