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 :
23 : #include "alignmentlayout.h"
24 :
25 : #include <set>
26 :
27 : #include "systemlayout.h"
28 :
29 : #include "dom/dynamic.h"
30 : #include "dom/expression.h"
31 : #include "dom/fret.h"
32 : #include "dom/hairpin.h"
33 : #include "dom/hammeronpulloff.h"
34 : #include "dom/harmony.h"
35 : #include "dom/system.h"
36 : #include "dom/tapping.h"
37 : #include "dom/text.h"
38 :
39 : namespace mu::engraving::rendering::score {
40 738 : void AlignmentLayout::alignItemsGroup(const std::vector<EngravingItem*>& elements, const System* system)
41 : {
42 738 : if (elements.empty()) {
43 279 : return;
44 : }
45 :
46 459 : double outermostY = yOpticalCenter(elements.front());
47 2036 : for (const EngravingItem* element : elements) {
48 1577 : double curY = yOpticalCenter(element);
49 1577 : outermostY = isAbove(element) ? std::min(outermostY, curY) : std::max(outermostY, curY);
50 : }
51 :
52 2036 : for (EngravingItem* element : elements) {
53 1577 : moveItemToY(element, outermostY, system);
54 : }
55 : }
56 :
57 61 : void AlignmentLayout::alignHopoLetters(const HammerOnPullOff* hopo, const System* system)
58 : {
59 61 : IF_ASSERT_FAILED(!hopo->segmentsEmpty()) {
60 0 : return;
61 : }
62 :
63 61 : std::vector<EngravingItem*> itemsToAlign;
64 61 : for (SpannerSegment* seg : hopo->spannerSegments()) {
65 61 : if (seg->system() == system) {
66 61 : HammerOnPullOffSegment* hopoSegment = toHammerOnPullOffSegment(seg);
67 136 : for (HammerOnPullOffText* hopoText : hopoSegment->hopoText()) {
68 75 : itemsToAlign.push_back(hopoText);
69 : }
70 61 : break;
71 : }
72 : }
73 :
74 61 : if (itemsToAlign.empty()) {
75 0 : return;
76 : }
77 :
78 61 : bool above = isAbove(itemsToAlign.front());
79 136 : for (EngravingItem* item : itemsToAlign) {
80 75 : if (isAbove(item) != above) {
81 0 : return; // Don't try to align items split above/below
82 : }
83 : }
84 :
85 61 : ChordRest* scr = toChordRest(hopo->startElement());
86 61 : if (scr && scr->isChord()) {
87 61 : for (Articulation* art : toChord(scr)->articulations()) {
88 0 : if (art->isTapping() && toTapping(art)->text() && isAbove(art) == above) {
89 0 : itemsToAlign.push_back(art);
90 : }
91 : }
92 : }
93 :
94 61 : alignItemsGroup(itemsToAlign, system);
95 61 : }
96 :
97 14776 : void AlignmentLayout::alignItemsWithTheirSnappingChain(const std::vector<EngravingItem*>& elements, const System* system)
98 : {
99 14776 : std::set<EngravingItem*> alignedItems;
100 :
101 14776 : double outermostY = 0.0;
102 14776 : bool firstItem = true;
103 2811 : auto computeOutermostY = [&outermostY, &firstItem](EngravingItem* item) {
104 2811 : double curY = yOpticalCenter(item);
105 2811 : if (firstItem) {
106 2605 : outermostY = curY;
107 2605 : firstItem = false;
108 : } else {
109 206 : outermostY = item->placeAbove() ? std::min(outermostY, curY) : std::max(outermostY, curY);
110 : }
111 2811 : };
112 :
113 2811 : auto moveElementsToOutermostY = [&outermostY, &alignedItems, system](EngravingItem* item) {
114 2811 : alignedItems.insert(item);
115 2811 : moveItemToY(item, outermostY, system);
116 17587 : };
117 :
118 17581 : for (EngravingItem* item : elements) {
119 2805 : if (muse::contains(alignedItems, item)) {
120 200 : continue;
121 : }
122 2605 : firstItem = true;
123 2605 : item->ldata()->scanConnectedItemsInSnappingChain(system, computeOutermostY);
124 2605 : item->ldata()->scanConnectedItemsInSnappingChain(system, moveElementsToOutermostY);
125 : }
126 14776 : }
127 :
128 4436 : void AlignmentLayout::alignItemsForSystem(const std::vector<EngravingItem*>& elements, const System* system)
129 : {
130 : struct StaffItemGroups {
131 : std::vector<EngravingItem*> itemsAbove;
132 : std::vector<EngravingItem*> itemsBelow;
133 : };
134 :
135 4436 : std::map<staff_idx_t, StaffItemGroups> staffItems;
136 :
137 5697 : for (EngravingItem* item : elements) {
138 1261 : if (item->addToSkyline() && !item->excludeVerticalAlign()) {
139 1259 : item->placeAbove() ? staffItems[item->staffIdx()].itemsAbove.push_back(item)
140 1259 : : staffItems[item->staffIdx()].itemsBelow.push_back(item);
141 : }
142 : }
143 :
144 4720 : for (const auto& staffItem : staffItems) {
145 284 : AlignmentLayout::alignItemsGroup(staffItem.second.itemsAbove, system);
146 284 : AlignmentLayout::alignItemsGroup(staffItem.second.itemsBelow, system);
147 : }
148 4436 : }
149 :
150 4388 : void AlignmentLayout::moveItemToY(EngravingItem* item, double y, const System* system)
151 : {
152 4388 : double curY = yOpticalCenter(item);
153 4388 : double yDiff = y - curY;
154 4388 : item->mutldata()->moveY(yDiff);
155 4388 : SystemLayout::updateSkylineForElement(item, system, yDiff);
156 4388 : }
157 :
158 9235 : double AlignmentLayout::yOpticalCenter(const EngravingItem* item)
159 : {
160 9235 : double curY = item->ldata()->pos().y();
161 9235 : switch (item->type()) {
162 2354 : case ElementType::DYNAMIC:
163 : case ElementType::EXPRESSION:
164 : {
165 : // Please keep `Dynamic::gripsPositions` in sync with this calculation
166 2354 : curY += item->staffOffsetY();
167 2354 : AlignV vertAlign = toTextBase(item)->align().vertical;
168 2354 : double bboxHeight = item->ldata()->bbox().height();
169 2354 : switch (vertAlign) {
170 16 : case AlignV::TOP:
171 16 : curY += 0.5 * bboxHeight;
172 16 : break;
173 0 : case AlignV::VCENTER:
174 0 : break;
175 0 : case AlignV::BOTTOM:
176 0 : curY -= 0.5 * bboxHeight;
177 0 : break;
178 2338 : case AlignV::BASELINE:
179 2338 : if (item->isDynamic()) {
180 2286 : curY -= 0.46 * item->spatium() * toDynamic(item)->symbolScale(); // approximated half x-height of dynamic
181 : } else {
182 52 : curY -= 0.5 * toExpression(item)->fontMetrics().xHeight();
183 : }
184 2338 : break;
185 : }
186 2354 : break;
187 : }
188 952 : case ElementType::HAIRPIN_SEGMENT:
189 : {
190 952 : const HairpinSegment* hairpinSeg = toHairpinSegment(item);
191 952 : if (hairpinSeg->hairpin()->isLineType()) {
192 52 : Text* text = hairpinSeg->text();
193 52 : if (!text) {
194 0 : text = hairpinSeg->endText();
195 : }
196 52 : if (text) {
197 52 : curY -= 0.5 * text->fontMetrics().xHeight();
198 : } else {
199 0 : curY -= 0.5 * item->spatium();
200 : }
201 : }
202 952 : break;
203 : }
204 653 : case ElementType::FRET_DIAGRAM:
205 653 : curY += toFretDiagram(item)->ldata()->gridHeight;
206 653 : break;
207 1881 : case ElementType::HARMONY: {
208 1881 : EngravingItem* parent = toHarmony(item)->layoutParent();
209 1881 : if (parent && parent->isFretDiagram()) {
210 509 : curY += parent->pos().y();
211 : }
212 1881 : break;
213 : }
214 3395 : default:
215 3395 : break;
216 : }
217 9235 : return curY;
218 : }
219 :
220 1713 : bool AlignmentLayout::isAbove(const EngravingItem* item)
221 : {
222 1713 : return item->isArticulationFamily() ? toArticulation(item)->ldata()->up() : item->placeAbove();
223 : }
224 : } // namespace mu::engraving::rendering::dev
|