1333 lines
51 KiB
C++
1333 lines
51 KiB
C++
/*
|
||
* SPDX-License-Identifier: GPL-3.0-only
|
||
* MuseScore-CLA-applies
|
||
*
|
||
* MuseScore
|
||
* Music Composition & Notation
|
||
*
|
||
* Copyright (C) 2021 MuseScore BVBA and others
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License version 3 as
|
||
* published by the Free Software Foundation.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
#include "layoutsystem.h"
|
||
|
||
#include "libmscore/factory.h"
|
||
#include "libmscore/barline.h"
|
||
#include "libmscore/box.h"
|
||
#include "libmscore/chord.h"
|
||
#include "libmscore/dynamic.h"
|
||
#include "libmscore/fingering.h"
|
||
#include "libmscore/measure.h"
|
||
#include "libmscore/measurenumber.h"
|
||
#include "libmscore/mmrestrange.h"
|
||
#include "libmscore/part.h"
|
||
#include "libmscore/score.h"
|
||
#include "libmscore/staff.h"
|
||
#include "libmscore/stafflines.h"
|
||
#include "libmscore/tie.h"
|
||
#include "libmscore/tremolo.h"
|
||
#include "libmscore/tuplet.h"
|
||
#include "libmscore/volta.h"
|
||
#include "libmscore/system.h"
|
||
#include "libmscore/slur.h"
|
||
|
||
#include "layoutbeams.h"
|
||
#include "layoutharmonies.h"
|
||
#include "layoutlyrics.h"
|
||
#include "layoutmeasure.h"
|
||
#include "layouttuplets.h"
|
||
|
||
#include "log.h"
|
||
|
||
using namespace mu::engraving;
|
||
using namespace Ms;
|
||
|
||
//---------------------------------------------------------
|
||
// collectSystem
|
||
//---------------------------------------------------------
|
||
|
||
System* LayoutSystem::collectSystem(const LayoutOptions& options, LayoutContext& ctx, Ms::Score* score)
|
||
{
|
||
TRACEFUNC;
|
||
|
||
if (!ctx.curMeasure) {
|
||
return nullptr;
|
||
}
|
||
|
||
const MeasureBase* measure = score->systems().empty() ? 0 : score->systems().back()->measures().back();
|
||
if (measure) {
|
||
measure = measure->findPotentialSectionBreak();
|
||
}
|
||
|
||
if (measure) {
|
||
const LayoutBreak* layoutBreak = measure->sectionBreakElement();
|
||
ctx.firstSystem = measure->sectionBreak() && !options.isMode(LayoutMode::FLOAT);
|
||
ctx.firstSystemIndent = ctx.firstSystem && options.firstSystemIndent && layoutBreak->firstSystemIndentation();
|
||
ctx.startWithLongNames = ctx.firstSystem && layoutBreak->startWithLongNames();
|
||
}
|
||
|
||
System* system = getNextSystem(ctx);
|
||
Fraction lcmTick = ctx.curMeasure->tick();
|
||
system->setInstrumentNames(ctx, ctx.startWithLongNames, lcmTick);
|
||
|
||
qreal curSysWidth = 0;
|
||
qreal layoutSystemMinWidth = 0;
|
||
bool firstMeasure = true;
|
||
bool createHeader = false;
|
||
qreal systemWidth = score->styleD(Sid::pagePrintableWidth) * DPI;
|
||
system->setWidth(systemWidth);
|
||
|
||
// save state of measure
|
||
bool curHeader = ctx.curMeasure->header();
|
||
bool curTrailer = ctx.curMeasure->trailer();
|
||
MeasureBase* breakMeasure = nullptr;
|
||
|
||
Fraction minTicks = Fraction::max(); // Initializing this variable at an arbitrary high value.
|
||
// In principle, it just needs to be longer than any possible note.
|
||
Fraction prevMinTicks = Fraction(1, 1);
|
||
bool changeMinSysTicks = false;
|
||
qreal oldStretch = 1;
|
||
|
||
while (ctx.curMeasure) { // collect measure for system
|
||
System* oldSystem = ctx.curMeasure->system();
|
||
system->appendMeasure(ctx.curMeasure);
|
||
|
||
qreal ww = 0; // width of current measure
|
||
|
||
// After appending a new measure, the shortest note in the system may change, in which case
|
||
// we need to recompute the layout of the previous measures. When updating the width of these
|
||
// measures, curSysWidth must be updated accordingly.
|
||
if (ctx.curMeasure->isMeasure()) {
|
||
if (toMeasure(ctx.curMeasure)->shortestChordRest() < minTicks) {
|
||
prevMinTicks = minTicks; // We save the previous value in case we need to restore it (see later)
|
||
minTicks = toMeasure(ctx.curMeasure)->shortestChordRest();
|
||
changeMinSysTicks = true;
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (mb == ctx.curMeasure) {
|
||
break; // Cause I want to change only previous measures, not current one
|
||
}
|
||
if (mb->isMeasure()) {
|
||
qreal prevWidth = toMeasure(mb)->width();
|
||
toMeasure(mb)->computeWidth(minTicks, 1);
|
||
qreal newWidth = toMeasure(mb)->width();
|
||
curSysWidth += newWidth - prevWidth;
|
||
}
|
||
}
|
||
} else {
|
||
changeMinSysTicks = false;
|
||
}
|
||
}
|
||
|
||
if (ctx.curMeasure->isMeasure()) {
|
||
Measure* m = toMeasure(ctx.curMeasure);
|
||
if (firstMeasure) {
|
||
layoutSystemMinWidth = curSysWidth;
|
||
system->layoutSystem(ctx, curSysWidth, ctx.firstSystem, ctx.firstSystemIndent);
|
||
curSysWidth += system->leftMargin();
|
||
if (m->repeatStart()) {
|
||
Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0, 1));
|
||
if (!s->enabled()) {
|
||
s->setEnabled(true);
|
||
}
|
||
}
|
||
m->addSystemHeader(ctx.firstSystem);
|
||
firstMeasure = false;
|
||
createHeader = false;
|
||
} else {
|
||
if (createHeader) {
|
||
m->addSystemHeader(false);
|
||
createHeader = false;
|
||
} else if (m->header()) {
|
||
m->removeSystemHeader();
|
||
}
|
||
}
|
||
|
||
m->createEndBarLines(true);
|
||
// measures with nobreak cannot end a system
|
||
// thus they will not contain a trailer
|
||
if (m->noBreak()) {
|
||
m->removeSystemTrailer();
|
||
} else {
|
||
m->addSystemTrailer(m->nextMeasure());
|
||
}
|
||
m->computeWidth(minTicks, 1);
|
||
ww = m->width();
|
||
} else if (ctx.curMeasure->isHBox()) {
|
||
ctx.curMeasure->computeMinWidth();
|
||
ww = ctx.curMeasure->width();
|
||
createHeader = toHBox(ctx.curMeasure)->createSystemHeader();
|
||
} else {
|
||
// vbox:
|
||
LayoutMeasure::getNextMeasure(options, ctx);
|
||
system->layout2(ctx); // compute staff distances
|
||
return system;
|
||
}
|
||
|
||
// check if lc.curMeasure fits, remove if not
|
||
// collect at least one measure and the break
|
||
static constexpr double squeezability = 0.3; // We may consider exposing in Style settings (M.S.)
|
||
double acceptanceRange = squeezability * system->squeezableSpace();
|
||
bool doBreak = (system->measures().size() > 1) && ((curSysWidth + ww) > systemWidth + acceptanceRange);
|
||
/* acceptanceRange allows some systems to be initially slightly larger than the margins and be
|
||
* justified by squeezing instead of stretching. Allows to make much better choices of how many
|
||
* measures to fit per system. */
|
||
if (doBreak) {
|
||
breakMeasure = ctx.curMeasure;
|
||
system->removeLastMeasure();
|
||
ctx.curMeasure->setParent(oldSystem);
|
||
while (ctx.prevMeasure && ctx.prevMeasure->noBreak() && system->measures().size() > 1) {
|
||
// remove however many measures are grouped with nobreak, working backwards
|
||
// but if too many are grouped, stop before we get 0 measures left on system
|
||
// TODO: intelligently break group into smaller groups instead
|
||
ctx.tick -= ctx.curMeasure->ticks();
|
||
ctx.measureNo = ctx.prevMeasure->no();
|
||
|
||
ctx.nextMeasure = ctx.curMeasure;
|
||
ctx.curMeasure = ctx.prevMeasure;
|
||
ctx.prevMeasure = ctx.curMeasure->prevMeasure();
|
||
|
||
curSysWidth -= system->lastMeasure()->width();
|
||
system->removeLastMeasure();
|
||
ctx.curMeasure->setParent(oldSystem);
|
||
}
|
||
// If the last appended measure caused a re-layout of the previous measures, now that we are
|
||
// removing it we need to re-layout the previous measures again.
|
||
if (changeMinSysTicks) {
|
||
minTicks = prevMinTicks; // If the last measure caused it to change, now we need to restore it!
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (mb->isMeasure()) {
|
||
qreal prevWidth = toMeasure(mb)->width();
|
||
toMeasure(mb)->computeWidth(minTicks, 1);
|
||
qreal newWidth = toMeasure(mb)->width();
|
||
curSysWidth += newWidth - prevWidth;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (ctx.prevMeasure && ctx.prevMeasure->isMeasure() && ctx.prevMeasure->system() == system) {
|
||
//
|
||
// now we know that the previous measure is not the last
|
||
// measure in the system and we finally can create the end barline for it
|
||
|
||
Measure* m = toMeasure(ctx.prevMeasure);
|
||
// TODO: if lc.curMeasure is a frame, removing the trailer may be premature
|
||
// but merely skipping this code isn't good enough,
|
||
// we need to find the right time to re-enable the trailer,
|
||
// since it seems to be disabled somewhere else
|
||
if (m->trailer()) {
|
||
qreal ow = m->width();
|
||
m->removeSystemTrailer();
|
||
curSysWidth += m->width() - ow;
|
||
}
|
||
// if the prev measure is an end repeat and the cur measure
|
||
// is an repeat, the createEndBarLines() created an start-end repeat barline
|
||
// and we can remove the start repeat barline of the current barline
|
||
|
||
if (ctx.curMeasure->isMeasure()) {
|
||
Measure* m1 = toMeasure(ctx.curMeasure);
|
||
if (m1->repeatStart()) {
|
||
Segment* s = m1->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0, 1));
|
||
if (!s->enabled()) {
|
||
s->setEnabled(true);
|
||
m1->computeWidth(minTicks, 1);
|
||
ww = m1->width();
|
||
}
|
||
}
|
||
}
|
||
// TODO: we actually still don't know for sure
|
||
// if this will be the last true measure of the system or not
|
||
// since the lc.curMeasure may be a frame
|
||
// but at this point we have no choice but to assume it isn't
|
||
// since we don't know yet if another true measure will fit
|
||
// worst that happens is we don't get the automatic double bar before a courtesy key signature
|
||
curSysWidth += m->createEndBarLines(false); // create final barLine
|
||
}
|
||
|
||
MeasureBase* mb = ctx.curMeasure;
|
||
bool lineBreak = false;
|
||
switch (options.mode) {
|
||
case LayoutMode::PAGE:
|
||
case LayoutMode::SYSTEM:
|
||
lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak();
|
||
break;
|
||
case LayoutMode::FLOAT:
|
||
case LayoutMode::LINE:
|
||
case LayoutMode::HORIZONTAL_FIXED:
|
||
lineBreak = false;
|
||
break;
|
||
}
|
||
|
||
// preserve state of next measure (which is about to become current measure)
|
||
if (ctx.nextMeasure) {
|
||
MeasureBase* nmb = ctx.nextMeasure;
|
||
if (nmb->isMeasure() && score->styleB(Sid::createMultiMeasureRests)) {
|
||
Measure* nm = toMeasure(nmb);
|
||
if (nm->hasMMRest()) {
|
||
nmb = nm->mmRest();
|
||
}
|
||
}
|
||
if (nmb->isMeasure()) {
|
||
oldStretch = toMeasure(nmb)->layoutStretch();
|
||
}
|
||
if (!ctx.curMeasure->noBreak()) {
|
||
// current measure is not a nobreak,
|
||
// so next measure could possibly start a system
|
||
curHeader = nmb->header();
|
||
}
|
||
if (!nmb->noBreak()) {
|
||
// next measure is not a nobreak
|
||
// so it could possibly end a system
|
||
curTrailer = nmb->trailer();
|
||
}
|
||
}
|
||
|
||
LayoutMeasure::getNextMeasure(options, ctx);
|
||
|
||
curSysWidth += ww;
|
||
|
||
// ElementType nt = lc.curMeasure ? lc.curMeasure->type() : ElementType::INVALID;
|
||
mb = ctx.curMeasure;
|
||
bool tooWide = false; // curSysWidth + minMeasureWidth > systemWidth; // TODO: noBreak
|
||
if (lineBreak || !mb || mb->isVBox() || mb->isTBox() || mb->isFBox() || tooWide) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (ctx.endTick < ctx.prevMeasure->tick()) {
|
||
// we've processed the entire range
|
||
// but we need to continue layout until we reach a system whose last measure is the same as previous layout
|
||
if (ctx.prevMeasure == ctx.systemOldMeasure) {
|
||
// this system ends in the same place as the previous layout
|
||
// ok to stop
|
||
if (ctx.curMeasure && ctx.curMeasure->isMeasure()) {
|
||
// we may have previously processed first measure(s) of next system
|
||
// so now we must restore to original state
|
||
Measure* m = toMeasure(ctx.curMeasure);
|
||
if (m->repeatStart()) {
|
||
Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0, 1));
|
||
if (!s->enabled()) {
|
||
s->setEnabled(true);
|
||
}
|
||
}
|
||
const MeasureBase* pbmb = ctx.prevMeasure->findPotentialSectionBreak();
|
||
bool localFirstSystem = pbmb->sectionBreak() && !options.isMode(LayoutMode::FLOAT);
|
||
MeasureBase* nm = breakMeasure ? breakMeasure : m;
|
||
if (curHeader) {
|
||
m->addSystemHeader(localFirstSystem);
|
||
} else {
|
||
m->removeSystemHeader();
|
||
}
|
||
for (;;) {
|
||
// TODO: what if the nobreak group takes the entire system - is this correct?
|
||
if (curTrailer && !m->noBreak()) {
|
||
m->addSystemTrailer(m->nextMeasure());
|
||
} else {
|
||
m->removeSystemTrailer();
|
||
}
|
||
m->computeWidth(m->system()->minSysTicks(), oldStretch);
|
||
m->layoutMeasureElements();
|
||
LayoutBeams::restoreBeams(m);
|
||
if (m == nm || !m->noBreak()) {
|
||
break;
|
||
}
|
||
m = m->nextMeasure();
|
||
}
|
||
}
|
||
ctx.rangeDone = true;
|
||
}
|
||
}
|
||
|
||
//
|
||
// now we have a complete set of measures for this system
|
||
//
|
||
// prevMeasure is the last measure in the system
|
||
if (ctx.prevMeasure && ctx.prevMeasure->isMeasure()) {
|
||
LayoutBeams::breakCrossMeasureBeams(ctx, toMeasure(ctx.prevMeasure));
|
||
qreal w = toMeasure(ctx.prevMeasure)->createEndBarLines(true);
|
||
curSysWidth += w;
|
||
}
|
||
|
||
hideEmptyStaves(score, system, ctx.firstSystem);
|
||
// Relayout system decorations to reuse space properly for
|
||
// hidden staves' instrument names or other hidden elements.
|
||
curSysWidth -= system->leftMargin();
|
||
system->layoutSystem(ctx, layoutSystemMinWidth, ctx.firstSystem, ctx.firstSystemIndent);
|
||
curSysWidth += system->leftMargin();
|
||
|
||
//-------------------------------------------------------
|
||
// add system trailer if needed
|
||
// (cautionary time/key signatures etc)
|
||
//-------------------------------------------------------
|
||
|
||
Measure* lm = system->lastMeasure();
|
||
if (lm) {
|
||
Measure* nm = lm->nextMeasure();
|
||
if (nm) {
|
||
qreal w = lm->width();
|
||
lm->addSystemTrailer(nm);
|
||
if (lm->trailer()) {
|
||
lm->computeWidth(minTicks, 1);
|
||
}
|
||
curSysWidth += lm->width() - w;
|
||
}
|
||
}
|
||
|
||
// BRING THE WIDTH OF THE SYSTEM TO THE DESIRED VALUE
|
||
// Gradient descent method: calls the computeWidth() function with a stretch parameter
|
||
// proportional to the difference between the current length and the target length.
|
||
// After few iterations, the length will converge to the target length.
|
||
qreal newRest = systemWidth - curSysWidth;
|
||
if ((ctx.curMeasure == 0 || (lm && lm->sectionBreak()))
|
||
&& ((curSysWidth / systemWidth) <= score->styleD(Sid::lastSystemFillLimit))) {
|
||
// We do not stretch last system of a section (or the last of the piece) if curSysWidth is <= lastSystemFillLimit
|
||
newRest = 0;
|
||
}
|
||
if (MScore::noHorizontalStretch) { // Debug feature
|
||
newRest = 0;
|
||
}
|
||
qreal stretchCoeff = 1;
|
||
qreal prevWidth = 0;
|
||
int iter = 0;
|
||
double epsilon = score->spatium() * 0.05; // For reference: this is smaller than the width of a note stem
|
||
static constexpr float multiplier = 1.4f; // Empirically optimized value which allows the fastest convergence of the following algorithm.
|
||
static constexpr int maxIter = 100;
|
||
// Different systems need different numbers of iterations of the following loop to reach the target width.
|
||
// The average is less than 3 iterations, and the maximum I've ever seen (very rare) is 30-40 iterations.
|
||
// maxIter just serves as a safety exit to not get stuck in the loop in case a system can't be justified
|
||
// (which can only happen if errors are made before getting here). It's set to a very high value to make
|
||
// sure that the system really can't be justified, and it isn't just a "tricky" one needing more iterations.
|
||
while (abs(newRest) > epsilon && iter < maxIter) {
|
||
stretchCoeff *= (1 + multiplier * newRest / curSysWidth);
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (mb->isMeasure()) {
|
||
Measure* m = toMeasure(mb);
|
||
if (!(m->isWidthLocked() && stretchCoeff < m->layoutStretch())) { // It would be pointless to re-compute the layout of a measure
|
||
prevWidth = m->width(); // that is already widthLocked to a larger value.
|
||
m->computeWidth(minTicks, stretchCoeff);
|
||
curSysWidth += m->width() - prevWidth;
|
||
}
|
||
}
|
||
}
|
||
newRest = systemWidth - curSysWidth;
|
||
iter++;
|
||
}
|
||
|
||
// LAYOUT MEASURES
|
||
PointF pos;
|
||
firstMeasure = true;
|
||
bool createBrackets = false;
|
||
for (MeasureBase* mb : system->measures()) {
|
||
qreal ww = mb->width();
|
||
if (mb->isMeasure()) {
|
||
if (firstMeasure) {
|
||
pos.rx() += system->leftMargin();
|
||
firstMeasure = false;
|
||
}
|
||
mb->setPos(pos);
|
||
mb->setParent(system);
|
||
Measure* m = toMeasure(mb);
|
||
m->layoutMeasureElements();
|
||
m->layoutStaffLines();
|
||
if (createBrackets) {
|
||
system->addBrackets(ctx, toMeasure(mb));
|
||
createBrackets = false;
|
||
}
|
||
} else if (mb->isHBox()) {
|
||
mb->setPos(pos + PointF(toHBox(mb)->topGap(), 0.0));
|
||
mb->layout();
|
||
createBrackets = toHBox(mb)->createSystemHeader();
|
||
} else if (mb->isVBox()) {
|
||
mb->setPos(pos);
|
||
}
|
||
pos.rx() += ww;
|
||
}
|
||
system->setWidth(pos.x());
|
||
|
||
layoutSystemElements(options, ctx, score, system);
|
||
system->layout2(ctx); // compute staff distances
|
||
for (MeasureBase* mb : system->measures()) {
|
||
mb->layoutCrossStaff();
|
||
}
|
||
// TODO: now that the code at the top of this function does this same backwards search,
|
||
// we might be able to eliminate this block
|
||
// but, lc might be used elsewhere so we need to be careful
|
||
measure = system->measures().back();
|
||
|
||
if (measure) {
|
||
measure = measure->findPotentialSectionBreak();
|
||
}
|
||
|
||
if (measure) {
|
||
const LayoutBreak* layoutBreak = measure->sectionBreakElement();
|
||
ctx.firstSystem = measure->sectionBreak() && !options.isMode(LayoutMode::FLOAT);
|
||
ctx.firstSystemIndent = ctx.firstSystem && options.firstSystemIndent && layoutBreak->firstSystemIndentation();
|
||
ctx.startWithLongNames = ctx.firstSystem && layoutBreak->startWithLongNames();
|
||
}
|
||
|
||
return system;
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// getNextSystem
|
||
//---------------------------------------------------------
|
||
|
||
System* LayoutSystem::getNextSystem(LayoutContext& ctx)
|
||
{
|
||
Ms::Score* score = ctx.score();
|
||
bool isVBox = ctx.curMeasure->isVBox();
|
||
System* system = nullptr;
|
||
if (ctx.systemList.empty()) {
|
||
system = Factory::createSystem(score->dummy()->page());
|
||
ctx.systemOldMeasure = 0;
|
||
} else {
|
||
system = mu::takeFirst(ctx.systemList);
|
||
ctx.systemOldMeasure = system->measures().empty() ? 0 : system->measures().back();
|
||
system->clear(); // remove measures from system
|
||
}
|
||
score->systems().push_back(system);
|
||
if (!isVBox) {
|
||
size_t nstaves = score->Score::nstaves();
|
||
system->adjustStavesNumber(nstaves);
|
||
for (staff_idx_t i = 0; i < nstaves; ++i) {
|
||
system->staff(i)->setShow(score->staff(i)->show());
|
||
}
|
||
}
|
||
return system;
|
||
}
|
||
|
||
void LayoutSystem::hideEmptyStaves(Score* score, System* system, bool isFirstSystem)
|
||
{
|
||
size_t staves = score->nstaves();
|
||
staff_idx_t staffIdx = 0;
|
||
bool systemIsEmpty = true;
|
||
|
||
for (Staff* staff : qAsConst(score->staves())) {
|
||
SysStaff* ss = system->staff(staffIdx);
|
||
|
||
Staff::HideMode hideMode = staff->hideWhenEmpty();
|
||
|
||
if (hideMode == Staff::HideMode::ALWAYS
|
||
|| (score->styleB(Sid::hideEmptyStaves)
|
||
&& (staves > 1)
|
||
&& !(isFirstSystem && score->styleB(Sid::dontHideStavesInFirstSystem))
|
||
&& hideMode != Staff::HideMode::NEVER)) {
|
||
bool hideStaff = true;
|
||
for (MeasureBase* m : system->measures()) {
|
||
if (!m->isMeasure()) {
|
||
continue;
|
||
}
|
||
Measure* measure = toMeasure(m);
|
||
if (!measure->isEmpty(staffIdx)) {
|
||
hideStaff = false;
|
||
break;
|
||
}
|
||
}
|
||
// check if notes moved into this staff
|
||
Part* part = staff->part();
|
||
const size_t n = part->nstaves();
|
||
if (hideStaff && (n > 1)) {
|
||
staff_idx_t idx = part->staves().front()->idx();
|
||
for (staff_idx_t i = 0; i < n; ++i) {
|
||
staff_idx_t st = idx + i;
|
||
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (!mb->isMeasure()) {
|
||
continue;
|
||
}
|
||
Measure* m = toMeasure(mb);
|
||
if (staff->hideWhenEmpty() == Staff::HideMode::INSTRUMENT && !m->isEmpty(st)) {
|
||
hideStaff = false;
|
||
break;
|
||
}
|
||
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
|
||
for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
|
||
ChordRest* cr = s->cr(st * VOICES + voice);
|
||
if (cr == 0 || cr->isRest()) {
|
||
continue;
|
||
}
|
||
int staffMove = cr->staffMove();
|
||
if (staffIdx == st + staffMove) {
|
||
hideStaff = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (!hideStaff) {
|
||
break;
|
||
}
|
||
}
|
||
if (!hideStaff) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
ss->setShow(hideStaff ? false : staff->show());
|
||
if (ss->show()) {
|
||
systemIsEmpty = false;
|
||
}
|
||
} else if (!staff->show()) {
|
||
// TODO: OK to check this first and not bother with checking if empty?
|
||
ss->setShow(false);
|
||
} else {
|
||
systemIsEmpty = false;
|
||
ss->setShow(true);
|
||
}
|
||
|
||
++staffIdx;
|
||
}
|
||
Staff* firstVisible = nullptr;
|
||
if (systemIsEmpty) {
|
||
for (Staff* staff : qAsConst(score->staves())) {
|
||
SysStaff* ss = system->staff(staff->idx());
|
||
if (staff->showIfEmpty() && !ss->show()) {
|
||
ss->setShow(true);
|
||
systemIsEmpty = false;
|
||
} else if (!firstVisible && staff->show()) {
|
||
firstVisible = staff;
|
||
}
|
||
}
|
||
}
|
||
// don’t allow a complete empty system
|
||
if (systemIsEmpty && !score->staves().empty()) {
|
||
Staff* staff = firstVisible ? firstVisible : score->staves().front();
|
||
SysStaff* ss = system->staff(staff->idx());
|
||
ss->setShow(true);
|
||
}
|
||
}
|
||
|
||
void LayoutSystem::layoutSystemElements(const LayoutOptions& options, LayoutContext& lc, Score* score, System* system)
|
||
{
|
||
if (score->noStaves()) {
|
||
return;
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// create cr segment list to speed up computations
|
||
//-------------------------------------------------------------
|
||
|
||
std::vector<Segment*> sl;
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (!mb->isMeasure()) {
|
||
continue;
|
||
}
|
||
Measure* m = toMeasure(mb);
|
||
m->layoutMeasureNumber();
|
||
m->layoutMMRestRange();
|
||
|
||
// in continuous view, entire score is one system
|
||
// but we only need to process the range
|
||
if (options.isLinearMode() && (m->tick() < lc.startTick || m->tick() > lc.endTick)) {
|
||
continue;
|
||
}
|
||
for (Segment* s = m->first(); s; s = s->next()) {
|
||
if (s->isChordRestType() || !s->annotations().empty()) {
|
||
sl.push_back(s);
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout beams
|
||
// Needs to be done before creating skylines as stem lengths
|
||
// may change.
|
||
//-------------------------------------------------------------
|
||
|
||
for (Segment* s : sl) {
|
||
for (EngravingItem* e : s->elist()) {
|
||
if (!e || !e->isChordRest() || !score->staff(e->staffIdx())->show()) {
|
||
// the beam and its system may still be referenced when selecting all,
|
||
// even if the staff is invisible. The old system is invalid and does cause problems in #284012
|
||
if (e && e->isChordRest() && !score->staff(e->staffIdx())->show() && toChordRest(e)->beam()) {
|
||
toChordRest(e)->beam()->resetExplicitParent();
|
||
}
|
||
continue;
|
||
}
|
||
ChordRest* cr = toChordRest(e);
|
||
|
||
// layout beam
|
||
if (LayoutBeams::isTopBeam(cr)) {
|
||
Beam* b = cr->beam();
|
||
b->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// create skylines
|
||
//-------------------------------------------------------------
|
||
|
||
for (size_t staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
|
||
SysStaff* ss = system->staff(staffIdx);
|
||
Skyline& skyline = ss->skyline();
|
||
skyline.clear();
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (!mb->isMeasure()) {
|
||
continue;
|
||
}
|
||
Measure* m = toMeasure(mb);
|
||
MeasureNumber* mno = m->noText(staffIdx);
|
||
MMRestRange* mmrr = m->mmRangeText(staffIdx);
|
||
// no need to build skyline outside of range in continuous view
|
||
if (options.isLinearMode() && (m->tick() < lc.startTick || m->tick() > lc.endTick)) {
|
||
continue;
|
||
}
|
||
if (mno && mno->addToSkyline()) {
|
||
ss->skyline().add(mno->bbox().translated(m->pos() + mno->pos()));
|
||
}
|
||
if (mmrr && mmrr->addToSkyline()) {
|
||
ss->skyline().add(mmrr->bbox().translated(m->pos() + mmrr->pos()));
|
||
}
|
||
if (m->staffLines(staffIdx)->addToSkyline()) {
|
||
ss->skyline().add(m->staffLines(staffIdx)->bbox().translated(m->pos()));
|
||
}
|
||
for (Segment& s : m->segments()) {
|
||
if (!s.enabled() || s.isTimeSigType()) { // hack: ignore time signatures
|
||
continue;
|
||
}
|
||
PointF p(s.pos() + m->pos());
|
||
if (s.segmentType()
|
||
& (SegmentType::BarLine | SegmentType::EndBarLine | SegmentType::StartRepeatBarLine | SegmentType::BeginBarLine)) {
|
||
BarLine* bl = toBarLine(s.element(staffIdx * VOICES));
|
||
if (bl && bl->addToSkyline()) {
|
||
RectF r = bl->layoutRect();
|
||
skyline.add(r.translated(bl->pos() + p));
|
||
}
|
||
} else {
|
||
track_idx_t strack = staffIdx * VOICES;
|
||
track_idx_t etrack = strack + VOICES;
|
||
for (EngravingItem* e : s.elist()) {
|
||
if (!e) {
|
||
continue;
|
||
}
|
||
track_idx_t effectiveTrack = e->vStaffIdx() * VOICES + e->voice();
|
||
if (effectiveTrack < strack || effectiveTrack >= etrack) {
|
||
continue;
|
||
}
|
||
|
||
// clear layout for chord-based fingerings
|
||
// do this before adding chord to skyline
|
||
if (e->isChord()) {
|
||
Chord* c = toChord(e);
|
||
std::list<Note*> notes;
|
||
for (auto gc : c->graceNotes()) {
|
||
for (auto n : gc->notes()) {
|
||
notes.push_back(n);
|
||
}
|
||
}
|
||
for (auto n : c->notes()) {
|
||
notes.push_back(n);
|
||
}
|
||
for (Note* note : notes) {
|
||
for (EngravingItem* en : note->el()) {
|
||
if (en->isFingering()) {
|
||
Fingering* f = toFingering(en);
|
||
if (f->layoutType() == ElementType::CHORD) {
|
||
f->setPos(PointF());
|
||
f->setbbox(RectF());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// add element to skyline
|
||
if (e->addToSkyline()) {
|
||
skyline.add(e->shape().translated(e->pos() + p));
|
||
}
|
||
|
||
// add tremolo to skyline
|
||
if (e->isChord() && toChord(e)->tremolo()) {
|
||
Tremolo* t = toChord(e)->tremolo();
|
||
Chord* c1 = t->chord1();
|
||
Chord* c2 = t->chord2();
|
||
if (!t->twoNotes() || (c1 && !c1->staffMove() && c2 && !c2->staffMove())) {
|
||
if (t->chord() == e && t->addToSkyline()) {
|
||
skyline.add(t->shape().translated(t->pos() + e->pos() + p));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout fingerings, add beams to skylines
|
||
//-------------------------------------------------------------
|
||
|
||
for (Segment* s : sl) {
|
||
std::set<staff_idx_t> recreateShapes;
|
||
for (EngravingItem* e : s->elist()) {
|
||
if (!e || !e->isChordRest() || !score->staff(e->staffIdx())->show()) {
|
||
continue;
|
||
}
|
||
ChordRest* cr = toChordRest(e);
|
||
|
||
// add beam to skyline
|
||
if (LayoutBeams::isTopBeam(cr)) {
|
||
Beam* b = cr->beam();
|
||
b->addSkyline(system->staff(b->staffIdx())->skyline());
|
||
}
|
||
|
||
// layout chord-based fingerings
|
||
if (e->isChord()) {
|
||
Chord* c = toChord(e);
|
||
std::list<Note*> notes;
|
||
for (auto gc : c->graceNotes()) {
|
||
for (auto n : gc->notes()) {
|
||
notes.push_back(n);
|
||
}
|
||
}
|
||
for (auto n : c->notes()) {
|
||
notes.push_back(n);
|
||
}
|
||
std::list<Fingering*> fingerings;
|
||
for (Note* note : notes) {
|
||
for (EngravingItem* el : note->el()) {
|
||
if (el->isFingering()) {
|
||
Fingering* f = toFingering(el);
|
||
if (f->layoutType() == ElementType::CHORD) {
|
||
if (f->placeAbove()) {
|
||
fingerings.push_back(f);
|
||
} else {
|
||
fingerings.push_front(f);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for (Fingering* f : fingerings) {
|
||
f->layout();
|
||
if (f->addToSkyline()) {
|
||
Note* n = f->note();
|
||
RectF r = f->bbox().translated(f->pos() + n->pos() + n->chord()->pos() + s->pos() + s->measure()->pos());
|
||
system->staff(f->note()->chord()->vStaffIdx())->skyline().add(r);
|
||
}
|
||
recreateShapes.insert(f->staffIdx());
|
||
}
|
||
}
|
||
}
|
||
for (staff_idx_t staffIdx : recreateShapes) {
|
||
s->createShape(staffIdx);
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout articulations
|
||
//-------------------------------------------------------------
|
||
|
||
for (Segment* s : sl) {
|
||
for (EngravingItem* e : s->elist()) {
|
||
if (!e || !e->isChordRest() || !score->staff(e->staffIdx())->show()) {
|
||
continue;
|
||
}
|
||
ChordRest* cr = toChordRest(e);
|
||
// articulations
|
||
if (cr->isChord()) {
|
||
Chord* c = toChord(cr);
|
||
c->layoutArticulations();
|
||
c->layoutArticulations2();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout tuplets
|
||
//-------------------------------------------------------------
|
||
|
||
std::map<track_idx_t, Fraction> skipTo;
|
||
for (Segment* s : sl) {
|
||
for (EngravingItem* e : s->elist()) {
|
||
if (!e || !e->isChordRest() || !score->staff(e->staffIdx())->show()) {
|
||
continue;
|
||
}
|
||
track_idx_t track = e->track();
|
||
if (skipTo.count(track) && e->tick() < skipTo[track]) {
|
||
continue; // don't lay out tuplets for this voice that have already been done
|
||
}
|
||
// find the top tuplet for this segment
|
||
DurationElement* de = toChordRest(e);
|
||
if (!de->tuplet()) {
|
||
continue;
|
||
}
|
||
while (de->tuplet()) {
|
||
de = de->tuplet();
|
||
}
|
||
LayoutTuplets::layout(de); // recursively lay out all tuplets covered by this tuplet
|
||
|
||
// don't layout any tuplets covered by this top level tuplet for this voice--
|
||
// they've already been laid out by layoutTuplet().
|
||
skipTo[track] = de->tick() + de->ticks();
|
||
break;
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// Drumline sticking
|
||
//-------------------------------------------------------------
|
||
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isSticking()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout slurs
|
||
//-------------------------------------------------------------
|
||
|
||
bool useRange = false; // TODO: lineMode();
|
||
Fraction stick = useRange ? lc.startTick : system->measures().front()->tick();
|
||
Fraction etick = useRange ? lc.endTick : system->measures().back()->endTick();
|
||
auto spanners = score->spannerMap().findOverlapping(stick.ticks(), etick.ticks());
|
||
|
||
// ties
|
||
doLayoutTies(system, sl, stick, etick);
|
||
|
||
// slurs
|
||
std::vector<Spanner*> spanner;
|
||
for (auto interval : spanners) {
|
||
Spanner* sp = interval.value;
|
||
sp->computeStartElement();
|
||
sp->computeEndElement();
|
||
lc.processedSpanners.insert(sp);
|
||
if (sp->tick() < etick && sp->tick2() >= stick) {
|
||
if (sp->isSlur()) {
|
||
// skip cross-staff slurs
|
||
ChordRest* scr = sp->startCR();
|
||
ChordRest* ecr = sp->endCR();
|
||
staff_idx_t idx = sp->vStaffIdx();
|
||
if (scr && ecr && (scr->vStaffIdx() != idx || ecr->vStaffIdx() != idx)) {
|
||
continue;
|
||
}
|
||
spanner.push_back(sp);
|
||
}
|
||
}
|
||
}
|
||
processLines(system, spanner, false);
|
||
for (auto s : spanner) {
|
||
Slur* slur = toSlur(s);
|
||
ChordRest* scr = s->startCR();
|
||
ChordRest* ecr = s->endCR();
|
||
if (scr && scr->isChord()) {
|
||
toChord(scr)->layoutArticulations3(slur);
|
||
}
|
||
if (ecr && ecr->isChord()) {
|
||
toChord(ecr)->layoutArticulations3(slur);
|
||
}
|
||
}
|
||
|
||
std::vector<Dynamic*> dynamics;
|
||
for (Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isDynamic()) {
|
||
Dynamic* d = toDynamic(e);
|
||
d->layout();
|
||
|
||
if (d->autoplace()) {
|
||
d->autoplaceSegmentElement(false);
|
||
dynamics.push_back(d);
|
||
}
|
||
} else if (e->isFiguredBass()) {
|
||
e->layout();
|
||
e->autoplaceSegmentElement();
|
||
}
|
||
}
|
||
}
|
||
|
||
// add dynamics shape to skyline
|
||
|
||
for (Dynamic* d : dynamics) {
|
||
if (!d->addToSkyline()) {
|
||
continue;
|
||
}
|
||
staff_idx_t si = d->staffIdx();
|
||
Segment* s = d->segment();
|
||
Measure* m = s->measure();
|
||
system->staff(si)->skyline().add(d->shape().translated(d->pos() + s->pos() + m->pos()));
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout SpannerSegments for current system
|
||
// ottavas, pedals, voltas are collected here, but laid out later
|
||
//-------------------------------------------------------------
|
||
|
||
spanner.clear();
|
||
std::vector<Spanner*> hairpins;
|
||
std::vector<Spanner*> ottavas;
|
||
std::vector<Spanner*> pedal;
|
||
std::vector<Spanner*> voltas;
|
||
|
||
for (auto interval : spanners) {
|
||
Spanner* sp = interval.value;
|
||
if (sp->tick() < etick && sp->tick2() > stick) {
|
||
if (sp->isOttava()) {
|
||
if (sp->staff()->staffType()->isTabStaff()) {
|
||
continue;
|
||
}
|
||
|
||
ottavas.push_back(sp);
|
||
} else if (sp->isPedal()) {
|
||
pedal.push_back(sp);
|
||
} else if (sp->isVolta()) {
|
||
voltas.push_back(sp);
|
||
} else if (sp->isHairpin()) {
|
||
hairpins.push_back(sp);
|
||
} else if (!sp->isSlur() && !sp->isVolta()) { // slurs are already
|
||
spanner.push_back(sp);
|
||
}
|
||
}
|
||
}
|
||
processLines(system, hairpins, false);
|
||
processLines(system, spanner, false);
|
||
|
||
//-------------------------------------------------------------
|
||
// Fermata, TremoloBar
|
||
//-------------------------------------------------------------
|
||
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isFermata() || e->isTremoloBar()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// Ottava, Pedal
|
||
//-------------------------------------------------------------
|
||
|
||
processLines(system, ottavas, false);
|
||
processLines(system, pedal, true);
|
||
|
||
//-------------------------------------------------------------
|
||
// Lyric
|
||
//-------------------------------------------------------------
|
||
|
||
LayoutLyrics::layoutLyrics(options, score, system);
|
||
|
||
// here are lyrics dashes and melisma
|
||
for (Spanner* sp : score->unmanagedSpanners()) {
|
||
if (sp->tick() >= etick || sp->tick2() <= stick) {
|
||
continue;
|
||
}
|
||
sp->layoutSystem(system);
|
||
}
|
||
|
||
//
|
||
// We need to known if we have FretDiagrams in the system to decide when to layout the Harmonies
|
||
//
|
||
|
||
bool hasFretDiagram = false;
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isFretDiagram()) {
|
||
hasFretDiagram = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (hasFretDiagram) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// Harmony, 1st place
|
||
// If we have FretDiagrams, we want the Harmony above this and
|
||
// above the volta, therefore we delay the layout.
|
||
//-------------------------------------------------------------
|
||
|
||
if (!hasFretDiagram) {
|
||
LayoutHarmonies::layoutHarmonies(sl);
|
||
LayoutHarmonies::alignHarmonies(system, sl, true, options.maxFretShiftAbove, options.maxFretShiftBelow);
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// StaffText, InstrumentChange
|
||
//-------------------------------------------------------------
|
||
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isPlayTechAnnotation() || e->isStaffText() || e->isSystemText() || e->isInstrumentChange()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// Jump, Marker
|
||
//-------------------------------------------------------------
|
||
|
||
for (MeasureBase* mb : system->measures()) {
|
||
if (!mb->isMeasure()) {
|
||
continue;
|
||
}
|
||
Measure* m = toMeasure(mb);
|
||
for (EngravingItem* e : m->el()) {
|
||
if (e->isJump() || e->isMarker()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// TempoText
|
||
//-------------------------------------------------------------
|
||
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isTempoText()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// layout Voltas for current system
|
||
//-------------------------------------------------------------
|
||
|
||
processLines(system, voltas, false);
|
||
|
||
//
|
||
// vertical align volta segments
|
||
//
|
||
for (staff_idx_t staffIdx = 0; staffIdx < score->nstaves(); ++staffIdx) {
|
||
std::vector<SpannerSegment*> voltaSegments;
|
||
for (SpannerSegment* ss : system->spannerSegments()) {
|
||
if (ss->isVoltaSegment() && ss->staffIdx() == staffIdx) {
|
||
voltaSegments.push_back(ss);
|
||
}
|
||
}
|
||
while (!voltaSegments.empty()) {
|
||
// we assume voltas are sorted left to right (by tick values)
|
||
qreal y = 0;
|
||
int idx = 0;
|
||
Volta* prevVolta = 0;
|
||
for (SpannerSegment* ss : voltaSegments) {
|
||
Volta* volta = toVolta(ss->spanner());
|
||
if (prevVolta && prevVolta != volta) {
|
||
// check if volta is adjacent to prevVolta
|
||
if (prevVolta->tick2() != volta->tick()) {
|
||
break;
|
||
}
|
||
}
|
||
y = qMin(y, ss->rypos());
|
||
++idx;
|
||
prevVolta = volta;
|
||
}
|
||
|
||
for (int i = 0; i < idx; ++i) {
|
||
SpannerSegment* ss = voltaSegments[i];
|
||
if (ss->autoplace() && ss->isStyled(Pid::OFFSET)) {
|
||
ss->rypos() = y;
|
||
}
|
||
if (ss->addToSkyline()) {
|
||
system->staff(staffIdx)->skyline().add(ss->shape().translated(ss->pos()));
|
||
}
|
||
}
|
||
|
||
voltaSegments.erase(voltaSegments.begin(), voltaSegments.begin() + idx);
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// FretDiagram
|
||
//-------------------------------------------------------------
|
||
|
||
if (hasFretDiagram) {
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isFretDiagram()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// Harmony, 2nd place
|
||
// We have FretDiagrams, we want the Harmony above this and
|
||
// above the volta.
|
||
//-------------------------------------------------------------
|
||
|
||
LayoutHarmonies::layoutHarmonies(sl);
|
||
LayoutHarmonies::alignHarmonies(system, sl, false, options.maxFretShiftAbove, options.maxFretShiftBelow);
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// RehearsalMark
|
||
//-------------------------------------------------------------
|
||
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isRehearsalMark()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// Image
|
||
//-------------------------------------------------------------
|
||
|
||
for (const Segment* s : sl) {
|
||
for (EngravingItem* e : s->annotations()) {
|
||
if (e->isImage()) {
|
||
e->layout();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void LayoutSystem::doLayoutTies(System* system, std::vector<Ms::Segment*> sl, const Fraction& stick, const Fraction& etick)
|
||
{
|
||
Q_UNUSED(etick);
|
||
|
||
for (Segment* s : sl) {
|
||
for (EngravingItem* e : s->elist()) {
|
||
if (!e || !e->isChord()) {
|
||
continue;
|
||
}
|
||
Chord* c = toChord(e);
|
||
for (Chord* ch : c->graceNotes()) {
|
||
layoutTies(ch, system, stick);
|
||
}
|
||
layoutTies(c, system, stick);
|
||
}
|
||
}
|
||
}
|
||
|
||
void LayoutSystem::processLines(System* system, std::vector<Spanner*> lines, bool align)
|
||
{
|
||
std::vector<SpannerSegment*> segments;
|
||
for (Spanner* sp : lines) {
|
||
SpannerSegment* ss = sp->layoutSystem(system); // create/layout spanner segment for this system
|
||
if (ss->autoplace()) {
|
||
segments.push_back(ss);
|
||
}
|
||
}
|
||
|
||
if (align && segments.size() > 1) {
|
||
const size_t nstaves = system->staves().size();
|
||
constexpr qreal minY = -1000000.0;
|
||
const qreal defaultY = segments[0]->rypos();
|
||
std::vector<qreal> y(nstaves, minY);
|
||
|
||
for (SpannerSegment* ss : segments) {
|
||
if (ss->visible()) {
|
||
qreal& staffY = y[ss->staffIdx()];
|
||
staffY = qMax(staffY, ss->rypos());
|
||
}
|
||
}
|
||
for (SpannerSegment* ss : segments) {
|
||
if (!ss->isStyled(Pid::OFFSET)) {
|
||
continue;
|
||
}
|
||
const qreal staffY = y[ss->staffIdx()];
|
||
if (staffY > minY) {
|
||
ss->rypos() = staffY;
|
||
} else {
|
||
ss->rypos() = defaultY;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (segments.size() > 1) {
|
||
//how far vertically an endpoint should adjust to avoid other slur endpoints:
|
||
const qreal slurCollisionVertOffset = 0.65 * system->spatium();
|
||
const qreal fuzzyHorizCompare = 0.1 * system->spatium();
|
||
auto compare = [fuzzyHorizCompare](qreal x1, qreal x2) { return std::abs(x1 - x2) < fuzzyHorizCompare; };
|
||
for (SpannerSegment* seg1 : segments) {
|
||
if (!seg1->isSlurSegment()) {
|
||
continue;
|
||
}
|
||
SlurSegment* slur1 = toSlurSegment(seg1);
|
||
for (SpannerSegment* seg2 : segments) {
|
||
if (!seg2->isSlurTieSegment()) {
|
||
continue;
|
||
}
|
||
|
||
SlurTieSegment* slurTie2 = toSlurTieSegment(seg2);
|
||
|
||
// slurs don't collide with themselves or slurs on other staves
|
||
if (slur1 == slurTie2 || slur1->vStaffIdx() != slurTie2->vStaffIdx()) {
|
||
continue;
|
||
}
|
||
// slurs which don't overlap don't need to be checked
|
||
if (slur1->ups(Grip::END).p.x() < slurTie2->ups(Grip::START).p.x()
|
||
|| slurTie2->ups(Grip::END).p.x() < slur1->ups(Grip::START).p.x()
|
||
|| slur1->slur()->up() != slurTie2->slurTie()->up()) {
|
||
continue;
|
||
}
|
||
// START POINT
|
||
if (compare(slur1->ups(Grip::START).p.x(), slurTie2->ups(Grip::START).p.x())) {
|
||
if (slur1->ups(Grip::END).p.x() > slurTie2->ups(Grip::END).p.x() || slurTie2->isTieSegment()) {
|
||
// slur1 is the "outside" slur
|
||
slur1->ups(Grip::START).p.ry() += slurCollisionVertOffset * (slur1->slur()->up() ? -1 : 1);
|
||
slur1->computeBezier();
|
||
}
|
||
}
|
||
// END POINT
|
||
if (compare(slur1->ups(Grip::END).p.x(), slurTie2->ups(Grip::END).p.x())) {
|
||
// slurs have the same endpoint
|
||
if (slur1->ups(Grip::START).p.x() < slurTie2->ups(Grip::START).p.x() || slurTie2->isTieSegment()) {
|
||
// slur1 is the "outside" slur
|
||
slur1->ups(Grip::END).p.ry() += slurCollisionVertOffset * (slur1->slur()->up() ? -1 : 1);
|
||
slur1->computeBezier();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// add shapes to skyline
|
||
//
|
||
for (SpannerSegment* ss : segments) {
|
||
if (ss->addToSkyline()) {
|
||
system->staff(ss->staffIdx())->skyline().add(ss->shape().translated(ss->pos()));
|
||
}
|
||
}
|
||
}
|
||
|
||
void LayoutSystem::layoutTies(Chord* ch, System* system, const Fraction& stick)
|
||
{
|
||
SysStaff* staff = system->staff(ch->staffIdx());
|
||
if (!staff->show()) {
|
||
return;
|
||
}
|
||
for (Note* note : ch->notes()) {
|
||
Tie* t = note->tieFor();
|
||
if (t) {
|
||
TieSegment* ts = t->layoutFor(system);
|
||
if (ts && ts->addToSkyline()) {
|
||
staff->skyline().add(ts->shape().translated(ts->pos()));
|
||
}
|
||
}
|
||
t = note->tieBack();
|
||
if (t) {
|
||
if (t->startNote()->tick() < stick) {
|
||
TieSegment* ts = t->layoutBack(system);
|
||
if (ts && ts->addToSkyline()) {
|
||
staff->skyline().add(ts->shape().translated(ts->pos()));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|