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) 2023 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 : #include <cfloat>
23 : #include <unordered_set>
24 :
25 : #include "systemlayout.h"
26 :
27 : #include "realfn.h"
28 :
29 : #include "style/defaultstyle.h"
30 :
31 : #include "dom/barline.h"
32 : #include "dom/beam.h"
33 : #include "dom/box.h"
34 : #include "dom/bracket.h"
35 : #include "dom/bracketitem.h"
36 : #include "dom/chord.h"
37 : #include "dom/dynamic.h"
38 : #include "dom/factory.h"
39 : #include "dom/guitarbend.h"
40 : #include "dom/instrumentname.h"
41 : #include "dom/layoutbreak.h"
42 : #include "dom/lyrics.h"
43 : #include "dom/measure.h"
44 : #include "dom/measurenumber.h"
45 : #include "dom/mmrest.h"
46 : #include "dom/mmrestrange.h"
47 : #include "dom/note.h"
48 : #include "dom/ornament.h"
49 : #include "dom/page.h"
50 : #include "dom/pagelockindicator.h"
51 : #include "dom/part.h"
52 : #include "dom/parenthesis.h"
53 : #include "dom/pedal.h"
54 : #include "dom/playcounttext.h"
55 : #include "dom/rest.h"
56 : #include "dom/score.h"
57 : #include "dom/slur.h"
58 : #include "dom/spacer.h"
59 : #include "dom/staff.h"
60 : #include "dom/stafflines.h"
61 : #include "dom/system.h"
62 : #include "dom/systemlockindicator.h"
63 : #include "dom/tie.h"
64 : #include "dom/timesig.h"
65 : #include "dom/tremolosinglechord.h"
66 : #include "dom/tremolotwochord.h"
67 : #include "dom/tuplet.h"
68 : #include "dom/volta.h"
69 : #include "dom/whammybar.h"
70 :
71 : #include "tlayout.h"
72 : #include "alignmentlayout.h"
73 : #include "autoplace.h"
74 : #include "beamlayout.h"
75 : #include "beamtremololayout.h"
76 : #include "boxlayout.h"
77 : #include "chordlayout.h"
78 : #include "guitarbendlayout.h"
79 : #include "guitardivelayout.h"
80 : #include "harmonylayout.h"
81 : #include "lyricslayout.h"
82 : #include "measurelayout.h"
83 : #include "mmrestlayout.h"
84 : #include "tupletlayout.h"
85 : #include "restlayout.h"
86 : #include "slurtielayout.h"
87 : #include "horizontalspacing.h"
88 : #include "dynamicslayout.h"
89 : #include "stavesharinglayout.h"
90 : #include "systemheaderlayout.h"
91 :
92 : #include "defer.h"
93 : #include "log.h"
94 :
95 : using namespace mu::engraving;
96 : using namespace mu::engraving::rendering::score;
97 :
98 : //---------------------------------------------------------
99 : // collectSystem
100 : //---------------------------------------------------------
101 :
102 13158 : System* SystemLayout::collectSystem(LayoutContext& ctx)
103 : {
104 13173 : TRACEFUNC;
105 :
106 13158 : if (!ctx.state().curMeasure()) {
107 3685 : return nullptr;
108 : }
109 :
110 9473 : const MeasureBase* measure = ctx.dom().systems().empty() ? 0 : ctx.dom().systems().back()->measures().back();
111 9473 : if (measure) {
112 5624 : measure = measure->mbWithPrecedingSectionBreak();
113 : }
114 :
115 9473 : bool firstSysLongName = ctx.conf().styleV(Sid::firstSystemInstNameVisibility).value<InstrumentLabelVisibility>()
116 9473 : == InstrumentLabelVisibility::LONG;
117 9473 : if (measure) {
118 3332 : ctx.mutState().setFirstSystem(measure->sectionBreak() && !ctx.conf().isFloatMode());
119 3332 : if (const LayoutBreak* layoutBreak = measure->sectionBreakElement()) {
120 116 : ctx.mutState().setFirstSystemIndent(ctx.state().firstSystem()
121 58 : && ctx.conf().firstSystemIndent()
122 116 : && layoutBreak->firstSystemIndentation());
123 116 : ctx.mutState().setStartWithLongNames(
124 58 : ctx.state().firstSystem() && firstSysLongName && layoutBreak->startWithLongNames());
125 : }
126 : } else {
127 6141 : ctx.mutState().setStartWithLongNames(ctx.state().firstSystem() && firstSysLongName);
128 : }
129 :
130 9473 : System* system = getNextSystem(ctx);
131 20499 : for (SysStaff* staff : system->staves()) {
132 11026 : staff->skyline().clear();
133 : }
134 :
135 : LAYOUT_CALL() << LAYOUT_ITEM_INFO(system);
136 :
137 9473 : double curSysWidth = 0.0;
138 9473 : double leadingHBoxesWidth = 0.0;
139 9473 : double targetSystemWidth = ctx.conf().styleD(Sid::pagePrintableWidth) * DPI;
140 9473 : system->setWidth(targetSystemWidth);
141 :
142 : // save state of measure
143 9473 : MeasureBase* breakMeasure = nullptr;
144 :
145 9473 : System* oldSystem = nullptr;
146 :
147 9473 : MeasureState prevMeasureState;
148 9473 : prevMeasureState.curHeader = ctx.state().curMeasure()->header();
149 9473 : prevMeasureState.curTrailer = ctx.state().curMeasure()->trailer();
150 :
151 9473 : const RangeLock* systemLock = ctx.conf().viewMode() == LayoutMode::PAGE || ctx.conf().viewMode() == LayoutMode::SYSTEM
152 9473 : ? ctx.dom().systemLocks()->lockStartingAt(ctx.state().curMeasure()) : nullptr;
153 :
154 9473 : if (systemLock) {
155 79 : StaveSharingLayout::updateStaveSharingForFullSystem(systemLock->startMB(), systemLock->endMB(), ctx);
156 : }
157 :
158 34456 : while (ctx.state().curMeasure()) { // collect measure for system
159 34456 : oldSystem = ctx.mutState().curMeasure()->system();
160 34456 : system->appendMeasure(ctx.mutState().curMeasure());
161 :
162 34456 : bool sharedTrackMapChanged = false;
163 34456 : if (!systemLock) {
164 78591 : for (Part* p : ctx.dom().parts()) {
165 44477 : if (p->isSharedPart()) {
166 382 : SharedPart* sp = toSharedPart(p);
167 382 : prevMeasureState.sharedTrackMaps[sp] = sp->trackMapAtTick(ctx.mutState().curMeasure()->tick());
168 : }
169 : }
170 :
171 34114 : sharedTrackMapChanged = StaveSharingLayout::updateStaveSharingForLastAddedMeasure(system, ctx);
172 : }
173 :
174 34456 : MeasureLayout::layoutMeasure(ctx.mutState().curMeasure(), ctx);
175 :
176 34456 : if (ctx.state().curMeasure()->isMeasure()) {
177 32019 : Measure* m = toMeasure(ctx.mutState().curMeasure());
178 32019 : if (!(oldSystem && oldSystem->page() && oldSystem->page() != ctx.state().page())) {
179 32006 : MeasureLayout::computePreSpacingItems(m, ctx);
180 : }
181 :
182 32019 : if (measureHasCrossStuffOrModifiedBeams(m)) {
183 10 : updateCrossBeams(system, ctx);
184 : }
185 :
186 32019 : if (m->isFirstInSystem()) {
187 7177 : leadingHBoxesWidth = curSysWidth;
188 7177 : SystemLayout::layoutSystem(system, ctx, curSysWidth);
189 7177 : MeasureLayout::addSystemHeader(m, ctx.state().firstSystem(), ctx);
190 : } else {
191 24842 : bool createHeader = ctx.state().prevMeasure()->isHBox() && toHBox(ctx.state().prevMeasure())->createSystemHeader();
192 24842 : if (createHeader) {
193 47 : MeasureLayout::addSystemHeader(m, false, ctx);
194 24795 : } else if (m->header()) {
195 752 : MeasureLayout::removeSystemHeader(m);
196 : }
197 : }
198 :
199 32019 : MeasureLayout::createEndBarLines(m, true, ctx);
200 :
201 32019 : if (m->noBreak() || systemLock) {
202 420 : MeasureLayout::removeSystemTrailer(m, ctx);
203 : } else {
204 31599 : MeasureLayout::addSystemTrailer(m, m->nextMeasure(), ctx);
205 : }
206 :
207 32019 : MeasureLayout::setRepeatCourtesiesAndParens(m, ctx);
208 :
209 32019 : MeasureLayout::updateGraceNotes(m, ctx);
210 :
211 32019 : if (!systemLock) {
212 31682 : curSysWidth = HorizontalSpacing::updateSpacingForLastAddedMeasure(system);
213 : }
214 2437 : } else if (ctx.state().curMeasure()->isHBox()) {
215 149 : if (!systemLock) {
216 144 : curSysWidth = HorizontalSpacing::updateSpacingForLastAddedMeasure(system);
217 : }
218 : } else {
219 : // vbox:
220 2288 : MeasureLayout::getNextMeasure(ctx);
221 2288 : SystemLayout::layout2(system, ctx); // compute staff distances
222 2288 : return system;
223 : }
224 :
225 31826 : bool doBreak = !systemLock && system->measures().size() > 1 && curSysWidth > targetSystemWidth
226 63994 : && !ctx.state().prevMeasure()->noBreak();
227 32168 : if (doBreak) {
228 1652 : breakMeasure = ctx.mutState().curMeasure();
229 1652 : system->removeLastMeasure();
230 1652 : ctx.mutState().curMeasure()->setSystem(oldSystem);
231 1652 : while (ctx.state().prevMeasure() && ctx.state().prevMeasure()->noBreak() && system->measures().size() > 1) {
232 0 : ctx.mutState().setTick(ctx.state().tick() - ctx.state().curMeasure()->ticks());
233 0 : if (ctx.state().curMeasure()->isMeasure()) {
234 0 : ctx.mutState().setMeasureNumber(toMeasure(ctx.state().curMeasure())->measureNumber());
235 : }
236 :
237 0 : ctx.mutState().setNextMeasure(ctx.mutState().curMeasure());
238 0 : ctx.mutState().setCurMeasure(ctx.mutState().prevMeasure());
239 0 : ctx.mutState().setPrevMeasure(ctx.mutState().curMeasure()->prev());
240 :
241 0 : system->removeLastMeasure();
242 0 : ctx.mutState().curMeasure()->setSystem(oldSystem);
243 : }
244 :
245 1652 : if (sharedTrackMapChanged) {
246 : // The last added measure caused sharedTrackMap to change. Now we are removing that measure so we must restore it.
247 0 : StaveSharingLayout::updateStaveSharingForFullSystem(system->first(), system->last(), ctx);
248 : }
249 :
250 1652 : break;
251 : }
252 :
253 30516 : if (oldSystem && system != oldSystem && muse::contains(ctx.state().systemList(), oldSystem)) {
254 46 : oldSystem->clear();
255 : }
256 :
257 30516 : if (ctx.state().prevMeasure() && ctx.state().prevMeasure()->isMeasure() && ctx.state().prevMeasure()->system() == system) {
258 23216 : Measure* m = toMeasure(ctx.mutState().prevMeasure());
259 :
260 23216 : MeasureLayout::createEndBarLines(m, false, ctx);
261 :
262 23216 : if (m->trailer()) {
263 400 : MeasureLayout::removeSystemTrailer(m, ctx);
264 : }
265 :
266 23216 : MeasureLayout::setRepeatCourtesiesAndParens(m, ctx);
267 :
268 23216 : MeasureLayout::updateGraceNotes(m, ctx);
269 :
270 23216 : if (!systemLock) {
271 22957 : curSysWidth = HorizontalSpacing::updateSpacingForLastAddedMeasure(system);
272 : }
273 :
274 23216 : if (ctx.state().curMeasure()->isMeasure()) {
275 23147 : MeasureLayout::updateKeySignatures(toMeasure(ctx.state().curMeasure()), ctx);
276 : }
277 : }
278 :
279 30516 : const MeasureBase* mb = ctx.state().curMeasure();
280 30516 : const MeasureBase* next = mb->nextMM();
281 30516 : bool lineBreak = false;
282 30516 : switch (ctx.conf().viewMode()) {
283 30516 : case LayoutMode::PAGE:
284 : case LayoutMode::SYSTEM:
285 61008 : lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak() || mb->isEndOfSystemLock() || mb->isEndOfPageLock()
286 61008 : || (next && next->isStartOfSystemLock()) || (next && next->isStartOfPageLock());
287 30516 : break;
288 0 : case LayoutMode::FLOAT:
289 : case LayoutMode::LINE:
290 : case LayoutMode::HORIZONTAL_FIXED:
291 0 : lineBreak = false;
292 0 : break;
293 : }
294 :
295 : // preserve state of next measure (which is about to become current measure)
296 30516 : if (ctx.state().nextMeasure()) {
297 26711 : MeasureBase* nmb = ctx.mutState().nextMeasure();
298 26711 : if (nmb->isMeasure() && ctx.conf().styleB(Sid::createMultiMeasureRests)) {
299 441 : Measure* nm = toMeasure(nmb);
300 441 : if (nm->hasMMRest()) {
301 86 : nmb = nm->mmRest();
302 : }
303 : }
304 26711 : if (nmb->isMeasure()) {
305 26558 : prevMeasureState.clear();
306 26558 : prevMeasureState.measure = toMeasure(nmb);
307 26558 : prevMeasureState.measurePos = nmb->x();
308 26558 : prevMeasureState.measureWidth = nmb->width();
309 96144 : for (Segment& seg : toMeasure(nmb)->segments()) {
310 69586 : prevMeasureState.elementPositions.emplace(&seg, seg.ldata()->pos());
311 69586 : prevMeasureState.elementWidths.emplace(&seg, seg.width());
312 74418 : for (EngravingItem* item : seg.annotations()) {
313 4832 : if (item->isHarmony() || item->isFretDiagram()) {
314 2333 : prevMeasureState.elementPositions.emplace(item, item->ldata()->pos());
315 : }
316 : }
317 : }
318 : }
319 26711 : if (!ctx.state().curMeasure()->noBreak()) {
320 : // current measure is not a nobreak,
321 : // so next measure could possibly start a system
322 26628 : prevMeasureState.curHeader = nmb->header();
323 : }
324 26711 : if (!nmb->noBreak()) {
325 : // next measure is not a nobreak
326 : // so it could possibly end a system
327 26628 : prevMeasureState.curTrailer = nmb->trailer();
328 : }
329 : }
330 :
331 30516 : MeasureLayout::getNextMeasure(ctx);
332 :
333 : // ElementType nt = lc.curMeasure ? lc.curMeasure->type() : ElementType::INVALID;
334 30516 : mb = ctx.state().curMeasure();
335 30516 : if (lineBreak || !mb || mb->isVBoxBase()) {
336 5533 : break;
337 : }
338 : }
339 :
340 7185 : assert(ctx.state().prevMeasure());
341 :
342 7185 : if (ctx.state().endTick() < ctx.state().prevMeasure()->tick()) {
343 : // we've processed the entire range
344 : // but we need to continue layout until we reach a system whose last measure is the same as previous layout
345 332 : MeasureBase* curMB = ctx.mutState().curMeasure();
346 332 : Measure* m = curMB && curMB->isMeasure() ? toMeasure(curMB) : nullptr;
347 332 : bool curMeasureMayHaveJoinedBeams = m && BeamLayout::measureMayHaveBeamsJoinedIntoNext(m);
348 332 : if (ctx.state().prevMeasure() == ctx.state().systemOldMeasure() && !curMeasureMayHaveJoinedBeams) {
349 : // If current measure has possible beams joining to the next, we need to continue layout. This needs a better solution in future. [M.S.]
350 : // this system ends in the same place as the previous layout
351 : // ok to stop
352 279 : if (m) {
353 : // we may have previously processed first measure(s) of next system
354 : // so now we must restore to original state
355 142 : if (m->repeatStart()) {
356 3 : Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0, 1));
357 3 : if (!s->enabled()) {
358 0 : s->setEnabled(true);
359 : }
360 : }
361 142 : const MeasureBase* pbmb = ctx.state().prevMeasure()->mbWithPrecedingSectionBreak();
362 142 : bool localFirstSystem = pbmb->sectionBreak() && !ctx.conf().isMode(LayoutMode::FLOAT);
363 142 : MeasureBase* nm = breakMeasure ? breakMeasure : m;
364 142 : if (prevMeasureState.curHeader) {
365 142 : MeasureLayout::addSystemHeader(m, localFirstSystem, ctx);
366 : } else {
367 0 : MeasureLayout::removeSystemHeader(m);
368 : }
369 : for (;;) {
370 : // TODO: what if the nobreak group takes the entire system - is this correct?
371 142 : if (prevMeasureState.curTrailer && !m->noBreak()) {
372 0 : MeasureLayout::addSystemTrailer(m, m->nextMeasure(), ctx);
373 : } else {
374 142 : MeasureLayout::removeSystemTrailer(m, ctx);
375 : }
376 :
377 142 : MeasureLayout::setRepeatCourtesiesAndParens(m, ctx);
378 :
379 142 : MeasureLayout::updateGraceNotes(m, ctx);
380 :
381 142 : prevMeasureState.restoreMeasure(ctx);
382 142 : MeasureLayout::layoutMeasureElements(m, ctx);
383 142 : BeamLayout::restoreBeams(m, ctx);
384 142 : SystemLayout::restoreOldSystemLayout(m->system(), ctx);
385 :
386 142 : if (m == nm || !m->noBreak()) {
387 142 : break;
388 : }
389 0 : m = m->nextMeasure();
390 : }
391 : }
392 279 : ctx.mutState().setRangeDone(true);
393 : }
394 : }
395 :
396 7185 : if (ctx.dom().allStavesInvisible()) {
397 : // Edge case. Can only happen if all instruments have been deleted.
398 3 : return system;
399 : }
400 :
401 : /*************************************************************
402 : * SYSTEM NOW HAS A COMPLETE SET OF MEASURES
403 : * Now perform all operation to finalize system.
404 : * **********************************************************/
405 :
406 : // Break cross-measure beams
407 7182 : if (ctx.state().prevMeasure() && ctx.state().prevMeasure()->isMeasure()) {
408 7150 : Measure* pm = toMeasure(ctx.mutState().prevMeasure());
409 7150 : BeamLayout::breakCrossMeasureBeams(pm, ctx);
410 : }
411 :
412 : // Hide empty staves
413 7182 : hideEmptyStaves(system, ctx, ctx.state().firstSystem());
414 :
415 : // Re-create shapes to account for newly hidden/unhidden staves
416 : // (and for potential forgotten shape updates, for example in MeasureLayout::setRepeatCourtesiesAndParens)
417 37602 : for (MeasureBase* mb : system->measures()) {
418 30420 : if (mb->isMeasure()) {
419 146597 : for (Segment& seg : toMeasure(mb)->segments()) {
420 116324 : seg.createShapes();
421 : }
422 : }
423 : }
424 :
425 : // Relayout system to account for newly hidden/unhidden staves
426 7182 : SystemLayout::layoutSystem(system, ctx, leadingHBoxesWidth);
427 :
428 : // Create end barlines and system trailer if needed (cautionary time/key signatures etc)
429 7182 : Measure* lm = system->lastMeasure();
430 7182 : if (lm) {
431 7174 : MeasureLayout::createEndBarLines(lm, true, ctx);
432 7174 : Measure* nm = lm->nextMeasure();
433 7174 : if (nm) {
434 3356 : MeasureLayout::addSystemTrailer(lm, nm, ctx);
435 : }
436 : }
437 :
438 7182 : updateTimeSigAboveStavesXPos(system, ctx);
439 :
440 : // Recompute spacing to account for the last changes (barlines, hidden staves, etc)
441 7182 : curSysWidth = HorizontalSpacing::computeSpacingForFullSystem(system);
442 :
443 7182 : if (curSysWidth > targetSystemWidth) {
444 3 : HorizontalSpacing::squeezeSystemToFit(system, curSysWidth, targetSystemWidth);
445 : }
446 :
447 7182 : if (shouldBeJustified(system, curSysWidth, targetSystemWidth, ctx)) {
448 6144 : HorizontalSpacing::justifySystem(system, curSysWidth, targetSystemWidth);
449 : }
450 :
451 7182 : clearBigTimeSigNotShown(system, ctx);
452 :
453 : // LAYOUT MEASURES
454 7182 : bool createBrackets = false;
455 37602 : for (MeasureBase* mb : system->measures()) {
456 30420 : if (mb->isMeasure()) {
457 30273 : mb->setSystem(system);
458 30273 : Measure* m = toMeasure(mb);
459 30273 : MeasureLayout::layoutMeasureElements(m, ctx);
460 30273 : MeasureLayout::layoutStaffLines(m, ctx);
461 30273 : if (createBrackets) {
462 99 : SystemHeaderLayout::addBrackets(system, toMeasure(mb), ctx);
463 99 : createBrackets = false;
464 : }
465 147 : } else if (mb->isHBox()) {
466 147 : HBox* curHBox = toHBox(mb);
467 147 : TLayout::layoutMeasureBase(curHBox, ctx);
468 147 : createBrackets = curHBox->createSystemHeader();
469 : }
470 : }
471 :
472 7182 : layoutSystemElements(system, ctx);
473 7182 : SystemLayout::layout2(system, ctx); // compute staff distances
474 :
475 7461 : if (ctx.state().rangeDone() && oldSystem && !oldSystem->measures().empty() && oldSystem->measures().front()->tick() >= system->endTick()
476 7461 : && !(oldSystem->page() && oldSystem->page() != ctx.state().page())) {
477 : // We may have unfinished layouts of certain elements in the next system
478 : // - ties & bends (in LayoutChords::updateLineAttachPoints())
479 : // - fret diagrams & harmony (in layoutMeasure)
480 : // Restore them to the correct state.
481 72 : SystemLayout::restoreOldSystemLayout(oldSystem, ctx);
482 : }
483 :
484 7182 : return system;
485 13158 : }
486 :
487 7182 : bool SystemLayout::shouldBeJustified(System* system, double curSysWidth, double targetSystemWidth, LayoutContext& ctx)
488 : {
489 7182 : bool shouldJustify = true;
490 :
491 7182 : MeasureBase* lm = system->measures().back();
492 7182 : if ((curSysWidth / targetSystemWidth) < ctx.conf().styleD(Sid::lastSystemFillLimit)) {
493 1211 : shouldJustify = false;
494 1211 : const MeasureBase* lastMb = ctx.state().curMeasure();
495 :
496 : // For systems with a section break, don't justify
497 1211 : if (lm && lm->sectionBreak()) {
498 6 : lastMb = nullptr;
499 : }
500 :
501 : // Justify if system is followed by a measure or HBox
502 1240 : while (lastMb) {
503 202 : if (lastMb->isMeasure() || lastMb->isHBox()) {
504 173 : shouldJustify = true;
505 173 : break;
506 : }
507 : // Frames can contain section breaks too, account for that here
508 29 : if (lastMb->sectionBreak()) {
509 0 : shouldJustify = false;
510 0 : break;
511 : }
512 29 : lastMb = lastMb->nextMeasure();
513 : }
514 : }
515 :
516 7182 : return shouldJustify && !MScore::noHorizontalStretch;
517 : }
518 :
519 10195 : void SystemLayout::layoutSystemLockIndicators(System* system, LayoutContext& ctx)
520 : {
521 : UNUSED(ctx);
522 :
523 10195 : const std::vector<SystemLockIndicator*> lockIndicators = system->systemLockIndicators();
524 : // In PAGE view, at most ONE lock indicator can exist per system.
525 10195 : assert(lockIndicators.size() <= 1);
526 10195 : system->deleteSystemLockIndicators();
527 :
528 10195 : const RangeLock* lock = system->systemLock();
529 10195 : if (!lock) {
530 10096 : return;
531 : }
532 :
533 99 : SystemLockIndicator* lockIndicator = Factory::createSystemLockIndicator(system, lock);
534 99 : lockIndicator->setTrack(0);
535 99 : system->addSystemLockIndicator(lockIndicator);
536 :
537 99 : TLayout::layoutIndicatorIcon(lockIndicator, lockIndicator->mutldata());
538 10195 : }
539 :
540 10195 : void SystemLayout::layoutPageLockIndicators(System* system)
541 : {
542 10195 : Page* page = system->page();
543 10195 : system->deletePageLockIndicator();
544 :
545 10195 : IF_ASSERT_FAILED(!page->systems().empty()) {
546 0 : return;
547 : }
548 :
549 10195 : const RangeLock* lock = page->pageLock();
550 10195 : if (!lock || page->systems().back() != system) {
551 10139 : return;
552 : }
553 :
554 56 : PageLockIndicator* lockIndicator = Factory::createPageLockIndicator(system, lock);
555 56 : lockIndicator->setTrack(0);
556 56 : system->setPageLockIndicator(lockIndicator);
557 :
558 56 : TLayout::layoutPageLockIndicator(lockIndicator, lockIndicator->mutldata());
559 : }
560 :
561 : //---------------------------------------------------------
562 : // getNextSystem
563 : //---------------------------------------------------------
564 :
565 9473 : System* SystemLayout::getNextSystem(LayoutContext& ctx)
566 : {
567 9473 : bool isVBox = ctx.state().curMeasure()->isVBox();
568 9473 : System* system = nullptr;
569 9473 : if (ctx.state().systemList().empty()) {
570 8361 : system = Factory::createSystem(ctx.mutDom().score());
571 8361 : ctx.mutState().setSystemOldMeasure(nullptr);
572 : } else {
573 1112 : system = muse::takeFirst(ctx.mutState().systemList());
574 1112 : ctx.mutState().setSystemOldMeasure(system->measures().empty() ? 0 : system->measures().back());
575 1112 : system->clear(); // remove measures from system
576 : }
577 9473 : ctx.mutDom().systems().push_back(system);
578 9473 : if (!isVBox) {
579 7328 : size_t nstaves = ctx.dom().nstaves();
580 7328 : system->adjustStavesNumber(nstaves);
581 18351 : for (staff_idx_t i = 0; i < nstaves; ++i) {
582 11023 : system->staff(i)->setShow(ctx.dom().staff(i)->show());
583 : }
584 : }
585 9473 : return system;
586 : }
587 :
588 : enum class StaffHideMode {
589 : HIDE_WHEN_STAFF_EMPTY,
590 : HIDE_WHEN_INSTRUMENT_EMPTY,
591 : ALWAYS_SHOW
592 : };
593 :
594 5711 : static StaffHideMode computeHideMode(const System* system, const Staff* staff, const staff_idx_t staffIdx, const bool globalHideIfEmpty,
595 : bool& hasSystemSpecificOverrides)
596 : {
597 5711 : if (Part* part = staff->part(); part && part->isSharedPart() && staff != part->staves().front()) {
598 50 : return StaffHideMode::HIDE_WHEN_STAFF_EMPTY;
599 : }
600 :
601 : // Check for system-specific overrides
602 5661 : bool hasSystemSpecificOverrideHide = false;
603 5661 : bool hasSystemSpecificOverrideDontHide = false;
604 :
605 28480 : for (const MeasureBase* mb : system->measures()) {
606 22819 : if (!mb->isMeasure()) {
607 94 : continue;
608 : }
609 :
610 22725 : const Measure* measure = toMeasure(mb);
611 22725 : const AutoOnOff hideIfEmpty = measure->hideStaffIfEmpty(staffIdx);
612 :
613 22725 : hasSystemSpecificOverrideHide |= (hideIfEmpty == AutoOnOff::ON);
614 22725 : hasSystemSpecificOverrideDontHide |= (hideIfEmpty == AutoOnOff::OFF);
615 : }
616 :
617 5661 : if (hasSystemSpecificOverrideDontHide) {
618 0 : hasSystemSpecificOverrides = true;
619 0 : return StaffHideMode::ALWAYS_SHOW;
620 5661 : } else if (hasSystemSpecificOverrideHide) {
621 0 : hasSystemSpecificOverrides = true;
622 0 : return StaffHideMode::HIDE_WHEN_STAFF_EMPTY;
623 : }
624 :
625 : // Consider staff setting
626 5661 : AutoOnOff staffHideMode = staff->hideWhenEmpty();
627 5661 : switch (staffHideMode) {
628 9 : case AutoOnOff::ON:
629 9 : return StaffHideMode::HIDE_WHEN_STAFF_EMPTY;
630 6 : case AutoOnOff::OFF:
631 6 : return StaffHideMode::ALWAYS_SHOW;
632 5646 : case AutoOnOff::AUTO:
633 5646 : break;
634 : }
635 :
636 : // Consider part setting
637 5646 : AutoOnOff partHideMode = staff->part()->hideWhenEmpty();
638 5646 : switch (partHideMode) {
639 6 : case AutoOnOff::ON:
640 6 : break;
641 6 : case AutoOnOff::OFF:
642 6 : return StaffHideMode::ALWAYS_SHOW;
643 5634 : case AutoOnOff::AUTO:
644 : // Consider global setting
645 5634 : if (!globalHideIfEmpty) {
646 5590 : return StaffHideMode::ALWAYS_SHOW;
647 : }
648 : }
649 :
650 50 : return staff->part()->hideStavesWhenIndividuallyEmpty()
651 50 : ? StaffHideMode::HIDE_WHEN_STAFF_EMPTY
652 50 : : StaffHideMode::HIDE_WHEN_INSTRUMENT_EMPTY;
653 : }
654 :
655 5711 : static bool computeShowSysStaff(const System* system, const Staff* staff, const staff_idx_t staffIdx,
656 : const Fraction& stick, const SpannerMap::IntervalList& spanners,
657 : const StaffHideMode hideMode)
658 : {
659 5711 : if (hideMode == StaffHideMode::ALWAYS_SHOW) {
660 5602 : return true;
661 : }
662 :
663 : // Check if there are spanners
664 113 : for (const auto& spanner : spanners) {
665 4 : if (spanner.value->staff() == staff
666 0 : && !spanner.value->systemFlag()
667 4 : && !(spanner.stop == stick.ticks() && !spanner.value->isSlur())) {
668 0 : return true;
669 : }
670 : }
671 :
672 : // Check if the staff is empty in the system
673 431 : for (const MeasureBase* m : system->measures()) {
674 356 : if (!m->isMeasure()) {
675 0 : continue;
676 : }
677 356 : const Measure* measure = toMeasure(m);
678 356 : if (!measure->isEmpty(staffIdx)) {
679 34 : return true;
680 : }
681 : }
682 :
683 : // check if notes moved into this staff
684 75 : Part* part = staff->part();
685 75 : const size_t n = part->nstaves();
686 75 : if (n > 1) {
687 66 : staff_idx_t idx = part->staves().front()->idx();
688 198 : for (staff_idx_t i = 0; i < n; ++i) {
689 132 : staff_idx_t st = idx + i;
690 :
691 744 : for (MeasureBase* mb : system->measures()) {
692 612 : if (!mb->isMeasure()) {
693 0 : continue;
694 : }
695 :
696 612 : const Measure* m = toMeasure(mb);
697 612 : bool empty = m->isEmpty(st);
698 612 : if (hideMode == StaffHideMode::HIDE_WHEN_INSTRUMENT_EMPTY && !empty) {
699 0 : return true;
700 612 : } else if (empty) {
701 604 : continue;
702 : }
703 :
704 50 : for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
705 210 : for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
706 168 : ChordRest* cr = s->cr(st * VOICES + voice);
707 168 : int staffMove = cr ? cr->staffMove() : 0;
708 168 : if (!cr || cr->isRest() || cr->staffMove() == 0) {
709 : // The case staffMove == 0 has already been checked by measure->isEmpty()
710 168 : continue;
711 : }
712 0 : if (staffIdx == st + staffMove) {
713 0 : return true;
714 : }
715 : }
716 : }
717 : }
718 : }
719 : }
720 :
721 75 : return false;
722 : }
723 :
724 7182 : void SystemLayout::hideEmptyStaves(System* system, LayoutContext& ctx, bool isFirstSystem)
725 : {
726 7182 : if (ctx.dom().nstaves() == 0) {
727 : // No staves, nothing to hide
728 5029 : return;
729 : }
730 :
731 7182 : if (ctx.dom().nstaves() == 1) {
732 : // One staff, show if not manually hidden score-wide
733 5029 : const bool show = ctx.dom().staves().front()->show();
734 5029 : system->staves().front()->setShow(show);
735 5029 : system->setHasStaffVisibilityIndicator(false);
736 5029 : return;
737 : }
738 :
739 2153 : const Fraction stick = system->first()->tick();
740 2153 : const Fraction etick = system->last()->endTick();
741 2153 : const auto& spanners = ctx.dom().spannerMap().findOverlapping(stick.ticks(), etick.ticks() - 1);
742 :
743 2153 : const bool globalHideIfEmpty = ctx.conf().styleB(Sid::hideEmptyStaves)
744 2153 : && !(isFirstSystem && ctx.conf().styleB(Sid::dontHideStavesInFirstSystem));
745 :
746 2153 : bool hasSystemSpecificOverrides = false;
747 2153 : bool hasHiddenStaves = false;
748 2153 : bool systemIsEmpty = true;
749 2153 : staff_idx_t staffIdx = 0;
750 :
751 7993 : for (const Staff* staff : ctx.dom().staves()) {
752 5840 : SysStaff* ss = system->staff(staffIdx);
753 :
754 5840 : DEFER {
755 5840 : ++staffIdx;
756 5840 : };
757 :
758 5840 : if (!staff->show()) {
759 129 : ss->setShow(false);
760 129 : continue;
761 : }
762 :
763 5711 : const StaffHideMode hideMode = computeHideMode(system, staff, staffIdx, globalHideIfEmpty, hasSystemSpecificOverrides);
764 5711 : const bool show = computeShowSysStaff(system, staff, staffIdx, stick, spanners, hideMode);
765 5711 : ss->setShow(show);
766 5711 : if (show) {
767 5636 : systemIsEmpty = false;
768 : } else {
769 75 : hasHiddenStaves = true;
770 : }
771 5840 : }
772 :
773 2153 : system->setHasStaffVisibilityIndicator(hasSystemSpecificOverrides || hasHiddenStaves);
774 :
775 : // If the system is empty, unhide the staves with `showIfEntireSystemEmpty` set to true, if any
776 2153 : const Staff* firstVisible = nullptr;
777 2153 : if (systemIsEmpty) {
778 7 : for (const Staff* staff : ctx.dom().staves()) {
779 6 : SysStaff* ss = system->staff(staff->idx());
780 6 : if (staff->showIfEntireSystemEmpty() && !ss->show()) {
781 0 : ss->setShow(true);
782 0 : systemIsEmpty = false;
783 6 : } else if (!firstVisible && staff->show()) {
784 1 : firstVisible = staff;
785 : }
786 : }
787 : }
788 :
789 : // If there are no such staves, unhide the first one
790 2153 : if (systemIsEmpty) {
791 1 : const Staff* staff = firstVisible ? firstVisible : ctx.dom().staves().front();
792 1 : SysStaff* ss = system->staff(staff->idx());
793 1 : ss->setShow(true);
794 : }
795 : }
796 :
797 0 : bool SystemLayout::canChangeSysStaffVisibility(const System* system, const staff_idx_t staffIdx)
798 : {
799 0 : if (system->staves().size() <= 1) {
800 : // Only one staff; always visible
801 0 : return false;
802 : }
803 :
804 0 : const Staff* staff = system->score()->staff(staffIdx);
805 :
806 0 : if (system->staff(staffIdx)->show()) {
807 : // SysStaff is visible; check if can hide
808 0 : const Fraction stick = system->first()->tick();
809 0 : const Fraction etick = system->last()->endTick();
810 0 : const auto& spanners = system->score()->spannerMap().findOverlapping(stick.ticks(), etick.ticks() - 1);
811 :
812 0 : return !computeShowSysStaff(system, staff, staffIdx, system->first()->tick(), spanners, StaffHideMode::HIDE_WHEN_STAFF_EMPTY);
813 : }
814 :
815 : // SysStaff is hidden; check if can show
816 0 : if (!staff->show()) {
817 0 : return false;
818 : }
819 :
820 0 : return true;
821 : }
822 :
823 7182 : void SystemLayout::updateTimeSigAboveStavesXPos(System* system, LayoutContext& ctx)
824 : {
825 7182 : if (ctx.conf().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>() != TimeSigPlacement::ABOVE_STAVES) {
826 7182 : return;
827 : }
828 :
829 0 : staff_idx_t nstaves = ctx.dom().nstaves();
830 0 : bool centerOnBarline = ctx.conf().styleB(Sid::timeSigCenterOnBarline);
831 :
832 0 : for (MeasureBase* mb : system->measures()) {
833 0 : if (!mb->isMeasure()) {
834 0 : continue;
835 : }
836 0 : Measure* measure = toMeasure(mb);
837 0 : for (Segment& seg : measure->segments()) {
838 0 : if (!seg.isType(SegmentType::TimeSigTypes)) {
839 0 : continue;
840 : }
841 :
842 0 : std::set<TimeSig*> timeSigToKeep;
843 0 : for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
844 0 : TimeSig* timeSig = toTimeSig(seg.element(staff2track(staffIdx)));
845 0 : if (!timeSig || !timeSig->showOnThisStaff()) {
846 0 : continue;
847 : }
848 :
849 0 : timeSigToKeep.insert(timeSig);
850 0 : if (system->staff(staffIdx)->show()) {
851 0 : continue;
852 : }
853 :
854 0 : staff_idx_t nextVisStaff = system->nextVisibleStaff(staffIdx);
855 0 : if (nextVisStaff == muse::nidx) {
856 0 : continue;
857 : }
858 :
859 0 : TimeSig* nextVisTimeSig = toTimeSig(seg.element(staff2track(nextVisStaff)));
860 0 : if (nextVisTimeSig) {
861 0 : timeSigToKeep.insert(nextVisTimeSig);
862 : }
863 : }
864 :
865 0 : Segment* prevBarlineSeg = nullptr;
866 0 : Segment* prevRepeatAnnounceTimeSigSeg = nullptr;
867 0 : if (centerOnBarline) {
868 0 : for (Segment* prevSeg = seg.prev1(); prevSeg && prevSeg->tick() == seg.tick(); prevSeg = prevSeg->prev1()) {
869 0 : if (prevSeg->isEndBarLineType()) {
870 0 : prevBarlineSeg = prevSeg;
871 0 : } else if (prevSeg->isTimeSigRepeatAnnounceType()) {
872 0 : prevRepeatAnnounceTimeSigSeg = prevSeg;
873 : }
874 : }
875 : }
876 :
877 0 : for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
878 0 : TimeSig* timeSig = toTimeSig(seg.element(staff2track(staffIdx)));
879 0 : if (!timeSig) {
880 0 : continue;
881 : }
882 0 : Parenthesis* leftParen = timeSig->leftParen();
883 0 : Parenthesis* rightParen = timeSig->rightParen();
884 0 : if (!muse::contains(timeSigToKeep, timeSig)) {
885 0 : timeSig->mutldata()->reset(); // Eliminates the shape
886 0 : if (leftParen) {
887 0 : leftParen->mutldata()->reset();
888 : }
889 0 : if (rightParen) {
890 0 : rightParen->mutldata()->reset();
891 : }
892 0 : continue;
893 : }
894 :
895 0 : if (prevBarlineSeg && prevBarlineSeg->system() == system && !prevRepeatAnnounceTimeSigSeg) {
896 : // Center timeSig on its segment
897 0 : RectF bbox = timeSig->ldata()->bbox();
898 0 : double newXPos = -0.5 * (bbox.right() + bbox.left());
899 0 : timeSig->mutldata()->setPosX(newXPos);
900 0 : } else if (!seg.isTimeSigRepeatAnnounceType()) {
901 : // Left-align to parenthesis if present
902 0 : double xLeftParens = DBL_MAX;
903 0 : if (leftParen) {
904 0 : xLeftParens = leftParen->x() + leftParen->ldata()->bbox().left();
905 : }
906 0 : if (xLeftParens != DBL_MAX) {
907 0 : timeSig->mutldata()->moveX(-xLeftParens);
908 : }
909 : } else {
910 : // TimeSigRepeatAnnounce: right-align to segment
911 0 : double xRight = -DBL_MAX;
912 0 : xRight = std::max(xRight, timeSig->shape().right() + timeSig->x());
913 0 : if (leftParen) {
914 0 : xRight = std::max(xRight, leftParen->ldata()->bbox().right() + leftParen->x());
915 : }
916 0 : if (rightParen) {
917 0 : xRight = std::max(xRight, rightParen->ldata()->bbox().right() + rightParen->x());
918 : }
919 0 : if (xRight != -DBL_MAX) {
920 0 : timeSig->mutldata()->moveX(-xRight);
921 : }
922 : }
923 : }
924 :
925 0 : seg.createShapes();
926 0 : }
927 : }
928 : }
929 :
930 7182 : void SystemLayout::clearBigTimeSigNotShown(System* system, LayoutContext& ctx)
931 : {
932 7182 : if (ctx.conf().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>() == TimeSigPlacement::NORMAL) {
933 7170 : return;
934 : }
935 :
936 78 : for (MeasureBase* mb : system->measures()) {
937 66 : if (!mb->isMeasure()) {
938 0 : continue;
939 : }
940 238 : for (Segment& seg : toMeasure(mb)->segments()) {
941 172 : if (!seg.isType(SegmentType::TimeSigTypes)) {
942 168 : continue;
943 : }
944 38 : for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
945 34 : TimeSig* ts = toTimeSig(seg.element(staff2track(staffIdx)));
946 34 : if (!ts) {
947 0 : continue;
948 : }
949 34 : if (!ts->showOnThisStaff() || ts->effectiveStaffIdx() == muse::nidx) {
950 22 : ts->mutldata()->reset(); // Deletes shape
951 : }
952 : }
953 : }
954 : }
955 : }
956 :
957 7388 : void SystemLayout::layoutSticking(const std::vector<Sticking*> stickings, System* system, LayoutContext& ctx)
958 : {
959 7388 : if (stickings.empty()) {
960 7377 : return;
961 : }
962 :
963 142 : for (Sticking* sticking : stickings) {
964 131 : TLayout::layoutItem(sticking, ctx);
965 : }
966 :
967 11 : std::vector<EngravingItem*> stickingItems(stickings.begin(), stickings.end());
968 11 : AlignmentLayout::alignItemsForSystem(stickingItems, system);
969 11 : }
970 :
971 7388 : void SystemLayout::layoutLyrics(const ElementsToLayout& elements, LayoutContext& ctx)
972 : {
973 7388 : System* system = elements.system;
974 : // NOTE: in continuous view, this means we layout spanners for the entire score.
975 : // TODO: find way to optimize this and only layout where necessary.
976 7388 : Fraction stick = system->measures().front()->tick();
977 7388 : Fraction etick = system->measures().back()->endTick();
978 :
979 7394 : for (Spanner* sp : elements.partialLyricsLines) {
980 6 : TLayout::layoutSystem(sp, system, ctx);
981 : }
982 :
983 : //-------------------------------------------------------------
984 : // Lyric
985 : //-------------------------------------------------------------
986 : // Layout lyrics dashes and melisma
987 : // NOTE: loop on a *copy* of unmanagedSpanners because in some cases
988 : // the underlying operation may invalidate some of the iterators.
989 : // TODO: figure out why lyrics lines are in this "unmanagedSpanners" container
990 7388 : bool dashOnFirstNoteSyllable = ctx.conf().style().styleB(Sid::lyricsShowDashIfSyllableOnFirstNote);
991 7388 : std::set<Spanner*> unmanagedSpanners = ctx.dom().unmanagedSpanners();
992 7886 : for (Spanner* sp : unmanagedSpanners) {
993 498 : if (!sp->systemFlag() && sp->staff() && !sp->staff()->show()) {
994 0 : continue;
995 : }
996 498 : bool dashOnFirst = dashOnFirstNoteSyllable && !toLyricsLine(sp)->isEndMelisma();
997 498 : if (sp->tick() >= etick || sp->tick2() < stick || (sp->tick2() == stick && !dashOnFirst)) {
998 156 : continue;
999 : }
1000 342 : TLayout::layoutSystem(sp, system, ctx);
1001 : }
1002 7388 : LyricsLayout::computeVerticalPositions(system, ctx);
1003 7388 : }
1004 :
1005 7388 : void SystemLayout::layoutVoltas(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
1006 : {
1007 7388 : System* system = elementsToLayout.system;
1008 :
1009 7388 : processLines(system, ctx, elementsToLayout.voltas);
1010 :
1011 7388 : std::set<Volta*> alignedVoltas;
1012 7667 : for (size_t i = 0; i < elementsToLayout.voltas.size(); ++i) {
1013 279 : Volta* volta1 = toVolta(elementsToLayout.voltas[i]);
1014 279 : if (alignedVoltas.count(volta1)) {
1015 134 : continue;
1016 : }
1017 :
1018 145 : std::vector<EngravingItem*> voltasToAlign;
1019 145 : voltasToAlign.push_back(volta1->backSegment());
1020 :
1021 312 : for (size_t j = i + 1; j < elementsToLayout.voltas.size(); ++j) {
1022 167 : Volta* volta2 = toVolta(elementsToLayout.voltas[j]);
1023 167 : if (volta2->staffIdx() == volta1->staffIdx() && volta2->tick() == volta1->tick2()) {
1024 134 : voltasToAlign.push_back(volta2->frontSegment());
1025 134 : volta1 = volta2;
1026 : }
1027 : }
1028 :
1029 145 : if (voltasToAlign.size() > 1) {
1030 109 : AlignmentLayout::alignItemsGroup(voltasToAlign, system);
1031 352 : for (EngravingItem* vs : voltasToAlign) {
1032 243 : alignedVoltas.insert(toVoltaSegment(vs)->volta());
1033 : }
1034 : }
1035 145 : }
1036 7388 : }
1037 :
1038 7388 : void SystemLayout::layoutDynamicExpressionAndHairpins(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
1039 : {
1040 7388 : System* system = elementsToLayout.system;
1041 :
1042 7388 : std::vector<EngravingItem*> dynamicsExprAndHairpinsToAlign;
1043 :
1044 8529 : for (Dynamic* dynamic : elementsToLayout.dynamics) {
1045 1141 : TLayout::layoutItem(dynamic, ctx);
1046 1141 : if (dynamic->autoplace()) {
1047 1141 : Autoplace::autoplaceSegmentElement(dynamic, dynamic->mutldata());
1048 1141 : dynamicsExprAndHairpinsToAlign.push_back(dynamic);
1049 : }
1050 : }
1051 :
1052 7422 : for (Expression* e : elementsToLayout.expressions) {
1053 34 : TLayout::layoutItem(e, ctx);
1054 34 : if (e->addToSkyline()) {
1055 34 : dynamicsExprAndHairpinsToAlign.push_back(e);
1056 : }
1057 : }
1058 :
1059 7388 : processLines(system, ctx, elementsToLayout.hairpins);
1060 :
1061 12234 : for (SpannerSegment* spannerSegment : system->spannerSegments()) {
1062 4846 : if (spannerSegment->isHairpinSegment()) {
1063 472 : dynamicsExprAndHairpinsToAlign.push_back(spannerSegment);
1064 : }
1065 : }
1066 :
1067 7388 : AlignmentLayout::alignItemsWithTheirSnappingChain(dynamicsExprAndHairpinsToAlign, system);
1068 7388 : }
1069 :
1070 7388 : void SystemLayout::layoutParenthesisAndBigTimeSigs(const ElementsToLayout& elementsToLayout)
1071 : {
1072 7388 : System* system = elementsToLayout.system;
1073 :
1074 7388 : for (Parenthesis* e : elementsToLayout.parenthesis) {
1075 0 : Segment* s = e->segment();
1076 0 : if (s->isType(SegmentType::TimeSigTypes)) {
1077 0 : EngravingItem* el = s->element(e->track());
1078 0 : TimeSig* timeSig = el ? toTimeSig(el) : nullptr;
1079 0 : if (!timeSig) {
1080 0 : continue;
1081 : }
1082 0 : TimeSigPlacement timeSigPlacement = timeSig->style().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>();
1083 0 : if (timeSigPlacement == TimeSigPlacement::ACROSS_STAVES) {
1084 0 : if (!timeSig->showOnThisStaff()) {
1085 0 : e->mutldata()->reset();
1086 : }
1087 0 : continue;
1088 : }
1089 : }
1090 :
1091 0 : staff_idx_t si = e->staffIdx();
1092 0 : Measure* m = s->measure();
1093 0 : system->staff(si)->skyline().add(e->shape().translate(e->pos() + s->pos() + m->pos() + e->staffOffset()));
1094 : }
1095 :
1096 7388 : for (TimeSig* timeSig : elementsToLayout.timeSigAboveStaves) {
1097 0 : Autoplace::autoplaceSegmentElement(timeSig, timeSig->mutldata());
1098 : }
1099 7388 : }
1100 :
1101 2518 : static void autoplaceHarmony(EngravingItem* harmony)
1102 : {
1103 2518 : FretDiagram* fdParent = harmony->parent()->isFretDiagram() ? toFretDiagram(harmony->parent()) : nullptr;
1104 2518 : if (fdParent && !fdParent->visible()) {
1105 0 : harmony->mutldata()->moveY(-fdParent->pos().y());
1106 : }
1107 2518 : Autoplace::autoplaceSegmentElement(harmony, harmony->mutldata());
1108 2518 : }
1109 :
1110 7358 : void SystemLayout::layoutHarmonies(const std::vector<Harmony*> harmonies, System* system, LayoutContext& ctx)
1111 : {
1112 7358 : if (!ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
1113 4658 : for (Harmony* harmony : harmonies) {
1114 1650 : autoplaceHarmony(harmony);
1115 : }
1116 3008 : return;
1117 : }
1118 :
1119 : // Only vertically align one chord symbol per tick & staff
1120 4350 : std::set<std::pair<Fraction, staff_idx_t> > harmonyPositions;
1121 4350 : std::vector<EngravingItem*> harmonyItemsAlign;
1122 4350 : std::vector<EngravingItem*> harmonyItemsNoAlign;
1123 :
1124 5194 : for (Harmony* h : harmonies) {
1125 844 : if (muse::contains(harmonyPositions, { h->tick(), h->staffIdx() })) {
1126 3 : harmonyItemsNoAlign.push_back(h);
1127 3 : continue;
1128 : }
1129 :
1130 841 : TLayout::layoutHarmony(h, h->mutldata(), ctx);
1131 841 : autoplaceHarmony(h);
1132 841 : harmonyItemsAlign.push_back(h);
1133 841 : harmonyPositions.insert({ h->tick(), h->staffIdx() });
1134 : }
1135 :
1136 4350 : AlignmentLayout::alignItemsForSystem(harmonyItemsAlign, system);
1137 :
1138 4353 : for (EngravingItem* harmony : harmonyItemsNoAlign) {
1139 3 : autoplaceHarmony(harmony);
1140 : }
1141 4350 : }
1142 :
1143 105 : void SystemLayout::layoutFretDiagrams(const ElementsToLayout& elements, System* system, LayoutContext& ctx)
1144 : {
1145 105 : if (!ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
1146 89 : for (FretDiagram* fretDiag : elements.fretDiagrams) {
1147 59 : Autoplace::autoplaceSegmentElement(fretDiag, fretDiag->mutldata());
1148 : }
1149 :
1150 54 : for (Harmony* harmony : elements.harmonies) {
1151 24 : autoplaceHarmony(harmony);
1152 : }
1153 :
1154 89 : for (FretDiagram* fretDiag : elements.fretDiagrams) {
1155 59 : if (Harmony* harmony = fretDiag->harmony()) {
1156 20 : SkylineLine& skl = system->staff(fretDiag->staffIdx())->skyline().north();
1157 20 : Segment* s = fretDiag->segment();
1158 20 : Shape harmShape = harmony->ldata()->shape().translated(harmony->pos() + fretDiag->pos() + s->pos() + s->measure()->pos());
1159 20 : skl.add(harmShape);
1160 20 : }
1161 : }
1162 :
1163 30 : return;
1164 : }
1165 :
1166 : // Only vertically align one fd per tick & staff
1167 75 : std::set<std::pair<Fraction, staff_idx_t> > fretHarmonyPositions;
1168 75 : std::vector<EngravingItem*> fretItemsAlign;
1169 75 : std::vector<EngravingItem*> fretOrHarmonyItemsNoAlign;
1170 75 : std::vector<Harmony*> harmonyItemsAlign(elements.harmonies.begin(), elements.harmonies.end());
1171 :
1172 364 : for (FretDiagram* fd : elements.fretDiagrams) {
1173 289 : if (muse::contains(fretHarmonyPositions, { fd->tick(), fd->staffIdx() })) {
1174 0 : fretOrHarmonyItemsNoAlign.push_back(fd);
1175 0 : if (fd->harmony()) {
1176 0 : harmonyItemsAlign.erase(std::remove(harmonyItemsAlign.begin(), harmonyItemsAlign.end(), fd->harmony()));
1177 0 : fretOrHarmonyItemsNoAlign.push_back(fd->harmony());
1178 : }
1179 0 : continue;
1180 : }
1181 :
1182 289 : TLayout::layoutFretDiagram(fd, fd->mutldata(), ctx);
1183 289 : Autoplace::autoplaceSegmentElement(fd, fd->mutldata());
1184 289 : fretItemsAlign.push_back(fd);
1185 289 : fretHarmonyPositions.insert({ fd->tick(), fd->staffIdx() });
1186 : }
1187 :
1188 : // Find harmony with no fret diagram at the same tick as a fret diagram
1189 302 : for (Harmony* h : elements.harmonies) {
1190 227 : if (h->getParentFretDiagram()) {
1191 223 : continue;
1192 : }
1193 4 : if (muse::contains(fretHarmonyPositions, { h->tick(), h->staffIdx() })) {
1194 0 : harmonyItemsAlign.erase(std::remove(harmonyItemsAlign.begin(), harmonyItemsAlign.end(), h));
1195 0 : fretOrHarmonyItemsNoAlign.push_back(h);
1196 0 : continue;
1197 : }
1198 :
1199 4 : Autoplace::autoplaceSegmentElement(h, h->mutldata());
1200 4 : fretHarmonyPositions.insert({ h->tick(), h->staffIdx() });
1201 : }
1202 :
1203 : // align 1 fret diagram per tick & staff
1204 75 : AlignmentLayout::alignItemsForSystem(fretItemsAlign, system);
1205 :
1206 75 : layoutHarmonies(harmonyItemsAlign, system, ctx);
1207 :
1208 : // autoplace everything else
1209 75 : for (EngravingItem* item : fretOrHarmonyItemsNoAlign) {
1210 0 : if (item->isFretDiagram()) {
1211 0 : Autoplace::autoplaceSegmentElement(item, item->mutldata());
1212 0 : Harmony* harmony = toFretDiagram(item)->harmony();
1213 0 : if (harmony) {
1214 0 : autoplaceHarmony(harmony);
1215 : }
1216 0 : } else if (item->isHarmony()) {
1217 0 : autoplaceHarmony(item);
1218 : }
1219 : }
1220 75 : }
1221 :
1222 7396 : void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx)
1223 : {
1224 7411 : TRACEFUNC;
1225 :
1226 7396 : if (ctx.dom().nstaves() == 0) {
1227 0 : return;
1228 : }
1229 :
1230 : // Continuous view does not center items between staves (it collects no pages and doesn't call centerPendingSystems)
1231 7396 : if (!ctx.conf().isLinearMode()) {
1232 7396 : ctx.mutState().addSystemNeedingCentering(system);
1233 : }
1234 :
1235 7396 : ElementsToLayout elementsToLayout(system);
1236 :
1237 39334 : for (MeasureBase* mb : system->measures()) {
1238 31938 : if (!mb->isMeasure()) {
1239 148 : continue;
1240 : }
1241 31790 : if (ctx.conf().isLinearMode() && (mb->tick() < ctx.state().startTick() || mb->tick() > ctx.state().endTick())) {
1242 : // in continuous view, entire score is one system but we only need to process the range
1243 0 : continue;
1244 : }
1245 :
1246 31790 : Measure* measure = toMeasure(mb);
1247 :
1248 31790 : MeasureLayout::layoutMeasureNumber(measure, ctx);
1249 31790 : MMRestLayout::layoutMMRestRange(measure, ctx);
1250 31790 : MeasureLayout::layoutPlayCountText(measure, ctx);
1251 31790 : MeasureLayout::layoutTimeTickAnchors(measure, ctx);
1252 :
1253 31790 : collectElementsToLayout(measure, elementsToLayout, ctx);
1254 : }
1255 :
1256 7396 : const std::vector<Segment*>& sl = elementsToLayout.segments;
1257 7396 : if (sl.empty()) {
1258 8 : return;
1259 : }
1260 :
1261 55607 : for (Chord* chord : elementsToLayout.chords) {
1262 48219 : GraceNotesGroup& graceBefore = chord->graceNotesBefore();
1263 48219 : GraceNotesGroup& graceAfter = chord->graceNotesAfter();
1264 48219 : TLayout::layoutGraceNotesGroup2(&graceBefore, graceBefore.mutldata());
1265 48219 : TLayout::layoutGraceNotesGroup2(&graceAfter, graceAfter.mutldata());
1266 48219 : if (TabDurationSymbol* symbol = chord->tabDur()) {
1267 24 : TLayout::layoutTabDurationSymbol2(symbol, symbol->mutldata());
1268 24 : ChordLayout::fillShape(chord, chord->mutldata());
1269 : }
1270 : }
1271 :
1272 97071 : for (ChordRest* cr : elementsToLayout.chordRests) {
1273 89683 : BeamLayout::layoutNonCrossBeams(cr, ctx);
1274 : }
1275 :
1276 7388 : RestLayout::alignRests(elementsToLayout.system, ctx);
1277 7388 : RestLayout::checkFullMeasureRestCollisions(elementsToLayout.system, ctx);
1278 :
1279 59709 : for (BarLine* bl : elementsToLayout.barlines) {
1280 52321 : TLayout::updateBarlineShape(bl, bl->mutldata(), ctx);
1281 : }
1282 :
1283 7388 : createSkylines(elementsToLayout, ctx);
1284 :
1285 : /* The skyline grows as elements are laid out, so whatever is laid out first ends up closest to the
1286 : * staff. We changed the stacking order of lyrics, from after pedals to before dynamics, so we use
1287 : * this style to keep compatibility for <5.0 scores. This will no longer be necessary if we implement
1288 : * a proper customisable stacking order. */
1289 7388 : const LyricsStackingOrder lyricsStackingOrder = ctx.conf().styleV(Sid::lyricsStackingOrder).value<LyricsStackingOrder>();
1290 :
1291 7388 : layoutTiesAndBends(elementsToLayout, ctx);
1292 :
1293 7388 : if (ctx.conf().isLinearMode()) {
1294 : // TODO: get rid of this
1295 0 : doLayoutNoteSpannersLinear(system, ctx);
1296 : }
1297 :
1298 55607 : for (Chord* c : elementsToLayout.chords) {
1299 48219 : ChordLayout::layoutArticulations(c, ctx);
1300 48219 : ChordLayout::layoutArticulations2(c, ctx);
1301 48219 : ChordLayout::layoutChordBaseFingering(c, system, ctx);
1302 : }
1303 :
1304 7388 : layoutTuplets(elementsToLayout.chordRests, ctx);
1305 :
1306 7388 : collectSpannersToLayout(elementsToLayout, ctx);
1307 :
1308 7388 : processLines(system, ctx, elementsToLayout.slurs);
1309 :
1310 8261 : for (Spanner* sp : elementsToLayout.slurs) {
1311 873 : Slur* slur = toSlur(sp);
1312 873 : ChordRest* scr = toChordRest(slur->startElement());
1313 873 : ChordRest* ecr = toChordRest(slur->endElement());
1314 873 : if (scr && scr->isChord()) {
1315 873 : ChordLayout::layoutArticulations3(toChord(scr), slur, ctx);
1316 : }
1317 873 : if (ecr && ecr->isChord()) {
1318 860 : ChordLayout::layoutArticulations3(toChord(ecr), slur, ctx);
1319 : }
1320 873 : if (slur->isHammerOnPullOff()) {
1321 61 : StaffType* staffType = slur->staff()->staffType(slur->tick());
1322 71 : if ((staffType->isTabStaff() && ctx.conf().styleB(Sid::hopoAlignLettersTabStaves))
1323 71 : || (!staffType->isTabStaff() && ctx.conf().styleB(Sid::hopoAlignLettersStandardStaves))) {
1324 61 : AlignmentLayout::alignHopoLetters(toHammerOnPullOff(slur), system);
1325 : }
1326 : }
1327 : }
1328 :
1329 7388 : processLines(system, ctx, elementsToLayout.trills);
1330 :
1331 7388 : layoutSticking(elementsToLayout.stickings, system, ctx);
1332 :
1333 7717 : for (EngravingItem* item : elementsToLayout.fermatasAndTremoloBars) {
1334 329 : TLayout::layoutItem(item, ctx);
1335 : }
1336 :
1337 7544 : for (FiguredBass* item : elementsToLayout.figuredBass) {
1338 156 : TLayout::layoutItem(item, ctx);
1339 156 : if (item->autoplace()) {
1340 156 : Autoplace::autoplaceSegmentElement(item, item->mutldata(), true);
1341 : }
1342 : }
1343 :
1344 7388 : if (lyricsStackingOrder == LyricsStackingOrder::LYRICS_BEFORE_DYNAMICS) {
1345 4813 : layoutLyrics(elementsToLayout, ctx);
1346 : }
1347 :
1348 7388 : layoutDynamicExpressionAndHairpins(elementsToLayout, ctx);
1349 :
1350 7388 : processLines(system, ctx, elementsToLayout.allOtherSpanners);
1351 :
1352 11239 : for (MeasureNumber* mno : elementsToLayout.measureNumbers) {
1353 3851 : if (!mno->visible()) {
1354 0 : continue;
1355 : }
1356 3851 : Autoplace::autoplaceMeasureElement(mno, mno->mutldata());
1357 11553 : system->staff(mno->staffIdx())->skyline().add(mno->ldata()->bbox().translated(mno->measure()->pos() + mno->pos()
1358 11553 : + mno->staffOffset()), mno);
1359 : }
1360 :
1361 7388 : for (MMRestRange* mmrr : elementsToLayout.mmrRanges) {
1362 0 : Autoplace::autoplaceMeasureElement(mmrr, mmrr->mutldata());
1363 0 : system->staff(mmrr->staffIdx())->skyline().add(mmrr->ldata()->bbox().translated(mmrr->measure()->pos() + mmrr->pos()), mmrr);
1364 : }
1365 :
1366 7388 : processLines(system, ctx, elementsToLayout.ottavas);
1367 7388 : processLines(system, ctx, elementsToLayout.pedal, /*align=*/ true);
1368 :
1369 7388 : if (lyricsStackingOrder == LyricsStackingOrder::LYRICS_AFTER_PEDALS) {
1370 2575 : layoutLyrics(elementsToLayout, ctx);
1371 : }
1372 :
1373 7420 : for (HarpPedalDiagram* hpd : elementsToLayout.harpDiagrams) {
1374 32 : TLayout::layoutItem(hpd, ctx);
1375 : }
1376 :
1377 7388 : bool hasFretDiagram = elementsToLayout.fretDiagrams.size() > 0;
1378 7388 : if (!hasFretDiagram) {
1379 7283 : layoutHarmonies(elementsToLayout.harmonies, system, ctx);
1380 : }
1381 :
1382 8522 : for (StaffTextBase* st : elementsToLayout.staffText) {
1383 1134 : TLayout::layoutItem(st, ctx);
1384 : }
1385 :
1386 7479 : for (InstrumentChange* ic : elementsToLayout.instrChanges) {
1387 91 : TLayout::layoutItem(ic, ctx);
1388 : }
1389 :
1390 7538 : for (EngravingItem* item : elementsToLayout.playTechCapoStringTunTripletFeel) {
1391 150 : TLayout::layoutItem(item, ctx);
1392 : }
1393 :
1394 7388 : if (hasFretDiagram) {
1395 105 : layoutFretDiagrams(elementsToLayout, system, ctx);
1396 : }
1397 :
1398 7524 : for (SystemText* systemText : elementsToLayout.systemText) {
1399 136 : TLayout::layoutSystemText(systemText, systemText->mutldata());
1400 : }
1401 :
1402 7388 : layoutVoltas(elementsToLayout, ctx);
1403 :
1404 7515 : for (RehearsalMark* rm : elementsToLayout.rehMarks) {
1405 127 : TLayout::layoutItem(rm, ctx);
1406 : }
1407 :
1408 7388 : std::vector<EngravingItem*> tempoElementsToAlign;
1409 8524 : for (TempoText* tt : elementsToLayout.tempoText) {
1410 1136 : TLayout::layoutItem(tt, ctx);
1411 1136 : tempoElementsToAlign.push_back(tt);
1412 : }
1413 :
1414 7388 : processLines(system, ctx, elementsToLayout.tempoChangeLines);
1415 13078 : for (SpannerSegment* spannerSeg : system->spannerSegments()) {
1416 5690 : if (spannerSeg->isGradualTempoChangeSegment()) {
1417 22 : tempoElementsToAlign.push_back(spannerSeg);
1418 : }
1419 : }
1420 :
1421 7394 : for (PlayCountText* pt : elementsToLayout.playCountText) {
1422 6 : TLayout::layoutPlayCountText(pt, pt->mutldata());
1423 6 : if (pt->autoplace()) {
1424 6 : Autoplace::autoplaceSegmentElement(pt, pt->mutldata());
1425 : }
1426 : }
1427 :
1428 7388 : AlignmentLayout::alignItemsWithTheirSnappingChain(tempoElementsToAlign, system);
1429 :
1430 7515 : for (RehearsalMark* rehearsMark : elementsToLayout.rehMarks) {
1431 127 : Autoplace::autoplaceSegmentElement(rehearsMark, rehearsMark->mutldata());
1432 : }
1433 :
1434 7909 : for (EngravingItem* item : elementsToLayout.markersAndJumps) {
1435 521 : TLayout::layoutItem(item, ctx);
1436 : }
1437 :
1438 7388 : for (Image* image : elementsToLayout.images) {
1439 0 : TLayout::layoutItem(image, ctx);
1440 : }
1441 :
1442 7388 : layoutParenthesisAndBigTimeSigs(elementsToLayout);
1443 7404 : }
1444 :
1445 31790 : void SystemLayout::collectElementsToLayout(Measure* measure, ElementsToLayout& elements, const LayoutContext& ctx)
1446 : {
1447 31790 : elements.measures.push_back(measure);
1448 :
1449 31790 : System* system = elements.system;
1450 78630 : for (size_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
1451 46840 : if (measure->showMeasureNumberOnStaff(staffIdx)) {
1452 3851 : if (MeasureNumber* mno = measure->measureNumber(staffIdx)) {
1453 3851 : elements.measureNumbers.push_back(mno);
1454 : }
1455 : }
1456 :
1457 46840 : if (!system->staff(staffIdx)->show()) {
1458 981 : continue;
1459 : }
1460 :
1461 45859 : MMRestRange* mmrr = measure->mmRangeText(staffIdx);
1462 45859 : if (mmrr && mmrr->addToSkyline()) {
1463 0 : elements.mmrRanges.push_back(mmrr);
1464 : }
1465 :
1466 48655 : for (EngravingItem* item : measure->el()) {
1467 2796 : if (item->effectiveStaffIdx() == staffIdx && (item->isMarker() || item->isJump())) {
1468 521 : elements.markersAndJumps.push_back(item);
1469 : }
1470 : }
1471 : }
1472 :
1473 31790 : track_idx_t nTracks = ctx.dom().ntracks();
1474 152136 : for (Segment* s = measure->first(); s; s = s->next()) {
1475 120346 : if (s->isChordRestType() || !s->annotations().empty()) {
1476 65106 : elements.segments.push_back(s);
1477 : }
1478 :
1479 659283 : for (track_idx_t track = 0; track < nTracks; /* intentionally empty*/ ) {
1480 538937 : if (s->hasTimeSigAboveStaves()) {
1481 0 : TimeSig* timeSig = toTimeSig(s->element(track));
1482 0 : if (timeSig && timeSig->showOnThisStaff()) {
1483 0 : elements.timeSigAboveStaves.push_back(timeSig);
1484 : }
1485 0 : track += VOICES;
1486 0 : continue;
1487 0 : }
1488 :
1489 538937 : if (!system->staff(track2staff(track))->show()) {
1490 3016 : track += VOICES;
1491 3016 : continue;
1492 : }
1493 :
1494 535921 : if (s->isType(SegmentType::BarLineTypes)) {
1495 52333 : if (BarLine* bl = toBarLine(s->element(track))) {
1496 52321 : elements.barlines.push_back(bl);
1497 : }
1498 52333 : track += VOICES;
1499 52333 : continue;
1500 52333 : }
1501 :
1502 483588 : if (s->isChordRestType()) {
1503 361328 : if (ChordRest* cr = toChordRest(s->element(track))) {
1504 89683 : elements.chordRests.push_back(cr);
1505 89683 : if (cr->isChord()) {
1506 48219 : elements.chords.push_back(toChord(cr));
1507 :
1508 49124 : auto collectBends = [&elements, system] (Chord* chord) {
1509 107378 : for (Note* note : chord->notes()) {
1510 58900 : for (Spanner* sp : note->spannerBack()) {
1511 646 : if (sp->isGuitarBend()) {
1512 409 : elements.guitarBends.push_back(toGuitarBend(sp));
1513 : }
1514 : }
1515 :
1516 58900 : for (Spanner* sp : note->spannerFor()) {
1517 646 : if (sp->isGuitarBend()) {
1518 409 : GuitarBend* bend = toGuitarBend(sp);
1519 409 : Note* endNote = bend->endNote();
1520 409 : if (bend->bendType() == GuitarBendType::SLIGHT_BEND
1521 409 : || (endNote && endNote->tick() >= system->endTick())) {
1522 17 : elements.guitarBends.push_back(bend);
1523 : }
1524 : }
1525 : }
1526 : }
1527 49124 : };
1528 :
1529 48219 : Chord* chord = toChord(cr);
1530 48894 : for (Chord* grace : chord->graceNotesBefore()) {
1531 675 : collectBends(grace);
1532 : }
1533 48219 : collectBends(chord);
1534 48449 : for (Chord* grace : chord->graceNotesAfter()) {
1535 230 : collectBends(grace);
1536 : }
1537 : }
1538 : }
1539 361328 : ++track;
1540 361328 : continue;
1541 361328 : }
1542 :
1543 122260 : ++track;
1544 : }
1545 :
1546 127608 : for (EngravingItem* item : s->annotations()) {
1547 7262 : if (!item->systemFlag() && !system->staff(item->staffIdx())->show()) {
1548 13 : continue;
1549 : }
1550 7249 : switch (item->type()) {
1551 131 : case ElementType::STICKING:
1552 131 : elements.stickings.push_back(toSticking(item));
1553 131 : break;
1554 329 : case ElementType::FERMATA:
1555 : case ElementType::TREMOLOBAR:
1556 329 : elements.fermatasAndTremoloBars.push_back(item);
1557 329 : break;
1558 156 : case ElementType::FIGURED_BASS:
1559 156 : elements.figuredBass.push_back(toFiguredBass(item));
1560 156 : break;
1561 1141 : case ElementType::DYNAMIC:
1562 1141 : elements.dynamics.push_back(toDynamic(item));
1563 1141 : break;
1564 34 : case ElementType::EXPRESSION:
1565 34 : elements.expressions.push_back(toExpression(item));
1566 34 : break;
1567 32 : case ElementType::HARP_DIAGRAM:
1568 32 : elements.harpDiagrams.push_back(toHarpPedalDiagram(item));
1569 32 : break;
1570 348 : case ElementType::FRET_DIAGRAM:
1571 348 : elements.fretDiagrams.push_back(toFretDiagram(item));
1572 348 : if (Harmony* h = toFretDiagram(item)->harmony()) {
1573 243 : elements.harmonies.push_back(h);
1574 : }
1575 348 : break;
1576 1134 : case ElementType::STAFF_TEXT:
1577 : case ElementType::STAVE_SHARING_LABEL:
1578 1134 : elements.staffText.push_back(toStaffTextBase(item));
1579 1134 : break;
1580 91 : case ElementType::INSTRUMENT_CHANGE:
1581 91 : elements.instrChanges.push_back(toInstrumentChange(item));
1582 91 : break;
1583 150 : case ElementType::PLAYTECH_ANNOTATION:
1584 : case ElementType::CAPO:
1585 : case ElementType::STRING_TUNINGS:
1586 : case ElementType::TRIPLET_FEEL:
1587 150 : elements.playTechCapoStringTunTripletFeel.push_back(item);
1588 150 : break;
1589 136 : case ElementType::SYSTEM_TEXT:
1590 136 : elements.systemText.push_back(toSystemText(item));
1591 136 : break;
1592 127 : case ElementType::REHEARSAL_MARK:
1593 127 : elements.rehMarks.push_back(toRehearsalMark(item));
1594 127 : break;
1595 1136 : case ElementType::TEMPO_TEXT:
1596 1136 : elements.tempoText.push_back(toTempoText(item));
1597 1136 : break;
1598 0 : case ElementType::IMAGE:
1599 0 : elements.images.push_back(toImage(item));
1600 0 : break;
1601 0 : case ElementType::PARENTHESIS:
1602 0 : elements.parenthesis.push_back(toParenthesis(item));
1603 0 : break;
1604 2275 : case ElementType::HARMONY:
1605 2275 : elements.harmonies.push_back(toHarmony(item));
1606 2275 : break;
1607 6 : case ElementType::PLAY_COUNT_TEXT:
1608 6 : elements.playCountText.push_back(toPlayCountText(item));
1609 6 : break;
1610 23 : default:
1611 23 : break;
1612 : }
1613 : }
1614 : }
1615 31790 : }
1616 :
1617 7388 : void SystemLayout::collectSpannersToLayout(ElementsToLayout& elements, const LayoutContext& ctx)
1618 : {
1619 7388 : const System* system = elements.system;
1620 :
1621 : // NOTE: in continuous view, this means we layout spanners for the entire score.
1622 : // TODO: find way to optimize this and only layout where necessary.
1623 7388 : Fraction stick = system->measures().front()->tick();
1624 7388 : Fraction etick = system->measures().back()->endTick();
1625 :
1626 7388 : auto spanners = ctx.dom().spannerMap().findOverlapping(stick.ticks(), etick.ticks());
1627 7388 : std::sort(spanners.begin(), spanners.end(), [](const auto& sp1, const auto& sp2) {
1628 3341 : return sp1.value->tick() < sp2.value->tick();
1629 : });
1630 :
1631 7388 : std::vector<Spanner*> allSpanners;
1632 7388 : allSpanners.reserve(spanners.size());
1633 9777 : for (auto item : spanners) {
1634 2389 : allSpanners.push_back(item.value);
1635 : }
1636 :
1637 9777 : for (Spanner* spanner : allSpanners) {
1638 2389 : if (!spanner->systemFlag() && !system->staff(spanner->staffIdx())->show()) {
1639 4 : continue;
1640 : }
1641 2385 : if (spanner->tick() >= etick || spanner->tick2() < stick) {
1642 97 : continue;
1643 : }
1644 :
1645 2288 : if (spanner->tick2() == stick) {
1646 : // Only these two spanner types are laid out if at the end of the previous system
1647 : // TODO: pedal makes sense, but slur doesn't... to figure out
1648 60 : if (spanner->isSlur() && !toSlur(spanner)->isCrossStaff() && !toSlur(spanner)->hasCrossBeams()) {
1649 8 : elements.slurs.push_back(spanner);
1650 52 : } else if (spanner->isPedal() && toPedal(spanner)->connect45HookToNext()) {
1651 0 : elements.pedal.push_back(spanner);
1652 : }
1653 : } else {
1654 2228 : switch (spanner->type()) {
1655 889 : case ElementType::SLUR:
1656 : case ElementType::HAMMER_ON_PULL_OFF:
1657 889 : if (!toSlur(spanner)->isCrossStaff() && !toSlur(spanner)->hasCrossBeams()) {
1658 865 : elements.slurs.push_back(spanner);
1659 : }
1660 889 : break;
1661 119 : case ElementType::PEDAL:
1662 119 : elements.pedal.push_back(spanner);
1663 119 : break;
1664 116 : case ElementType::TRILL:
1665 116 : elements.trills.push_back(spanner);
1666 116 : break;
1667 279 : case ElementType::VOLTA:
1668 279 : elements.voltas.push_back(spanner);
1669 279 : break;
1670 472 : case ElementType::HAIRPIN:
1671 472 : elements.hairpins.push_back(spanner);
1672 472 : break;
1673 22 : case ElementType::GRADUAL_TEMPO_CHANGE:
1674 22 : elements.tempoChangeLines.push_back(spanner);
1675 22 : break;
1676 6 : case ElementType::PARTIAL_LYRICSLINE:
1677 6 : elements.partialLyricsLines.push_back(spanner);
1678 6 : break;
1679 162 : case ElementType::OTTAVA:
1680 162 : if (!spanner->staff()->staffType()->isTabStaff()) {
1681 161 : elements.ottavas.push_back(spanner);
1682 : }
1683 162 : break;
1684 163 : default:
1685 163 : elements.allOtherSpanners.push_back(spanner);
1686 163 : break;
1687 : }
1688 : }
1689 : }
1690 7388 : }
1691 :
1692 7388 : void SystemLayout::createSkylines(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
1693 : {
1694 7388 : System* system = elementsToLayout.system;
1695 18583 : for (size_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
1696 11195 : SysStaff* ss = system->staff(staffIdx);
1697 11195 : Skyline& skyline = ss->skyline();
1698 11195 : skyline.clear();
1699 58035 : for (Measure* m : elementsToLayout.measures) {
1700 46840 : if (m->staffLines(staffIdx)->addToSkyline()) {
1701 46822 : ss->skyline().add(m->staffLines(staffIdx)->ldata()->bbox().translated(m->pos()), m->staffLines(staffIdx));
1702 : }
1703 223086 : for (Segment& s : m->segments()) {
1704 176246 : if (!s.enabled()) {
1705 1753 : continue;
1706 : }
1707 174493 : PointF p(s.pos() + m->pos());
1708 174493 : if (s.isType(SegmentType::BarLineTypes)) {
1709 53000 : BarLine* bl = toBarLine(s.element(staffIdx * VOICES));
1710 53000 : if (bl && bl->addToSkyline()) {
1711 52983 : skyline.add(bl->shape().translated(bl->pos() + p + bl->staffOffset()));
1712 : }
1713 121493 : } else if (s.isType(SegmentType::TimeSigTypes)) {
1714 6312 : TimeSig* ts = toTimeSig(s.element(staffIdx * VOICES));
1715 6312 : if (ts && ts->addToSkyline() && ts->showOnThisStaff()) {
1716 6138 : TimeSigPlacement timeSigPlacement = ts->style().styleV(Sid::timeSigPlacement).value<TimeSigPlacement>();
1717 6138 : if (timeSigPlacement != TimeSigPlacement::ACROSS_STAVES) {
1718 6126 : skyline.add(ts->shape().translate(ts->pos() + p + ts->staffOffset()));
1719 : }
1720 : }
1721 : } else {
1722 115181 : track_idx_t strack = staffIdx * VOICES;
1723 115181 : track_idx_t etrack = strack + VOICES;
1724 1387193 : for (EngravingItem* e : s.elist()) {
1725 1272012 : if (!e) {
1726 961559 : continue;
1727 : }
1728 310453 : track_idx_t effectiveTrack = e->vStaffIdx() * VOICES + e->voice();
1729 310453 : if (effectiveTrack < strack || effectiveTrack >= etrack) {
1730 196459 : continue;
1731 : }
1732 :
1733 : // add element to skyline
1734 113994 : if (e->addToSkyline()) {
1735 113720 : const PointF offset = e->staffOffset();
1736 113720 : Shape shape = e->shape();
1737 : // add grace notes to skyline
1738 113720 : if (e->isChord()) {
1739 48351 : Chord* chord = toChord(e);
1740 48351 : GraceNotesGroup& graceBefore = chord->graceNotesBefore();
1741 48351 : GraceNotesGroup& graceAfter = chord->graceNotesAfter();
1742 48351 : if (!graceBefore.empty()) {
1743 515 : skyline.add(graceBefore.shape().translate(graceBefore.pos() + p + offset));
1744 : }
1745 48351 : if (!graceAfter.empty()) {
1746 180 : skyline.add(graceAfter.shape().translate(graceAfter.pos() + p + offset));
1747 : }
1748 :
1749 : // If present, add ornament cue note to skyline
1750 48351 : Ornament* ornament = chord->findOrnament();
1751 48351 : if (ornament) {
1752 386 : Chord* cue = ornament->cueNoteChord();
1753 386 : if (cue && cue->upNote()->visible()) {
1754 16 : skyline.add(cue->shape().translate(cue->pos() + p + cue->staffOffset()));
1755 : }
1756 : }
1757 :
1758 : // Don't include cross-staff arpeggios
1759 48351 : shape.remove_if([chord](ShapeElement& s) {
1760 137663 : return (s.item()->isArpeggio() || s.item()->isChordBracket()) && toArpeggio(
1761 137663 : s.item()) == chord->spanArpeggio();
1762 : });
1763 48351 : Arpeggio* arp = chord->spanArpeggio();
1764 48351 : if (arp) {
1765 311 : RectF staffBbox = ss->bbox();
1766 311 : RectF arpBbox = arp->ldata()->bbox().translated(e->pos() + p + offset);
1767 311 : if (chord->track() == arp->track()) {
1768 233 : staffBbox.setTop(arpBbox.top());
1769 78 : } else if (chord->track() == arp->endTrack()) {
1770 74 : staffBbox.setBottom(arpBbox.bottom());
1771 : }
1772 311 : shape.add(arpBbox & staffBbox, arp);
1773 : }
1774 : }
1775 113720 : skyline.add(shape.translate(e->pos() + p + offset));
1776 113720 : }
1777 :
1778 : // add tremolo to skyline
1779 113994 : if (e->isChord()) {
1780 48355 : Chord* ch = item_cast<Chord*>(e);
1781 : // tremoloSingleChord is added directly to chord shape
1782 48355 : if (ch->tremoloTwoChord()) {
1783 220 : TremoloTwoChord* t = ch->tremoloTwoChord();
1784 220 : Chord* c1 = t->chord1();
1785 220 : Chord* c2 = t->chord2();
1786 220 : if (c1 && !c1->staffMove() && c2 && !c2->staffMove()) {
1787 220 : if (t->chord() == e && t->addToSkyline()) {
1788 110 : skyline.add(t->shape().translate(t->pos() + e->pos() + p));
1789 : }
1790 : }
1791 : }
1792 : }
1793 :
1794 : // add beams to skline
1795 113994 : if (e->isChordRest()) {
1796 90801 : ChordRest* cr = toChordRest(e);
1797 90801 : if (BeamLayout::isStartOfNonCrossBeam(cr)) {
1798 4211 : Beam* b = cr->beam();
1799 4211 : b->addSkyline(skyline);
1800 : }
1801 : }
1802 : }
1803 : }
1804 : }
1805 : }
1806 : }
1807 7388 : }
1808 :
1809 0 : void SystemLayout::doLayoutTies(System* system, const std::vector<Segment*>& sl, const Fraction& stick, const Fraction& etick,
1810 : LayoutContext& ctx)
1811 : {
1812 : UNUSED(etick);
1813 :
1814 0 : for (Segment* s : sl) {
1815 0 : for (EngravingItem* e : s->elist()) {
1816 0 : if (!e || !e->isChord()) {
1817 0 : continue;
1818 : }
1819 0 : Chord* c = toChord(e);
1820 0 : for (Chord* ch : c->graceNotes()) {
1821 0 : layoutTies(ch, system, stick, ctx);
1822 : }
1823 0 : layoutTies(c, system, stick, ctx);
1824 : }
1825 : }
1826 0 : }
1827 :
1828 7388 : void SystemLayout::layoutTuplets(const std::vector<ChordRest*>& chordRests, LayoutContext& ctx)
1829 : {
1830 7388 : std::set<Tuplet*> laidoutTuplets;
1831 97071 : for (auto revIter = chordRests.rbegin(); revIter != chordRests.rend(); ++revIter) {
1832 89683 : ChordRest* cr = *revIter;
1833 89683 : Tuplet* tuplet = cr->topTuplet();
1834 :
1835 89683 : if (!tuplet || muse::contains(laidoutTuplets, tuplet)) {
1836 88149 : continue;
1837 : }
1838 :
1839 1534 : TupletLayout::layoutTupletAndNestedTuplets(tuplet, ctx); // this lays out also the inner tuplets
1840 1534 : laidoutTuplets.insert(tuplet);
1841 : }
1842 7388 : }
1843 :
1844 7388 : void SystemLayout::layoutTiesAndBends(const ElementsToLayout& elementsToLayout, LayoutContext& ctx)
1845 : {
1846 7388 : System* system = elementsToLayout.system;
1847 7388 : if (elementsToLayout.measures.empty()) {
1848 0 : return;
1849 : }
1850 7388 : Fraction stick = elementsToLayout.measures.front()->tick();
1851 :
1852 55607 : for (Chord* chord : elementsToLayout.chords) {
1853 48894 : for (Chord* grace : chord->graceNotesBefore()) {
1854 675 : layoutTies(grace, system, stick, ctx);
1855 : }
1856 48219 : layoutTies(chord, system, stick, ctx);
1857 48449 : for (Chord* grace : chord->graceNotesAfter()) {
1858 230 : layoutTies(grace, system, stick, ctx);
1859 : }
1860 : }
1861 :
1862 7388 : GuitarDiveLayout::updateDiveSequences(elementsToLayout.guitarBends, ctx);
1863 :
1864 7814 : for (GuitarBend* bend : elementsToLayout.guitarBends) {
1865 : /* Bends which cross a system break are collected by both systems they touch, so we
1866 : * lay out only the segment which belongs to this one: */
1867 426 : TLayout::layoutGuitarBend(bend, ctx, system);
1868 : }
1869 : }
1870 :
1871 0 : void SystemLayout::layoutNoteAnchoredSpanners(System* system, Chord* chord)
1872 : {
1873 : // Add all spanners attached to notes, otherwise these will be removed if outside of the layout range
1874 0 : for (Note* note : chord->notes()) {
1875 0 : for (Spanner* spanner : note->spannerFor()) {
1876 0 : for (SpannerSegment* spannerSeg : spanner->spannerSegments()) {
1877 0 : spannerSeg->moveToSystem(system);
1878 : }
1879 : }
1880 : }
1881 0 : }
1882 :
1883 0 : void SystemLayout::doLayoutNoteSpannersLinear(System* system, LayoutContext& ctx)
1884 : {
1885 0 : constexpr Fraction start = Fraction(0, 1);
1886 0 : for (Measure* measure = system->firstMeasure(); measure; measure = measure->nextMeasure()) {
1887 0 : for (Segment* segment = measure->first(); segment; segment = segment->next()) {
1888 0 : if (!segment->isChordRestType()) {
1889 0 : continue;
1890 : }
1891 0 : for (EngravingItem* e : segment->elist()) {
1892 0 : if (!e || !e->isChord()) {
1893 0 : continue;
1894 : }
1895 0 : Chord* c = toChord(e);
1896 0 : for (Chord* ch : c->graceNotes()) {
1897 0 : layoutTies(ch, system, start, ctx);
1898 0 : layoutNoteAnchoredSpanners(system, ch);
1899 : }
1900 0 : layoutTies(c, system, start, ctx);
1901 0 : layoutNoteAnchoredSpanners(system, c);
1902 : }
1903 : }
1904 : }
1905 0 : }
1906 :
1907 59104 : void SystemLayout::processLines(System* system, LayoutContext& ctx, const std::vector<Spanner*>& lines, bool align)
1908 : {
1909 59104 : std::vector<SpannerSegment*> segments;
1910 61309 : for (Spanner* sp : lines) {
1911 2205 : SpannerSegment* ss = TLayout::layoutSystem(sp, system, ctx); // create/layout spanner segment for this system
1912 2205 : if (ss->autoplace()) {
1913 2199 : segments.push_back(ss);
1914 : }
1915 : }
1916 :
1917 59104 : if (align && segments.size() > 1) {
1918 19 : const size_t nstaves = system->staves().size();
1919 19 : const double defaultY = segments[0]->ldata()->pos().y();
1920 19 : std::vector<double> yAbove(nstaves, -DBL_MAX);
1921 19 : std::vector<double> yBelow(nstaves, -DBL_MAX);
1922 :
1923 87 : for (SpannerSegment* ss : segments) {
1924 68 : if (ss->visible()) {
1925 66 : double& staffY = ss->spanner() && ss->spanner()->placeAbove() ? yAbove[ss->staffIdx()] : yBelow[ss->staffIdx()];
1926 66 : staffY = std::max(staffY, ss->ldata()->pos().y());
1927 : }
1928 : }
1929 87 : for (SpannerSegment* ss : segments) {
1930 68 : const double& staffY = ss->spanner() && ss->spanner()->placeAbove() ? yAbove[ss->staffIdx()] : yBelow[ss->staffIdx()];
1931 68 : if (staffY > -DBL_MAX) {
1932 68 : ss->mutldata()->setPosY(staffY);
1933 : } else {
1934 0 : ss->mutldata()->setPosY(defaultY);
1935 : }
1936 : }
1937 19 : }
1938 :
1939 59104 : if (segments.size() > 0 && segments.front()->isSlurSegment()) {
1940 385 : SlurTieLayout::adjustOverlappingSlurs(system->spannerSegments());
1941 : }
1942 :
1943 : //
1944 : // Fix harmonic marks and vibrato overlaps
1945 : //
1946 59104 : SpannerSegment* prevSegment = nullptr;
1947 59104 : bool fixed = false;
1948 :
1949 61303 : for (SpannerSegment* ss : segments) {
1950 2199 : if (fixed) {
1951 6 : fixed = false;
1952 6 : prevSegment = ss;
1953 6 : continue;
1954 : }
1955 2193 : if (prevSegment) {
1956 1135 : if (prevSegment->visible()
1957 1133 : && ss->visible()
1958 1125 : && prevSegment->isHarmonicMarkSegment()
1959 16 : && ss->isVibratoSegment()
1960 2268 : && muse::RealIsEqual(prevSegment->x(), ss->x())) {
1961 2 : double diff = ss->ldata()->bbox().bottom() - prevSegment->ldata()->bbox().bottom()
1962 2 : + prevSegment->ldata()->bbox().top();
1963 2 : prevSegment->mutldata()->moveY(diff);
1964 2 : fixed = true;
1965 : }
1966 1135 : if (prevSegment->visible()
1967 1133 : && ss->visible()
1968 1125 : && prevSegment->isVibratoSegment()
1969 21 : && ss->isHarmonicMarkSegment()
1970 2268 : && muse::RealIsEqual(prevSegment->x(), ss->x())) {
1971 4 : double diff = prevSegment->ldata()->bbox().bottom() - ss->ldata()->bbox().bottom()
1972 4 : + ss->ldata()->bbox().top();
1973 4 : ss->mutldata()->moveY(diff);
1974 4 : fixed = true;
1975 : }
1976 : }
1977 :
1978 2193 : prevSegment = ss;
1979 : }
1980 :
1981 : //
1982 : // add shapes to skyline
1983 : //
1984 61303 : for (SpannerSegment* ss : segments) {
1985 2199 : if (ss->addToSkyline()) {
1986 2157 : staff_idx_t stfIdx = ss->effectiveStaffIdx();
1987 2157 : if (stfIdx == muse::nidx) {
1988 0 : continue;
1989 : }
1990 2157 : system->staff(stfIdx)->skyline().add(ss->shape().translate(ss->pos()));
1991 2157 : if (ss->isHammerOnPullOffSegment()) {
1992 61 : TLayout::layoutHammerOnPullOffSegment(toHammerOnPullOffSegment(ss), ctx);
1993 : }
1994 : }
1995 : }
1996 59104 : }
1997 :
1998 49124 : void SystemLayout::layoutTies(Chord* ch, System* system, const Fraction& stick, LayoutContext& ctx)
1999 : {
2000 49124 : SysStaff* staff = system->staff(ch->staffIdx());
2001 49124 : if (!staff->show()) {
2002 0 : return;
2003 : }
2004 49124 : std::vector<TieSegment*> stackedForwardTies;
2005 49124 : std::vector<TieSegment*> stackedBackwardTies;
2006 107378 : for (Note* note : ch->notes()) {
2007 58254 : Tie* t = note->tieFor();
2008 58254 : if (t && !t->isLaissezVib()) {
2009 2206 : TieSegment* ts = SlurTieLayout::layoutTieFor(t, system);
2010 2206 : if (ts && ts->addToSkyline()) {
2011 2196 : staff->skyline().add(ts->shape().translate(ts->pos()));
2012 2196 : stackedForwardTies.push_back(ts);
2013 : }
2014 : }
2015 58254 : t = note->tieBack();
2016 58254 : if (t) {
2017 2280 : if (note->incomingPartialTie() || t->startNote()->tick() < stick) {
2018 167 : TieSegment* ts = SlurTieLayout::layoutTieBack(t, system, ctx);
2019 167 : if (ts && ts->addToSkyline()) {
2020 167 : staff->skyline().add(ts->shape().translate(ts->pos()));
2021 167 : stackedBackwardTies.push_back(ts);
2022 : }
2023 : }
2024 : }
2025 : }
2026 :
2027 49124 : SlurTieLayout::layoutLaissezVibChord(ch, ctx);
2028 :
2029 49124 : if (!ch->staffType()->isTabStaff()) {
2030 48304 : SlurTieLayout::resolveVerticalTieCollisions(stackedForwardTies);
2031 48304 : SlurTieLayout::resolveVerticalTieCollisions(stackedBackwardTies);
2032 : }
2033 49124 : }
2034 :
2035 32019 : bool SystemLayout::measureHasCrossStuffOrModifiedBeams(const Measure* measure)
2036 : {
2037 132840 : for (const Segment& seg : measure->segments()) {
2038 100831 : if (!seg.isChordRestType()) {
2039 34013 : continue;
2040 : }
2041 444598 : for (const EngravingItem* e : seg.elist()) {
2042 377790 : if (!e || !e->isChordRest()) {
2043 284222 : continue;
2044 : }
2045 93568 : const Beam* beam = toChordRest(e)->beam();
2046 93568 : if (beam && (beam->cross() || beam->userModified())) {
2047 10 : return true;
2048 : }
2049 93558 : const Chord* c = e->isChord() ? toChord(e) : nullptr;
2050 93558 : if (c && c->tremoloTwoChord()) {
2051 228 : const TremoloTwoChord* trem = c->tremoloTwoChord();
2052 228 : const Chord* c1 = trem->chord1();
2053 228 : const Chord* c2 = trem->chord2();
2054 228 : if (trem->userModified() || c1->staffMove() != c2->staffMove()) {
2055 0 : return true;
2056 : }
2057 : }
2058 93558 : if (e->isChord() && !toChord(e)->graceNotes().empty()) {
2059 1565 : for (const Chord* grace : toChord(e)->graceNotes()) {
2060 910 : if (grace->beam() && (grace->beam()->cross() || grace->beam()->userModified())) {
2061 0 : return true;
2062 : }
2063 : }
2064 : }
2065 : }
2066 : }
2067 :
2068 32009 : return false;
2069 : }
2070 :
2071 10 : void SystemLayout::updateCrossBeams(System* system, LayoutContext& ctx)
2072 : {
2073 : LAYOUT_CALL() << LAYOUT_ITEM_INFO(system);
2074 :
2075 10 : SystemLayout::layout2(system, ctx); // Computes staff distances, essential for the rest of the calculations
2076 : // Update grace cross beams
2077 32 : for (MeasureBase* mb : system->measures()) {
2078 22 : if (!mb->isMeasure()) {
2079 0 : continue;
2080 : }
2081 340 : for (Segment& seg : toMeasure(mb)->segments()) {
2082 318 : if (!seg.isChordRestType()) {
2083 40 : continue;
2084 : }
2085 2502 : for (EngravingItem* e : seg.elist()) {
2086 2224 : if (!e || !e->isChord()) {
2087 1895 : continue;
2088 : }
2089 329 : for (Chord* grace : toChord(e)->graceNotes()) {
2090 0 : if (grace->beam() && (grace->beam()->cross() || grace->beam()->userModified())
2091 0 : && grace->beam()->elements().front() == grace) {
2092 0 : BeamLayout::layout(grace->beam(), ctx);
2093 : }
2094 : }
2095 : }
2096 : }
2097 : }
2098 : // Update normal chords cross beams and respective segments
2099 32 : for (MeasureBase* mb : system->measures()) {
2100 22 : if (!mb->isMeasure()) {
2101 0 : continue;
2102 : }
2103 22 : bool somethingChanged = false;
2104 340 : for (Segment& seg : toMeasure(mb)->segments()) {
2105 318 : if (!seg.isChordRestType()) {
2106 40 : continue;
2107 : }
2108 2502 : for (EngravingItem* e : seg.elist()) {
2109 2224 : if (!e || !e->isChord()) {
2110 1895 : continue;
2111 : }
2112 329 : Chord* chord = toChord(e);
2113 599 : if (chord->beam() && (chord->beam()->cross() || chord->beam()->userModified())
2114 599 : && chord->beam()->elements().front() == chord) {
2115 27 : bool prevUp = chord->up();
2116 27 : Stem* stem = chord->stem();
2117 27 : double prevStemLength = stem ? stem->length() : 0.0;
2118 27 : BeamLayout::layout(chord->beam(), ctx);
2119 27 : if (chord->up() != prevUp || (stem && stem->length() != prevStemLength)) {
2120 : // If the chord has changed direction needs to be re-laid out
2121 18 : ChordLayout::layoutChords1(ctx, &seg, chord->vStaffIdx());
2122 18 : somethingChanged = true;
2123 : }
2124 302 : } else if (chord->tremoloTwoChord()) {
2125 0 : TremoloTwoChord* t = chord->tremoloTwoChord();
2126 0 : Chord* c1 = t->chord1();
2127 0 : Chord* c2 = t->chord2();
2128 0 : if (t->userModified() || (c1->staffMove() != 0 || c2->staffMove() != 0)) {
2129 0 : bool prevUp = chord->up();
2130 0 : ChordLayout::computeUp(chord, ctx);
2131 0 : if (chord->up() != prevUp) {
2132 0 : ChordLayout::layoutChords1(ctx, &seg, chord->vStaffIdx());
2133 0 : somethingChanged = true;
2134 : }
2135 : }
2136 : }
2137 : }
2138 278 : if (somethingChanged) {
2139 101 : seg.createShapes();
2140 : }
2141 : }
2142 : }
2143 10 : }
2144 :
2145 214 : void SystemLayout::restoreOldSystemLayout(System* system, LayoutContext& ctx)
2146 : {
2147 214 : layoutSystemElements(system, ctx);
2148 214 : }
2149 :
2150 14359 : void SystemLayout::layoutSystem(System* system, LayoutContext& ctx, double leadingHBoxesWidth)
2151 : {
2152 14359 : SystemHeaderLayout::updateSystemHeaderWidth(system, ctx);
2153 :
2154 14359 : size_t nstaves = system->staves().size();
2155 :
2156 36089 : for (size_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
2157 21730 : SysStaff* s = system->staves().at(staffIdx);
2158 21730 : const Staff* staff = ctx.dom().staff(staffIdx);
2159 21730 : if (!staff->show() || !s->show()) {
2160 332 : s->setbbox(RectF());
2161 332 : continue;
2162 : }
2163 :
2164 21398 : double staffMag = staff->staffMag(Fraction(0, 1)); // ??? TODO
2165 21398 : int staffLines = staff->lines(Fraction(0, 1));
2166 21398 : if (staffLines <= 1) {
2167 40 : double h = staff->lineDistance(Fraction(0, 1)) * staffMag * system->spatium();
2168 40 : s->setbbox(system->leftMargin() + leadingHBoxesWidth, -h, 0.0, 2 * h);
2169 : } else {
2170 21358 : double h = (staffLines - 1) * staff->lineDistance(Fraction(0, 1));
2171 21358 : h = h * staffMag * system->spatium();
2172 21358 : s->setbbox(system->leftMargin() + leadingHBoxesWidth, 0.0, 0.0, h);
2173 : }
2174 : }
2175 :
2176 : //---------------------------------------------------
2177 : // layout brackets
2178 : //---------------------------------------------------
2179 :
2180 14359 : SystemHeaderLayout::setBracketsXPosition(system, system->leftMargin() + leadingHBoxesWidth);
2181 :
2182 14359 : SystemHeaderLayout::setInstrumentNamesHorizontalPos(system);
2183 14359 : SystemHeaderLayout::setGroupBracketsHorizontalPos(system);
2184 :
2185 52012 : for (MeasureBase* mb : system->measures()) {
2186 37653 : if (!mb->isMeasure()) {
2187 203 : continue;
2188 : }
2189 37450 : Measure* m = toMeasure(mb);
2190 37450 : if (m == system->measures().front() || (m->prev() && m->prev()->isHBox())) {
2191 14396 : MeasureLayout::createSystemBeginBarLine(m, ctx);
2192 : }
2193 : }
2194 14359 : }
2195 :
2196 : //---------------------------------------------------------
2197 : // layout2
2198 : // called after measure layout
2199 : // adjusts staff distance
2200 : //---------------------------------------------------------
2201 :
2202 9480 : void SystemLayout::layout2(System* system, LayoutContext& ctx)
2203 : {
2204 9495 : TRACEFUNC;
2205 : LAYOUT_CALL() << LAYOUT_ITEM_INFO(system);
2206 :
2207 9480 : Box* vb = system->vbox();
2208 9480 : if (vb) {
2209 2288 : BoxLayout::layoutBox(vb, vb->mutldata(), ctx);
2210 2288 : system->setbbox(vb->ldata()->bbox());
2211 2288 : return;
2212 : }
2213 :
2214 7192 : system->setPos(0.0, 0.0);
2215 7192 : std::vector<std::pair<size_t, SysStaff*> > visibleStaves;
2216 :
2217 18081 : for (size_t i = 0; i < system->staves().size(); ++i) {
2218 10889 : const Staff* s = ctx.dom().staff(i);
2219 10889 : SysStaff* ss = system->staves().at(i);
2220 10889 : if (s->show() && ss->show()) {
2221 10686 : visibleStaves.push_back(std::pair<size_t, SysStaff*>(i, ss));
2222 : } else {
2223 203 : ss->setbbox(RectF()); // already done in layout() ?
2224 : }
2225 : }
2226 :
2227 7192 : double _spatium = system->spatium();
2228 7192 : double y = 0.0;
2229 7192 : double minVerticalDistance = ctx.conf().styleAbsolute(Sid::minVerticalDistance);
2230 7192 : double staffDistance = ctx.conf().styleAbsolute(Sid::staffDistance);
2231 7192 : double akkoladeDistance = ctx.conf().styleAbsolute(Sid::akkoladeDistance);
2232 7192 : if (ctx.conf().isVerticalSpreadEnabled()) {
2233 5089 : staffDistance = ctx.conf().styleAbsolute(Sid::minStaffSpread);
2234 5089 : akkoladeDistance = ctx.conf().styleAbsolute(Sid::minStaffSpread);
2235 : }
2236 :
2237 7192 : if (visibleStaves.empty()) {
2238 0 : return;
2239 : }
2240 :
2241 7192 : for (auto i = visibleStaves.begin();; ++i) {
2242 10686 : SysStaff* ss = i->second;
2243 10686 : staff_idx_t si1 = i->first;
2244 10686 : const Staff* staff = ctx.dom().staff(si1);
2245 10686 : auto ni = std::next(i);
2246 :
2247 10686 : double dist = staff->staffHeight();
2248 : double yOffset;
2249 : double h;
2250 10686 : if (staff->lines(Fraction(0, 1)) == 1) {
2251 20 : yOffset = _spatium * BARLINE_SPAN_1LINESTAFF_TO * 0.5;
2252 20 : h = _spatium * (BARLINE_SPAN_1LINESTAFF_TO - BARLINE_SPAN_1LINESTAFF_FROM) * 0.5;
2253 : } else {
2254 10666 : yOffset = 0.0;
2255 10666 : h = staff->staffHeight();
2256 : }
2257 10686 : if (ni == visibleStaves.end()) {
2258 7192 : ss->setYOff(yOffset);
2259 7192 : ss->setbbox(system->leftMargin(), y - yOffset, system->width() - system->leftMargin(), h);
2260 7192 : ss->saveLayout();
2261 7192 : break;
2262 : }
2263 :
2264 3494 : staff_idx_t si2 = ni->first;
2265 3494 : const Staff* staff2 = ctx.dom().staff(si2);
2266 :
2267 3494 : if (staff->part() == staff2->part()) {
2268 1208 : Measure* m = system->firstMeasure();
2269 1208 : double mag = m ? staff->staffMag(m->tick()) : 1.0;
2270 1208 : dist += akkoladeDistance * mag;
2271 : } else {
2272 2286 : dist += staffDistance;
2273 : }
2274 3494 : dist += staff2->absoluteFromSpatium(staff2->userDist());
2275 3494 : bool fixedSpace = false;
2276 16499 : for (const MeasureBase* mb : system->measures()) {
2277 13005 : if (!mb->isMeasure()) {
2278 47 : continue;
2279 : }
2280 12958 : const Measure* m = toMeasure(mb);
2281 12958 : Spacer* sp = m->vspacerDown(si1);
2282 12958 : if (sp) {
2283 0 : if (sp->spacerType() == SpacerType::FIXED) {
2284 0 : dist = staff->staffHeight(m->tick()) + sp->absoluteGap();
2285 0 : fixedSpace = true;
2286 0 : break;
2287 : } else {
2288 0 : dist = std::max(dist, staff->staffHeight(m->tick()) + sp->absoluteGap());
2289 : }
2290 : }
2291 12958 : sp = m->vspacerUp(si2);
2292 12958 : if (sp) {
2293 0 : dist = std::max(dist, staff->staffHeight(m->tick()) + sp->absoluteGap());
2294 : }
2295 : }
2296 3494 : if (!fixedSpace) {
2297 : // check minimum distance to next staff
2298 : // note that in continuous view, we normally only have a partial skyline for the system
2299 : // a full one is only built when triggering a full layout
2300 : // therefore, we don't know the value we get from minDistance will actually be enough
2301 : // so we remember the value between layouts and increase it when necessary
2302 : // (the first layout on switching to continuous view gives us good initial values)
2303 : // the result is space is good to start and grows as needed
2304 : // it does not, however, shrink when possible - only by trigger a full layout
2305 : // (such as by toggling to page view and back)
2306 3494 : const double minHorizontalClearance = ctx.conf().styleAbsolute(Sid::skylineMinHorizontalClearance);
2307 3494 : double d = ss->skyline().minDistance(system->System::staff(si2)->skyline(), minHorizontalClearance);
2308 3494 : if (ctx.conf().isLineMode()) {
2309 0 : double previousDist = ss->continuousDist();
2310 0 : if (d > previousDist) {
2311 0 : ss->setContinuousDist(d);
2312 : } else {
2313 0 : d = previousDist;
2314 : }
2315 : }
2316 3494 : dist = std::max(dist, d + minVerticalDistance);
2317 3494 : dist = std::max(dist, minVertSpaceForCrossStaffBeams(system, si1, si2, ctx));
2318 : }
2319 3494 : ss->setYOff(yOffset);
2320 3494 : ss->setbbox(system->leftMargin(), y - yOffset, system->width() - system->leftMargin(), h);
2321 3494 : ss->saveLayout();
2322 3494 : y += dist;
2323 3494 : }
2324 :
2325 7192 : system->setSystemHeight(system->staff(visibleStaves.back().first)->bbox().bottom());
2326 7192 : system->setHeight(system->systemHeight());
2327 :
2328 7192 : SystemLayout::setMeasureHeight(system, system->systemHeight(), ctx);
2329 :
2330 : //---------------------------------------------------
2331 : // layout brackets vertical position
2332 : //---------------------------------------------------
2333 :
2334 7192 : SystemHeaderLayout::layoutBracketsVertical(system, ctx);
2335 :
2336 : //---------------------------------------------------
2337 : // layout instrument names
2338 : //---------------------------------------------------
2339 :
2340 7192 : SystemHeaderLayout::setInstrumentNamesVerticalPos(system, ctx);
2341 9480 : }
2342 :
2343 3494 : double SystemLayout::minVertSpaceForCrossStaffBeams(System* system, staff_idx_t staffIdx1, staff_idx_t staffIdx2, LayoutContext& ctx)
2344 : {
2345 3494 : double minSpace = -DBL_MAX;
2346 3494 : track_idx_t startTrack = staffIdx1 * VOICES;
2347 3494 : track_idx_t endTrack = staffIdx2 * VOICES + VOICES;
2348 16499 : for (MeasureBase* mb : system->measures()) {
2349 13005 : if (!mb->isMeasure()) {
2350 47 : continue;
2351 : }
2352 63545 : for (Segment& segment : toMeasure(mb)->segments()) {
2353 50587 : if (!segment.isChordRestType()) {
2354 26178 : continue;
2355 : }
2356 219813 : for (track_idx_t track = startTrack; track < endTrack; ++track) {
2357 195404 : EngravingItem* item = segment.element(track);
2358 195404 : if (!item || !item->isChord()) {
2359 195388 : continue;
2360 : }
2361 21607 : Beam* beam = toChord(item)->beam();
2362 21607 : if (!beam || !beam->autoplace() || beam->elements().front() != item) {
2363 20230 : continue;
2364 : }
2365 1377 : if (beam->ldata()->crossStaffBeamPos != BeamBase::CrossStaffBeamPosition::BETWEEN) {
2366 1361 : continue;
2367 : }
2368 16 : const Chord* limitingChordAbove = nullptr;
2369 16 : const Chord* limitingChordBelow = nullptr;
2370 16 : double limitFromAbove = -DBL_MAX;
2371 16 : double limitFromBelow = DBL_MAX;
2372 70 : for (const ChordRest* cr : beam->elements()) {
2373 54 : if (!cr->isChord()) {
2374 0 : continue;
2375 : }
2376 54 : const Chord* chord = toChord(cr);
2377 54 : double minStemLength = BeamTremoloLayout::minStemLength(chord, beam->ldata()) * (chord->spatium() / 4);
2378 54 : bool isUnderCrossBeam = cr->isBelowCrossBeam(beam);
2379 54 : if (isUnderCrossBeam && chord->vStaffIdx() == staffIdx2) {
2380 26 : const Note* topNote = chord->upNote();
2381 26 : double noteLimit = topNote->y() - minStemLength;
2382 26 : if (noteLimit < limitFromBelow) {
2383 18 : limitFromBelow = noteLimit;
2384 18 : limitingChordBelow = chord;
2385 : }
2386 26 : limitFromBelow = std::min(limitFromBelow, noteLimit);
2387 28 : } else if (!isUnderCrossBeam && chord->vStaffIdx() == staffIdx1) {
2388 28 : const Note* bottomNote = chord->downNote();
2389 28 : double noteLimit = bottomNote->y() + minStemLength;
2390 28 : if (noteLimit > limitFromAbove) {
2391 22 : limitFromAbove = noteLimit;
2392 22 : limitingChordAbove = chord;
2393 : }
2394 : }
2395 : }
2396 16 : if (limitingChordAbove && limitingChordBelow) {
2397 16 : double minSpaceRequired = limitFromAbove - limitFromBelow;
2398 16 : beam->mutldata()->limitingChordAbove = limitingChordAbove;
2399 16 : beam->mutldata()->limitingChordBelow = limitingChordBelow;
2400 :
2401 16 : const BeamSegment* topBeamSegmentForChordAbove = beam->topLevelSegmentForElement(limitingChordAbove);
2402 16 : const BeamSegment* topBeamSegmentForChordBelow = beam->topLevelSegmentForElement(limitingChordBelow);
2403 16 : if (topBeamSegmentForChordAbove->above == topBeamSegmentForChordBelow->above) {
2404 : // In this case the two opposing stems overlap the beam height, so we must subtract it
2405 16 : int strokeCount = std::min(BeamTremoloLayout::strokeCount(beam->ldata(), limitingChordAbove),
2406 16 : BeamTremoloLayout::strokeCount(beam->ldata(), limitingChordBelow));
2407 16 : double beamHeight = ctx.conf().styleAbsolute(Sid::beamWidth) + beam->beamDist() * (strokeCount - 1);
2408 16 : minSpaceRequired -= beamHeight;
2409 : }
2410 16 : minSpace = std::max(minSpace, minSpaceRequired);
2411 : }
2412 : }
2413 : }
2414 : }
2415 :
2416 3494 : return minSpace;
2417 : }
2418 :
2419 10195 : void SystemLayout::restoreLayout2(System* system, LayoutContext& ctx)
2420 : {
2421 10210 : TRACEFUNC;
2422 10195 : if (system->vbox()) {
2423 2371 : return;
2424 : }
2425 :
2426 19526 : for (SysStaff* s : system->staves()) {
2427 11702 : s->restoreLayout();
2428 : }
2429 :
2430 7824 : system->setHeight(system->systemHeight());
2431 7824 : SystemLayout::setMeasureHeight(system, system->systemHeight(), ctx);
2432 :
2433 : // Reused systems can move across pages during partial relayout.
2434 : // Refresh geometry derived from SysStaff vertical positions.
2435 7824 : SystemHeaderLayout::layoutBracketsVertical(system, ctx);
2436 7824 : SystemHeaderLayout::setInstrumentNamesVerticalPos(system, ctx);
2437 10195 : }
2438 :
2439 20369 : void SystemLayout::setMeasureHeight(System* system, double height, const LayoutContext& ctx)
2440 : {
2441 20369 : double spatium = system->spatium();
2442 108134 : for (MeasureBase* m : system->measures()) {
2443 87765 : MeasureBase::LayoutData* mldata = m->mutldata();
2444 87765 : if (m->isMeasure()) {
2445 : // note that the factor 2 * _spatium must be corrected for when exporting
2446 : // system distance in MusicXML (issue #24733)
2447 87388 : mldata->setBbox(0.0, -spatium, m->width(), height + 2.0 * spatium);
2448 377 : } else if (m->isHBox()) {
2449 377 : mldata->setBbox(m->absoluteFromSpatium(toHBox(m)->topGap()), 0.0, m->width(), height);
2450 377 : BoxLayout::layoutHBox2(toHBox(m), ctx);
2451 0 : } else if (m->isTBox()) {
2452 0 : BoxLayout::layoutTBox(toTBox(m), toTBox(m)->mutldata(), ctx);
2453 0 : } else if (m->isFBox()) {
2454 0 : BoxLayout::layoutFBox(toFBox(m), toFBox(m)->mutldata(), ctx);
2455 : } else {
2456 0 : LOGD("unhandled measure type %s", m->typeName());
2457 : }
2458 : }
2459 20369 : }
2460 :
2461 : //---------------------------------------------------------
2462 : // minDistance
2463 : // Return the minimum distance between this system and s2
2464 : // without any element collisions.
2465 : //
2466 : // top - top system
2467 : // bottom - bottom system
2468 : //---------------------------------------------------------
2469 :
2470 12066 : double SystemLayout::minDistance(const System* top, const System* bottom, const LayoutContext& ctx)
2471 : {
2472 12079 : TRACEFUNC;
2473 :
2474 12066 : const LayoutConfiguration& conf = ctx.conf();
2475 12066 : const DomAccessor& dom = ctx.dom();
2476 :
2477 12066 : const VBox* topVBox = static_cast<VBox*>(top->vbox());
2478 12066 : const VBox* bottomVBox = static_cast<VBox*>(bottom->vbox());
2479 :
2480 12066 : if (topVBox && !bottomVBox) {
2481 4415 : return std::max(topVBox->absoluteFromSpatium(topVBox->bottomGap()),
2482 8830 : bottom->minTop() + topVBox->absoluteFromSpatium(topVBox->paddingToNotationBelow()));
2483 7651 : } else if (!topVBox && bottomVBox) {
2484 109 : return std::max(bottomVBox->absoluteFromSpatium(bottomVBox->topGap()),
2485 218 : top->minBottom() + bottomVBox->absoluteFromSpatium(bottomVBox->paddingToNotationAbove()));
2486 7542 : } else if (topVBox && bottomVBox) {
2487 167 : const double topToBottomGap = topVBox->absoluteFromSpatium(topVBox->bottomGap());
2488 167 : const double bottomToTopGap = bottomVBox->absoluteFromSpatium(bottomVBox->topGap());
2489 167 : if (topToBottomGap >= 0 && bottomToTopGap >= 0) {
2490 167 : double largestGap = std::max(bottomToTopGap, topToBottomGap);
2491 167 : return largestGap;
2492 : } else {
2493 0 : return topToBottomGap + bottomToTopGap;
2494 : }
2495 : }
2496 :
2497 7375 : if (top->staves().empty() || bottom->staves().empty()) {
2498 0 : return 0.0;
2499 : }
2500 :
2501 7375 : double minVerticalDistance = conf.styleAbsolute(Sid::minVerticalDistance);
2502 7375 : double dist = conf.isVerticalSpreadEnabled() ? conf.styleAbsolute(Sid::minSystemSpread) : conf.styleAbsolute(Sid::minSystemDistance);
2503 7375 : size_t firstStaff = 0;
2504 7375 : size_t lastStaff = 0;
2505 :
2506 7381 : for (firstStaff = 0; firstStaff < top->staves().size() - 1; ++firstStaff) {
2507 2246 : if (dom.staff(firstStaff)->show() && bottom->staff(firstStaff)->show()) {
2508 2240 : break;
2509 : }
2510 : }
2511 7525 : for (lastStaff = top->staves().size() - 1; lastStaff > 0; --lastStaff) {
2512 2336 : if (dom.staff(lastStaff)->show() && top->staff(lastStaff)->show()) {
2513 2186 : break;
2514 : }
2515 : }
2516 :
2517 7375 : const Staff* staff = dom.staff(firstStaff);
2518 7375 : double userDist = staff ? staff->absoluteFromSpatium(staff->userDist()) : 0.0;
2519 7375 : dist = std::max(dist, userDist);
2520 7375 : top->setFixedDownDistance(false);
2521 :
2522 7375 : const SysStaff* sysStaff = top->staff(lastStaff);
2523 7375 : const double minHorizontalClearance = conf.styleAbsolute(Sid::skylineMinHorizontalClearance);
2524 7375 : double sld = sysStaff ? sysStaff->skyline().minDistance(bottom->staff(firstStaff)->skyline(), minHorizontalClearance) : 0;
2525 7375 : sld -= sysStaff ? sysStaff->bbox().height() - minVerticalDistance : 0;
2526 :
2527 7375 : if (conf.isFloatMode()) {
2528 0 : return std::max(dist, sld);
2529 : }
2530 :
2531 50320 : for (const MeasureBase* mb1 : top->measures()) {
2532 42949 : if (mb1->isMeasure()) {
2533 42809 : const Measure* m = toMeasure(mb1);
2534 42809 : const Spacer* sp = m->vspacerDown(lastStaff);
2535 42809 : if (sp) {
2536 4 : if (sp->spacerType() == SpacerType::FIXED) {
2537 4 : dist = sp->absoluteGap();
2538 4 : top->setFixedDownDistance(true);
2539 4 : break;
2540 : } else {
2541 0 : dist = std::max(dist, sp->absoluteGap());
2542 : }
2543 : }
2544 : }
2545 : }
2546 7375 : if (!top->hasFixedDownDistance()) {
2547 44809 : for (const MeasureBase* mb2 : bottom->measures()) {
2548 37438 : if (mb2->isMeasure()) {
2549 37308 : const Measure* m = toMeasure(mb2);
2550 37308 : const Spacer* sp = m->vspacerUp(firstStaff);
2551 37308 : if (sp) {
2552 0 : dist = std::max(dist, sp->absoluteGap());
2553 : }
2554 : }
2555 : }
2556 :
2557 7371 : dist = std::max(dist, sld);
2558 : }
2559 7375 : return dist;
2560 12066 : }
2561 :
2562 4647 : static bool itemIsPlacedAbove(const EngravingItem* item)
2563 : {
2564 4647 : if (item->isArticulationFamily()) {
2565 2 : return toArticulation(item)->up();
2566 : }
2567 4645 : if (item->isLyricsLineSegment()) {
2568 0 : return toLyricsLineSegment(item)->lyricsPlaceAbove();
2569 : }
2570 4645 : return item->placeAbove();
2571 : }
2572 :
2573 0 : void SystemLayout::removeElementFromSkyline(EngravingItem* element, const System* system)
2574 : {
2575 0 : Skyline& skyline = system->staff(element->staffIdx())->skyline();
2576 0 : SkylineLine& skylineLine = itemIsPlacedAbove(element) ? skyline.north() : skyline.south();
2577 :
2578 0 : skylineLine.remove_if([element](ShapeElement& shapeEl) {
2579 0 : return shapeEl.item() && (element == shapeEl.item() || element == shapeEl.item()->ownershipParent());
2580 : });
2581 0 : }
2582 :
2583 4647 : void SystemLayout::updateSkylineForElement(EngravingItem* element, const System* system, double yMove)
2584 : {
2585 4647 : Skyline& skyline = system->staff(element->staffIdx())->skyline();
2586 4647 : SkylineLine& skylineLine = itemIsPlacedAbove(element) ? skyline.north() : skyline.south();
2587 139096 : for (ShapeElement& shapeEl : skylineLine.elements()) {
2588 134449 : const EngravingItem* itemInSkyline = shapeEl.item();
2589 134449 : if (itemInSkyline && itemInSkyline->isText()) {
2590 0 : const EngravingItem* owner = itemInSkyline->ownershipParentItem();
2591 0 : if (owner && owner->isSLineSegment()) {
2592 0 : itemInSkyline = owner;
2593 : }
2594 : }
2595 134449 : if (itemInSkyline == element) {
2596 8236 : shapeEl.translate(0.0, yMove);
2597 : }
2598 : }
2599 4647 : }
2600 :
2601 10198 : void SystemLayout::centerElementsBetweenStaves(const System* system)
2602 : {
2603 10198 : std::vector<Gap> gaps; // populated by collectCenterableItems
2604 10198 : std::vector<MMRest*> mmRestsToCenter; // populated by collectCenterableItems
2605 :
2606 10198 : collectCenterableItems(system, gaps, mmRestsToCenter);
2607 :
2608 10202 : for (MMRest* mmrest : mmRestsToCenter) {
2609 4 : centerMMRestBetweenStaves(mmrest, system);
2610 : }
2611 :
2612 10198 : const double minHorizontalClearance = system->style().styleAbsolute(Sid::skylineMinHorizontalClearance);
2613 :
2614 13890 : for (const Gap& gap : gaps) {
2615 3692 : if (!gap.items.empty()) {
2616 129 : centerItemsInGap(gap, system, minHorizontalClearance);
2617 : }
2618 : }
2619 10198 : }
2620 :
2621 3964 : void SystemLayout::centerPendingSystems(LayoutContext& ctx)
2622 : {
2623 3967 : for (System* system : ctx.state().systemsNeedingCentering()) {
2624 3 : centerElementsBetweenStaves(system);
2625 : }
2626 :
2627 3964 : ctx.mutState().clearSystemsNeedingCentering();
2628 3964 : }
2629 :
2630 257 : static Shape shapeRelativeToStaff(const EngravingItem* item, double systemX)
2631 : {
2632 : // Offsets are applied after centering, so measure from the non-offset position
2633 257 : const double x = item->pageX() - systemX - item->offset().x();
2634 257 : const double y = item->isLyrics() ? toLyrics(item)->yRelativeToStaff() - item->offset().y() : item->ldata()->pos().y();
2635 257 : return item->ldata()->shape().translated(PointF(x, y));
2636 : }
2637 :
2638 10198 : void SystemLayout::collectCenterableItems(const System* system, std::vector<Gap>& gaps, std::vector<MMRest*>& mmRestsToCenter)
2639 : {
2640 10198 : const size_t nStaves = system->staves().size();
2641 10198 : if (nStaves < 2) {
2642 7842 : return;
2643 : }
2644 :
2645 : struct GapsOfStaff {
2646 : size_t forItemsBelow = muse::nidx;
2647 : size_t forItemsAbove = muse::nidx;
2648 : };
2649 2356 : std::vector<GapsOfStaff> gapsOfStaff(nStaves);
2650 2356 : gaps.reserve(nStaves - 1);
2651 :
2652 2356 : staff_idx_t upperStaffIdx = system->firstVisibleStaff();
2653 2356 : staff_idx_t lowerStaffIdx = muse::nidx;
2654 6048 : for (; upperStaffIdx < nStaves; upperStaffIdx = lowerStaffIdx) {
2655 6048 : lowerStaffIdx = system->nextVisibleStaff(upperStaffIdx);
2656 6048 : if (lowerStaffIdx == muse::nidx) {
2657 2356 : break; // the last visible staff of the system
2658 : }
2659 :
2660 3692 : gapsOfStaff[upperStaffIdx].forItemsBelow = gaps.size();
2661 3692 : gapsOfStaff[lowerStaffIdx].forItemsAbove = gaps.size();
2662 3692 : gaps.emplace_back(upperStaffIdx, lowerStaffIdx, system->staff(lowerStaffIdx)->y() - system->staff(upperStaffIdx)->y());
2663 : }
2664 :
2665 2356 : const double systemX = system->pageX();
2666 :
2667 258 : auto addItem = [&](EngravingItem* item, bool above, bool isLyrics) {
2668 258 : const staff_idx_t staffIdx = item->staffIdx();
2669 258 : if (staffIdx >= nStaves) {
2670 1 : return;
2671 : }
2672 258 : const size_t gapIdx = above ? gapsOfStaff[staffIdx].forItemsAbove : gapsOfStaff[staffIdx].forItemsBelow;
2673 258 : if (gapIdx == muse::nidx) {
2674 : // The staff is hidden, or there is no staff on that side to center against
2675 1 : return;
2676 : }
2677 :
2678 257 : Shape shape = shapeRelativeToStaff(item, systemX);
2679 702 : shape.remove_if([](ShapeElement& shapeEl) { return shapeEl.ignoreForLayout(); });
2680 :
2681 : // Items left out of the skyline (e.g. invisible) must not take up space in the gap
2682 257 : const bool takesSpace = item->isLyricsLineSegment() ? toLyricsLineSegment(item)->lyricsAddToSkyline()
2683 257 : : item->addToSkyline();
2684 257 : if (!takesSpace) {
2685 0 : shape.clear();
2686 : }
2687 :
2688 257 : Gap& gap = gaps[gapIdx];
2689 257 : if (above) {
2690 : // Inside a gap, everything is expressed relative to its upper staff
2691 20 : shape.translate(PointF(0.0, gap.yStaffDiff));
2692 : }
2693 257 : gap.items.push_back(CenterableItem { item, std::move(shape), isLyrics, !above });
2694 257 : };
2695 :
2696 4836 : for (SpannerSegment* spannerSeg : system->spannerSegments()) {
2697 2734 : if ((spannerSeg->isHairpinSegment() && snapChainShouldBeCenteredBetweenStaves(spannerSeg, system))
2698 2734 : || (spannerSeg->isWhammyBarSegment() && whammyBarShouldBeCenteredBetweenStaves(toWhammyBarSegment(spannerSeg), system))) {
2699 114 : addItem(spannerSeg, spannerSeg->placeAbove(), false);
2700 2366 : } else if (spannerSeg->isLyricsLineSegment()) {
2701 185 : bool above = toLyricsLineSegment(spannerSeg)->lyricsPlaceAbove();
2702 185 : if (elementShouldBeCenteredBetweenStaves(spannerSeg, system, above)) {
2703 0 : addItem(spannerSeg, above, true);
2704 : }
2705 : }
2706 : }
2707 :
2708 13593 : for (const MeasureBase* mb : system->measures()) {
2709 11237 : if (!mb->isMeasure()) {
2710 54 : continue;
2711 : }
2712 51566 : for (const Segment& seg : toMeasure(mb)->segments()) {
2713 40383 : if (seg.isChordRestType()) {
2714 207352 : for (EngravingItem* item : seg.elist()) {
2715 187280 : if (!item) {
2716 144197 : continue;
2717 : }
2718 43083 : if (item->isMMRest() && mmRestShouldBeCenteredBetweenStaves(toMMRest(item), system)) {
2719 4 : mmRestsToCenter.push_back(toMMRest(item));
2720 43079 : } else if (item->isChordRest()) {
2721 44516 : for (Lyrics* lyrics : toChordRest(item)->lyrics()) {
2722 1437 : bool above = lyrics->placeAbove();
2723 1437 : if (elementShouldBeCenteredBetweenStaves(lyrics, system, above)) {
2724 0 : addItem(lyrics, above, true);
2725 : }
2726 : }
2727 : }
2728 : }
2729 : }
2730 42437 : for (EngravingItem* item : seg.annotations()) {
2731 2054 : if ((item->isDynamic() || item->isExpression()) && snapChainShouldBeCenteredBetweenStaves(item, system)) {
2732 144 : addItem(item, item->placeAbove(), false);
2733 : }
2734 : }
2735 : }
2736 : }
2737 2356 : }
2738 :
2739 188 : bool SystemLayout::shapesStackVertically(const Shape& shape1, const Shape& shape2, double minHorizontalClearance)
2740 : {
2741 434 : for (const ShapeElement& shapeEl1 : shape1.elements()) {
2742 260 : if (shapeEl1.height() <= 0.0) {
2743 0 : continue;
2744 : }
2745 748 : for (const ShapeElement& shapeEl2 : shape2.elements()) {
2746 502 : if (shapeEl2.height() <= 0.0) {
2747 0 : continue;
2748 : }
2749 502 : if (intersects(shapeEl1.left(), shapeEl1.right(), shapeEl2.left(), shapeEl2.right(), minHorizontalClearance)) {
2750 14 : return true;
2751 : }
2752 : }
2753 : }
2754 :
2755 174 : return false;
2756 : }
2757 :
2758 0 : void SystemLayout::centerBigTimeSigsAcrossStaves(const System* system)
2759 : {
2760 0 : staff_idx_t nstaves = system->score()->nstaves();
2761 0 : for (MeasureBase* mb : system->measures()) {
2762 0 : if (!mb->isMeasure()) {
2763 0 : continue;
2764 : }
2765 0 : for (Segment& segment : toMeasure(mb)->segments()) {
2766 0 : if (!segment.isType(SegmentType::TimeSigTypes)) {
2767 0 : continue;
2768 : }
2769 0 : for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
2770 0 : TimeSig* timeSig = toTimeSig(segment.element(staff2track(staffIdx)));
2771 0 : if (!timeSig || !timeSig->showOnThisStaff()) {
2772 0 : continue;
2773 : }
2774 0 : staff_idx_t thisStaffIdx = timeSig->effectiveStaffIdx();
2775 0 : if (thisStaffIdx == muse::nidx) {
2776 0 : continue;
2777 : }
2778 0 : staff_idx_t nextStaffIdx = thisStaffIdx;
2779 0 : for (staff_idx_t idx = thisStaffIdx + 1; idx < nstaves; ++idx) {
2780 0 : TimeSig* nextTimeSig = toTimeSig(segment.element(staff2track(idx)));
2781 0 : if (nextTimeSig && nextTimeSig->showOnThisStaff()) {
2782 0 : staff_idx_t nextTimeSigStave = nextTimeSig->effectiveStaffIdx();
2783 0 : if (nextTimeSigStave != muse::nidx) {
2784 0 : nextStaffIdx = system->prevVisibleStaff(nextTimeSigStave);
2785 0 : break;
2786 : }
2787 : }
2788 0 : if (idx == nstaves - 1) {
2789 0 : nextStaffIdx = system->prevVisibleStaff(nstaves);
2790 0 : break;
2791 : }
2792 : }
2793 0 : double yTop = system->staff(thisStaffIdx)->y() + system->score()->staff(thisStaffIdx)->staffHeight(segment.tick());
2794 0 : double yBottom = system->staff(nextStaffIdx)->y() + system->score()->staff(nextStaffIdx)->staffHeight(segment.tick());
2795 0 : double newYPos = 0.5 * (yBottom - yTop);
2796 0 : timeSig->mutldata()->setPosY(newYPos);
2797 : }
2798 : }
2799 : }
2800 0 : }
2801 :
2802 773 : bool SystemLayout::snapChainShouldBeCenteredBetweenStaves(EngravingItem* item, const System* system)
2803 : {
2804 : // Snapped items are centered as one, so if any of them cannot be centered, none of them can
2805 773 : bool allCenterable = true;
2806 773 : item->ldata()->scanConnectedItemsInSnappingChain(system, [&allCenterable, system](EngravingItem* chainItem) {
2807 953 : allCenterable = allCenterable && elementShouldBeCenteredBetweenStaves(chainItem, system, chainItem->placeAbove());
2808 953 : });
2809 :
2810 773 : return allCenterable;
2811 : }
2812 :
2813 2495 : bool SystemLayout::elementShouldBeCenteredBetweenStaves(const EngravingItem* item, const System* system, bool placeAbove)
2814 : {
2815 2495 : const Part* itemPart = item->part();
2816 2495 : IF_ASSERT_FAILED(itemPart) {
2817 0 : return false;
2818 : }
2819 :
2820 2495 : const Staff* otherStaff = item->staffToCenterAgainst(placeAbove, system);
2821 2495 : if (!otherStaff) {
2822 1809 : return false;
2823 : }
2824 :
2825 686 : const bool sameInstrument = otherStaff->part() == itemPart;
2826 :
2827 : Sid centerStyleId;
2828 686 : if (item->isLyrics() || item->isLyricsLineSegment()) {
2829 212 : centerStyleId = sameInstrument ? Sid::lyricsAutoCenterOnGrandStaff : Sid::lyricsAutoCenterOnVocalStaves;
2830 474 : } else if (item->isDynamic() || item->isExpression() || item->isHairpinSegment()) {
2831 474 : centerStyleId = sameInstrument ? Sid::dynamicsHairpinsAutoCenterOnGrandStaff : Sid::dynamicsHairpinsAutoCenterOnVocalStaves;
2832 : } else {
2833 0 : return false;
2834 : }
2835 :
2836 : AutoOnOff centerProperty;
2837 686 : if (item->isLyricsLineSegment() && !item->isPartialLyricsLineSegment()) {
2838 : // A dash or melisma line belonging to a lyric has no setting of its own: it always
2839 : // follows that lyric. Only partial lyrics lines are configurable individually.
2840 49 : const Lyrics* lyrics = toLyricsLineSegment(item)->lyrics();
2841 49 : centerProperty = lyrics ? lyrics->centerBetweenStaves() : AutoOnOff::AUTO;
2842 : } else {
2843 637 : centerProperty = item->getProperty(Pid::CENTER_BETWEEN_STAVES).value<AutoOnOff>();
2844 : }
2845 :
2846 686 : const bool centerStyle = item->style().styleB(centerStyleId);
2847 686 : if (centerProperty == AutoOnOff::OFF || (!centerStyle && centerProperty != AutoOnOff::ON)) {
2848 261 : return false;
2849 : }
2850 :
2851 425 : if (sameInstrument && centerProperty != AutoOnOff::ON && !itemPart->instrument()->isNormallyMultiStaveInstrument()) {
2852 65 : return false;
2853 : }
2854 :
2855 360 : if (item->isDynamic() || item->isExpression() || item->isHairpinSegment()) {
2856 360 : if (!(centerProperty == AutoOnOff::ON || item->appliesToAllVoicesInInstrument())) {
2857 2 : return false;
2858 : }
2859 : }
2860 :
2861 358 : return true;
2862 : }
2863 :
2864 36 : bool SystemLayout::mmRestShouldBeCenteredBetweenStaves(const MMRest* mmRest, const System* system)
2865 : {
2866 36 : if (!mmRest->style().styleB(Sid::mmRestBetweenStaves)) {
2867 0 : return false;
2868 : }
2869 :
2870 36 : const Part* itemPart = mmRest->part();
2871 36 : if (itemPart->nstaves() <= 1) {
2872 28 : return false;
2873 : }
2874 :
2875 8 : staff_idx_t thisStaffIdx = mmRest->staffIdx();
2876 8 : staff_idx_t prevStaffIdx = system->prevVisibleStaff(thisStaffIdx);
2877 :
2878 8 : return prevStaffIdx != muse::nidx && mmRest->score()->staff(prevStaffIdx)->part() == itemPart;
2879 : }
2880 :
2881 0 : bool SystemLayout::whammyBarShouldBeCenteredBetweenStaves(const WhammyBarSegment* wbar, const System* system)
2882 : {
2883 0 : staff_idx_t staffIdx = wbar->staffIdx();
2884 0 : Staff* thisStaff = wbar->staff();
2885 0 : Staff* nextStaff = wbar->score()->staff(staffIdx + 1);
2886 0 : bool nextIsLinkedTab = nextStaff && nextStaff->isTabStaff(wbar->tick()) && thisStaff->isLinked(nextStaff);
2887 0 : SysStaff* nextSysStaff = system->staff(staffIdx + 1);
2888 0 : return wbar->placeBelow() && nextIsLinkedTab && nextSysStaff && nextSysStaff->show();
2889 : }
2890 :
2891 257 : bool SystemLayout::elementHasAnotherStackedOutside(const EngravingItem* element, const Shape& elementShape, const SkylineLine& skylineLine)
2892 : {
2893 257 : double elemShapeLeft = elementShape.left();
2894 257 : double elemShapeRight = elementShape.right();
2895 257 : double elemShapeTop = elementShape.top();
2896 257 : double elemShapeBottom = elementShape.bottom();
2897 :
2898 15637 : for (const ShapeElement& skylineElement : skylineLine.elements()) {
2899 15380 : const EngravingItem* skylineItem = skylineElement.item();
2900 15380 : if (!skylineItem || skylineItem == element || skylineItem->parent() == element || skylineItem->isAccidental()
2901 30760 : || Autoplace::itemsShouldIgnoreEachOther(element, skylineItem)) {
2902 360 : continue;
2903 : }
2904 15020 : bool intersectHorizontally = elemShapeRight > skylineElement.left() && elemShapeLeft < skylineElement.right();
2905 15020 : if (!intersectHorizontally) {
2906 12765 : continue;
2907 : }
2908 4472 : bool skylineElementIsStackedOnIt = skylineLine.isNorth() ? skylineElement.bottom() <= elemShapeTop
2909 2217 : : skylineElement.top() >= elemShapeBottom;
2910 2255 : if (skylineElementIsStackedOnIt) {
2911 0 : return true;
2912 : }
2913 : }
2914 :
2915 257 : return false;
2916 : }
2917 :
2918 258 : static SkylineLine skylineFacingGap(const System* system, staff_idx_t staffIdx, bool isUpperStaffOfGap, double yShift,
2919 : const std::unordered_set<const EngravingItem*>& itemsToIgnore)
2920 : {
2921 129 : const SkylineLine& skyline = isUpperStaffOfGap ? system->staff(staffIdx)->skyline().south()
2922 258 : : system->staff(staffIdx)->skyline().north();
2923 :
2924 516 : SkylineLine filteredSkyline = skyline.getFilteredCopy([&itemsToIgnore](const ShapeElement& shapeEl) {
2925 7809 : const EngravingItem* shapeItem = shapeEl.item();
2926 15177 : return shapeItem && (muse::contains(itemsToIgnore, shapeItem)
2927 15177 : || muse::contains(itemsToIgnore, const_cast<const EngravingItem*>(shapeItem->ownershipParentItem())));
2928 258 : });
2929 :
2930 258 : if (yShift != 0.0) {
2931 129 : filteredSkyline.translateY(yShift);
2932 : }
2933 :
2934 258 : return filteredSkyline;
2935 0 : }
2936 :
2937 129 : void SystemLayout::centerItemsInGap(const Gap& gap, const System* system, double minHorizontalClearance)
2938 : {
2939 : /* Nothing which is itself centered in this gap may take part in measuring it */
2940 129 : std::unordered_set<const EngravingItem*> movableItems;
2941 386 : for (const CenterableItem& gapItem : gap.items) {
2942 257 : movableItems.insert(gapItem.item);
2943 : }
2944 :
2945 0 : auto getVerse = [](const EngravingItem* item) {
2946 0 : if (item->isLyrics()) {
2947 0 : return toLyrics(item)->verse();
2948 0 : } else if (item->isLyricsLineSegment()) {
2949 0 : return toLyricsLineSegment(item)->verse();
2950 : }
2951 0 : return -1;
2952 : };
2953 :
2954 : /* An item with something non-centered stacked outside it has no gap to be centered in: it
2955 : * stays where it is and joins the skyline, so that it constrains the items which do move */
2956 129 : while (!movableItems.empty()) {
2957 129 : const SkylineLine upperSkyline = skylineFacingGap(system, gap.upperStaffIdx, true, 0.0, movableItems);
2958 129 : const SkylineLine lowerSkyline = skylineFacingGap(system, gap.lowerStaffIdx, false, gap.yStaffDiff, movableItems);
2959 :
2960 129 : std::vector<const CenterableItem*> itemsToCenter;
2961 129 : bool canCenterGroup = true;
2962 386 : for (const CenterableItem& gapItem : gap.items) {
2963 257 : const EngravingItem* item = gapItem.item;
2964 257 : if (!muse::contains(movableItems, item)) {
2965 0 : continue;
2966 : }
2967 :
2968 257 : const Shape itemShape = gapItem.shape.adjusted(-minHorizontalClearance, 0.0, minHorizontalClearance, 0.0);
2969 257 : const SkylineLine& ownSkyline = gapItem.onUpperGapStaff ? upperSkyline : lowerSkyline;
2970 257 : if (!itemShape.empty() && elementHasAnotherStackedOutside(item, itemShape, ownSkyline)) {
2971 0 : movableItems.erase(item);
2972 0 : canCenterGroup = false;
2973 :
2974 : // Snapped items stay together:
2975 0 : gapItem.item->ldata()->scanConnectedItemsInSnappingChain(system, [&movableItems](EngravingItem* chainItem) {
2976 0 : movableItems.erase(chainItem);
2977 0 : });
2978 :
2979 : // A lyrics verse stays together:
2980 0 : if (gapItem.isLyrics) {
2981 0 : int verse = getVerse(item);
2982 0 : for (const CenterableItem& i : gap.items) {
2983 0 : if (i.isLyrics && i.onUpperGapStaff == gapItem.onUpperGapStaff && getVerse(i.item) == verse) {
2984 0 : movableItems.erase(i.item);
2985 : }
2986 : }
2987 : }
2988 : } else {
2989 257 : itemsToCenter.push_back(&gapItem);
2990 : }
2991 257 : }
2992 :
2993 129 : if (!canCenterGroup) {
2994 0 : continue;
2995 : }
2996 :
2997 328 : for (const std::vector<const CenterableItem*>& group : groupItemsToCenterTogether(itemsToCenter, minHorizontalClearance)) {
2998 199 : centerItemGroup(group, system, upperSkyline, lowerSkyline, gap.yStaffDiff, minHorizontalClearance);
2999 129 : }
3000 129 : return;
3001 387 : }
3002 129 : }
3003 :
3004 129 : std::vector<std::vector<const SystemLayout::CenterableItem*> > SystemLayout::groupItemsToCenterTogether(
3005 : const std::vector<const CenterableItem*>& items, double minHorizontalClearance)
3006 : {
3007 : /* We center lyrics as a single block so that the verses will not lose their alignment (separating the ones
3008 : * below the upper staff from the ones above the lower staff if they don't overlap). Other centerable items
3009 : * join the group if they overlap horizontally, or if they are snapped to each other, so that they will
3010 : * preserve their relative positions. */
3011 129 : const size_t nItems = items.size();
3012 129 : std::vector<size_t> groupIds(nItems, muse::nidx);
3013 129 : size_t nGroupIds = 0;
3014 :
3015 386 : for (size_t i = 0; i < nItems; ++i) {
3016 257 : if (groupIds[i] == muse::nidx) {
3017 201 : groupIds[i] = nGroupIds++;
3018 : }
3019 488 : for (size_t j = i + 1; j < nItems; ++j) {
3020 231 : const bool areVersesOfTheSameBlock = items[i]->isLyrics && items[j]->isLyrics
3021 231 : && items[i]->onUpperGapStaff == items[j]->onUpperGapStaff;
3022 231 : const bool areSnappedTogether = items[i]->item->ldata()->itemSnappedBefore() == items[j]->item
3023 231 : || items[i]->item->ldata()->itemSnappedAfter() == items[j]->item;
3024 231 : const bool mustMoveTogether = areVersesOfTheSameBlock || areSnappedTogether
3025 462 : || shapesStackVertically(items[i]->shape, items[j]->shape, minHorizontalClearance);
3026 231 : if (!mustMoveTogether) {
3027 173 : continue;
3028 : }
3029 58 : if (groupIds[j] == muse::nidx) {
3030 56 : groupIds[j] = groupIds[i];
3031 2 : } else if (groupIds[j] != groupIds[i]) {
3032 2 : const size_t idToMerge = groupIds[j];
3033 2 : const size_t idToKeep = groupIds[i];
3034 12 : for (size_t& groupId : groupIds) {
3035 10 : if (groupId == idToMerge) {
3036 4 : groupId = idToKeep;
3037 : }
3038 : }
3039 : }
3040 : }
3041 : }
3042 :
3043 129 : std::vector<size_t> groupIdToIndex(nGroupIds, muse::nidx);
3044 129 : std::vector<std::vector<const CenterableItem*> > groups;
3045 :
3046 386 : for (size_t i = 0; i < nItems; ++i) {
3047 257 : size_t& index = groupIdToIndex[groupIds[i]];
3048 257 : if (index == muse::nidx) {
3049 199 : index = groups.size();
3050 199 : groups.emplace_back();
3051 : }
3052 257 : groups[index].push_back(items[i]);
3053 : }
3054 :
3055 129 : return groups;
3056 129 : }
3057 :
3058 : // How far an item moves when its group is converged: only the half sitting above the lower staff moves to meet the upper one
3059 257 : double SystemLayout::convergenceMoveFor(const CenterableItem* centerableItem, double convergeDistance)
3060 : {
3061 257 : return centerableItem->onUpperGapStaff ? 0.0 : -convergeDistance;
3062 : }
3063 :
3064 199 : double SystemLayout::gapConvergeDistance(const std::vector<const CenterableItem*>& group, double yStaffDiff, double minHorizontalClearance)
3065 : {
3066 : /* A group that has items below the upper staff and above the lower one, spreads over the whole gap and
3067 : * has no space left to center the items, so we bring the items together into the middle of the gap. This
3068 : * function calculates how much slack there is for the lower items to converge towards the upper ones
3069 : * before centering, so that they will be centered together. */
3070 :
3071 199 : double slack = DBL_MAX;
3072 456 : for (const CenterableItem* upper : group) {
3073 257 : if (!upper->onUpperGapStaff || upper->shape.empty()) {
3074 20 : continue;
3075 : }
3076 :
3077 588 : for (const CenterableItem* lower : group) {
3078 351 : if (lower->onUpperGapStaff || lower->shape.empty()) {
3079 343 : continue;
3080 : }
3081 8 : if (!shapesStackVertically(upper->shape, lower->shape, minHorizontalClearance)) {
3082 1 : continue;
3083 : }
3084 :
3085 7 : const EngravingItem* upperItem = upper->item;
3086 7 : const EngravingItem* lowerItem = lower->item;
3087 7 : const Lyrics* upperLyrics = upperItem->isLyrics() ? toLyrics(upperItem) : nullptr;
3088 7 : const Lyrics* lowerLyrics = lowerItem->isLyrics() ? toLyrics(lowerItem) : nullptr;
3089 :
3090 7 : if (upperLyrics && lowerLyrics) {
3091 : // The distance between the top-staff and bottom-staff lyrics blocks will be like between verses:
3092 0 : const double verseSpace = std::max(upperLyrics->lineSpacing(), lowerLyrics->lineSpacing())
3093 0 : * upperItem->style().styleD(Sid::lyricsLineHeight);
3094 0 : const double spaceBetweenLyrics = (lowerLyrics->yRelativeToStaff() - lowerLyrics->offset().y()) + yStaffDiff
3095 0 : - (upperLyrics->yRelativeToStaff() - upperLyrics->offset().y());
3096 0 : slack = std::min(slack, spaceBetweenLyrics - verseSpace);
3097 0 : continue;
3098 0 : }
3099 :
3100 : // If it's not lyrics, we just use the items' min distance
3101 7 : const double minDist = std::max(upperItem->absoluteFromSpatium(upperItem->minDistance()),
3102 7 : lowerItem->absoluteFromSpatium(lowerItem->minDistance()));
3103 7 : slack = std::min(slack, upper->shape.verticalClearance(lower->shape, minHorizontalClearance) - minDist);
3104 : }
3105 : }
3106 :
3107 : // Nothing to do for a group that is on one staff only, or whose halves are already as close as possible
3108 199 : return slack == DBL_MAX ? 0.0 : std::max(slack, 0.0);
3109 : }
3110 :
3111 199 : void SystemLayout::centerItemGroup(const std::vector<const CenterableItem*>& group, const System* system,
3112 : const SkylineLine& upperSkyline, const SkylineLine& lowerSkyline, double yStaffDiff,
3113 : double minHorizontalClearance)
3114 : {
3115 : // The least space each half has on either side
3116 199 : double upperHalfSpaceAbove = DBL_MAX;
3117 199 : double upperHalfSpaceBelow = DBL_MAX;
3118 199 : double lowerHalfSpaceAbove = DBL_MAX;
3119 199 : double lowerHalfSpaceBelow = DBL_MAX;
3120 :
3121 456 : for (const CenterableItem* centerableItem : group) {
3122 257 : const Shape itemShape = centerableItem->shape.adjusted(-minHorizontalClearance, 0.0, minHorizontalClearance, 0.0);
3123 257 : if (itemShape.empty()) {
3124 0 : continue;
3125 : }
3126 :
3127 257 : const bool onUpperStaff = centerableItem->onUpperGapStaff;
3128 : const SkylineLine ownSkyline
3129 257 : = (onUpperStaff ? upperSkyline : lowerSkyline).getFilteredCopy([centerableItem](const ShapeElement& shapeEl) {
3130 15380 : const EngravingItem* shapeItem = shapeEl.item();
3131 15380 : return shapeItem && (shapeItem->isAccidental() || Autoplace::itemsShouldIgnoreEachOther(centerableItem->item, shapeItem));
3132 257 : });
3133 :
3134 257 : const double minDist = centerableItem->item->absoluteFromSpatium(centerableItem->item->minDistance());
3135 257 : const double spaceAbove = (onUpperStaff ? ownSkyline : upperSkyline).verticalClearanceBelow(itemShape) - minDist;
3136 257 : const double spaceBelow = (onUpperStaff ? lowerSkyline : ownSkyline).verticalClearanceAbove(itemShape) - minDist;
3137 :
3138 257 : if (onUpperStaff) {
3139 237 : upperHalfSpaceAbove = std::min(upperHalfSpaceAbove, spaceAbove);
3140 237 : upperHalfSpaceBelow = std::min(upperHalfSpaceBelow, spaceBelow);
3141 : } else {
3142 20 : lowerHalfSpaceAbove = std::min(lowerHalfSpaceAbove, spaceAbove);
3143 20 : lowerHalfSpaceBelow = std::min(lowerHalfSpaceBelow, spaceBelow);
3144 : }
3145 257 : }
3146 :
3147 199 : double convergeDistance = gapConvergeDistance(group, yStaffDiff, minHorizontalClearance);
3148 199 : if (lowerHalfSpaceAbove != DBL_MAX && upperHalfSpaceBelow != DBL_MAX) {
3149 : /* Convergence only measures the items of the two halves, but there might be other non-centered
3150 : * items in the gap, so we cap it at the space the group actually has in the gap */
3151 7 : convergeDistance = std::min(convergeDistance, std::max(lowerHalfSpaceAbove + upperHalfSpaceBelow, 0.0));
3152 : }
3153 :
3154 : // Space left above and below the entire group once converged, i.e. how far it can move as one rigid unit
3155 199 : const double groupSpaceAbove = std::min(upperHalfSpaceAbove, lowerHalfSpaceAbove - convergeDistance);
3156 199 : const double groupSpaceBelow = std::min(upperHalfSpaceBelow, lowerHalfSpaceBelow + convergeDistance);
3157 :
3158 199 : if (groupSpaceAbove == DBL_MAX || groupSpaceBelow == DBL_MAX) {
3159 0 : return;
3160 : }
3161 :
3162 199 : const double yMove = 0.5 * (groupSpaceBelow - groupSpaceAbove);
3163 :
3164 : // Move the items
3165 456 : for (const CenterableItem* centerableItem : group) {
3166 257 : const double itemMove = yMove + convergenceMoveFor(centerableItem, convergeDistance);
3167 257 : centerableItem->item->mutldata()->moveY(itemMove);
3168 257 : updateSkylineForElement(centerableItem->item, system, itemMove);
3169 : }
3170 : }
3171 :
3172 4 : void SystemLayout::centerMMRestBetweenStaves(MMRest* mmRest, const System* system)
3173 : {
3174 4 : staff_idx_t thisIdx = mmRest->staffIdx();
3175 4 : IF_ASSERT_FAILED(thisIdx > 0) {
3176 0 : return;
3177 : }
3178 :
3179 4 : staff_idx_t prevIdx = system->prevVisibleStaff(thisIdx);
3180 4 : IF_ASSERT_FAILED(prevIdx != muse::nidx) {
3181 0 : return;
3182 : }
3183 :
3184 4 : SysStaff* thisStaff = system->staff(thisIdx);
3185 4 : SysStaff* prevStaff = system->staff(prevIdx);
3186 4 : double prevStaffHeight = system->score()->staff(prevIdx)->staffHeight(mmRest->tick());
3187 4 : double yStaffDiff = prevStaff->y() + prevStaffHeight - thisStaff->y();
3188 :
3189 4 : PointF mmRestDefaultNumberPosition = mmRest->numberPos() - PointF(0.0, mmRest->absoluteFromSpatium(mmRest->numberOffset()));
3190 4 : RectF numberBbox = mmRest->numberRect().translated(mmRestDefaultNumberPosition + mmRest->pos());
3191 4 : double yBaseLine = 0.5 * (yStaffDiff - numberBbox.height());
3192 4 : double yDiff = yBaseLine - numberBbox.top();
3193 :
3194 4 : mmRest->mutldata()->yNumberPos += yDiff;
3195 : }
3196 :
3197 26558 : void SystemLayout::MeasureState::clear()
3198 : {
3199 26558 : measure = nullptr;
3200 26558 : measureWidth = 0.0;
3201 26558 : measurePos = 0.0;
3202 26558 : elementPositions.clear();
3203 26558 : elementWidths.clear();
3204 26558 : sharedTrackMaps.clear();
3205 26558 : }
3206 :
3207 142 : void SystemLayout::MeasureState::restoreMeasure(LayoutContext& ctx)
3208 : {
3209 142 : bool mapChanged = false;
3210 142 : Fraction tick = measure->tick();
3211 142 : for (auto& [sharedPart, sharedTrackMap] : sharedTrackMaps) {
3212 0 : if (sharedPart->trackMapAtTick(tick) != sharedTrackMap) {
3213 0 : sharedPart->setTrackMapAtTick(sharedTrackMap, tick);
3214 0 : mapChanged = true;
3215 : }
3216 : }
3217 142 : if (mapChanged) {
3218 0 : StaveSharingLayout::updateNotationWithoutRecomputingTrackMap(measure, ctx);
3219 : }
3220 :
3221 142 : measure->mutldata()->setPosX(measurePos);
3222 142 : measure->setWidth(measureWidth);
3223 882 : for (auto pair : elementPositions) {
3224 740 : pair.first->setPos(pair.second);
3225 : }
3226 880 : for (auto pair : elementWidths) {
3227 738 : pair.first->setWidth(pair.second);
3228 : }
3229 142 : }
|