Skip to content

Fix autoplace of fret diagrams pushed onto next system #29132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/engraving/dom/fret.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ class FretDiagram final : public EngravingItem
bool setProperty(Pid propertyId, const PropertyValue&) override;
PropertyValue propertyDefault(Pid) const override;

virtual void setVisible(bool f);
void setVisible(bool f) override;

void setTrack(track_idx_t val) override;

Expand Down
16 changes: 8 additions & 8 deletions src/engraving/dom/harmony.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,22 @@ struct HarmonyRenderItem {

struct ChordSymbolParen : HarmonyRenderItem {
ChordSymbolParen(Parenthesis* p, bool hAlign, double _x, double _y)
: HarmonyRenderItem(hAlign, _x, _y), paren(p) {}
~ChordSymbolParen() { delete paren; }
: HarmonyRenderItem(hAlign, _x, _y), parenItem(p) {}
~ChordSymbolParen() { delete parenItem; }

RectF boundingRect() const override { return paren->shape().bbox(); }
RectF tightBoundingRect() const override { return paren->shape().bbox(); }
RectF boundingRect() const override { return parenItem->shape().bbox(); }
RectF tightBoundingRect() const override { return parenItem->shape().bbox(); }

double top = DBL_MAX;
double bottom = -DBL_MAX;
double closingParenPos = -DBL_MAX;

Parenthesis* paren = nullptr;
Parenthesis* parenItem = nullptr;

double height() const override { return paren->height(); }
double height() const override { return parenItem->height(); }

double leftPadding() const override { return paren->direction() == DirectionH::LEFT ? OUTER_PADDING : INNER_PADDING; }
double rightPadding() const override { return paren->direction() == DirectionH::LEFT ? INNER_PADDING : OUTER_PADDING; }
double leftPadding() const override { return parenItem->direction() == DirectionH::LEFT ? OUTER_PADDING : INNER_PADDING; }
double rightPadding() const override { return parenItem->direction() == DirectionH::LEFT ? INNER_PADDING : OUTER_PADDING; }

double bboxBaseLine() const override { return bottom; }

Expand Down
68 changes: 41 additions & 27 deletions src/engraving/rendering/score/harmonylayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,55 +202,69 @@ void HarmonyLayout::layoutModifierParentheses(const Harmony* item)
const std::vector<HarmonyRenderItem*>& itemList = item->ldata()->renderItemList();
// Layout parentheses
std::vector<ChordSymbolParen*> openingParenStack;
double lastTextSegHeight = 0.0;
double lastTextSegTop = 0.0;
for (HarmonyRenderItem* renderItem : itemList) {
if (ChordSymbolParen* paren = dynamic_cast<ChordSymbolParen*>(renderItem)) {
if (paren->paren->direction() == DirectionH::LEFT) {
if (ChordSymbolParen* curParen = dynamic_cast<ChordSymbolParen*>(renderItem)) {
if (curParen->parenItem->direction() == DirectionH::LEFT) {
// Opening paren
openingParenStack.push_back(paren);
openingParenStack.push_back(curParen);
} else {
// Closing paren
ChordSymbolParen* openingParen = openingParenStack.empty() ? nullptr : openingParenStack.back();
if (!openingParen) {
continue;
}
openingParenStack.pop_back();
paren->top = openingParen->top;
paren->bottom = openingParen->bottom;
curParen->top = openingParen->top;
curParen->bottom = openingParen->bottom;

// Layout parenthesis pair
double startY = openingParen->top;
double height = openingParen->bottom - openingParen->top;
double midPointThickness = height / 30 * openingParen->paren->ldata()->mag();
if (std::isinf(height)) {
height = lastTextSegHeight;
}
if (muse::RealIsEqual(DBL_MAX, startY)) {
startY = lastTextSegTop;
}
double midPointThickness = height / 30 * openingParen->parenItem->ldata()->mag();
double endPointThickness = 0.05;
openingParen->paren->mutldata()->startY = startY;
openingParen->parenItem->mutldata()->startY = startY;
openingParen->sety(startY);
openingParen->paren->mutldata()->height = height;
openingParen->paren->mutldata()->midPointThickness.set_value(midPointThickness);
openingParen->paren->mutldata()->endPointThickness.set_value(endPointThickness);

paren->paren->mutldata()->startY = startY;
paren->sety(startY);
paren->paren->mutldata()->height = height;
paren->paren->mutldata()->midPointThickness.set_value(midPointThickness);
paren->paren->mutldata()->endPointThickness.set_value(endPointThickness);
paren->setx(openingParen->closingParenPos);
openingParen->parenItem->mutldata()->height = height;
openingParen->parenItem->mutldata()->midPointThickness.set_value(midPointThickness);
openingParen->parenItem->mutldata()->endPointThickness.set_value(endPointThickness);

curParen->parenItem->mutldata()->startY = startY;
curParen->sety(startY);
curParen->parenItem->mutldata()->height = height;
curParen->parenItem->mutldata()->midPointThickness.set_value(midPointThickness);
curParen->parenItem->mutldata()->endPointThickness.set_value(endPointThickness);
double closingPos = openingParen->closingParenPos;
if (muse::RealIsEqual(-DBL_MAX, closingPos)) {
closingPos = openingParen->x() + openingParen->boundingRect().width();
}
curParen->setx(closingPos);

ParenthesisLayout::createPathAndShape(openingParen->paren, openingParen->paren->mutldata());
ParenthesisLayout::createPathAndShape(paren->paren, paren->paren->mutldata());
ParenthesisLayout::createPathAndShape(openingParen->parenItem, openingParen->parenItem->mutldata());
ParenthesisLayout::createPathAndShape(curParen->parenItem, curParen->parenItem->mutldata());

// Outer parens must always be the same length or longer than inner parens
for (ChordSymbolParen* outerParen : openingParenStack) {
outerParen->top = std::min(openingParen->top, outerParen->top);
outerParen->bottom = std::max(openingParen->bottom, outerParen->bottom);
}
}
} else if (TextSegment* ts = dynamic_cast<TextSegment*>(renderItem)) {
} else if (TextSegment* textSeg = dynamic_cast<TextSegment*>(renderItem)) {
// Set top paren height
lastTextSegHeight = textSeg->height();
lastTextSegTop = textSeg->boundingRect().translated(textSeg->pos()).y();
if (!openingParenStack.empty()) {
ChordSymbolParen* topParen = openingParenStack.back();
topParen->top = std::min(topParen->top, ts->boundingRect().translated(ts->pos()).y());
topParen->bottom = std::max(topParen->bottom, ts->boundingRect().translated(ts->pos()).y() + ts->boundingRect().height());
topParen->closingParenPos = std::max(topParen->closingParenPos, ts->x() + ts->width());
topParen->top = std::min(topParen->top, textSeg->boundingRect().translated(textSeg->pos()).y());
topParen->bottom = std::max(topParen->bottom, textSeg->boundingRect().translated(textSeg->pos()).y() + textSeg->height());
topParen->closingParenPos = std::max(topParen->closingParenPos, textSeg->x() + textSeg->width());
continue;
}
}
Expand Down Expand Up @@ -282,13 +296,13 @@ void HarmonyLayout::layoutModifierParentheses(const Harmony* item)
double padding = i != 0 ? computePadding(itemList.at(i - 1), renderItem) : 0.0;

if (ChordSymbolParen* paren = dynamic_cast<ChordSymbolParen*>(renderItem)) {
if (paren->paren->direction() == DirectionH::LEFT) {
additionalSpace += paren->paren->width() + padding;
if (paren->parenItem->direction() == DirectionH::LEFT) {
additionalSpace += paren->parenItem->width() + padding;
paren->movex(additionalSpace);
}
if (paren->paren->direction() == DirectionH::RIGHT) {
if (paren->parenItem->direction() == DirectionH::RIGHT) {
paren->movex(additionalSpace + padding);
additionalSpace += paren->paren->width() + padding;
additionalSpace += paren->parenItem->width() + padding;
}
} else if (TextSegment* ts = dynamic_cast<TextSegment*>(renderItem)) {
additionalSpace += padding;
Expand Down
1 change: 1 addition & 0 deletions src/engraving/rendering/score/measurelayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include "dom/utils.h"
#include "types/typesconv.h"

#include "autoplace.h"
#include "tlayout.h"
#include "layoutcontext.h"
#include "arpeggiolayout.h"
Expand Down
5 changes: 5 additions & 0 deletions src/engraving/rendering/score/parenthesislayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ void ParenthesisLayout::createPathAndShape(Parenthesis* item, Parenthesis::Layou
const double heightInSpatium = height / spatium;
const double shoulderYOffset = 0.2 * height;

if (std::isinf(height)) {
LOGE() << "Error: parenthesis height is infinite";
return;
}

// Control width of parentheses. We don't want tall parens to be too wide, nor do we want parens at a small scale to lose their curve too much
double shoulderX = ldata->shoulderWidth.has_value() ? ldata->shoulderWidth() : 0.2 * std::pow(height, 0.95) * std::pow(mag, 0.1);
const double minShoulderX = 0.25 * spatium;
Expand Down
88 changes: 51 additions & 37 deletions src/engraving/rendering/score/systemlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,11 @@ System* SystemLayout::collectSystem(LayoutContext& ctx)

if (oldSystem && !oldSystem->measures().empty() && oldSystem->measures().front()->tick() >= system->endTick()
&& !(oldSystem->page() && oldSystem->page() != ctx.state().page())) {
// We may have previously processed the ties of the next system (in LayoutChords::updateLineAttachPoints()).
// We need to restore them to the correct state.
SystemLayout::restoreTiesAndBends(oldSystem, ctx);
// We may have unfinished layouts of certain elements in the next system
// - ties & bends (in LayoutChords::updateLineAttachPoints())
// - fret diagrams & harmony (in layoutMeasure)
// Restore them to the correct state.
SystemLayout::restoreOldSystemLayout(oldSystem, ctx);
}

return system;
Expand Down Expand Up @@ -990,19 +992,53 @@ void SystemLayout::layoutParenthesisAndBigTimeSigs(const ElementsToLayout& eleme
}
}

void SystemLayout::layoutHarmonies(const std::vector<Harmony*> harmonies, System* system, bool verticalAlign, LayoutContext& ctx)
void SystemLayout::layoutHarmonies(const std::vector<Harmony*> harmonies, System* system, LayoutContext& ctx)
{
for (Harmony* harmony : harmonies) {
TLayout::layoutHarmony(harmony, harmony->mutldata(), ctx);
Autoplace::autoplaceSegmentElement(harmony, harmony->mutldata());
}

if (ctx.conf().styleB(Sid::verticallyAlignChordSymbols) && verticalAlign) {
if (ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
std::vector<EngravingItem*> harmonyItems(harmonies.begin(), harmonies.end());
AlignmentLayout::alignItemsForSystem(harmonyItems, system);
}
}

void SystemLayout::layoutFretDiagrams(const ElementsToLayout& elements, System* system, LayoutContext& ctx)
{
for (FretDiagram* fretDiag : elements.fretDiagrams) {
Autoplace::autoplaceSegmentElement(fretDiag, fretDiag->mutldata());
}

if (ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
std::vector<EngravingItem*> fretItems(elements.fretDiagrams.begin(), elements.fretDiagrams.end());
AlignmentLayout::alignItemsForSystem(fretItems, system);
}

for (Harmony* harmony : elements.harmonies) {
FretDiagram* fdParent = harmony->parent()->isFretDiagram() ? toFretDiagram(harmony->parent()) : nullptr;
if (fdParent && !fdParent->visible()) {
harmony->mutldata()->moveY(-fdParent->pos().y());
}
Autoplace::autoplaceSegmentElement(harmony, harmony->mutldata());
}

if (ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
std::vector<EngravingItem*> harmonyItems(elements.harmonies.begin(), elements.harmonies.end());
AlignmentLayout::alignItemsForSystem(harmonyItems, system);
}

for (FretDiagram* fretDiag : elements.fretDiagrams) {
if (Harmony* harmony = fretDiag->harmony()) {
SkylineLine& skl = system->staff(fretDiag->staffIdx())->skyline().north();
Segment* s = fretDiag->segment();
Shape harmShape = harmony->ldata()->shape().translated(harmony->pos() + fretDiag->pos() + s->pos() + s->measure()->pos());
skl.add(harmShape);
}
}
}

void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx)
{
TRACEFUNC;
Expand Down Expand Up @@ -1136,7 +1172,7 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx)

bool hasFretDiagram = elementsToLayout.fretDiagrams.size() > 0;
if (!hasFretDiagram) {
layoutHarmonies(elementsToLayout.harmonies, system, true, ctx);
layoutHarmonies(elementsToLayout.harmonies, system, ctx);
}

for (StaffText* st : elementsToLayout.staffText) {
Expand All @@ -1152,36 +1188,7 @@ void SystemLayout::layoutSystemElements(System* system, LayoutContext& ctx)
}

if (hasFretDiagram) {
for (FretDiagram* fretDiag : elementsToLayout.fretDiagrams) {
Autoplace::autoplaceSegmentElement(fretDiag, fretDiag->mutldata());
}

if (ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
std::vector<EngravingItem*> fretItems(elementsToLayout.fretDiagrams.begin(), elementsToLayout.fretDiagrams.end());
AlignmentLayout::alignItemsForSystem(fretItems, system);
}

for (Harmony* harmony : elementsToLayout.harmonies) {
FretDiagram* fdParent = harmony->parent()->isFretDiagram() ? toFretDiagram(harmony->parent()) : nullptr;
if (fdParent && !fdParent->visible()) {
harmony->mutldata()->moveY(-fdParent->pos().y());
}
Autoplace::autoplaceSegmentElement(harmony, harmony->mutldata());
}

if (ctx.conf().styleB(Sid::verticallyAlignChordSymbols)) {
std::vector<EngravingItem*> harmonyItems(elementsToLayout.harmonies.begin(), elementsToLayout.harmonies.end());
AlignmentLayout::alignItemsForSystem(harmonyItems, system);
}

for (FretDiagram* fretDiag : elementsToLayout.fretDiagrams) {
if (Harmony* harmony = fretDiag->harmony()) {
SkylineLine& skl = system->staff(fretDiag->staffIdx())->skyline().north();
Segment* s = fretDiag->segment();
Shape harmShape = harmony->ldata()->shape().translated(harmony->pos() + fretDiag->pos() + s->pos() + s->measure()->pos());
skl.add(harmShape);
}
}
layoutFretDiagrams(elementsToLayout, system, ctx);
}

layoutVoltas(elementsToLayout, ctx);
Expand Down Expand Up @@ -1910,7 +1917,7 @@ void SystemLayout::updateCrossBeams(System* system, LayoutContext& ctx)
}
}

void SystemLayout::restoreTiesAndBends(System* system, LayoutContext& ctx)
void SystemLayout::restoreOldSystemLayout(System* system, LayoutContext& ctx)
{
ElementsToLayout elements(system);
for (MeasureBase* mb : system->measures()) {
Expand All @@ -1920,6 +1927,13 @@ void SystemLayout::restoreTiesAndBends(System* system, LayoutContext& ctx)
}

layoutTiesAndBends(elements, ctx);

bool hasFretDiagram = elements.fretDiagrams.size() > 0;
if (hasFretDiagram) {
layoutFretDiagrams(elements, system, ctx);
} else {
layoutHarmonies(elements.harmonies, system, ctx);
}
}

void SystemLayout::layoutSystem(System* system, LayoutContext& ctx, double xo1, const bool isFirstSystem, bool firstSystemIndent)
Expand Down
5 changes: 3 additions & 2 deletions src/engraving/rendering/score/systemlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class SystemLayout
static void layoutGuitarBends(Chord* chord, LayoutContext& ctx);
static void updateCrossBeams(System* system, LayoutContext& ctx);
static bool measureHasCrossStuffOrModifiedBeams(const Measure* measure);
static void restoreTiesAndBends(System* system, LayoutContext& ctx);
static void restoreOldSystemLayout(System* system, LayoutContext& ctx);
static void layoutTuplets(const std::vector<ChordRest*>& chordRests, LayoutContext& ctx);

static void layoutTiesAndBends(const ElementsToLayout& elementsToLayout, LayoutContext& ctx);
Expand Down Expand Up @@ -213,7 +213,8 @@ class SystemLayout

static void layoutParenthesisAndBigTimeSigs(const ElementsToLayout& elementsToLayout);

static void layoutHarmonies(const std::vector<Harmony*> harmonies, System* system, bool verticalAlign, LayoutContext& ctx);
static void layoutHarmonies(const std::vector<Harmony*> harmonies, System* system, LayoutContext& ctx);
static void layoutFretDiagrams(const ElementsToLayout& elements, System* system, LayoutContext& ctx);

static void alignRests(const ElementsToLayout& elementsToLayout, LayoutContext& ctx);
static void checkFullMeasureRestCollisions(const ElementsToLayout& elementsToLayout, LayoutContext& ctx);
Expand Down
2 changes: 1 addition & 1 deletion src/engraving/rendering/score/tdraw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1853,7 +1853,7 @@ void TDraw::draw(const Harmony* item, Painter* painter)
painter->drawText(ts->pos(), ts->text());
#endif
} else if (const ChordSymbolParen* parenItem = dynamic_cast<const ChordSymbolParen*>(renderItem)) {
Parenthesis* p = parenItem->paren;
Parenthesis* p = parenItem->parenItem;
painter->translate(parenItem->pos());
draw(p, painter);
painter->translate(-parenItem->pos());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void FretFrameChordListModel::load()
if (const TextSegment* textSeg = dynamic_cast<const TextSegment*>(segment)) {
name += textSeg->text().toQString();
} else if (const ChordSymbolParen* parenSeg = dynamic_cast<const ChordSymbolParen*>(segment)) {
name += parenSeg->paren->direction() == DirectionH::LEFT ? u"(" : u")";
name += parenSeg->parenItem->direction() == DirectionH::LEFT ? u"(" : u")";
}
}

Expand Down
Loading