Skip to content

Small HarmonyLayout tidy #26647

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 1 commit into from
May 6, 2025
Merged
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
238 changes: 121 additions & 117 deletions src/engraving/rendering/score/harmonylayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,139 +50,143 @@ void HarmonyLayout::autoplaceHarmonies(const std::vector<Segment*>& sl)
}
}

void HarmonyLayout::alignHarmonies(const System* system, const std::vector<Segment*>& sl, bool harmony, const double maxShiftAbove,
const double maxShiftBelow)
// Help class.
// Contains harmonies/fretboard per segment.
class HarmonyList : public std::vector<EngravingItem*>
{
// Help class.
// Contains harmonies/fretboard per segment.
class HarmonyList : public std::vector<EngravingItem*>
OBJECT_ALLOCATOR(mu::engraving, HarmonyList);

std::map<const Segment*, std::vector<EngravingItem*> > elements;
std::vector<EngravingItem*> modified;

EngravingItem* getReferenceElement(const Segment* s, bool above, bool visible) const;

public:
HarmonyList()
{
OBJECT_ALLOCATOR(engraving, HarmonyList)

std::map<const Segment*, std::vector<EngravingItem*> > elements;
std::vector<EngravingItem*> modified;

EngravingItem* getReferenceElement(const Segment* s, bool above, bool visible) const
{
// Returns the reference element for aligning.
// When a segments contains multiple harmonies/fretboard, the lowest placed
// element (for placement above, otherwise the highest placed element) is
// used for alignment.
EngravingItem* element { nullptr };
for (EngravingItem* e : elements.at(s)) {
// Only chord symbols have styled offset, fretboards don't.
if (!e->autoplace() || (e->isHarmony() && !e->isStyled(Pid::OFFSET)) || (visible && !e->visible())) {
continue;
}
if (!element) {
element = e;
} else {
if ((e->placeAbove() && above && (element->y() < e->y()))
|| (e->placeBelow() && !above && (element->y() > e->y()))) {
element = e;
}
}
}
return element;
}
elements.clear();
modified.clear();
}

public:
HarmonyList()
{
elements.clear();
modified.clear();
}
void append(const Segment* s, EngravingItem* e) { elements[s].push_back(e); }

double getReferenceHeight(bool above) const;

bool align(bool above, double reference, double maxShift);

void append(const Segment* s, EngravingItem* e)
{
elements[s].push_back(e);
void addToSkyline(const System* system);
};

double HarmonyList::getReferenceHeight(bool above) const
{
// The reference height is the height of
// the lowest element if placed above
// or
// the highest element if placed below.
bool first { true };
double ref { 0.0 };
for (auto s : muse::keys(elements)) {
EngravingItem* e { getReferenceElement(s, above, true) };
if (!e) {
continue;
}
if (e->placeAbove() && above) {
ref = first ? e->y() : std::min(ref, e->y());
first = false;
} else if (e->placeBelow() && !above) {
ref = first ? e->y() : std::max(ref, e->y());
first = false;
}
}
return ref;
}

bool HarmonyList::align(bool above, double reference, double maxShift)
{
// Align the elements. If a segment contains multiple elements,
// only the reference elements is used in the algorithm. All other
// elements will remain their original placement with respect to
// the reference element.
bool moved { false };
if (muse::RealIsNull(reference)) {
return moved;
}

double getReferenceHeight(bool above) const
{
// The reference height is the height of
// the lowest element if placed above
// or
// the highest element if placed below.
bool first { true };
double ref { 0.0 };
for (auto s : muse::keys(elements)) {
EngravingItem* e { getReferenceElement(s, above, true) };
if (!e) {
for (auto s : muse::keys(elements)) {
std::list<EngravingItem*> handled;
EngravingItem* be = getReferenceElement(s, above, false);
if (!be) {
// If there are only invisible elements, we have to use an invisible
// element for alignment reference.
be = getReferenceElement(s, above, true);
}
if (be && ((above && (be->y() < (reference + maxShift))) || ((!above && (be->y() > (reference - maxShift)))))) {
double shift = be->ldata()->pos().y();
be->mutldata()->setPosY(reference - be->ryoffset());
shift -= be->ldata()->pos().y();
for (EngravingItem* e : elements[s]) {
if ((above && e->placeBelow()) || (!above && e->placeAbove())) {
continue;
}
if (e->placeAbove() && above) {
ref = first ? e->y() : std::min(ref, e->y());
first = false;
} else if (e->placeBelow() && !above) {
ref = first ? e->y() : std::max(ref, e->y());
first = false;
modified.push_back(e);
handled.push_back(e);
moved = true;
if (e != be) {
e->mutldata()->moveY(-shift);
}
}
return ref;
}

bool align(bool above, double reference, double maxShift)
{
// Align the elements. If a segment contains multiple elements,
// only the reference elements is used in the algorithm. All other
// elements will remain their original placement with respect to
// the reference element.
bool moved { false };
if (muse::RealIsNull(reference)) {
return moved;
for (auto e : handled) {
muse::remove(elements[s], e);
}
}
}
return moved;
}

for (auto s : muse::keys(elements)) {
std::list<EngravingItem*> handled;
EngravingItem* be = getReferenceElement(s, above, false);
if (!be) {
// If there are only invisible elements, we have to use an invisible
// element for alignment reference.
be = getReferenceElement(s, above, true);
}
if (be && ((above && (be->y() < (reference + maxShift))) || ((!above && (be->y() > (reference - maxShift)))))) {
double shift = be->ldata()->pos().y();
be->mutldata()->setPosY(reference - be->ryoffset());
shift -= be->ldata()->pos().y();
for (EngravingItem* e : elements[s]) {
if ((above && e->placeBelow()) || (!above && e->placeAbove())) {
continue;
}
modified.push_back(e);
handled.push_back(e);
moved = true;
if (e != be) {
e->mutldata()->moveY(-shift);
}
}
for (auto e : handled) {
muse::remove(elements[s], e);
}
}
}
return moved;
void HarmonyList::addToSkyline(const System* system)
{
for (EngravingItem* e : modified) {
const Segment* s = toSegment(e->explicitParent());
const MeasureBase* m = toMeasureBase(s->explicitParent());
system->staff(e->staffIdx())->skyline().add(e->shape().translated(e->pos() + s->pos() + m->pos()));
if (!e->isFretDiagram()) {
continue;
}
FretDiagram* fd = toFretDiagram(e);
Harmony* h = fd->harmony();
if (h) {
system->staff(e->staffIdx())->skyline().add(h->shape().translated(h->pos() + fd->pos() + s->pos() + m->pos()));
} else {
system->staff(e->staffIdx())->skyline().add(fd->shape().translated(fd->pos() + s->pos() + m->pos()));
}
}
}

void addToSkyline(const System* system)
{
for (EngravingItem* e : modified) {
const Segment* s = toSegment(e->explicitParent());
const MeasureBase* m = toMeasureBase(s->explicitParent());
system->staff(e->staffIdx())->skyline().add(e->shape().translated(e->pos() + s->pos() + m->pos()));
if (e->isFretDiagram()) {
FretDiagram* fd = toFretDiagram(e);
Harmony* h = fd->harmony();
if (h) {
system->staff(e->staffIdx())->skyline().add(h->shape().translated(h->pos() + fd->pos() + s->pos() + m->pos()));
} else {
system->staff(e->staffIdx())->skyline().add(fd->shape().translated(fd->pos() + s->pos() + m->pos()));
}
}
}
EngravingItem* HarmonyList::getReferenceElement(const Segment* s, bool above, bool visible) const
{
// Returns the reference element for aligning.
// When a segments contains multiple harmonies/fretboard, the lowest placed
// element (for placement above, otherwise the highest placed element) is
// used for alignment.
EngravingItem* element { nullptr };
for (EngravingItem* e : elements.at(s)) {
// Only chord symbols have styled offset, fretboards don't.
if (!e->autoplace() || (e->isHarmony() && !e->isStyled(Pid::OFFSET)) || (visible && !e->visible())) {
continue;
}
if (!element) {
element = e;
} else if ((e->placeAbove() && above && (element->y() < e->y()))
|| (e->placeBelow() && !above && (element->y() > e->y()))) {
element = e;
}
};
}
return element;
}

void HarmonyLayout::alignHarmonies(const System* system, const std::vector<Segment*>& sl, bool harmony, const double maxShiftAbove,
const double maxShiftBelow)
{
if (muse::RealIsNull(maxShiftAbove) && muse::RealIsNull(maxShiftBelow)) {
return;
}
Expand Down
Loading