LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - boxlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 79.7 % 212 169
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) 2025 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 "boxlayout.h"
      24              : #include "dom/fret.h"
      25              : #include "dom/harmony.h"
      26              : #include "dom/system.h"
      27              : #include "dom/text.h"
      28              : #include "draw/fontmetrics.h"
      29              : 
      30              : #include "tlayout.h"
      31              : 
      32              : using namespace mu::engraving;
      33              : using namespace mu::engraving::rendering::score;
      34              : using namespace muse::draw;
      35              : 
      36         2288 : void BoxLayout::layoutBox(const Box* item, Box::LayoutData* ldata, const LayoutContext& ctx)
      37              : {
      38         2288 :     switch (item->type()) {
      39            0 :     case ElementType::HBOX:
      40            0 :         BoxLayout::layoutHBox(static_cast<const HBox*>(item), static_cast<HBox::LayoutData*>(ldata), ctx);
      41            0 :         break;
      42         2145 :     case ElementType::VBOX:
      43         2145 :         BoxLayout::layoutVBox(static_cast<const VBox*>(item), static_cast<VBox::LayoutData*>(ldata), ctx);
      44         2145 :         break;
      45           12 :     case ElementType::FBOX:
      46           12 :         BoxLayout::layoutFBox(static_cast<const FBox*>(item), static_cast<FBox::LayoutData*>(ldata), ctx);
      47           12 :         break;
      48          131 :     case ElementType::TBOX:
      49          131 :         BoxLayout::layoutTBox(static_cast<const TBox*>(item), static_cast<TBox::LayoutData*>(ldata), ctx);
      50          131 :         break;
      51            0 :     default:
      52            0 :         UNREACHABLE;
      53              :         break;
      54              :     }
      55         2288 : }
      56              : 
      57         2669 : void BoxLayout::layoutBaseBox(const Box* item, Box::LayoutData* ldata, const LayoutContext& ctx)
      58              : {
      59         2669 :     TLayout::layoutBaseMeasureBase(item, ldata, ctx);
      60         2669 : }
      61              : 
      62          147 : void BoxLayout::layoutHBox(const HBox* item, HBox::LayoutData* ldata, const LayoutContext& ctx)
      63              : {
      64          147 :     if (item->ownershipParent() && item->ownershipParent()->isVBox()) {
      65            0 :         const VBox* parentVBox = toVBox(item->ownershipParent());
      66              : 
      67            0 :         LD_CONDITION(parentVBox->ldata()->isSetBbox());
      68              : 
      69            0 :         double x = parentVBox->leftMargin() * DPMM;
      70            0 :         double y = parentVBox->topMargin() * DPMM;
      71            0 :         double w = item->absoluteFromSpatium(item->boxWidth());
      72            0 :         double h = parentVBox->ldata()->bbox().height() - (parentVBox->topMargin() + parentVBox->bottomMargin()) * DPMM;
      73            0 :         ldata->setPos(x, y);
      74            0 :         ldata->setBbox(0.0, 0.0, w, h);
      75          147 :     } else if (item->system()) {
      76          147 :         const System* parentSystem = item->system();
      77              : 
      78          147 :         LD_CONDITION(parentSystem->ldata()->isSetBbox());
      79              : 
      80          147 :         if (!ldata->isSetPos()) {
      81            0 :             ldata->setPos(PointF());
      82              :         }
      83          147 :         ldata->setBbox(item->absoluteFromSpatium(item->topGap()), 0.0, item->absoluteFromSpatium(item->boxWidth()),
      84          147 :                        parentSystem->ldata()->bbox().height());
      85              :     } else {
      86            0 :         ldata->setPos(PointF());
      87            0 :         ldata->setBbox(0.0, 0.0, 50, 50);
      88              :     }
      89          147 :     layoutBaseBox(item, ldata, ctx);
      90          147 : }
      91              : 
      92          377 : void BoxLayout::layoutHBox2(HBox* item, const LayoutContext& ctx)
      93              : {
      94          377 :     layoutBaseBox(item, item->mutldata(), ctx);
      95          377 : }
      96              : 
      97         2145 : void BoxLayout::layoutVBox(const VBox* item, VBox::LayoutData* ldata, const LayoutContext& ctx)
      98              : {
      99         2145 :     ldata->setPos(PointF());
     100              : 
     101         2145 :     if (item->system()) {
     102         2145 :         const System* parentSystem = item->system();
     103              : 
     104         2145 :         LD_CONDITION(parentSystem->ldata()->isSetBbox());
     105              : 
     106         2145 :         ldata->setBbox(0.0, 0.0, parentSystem->ldata()->bbox().width(), item->absoluteFromSpatium(item->boxHeight()));
     107              :     } else {
     108            0 :         ldata->setBbox(0.0, 0.0, 50, 50);
     109              :     }
     110              : 
     111         6142 :     for (EngravingItem* e : item->el()) {
     112         3997 :         TLayout::layoutItem(e, const_cast<LayoutContext&>(ctx));
     113              :     }
     114         2145 :     bool boxAutoSize = item->getProperty(Pid::BOX_AUTOSIZE).toBool();
     115         2145 :     bool heightChanged = false;
     116         2145 :     if (boxAutoSize) {
     117         1504 :         double contentHeight = item->contentRect().height();
     118              : 
     119         1504 :         if (contentHeight < item->minHeight()) {
     120          792 :             contentHeight = item->minHeight();
     121              :         }
     122              : 
     123         1504 :         ldata->setHeight(contentHeight);
     124         1504 :         heightChanged = true;
     125              :     }
     126              : 
     127         2145 :     if (boxAutoSize && MScore::noImages) {
     128              :         // adjustLayoutWithoutImages
     129            0 :         double calculatedVBoxHeight = 0;
     130            0 :         const int padding = item->sizeIsSpatiumDependent() ? ctx.conf().spatium() : ctx.conf().style().defaultSpatium();
     131            0 :         ElementList elist = item->el();
     132            0 :         for (EngravingItem* e : elist) {
     133            0 :             if (e->isText()) {
     134            0 :                 Text* txt = toText(e);
     135            0 :                 Text::LayoutData* txtLD = txt->mutldata();
     136              : 
     137            0 :                 LD_CONDITION(txtLD->isSetBbox());
     138              : 
     139            0 :                 RectF bbox = txtLD->bbox();
     140            0 :                 bbox.moveTop(0.0);
     141            0 :                 txtLD->setBbox(bbox);
     142            0 :                 calculatedVBoxHeight += txtLD->bbox().height() + padding;
     143              :             }
     144              :         }
     145              : 
     146            0 :         ldata->setHeight(calculatedVBoxHeight);
     147            0 :         heightChanged = true;
     148            0 :     }
     149         2145 :     if (heightChanged) {
     150         4339 :         for (EngravingItem* e : item->el()) {
     151         2835 :             TLayout::layoutItem(e, const_cast<LayoutContext&>(ctx));
     152              :         }
     153              :     }
     154              : 
     155         2145 :     layoutBaseBox(item, ldata, ctx);
     156         2145 : }
     157              : 
     158           12 : void BoxLayout::layoutFBox(const FBox* item, FBox::LayoutData* ldata, const LayoutContext& ctx)
     159              : {
     160           12 :     Score* score = item->score();
     161              : 
     162           12 :     if (item->needsRebuild()) {
     163           14 :         for (EngravingItem* element : item->el()) {
     164           12 :             if (!element || !element->isFretDiagram()) {
     165            0 :                 continue;
     166              :             }
     167              : 
     168           12 :             FretDiagram* diagram = toFretDiagram(element);
     169              :             //! NOTE: We need to layout the diagrams to get the harmony names to show in the UI
     170           12 :             TLayout::layoutItem(diagram, const_cast<LayoutContext&>(ctx));
     171           12 :             if (!diagram->visible()) {
     172              :                 //! but we don't need to draw them, so let's add a skip
     173            0 :                 diagram->mutldata()->setIsSkipDraw(true);
     174            0 :                 diagram->harmony()->mutldata()->setIsSkipDraw(true);
     175            0 :                 continue;
     176              :             }
     177              :         }
     178              : 
     179              :         /// FBox::init requires all chord symbols in the score have valid ldata
     180              :         /// Perform layout
     181           30 :         for (mu::engraving::Segment* segment = score->firstSegment(mu::engraving::SegmentType::ChordRest); segment;
     182           28 :              segment = segment->next1(mu::engraving::SegmentType::ChordRest)) {
     183           40 :             for (EngravingItem* segItem : segment->annotations()) {
     184           12 :                 if (!segItem || !segItem->part()) {
     185            0 :                     continue;
     186              :                 }
     187           12 :                 if (!(segItem->isHarmony() || segItem->isFretDiagram())) {
     188            0 :                     continue;
     189              :                 }
     190              : 
     191           12 :                 Harmony* harmony = segItem->isHarmony() ? toHarmony(segItem) : toFretDiagram(segItem)->harmony();
     192           12 :                 if (!harmony || harmony->harmonyType() != HarmonyType::STANDARD || harmony->ldata()->isValid()) {
     193            5 :                     continue;
     194              :                 }
     195              : 
     196            7 :                 TLayout::layoutHarmony(harmony, harmony->mutldata(), ctx);
     197              :             }
     198              :         }
     199              : 
     200            2 :         const_cast<FBox*>(item)->init();
     201            2 :         const_cast<FBox*>(item)->setNeedsRebuild(false);
     202              :     }
     203              : 
     204           12 :     const System* parentSystem = item->system();
     205           12 :     LD_CONDITION(parentSystem->ldata()->isSetBbox());
     206              : 
     207           12 :     ldata->setPos(PointF());
     208              : 
     209           12 :     std::vector<FretDiagram*> fretDiagrams;
     210           70 :     for (EngravingItem* element : item->el()) {
     211           58 :         if (!element || !element->isFretDiagram() || !element->visible()) {
     212            0 :             continue;
     213              :         }
     214              : 
     215           58 :         fretDiagrams.emplace_back(toFretDiagram(element));
     216              :     }
     217              : 
     218              :     //! NOTE: layout fret diagrams and calculate sizes
     219              : 
     220           12 :     const size_t totalDiagrams = fretDiagrams.size();
     221           12 :     double maxFretDiagramWidth = 0.0;
     222              : 
     223           70 :     for (size_t i = 0; i < totalDiagrams; ++i) {
     224           58 :         FretDiagram* fretDiagram = fretDiagrams[i];
     225           58 :         fretDiagram->setUserMag(item->diagramScale());
     226              : 
     227           58 :         Harmony* harmony = fretDiagram->harmony();
     228           58 :         harmony->mutldata()->setMag(item->textScale());
     229              : 
     230           58 :         TLayout::layoutItem(fretDiagram, const_cast<LayoutContext&>(ctx));
     231              : 
     232              :         //! reset the skip which was added above
     233           58 :         fretDiagram->mutldata()->setIsSkipDraw(false);
     234           58 :         harmony->mutldata()->setIsSkipDraw(false);
     235              : 
     236           58 :         double width = fretDiagram->ldata()->bbox().width();
     237           58 :         maxFretDiagramWidth = std::max(maxFretDiagramWidth, width);
     238              :     }
     239              : 
     240              :     //! NOTE: table view layout
     241              : 
     242           12 :     const double spatium = item->spatium();
     243           12 :     const size_t chordsPerRow = item->chordsPerRow();
     244           12 :     const double rowGap = item->rowGap().val() * spatium;
     245           12 :     const double columnGap = item->columnGap().val() * spatium;
     246              : 
     247              :     //! The height of each row is determined by the height of the tallest cell in that row
     248           12 :     const size_t numRows = chordsPerRow > 0 ? (totalDiagrams + chordsPerRow - 1) / chordsPerRow : 0;
     249           12 :     std::vector<double> rowHeights;
     250           12 :     rowHeights.reserve(numRows);
     251           12 :     std::vector<double> harmonyHeights;
     252           12 :     harmonyHeights.reserve(numRows);
     253           12 :     std::vector<double> harmonyBaselines;
     254           12 :     harmonyBaselines.reserve(numRows);
     255           24 :     for (size_t i = 0; i < totalDiagrams; i += chordsPerRow) {
     256           12 :         size_t itemsInRow = std::min(chordsPerRow, totalDiagrams - i);
     257           12 :         double maxRowHeight = 0.0;
     258           12 :         double maxHarmonyHeight = 0.0;
     259           12 :         double harmonyBaseline = 0.0;
     260              : 
     261           70 :         for (size_t j = 0; j < itemsInRow; ++j) {
     262           58 :             FretDiagram* fretDiagram = fretDiagrams[i + j];
     263              : 
     264           58 :             RectF fretRect = fretDiagram->ldata()->bbox();
     265              : 
     266           58 :             const Harmony::LayoutData* harmonyLdata = fretDiagram->harmony()->ldata();
     267           58 :             RectF harmonyRect = harmonyLdata->bbox().translated(harmonyLdata->pos());
     268              : 
     269           58 :             double height = fretRect.united(harmonyRect).height();
     270           58 :             maxRowHeight = std::max(maxRowHeight, height);
     271           58 :             maxHarmonyHeight = std::max(maxHarmonyHeight, -harmonyRect.top());
     272           58 :             harmonyBaseline = std::min(harmonyBaseline, harmonyLdata->pos().y());
     273              :         }
     274              : 
     275           12 :         rowHeights.push_back(maxRowHeight);
     276           12 :         harmonyHeights.push_back(maxHarmonyHeight);
     277           12 :         harmonyBaselines.push_back(harmonyBaseline);
     278              :     }
     279              : 
     280           12 :     const double cellWidth = maxFretDiagramWidth;
     281           12 :     const size_t columns = std::min(totalDiagrams, chordsPerRow);
     282              : 
     283           12 :     const double totalTableWidth = cellWidth * columns + (columns - 1) * columnGap;
     284              : 
     285           12 :     AlignH alignH = item->contentHorizontalAlignment();
     286           12 :     const double leftMargin = item->getProperty(Pid::LEFT_MARGIN).toDouble() * spatium;
     287           12 :     const double rightMargin = item->getProperty(Pid::RIGHT_MARGIN).toDouble() * spatium;
     288           12 :     const double topMargin = item->getProperty(Pid::TOP_MARGIN).toDouble() * spatium;
     289           12 :     const double bottomMargin = item->getProperty(Pid::BOTTOM_MARGIN).toDouble() * spatium;
     290              : 
     291           12 :     const double width = item->system()->width();
     292              : 
     293           12 :     const double startX = alignH == AlignH::HCENTER
     294           12 :                           ? (width - totalTableWidth) / 2
     295            0 :                           : alignH == AlignH::RIGHT ? width - totalTableWidth : 0.0;
     296           12 :     const double startY = topMargin;
     297              : 
     298           12 :     double bottomY = 0.0;
     299              : 
     300           70 :     for (size_t i = 0; i < totalDiagrams; ++i) {
     301           58 :         FretDiagram* fretDiagram = fretDiagrams[i];
     302           58 :         Harmony* harmony = fretDiagram->harmony();
     303              : 
     304           58 :         size_t row = i / chordsPerRow;
     305           58 :         size_t col = i % chordsPerRow;
     306              : 
     307           58 :         size_t itemsInRow = std::min(chordsPerRow, totalDiagrams - row * chordsPerRow);
     308           58 :         double rowOffsetX = alignH == AlignH::HCENTER
     309           58 :                             ? (totalTableWidth - (itemsInRow * cellWidth + (itemsInRow - 1) * columnGap)) / 2
     310              :                             : alignH == AlignH::RIGHT
     311            0 :                             ? totalTableWidth - (itemsInRow * cellWidth + (itemsInRow - 1) * columnGap) - rightMargin + spatium
     312              :                             : leftMargin + spatium;
     313              : 
     314           58 :         double x = startX + rowOffsetX + col * (cellWidth + columnGap);
     315              : 
     316           58 :         double y = startY;
     317           58 :         for (size_t r = 0; r < row; ++r) {
     318            0 :             y += rowHeights[r] + rowGap;
     319              :         }
     320              : 
     321           58 :         double commonBaseline = harmonyBaselines[row];
     322           58 :         double thisBaseline = harmony->ldata()->pos().y();
     323           58 :         double baselineOff = commonBaseline - thisBaseline;
     324           58 :         harmony->mutldata()->moveY(baselineOff);
     325              : 
     326           58 :         double fretDiagramX = x;
     327           58 :         double fretDiagramY = y + harmonyHeights[row];
     328              : 
     329           58 :         fretDiagram->mutldata()->setPos(PointF(fretDiagramX, fretDiagramY));
     330              : 
     331           58 :         bottomY = std::max(bottomY, fretDiagram->mutldata()->bbox().translated(fretDiagram->mutldata()->pos()).bottom());
     332              :     }
     333              : 
     334           12 :     double height = bottomY + bottomMargin;
     335           12 :     if (muse::RealIsNull(height)) {
     336            0 :         height = item->minHeight();
     337              :     }
     338              : 
     339           12 :     ldata->setBbox(0.0, 0.0, width, height);
     340           12 : }
     341              : 
     342          131 : void BoxLayout::layoutTBox(const TBox* item, TBox::LayoutData* ldata, const LayoutContext& ctx)
     343              : {
     344          131 :     const System* parentSystem = item->system();
     345              : 
     346          131 :     LD_CONDITION(parentSystem->ldata()->isSetBbox());
     347              : 
     348          131 :     ldata->setPos(PointF());
     349          131 :     ldata->setBbox(0.0, 0.0, parentSystem->ldata()->bbox().width(), 0);
     350              : 
     351          131 :     TLayout::layoutText(item->text(), item->text()->mutldata());
     352              : 
     353          131 :     Text::LayoutData* textLD = item->text()->mutldata();
     354              : 
     355          131 :     double h = 0.0;
     356          131 :     if (item->text()->empty()) {
     357            6 :         h = FontMetrics::ascent(item->text()->font());
     358              :     } else {
     359          125 :         h = textLD->bbox().height();
     360              :     }
     361          131 :     double y = item->topMargin() * DPMM;
     362          131 :     textLD->setPos(item->leftMargin() * DPMM, y);
     363          131 :     h += item->topMargin() * DPMM + item->bottomMargin() * DPMM;
     364          131 :     ldata->setBbox(0.0, 0.0, item->system()->width(), h);
     365              : 
     366          131 :     TLayout::layoutBaseMeasureBase(item, ldata, ctx);   // layout LayoutBreak's
     367          131 : }
        

Generated by: LCOV version 2.0-1