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 : }
|