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 "dynamicslayout.h"
24 : #include "layoutcontext.h"
25 : #include "tlayout.h"
26 : #include "textlayout.h"
27 :
28 : #include "../dom/hairpin.h"
29 : #include "../dom/measure.h"
30 : #include "../dom/staff.h"
31 : #include "../dom/system.h"
32 : #include "../types/typesconv.h"
33 :
34 : using namespace mu::engraving::rendering::score;
35 :
36 1145 : void DynamicsLayout::layoutDynamic(Dynamic* item, TextBase::LayoutData* ldata, const LayoutConfiguration& conf)
37 : {
38 1145 : doLayoutDynamic(item, ldata, conf);
39 :
40 : // If the dynamic contains custom text, keep it aligned
41 1145 : double customTextOffset = computeCustomTextOffset(item, ldata, conf);
42 1145 : ldata->moveX(-customTextOffset);
43 :
44 1145 : if (item->autoplace() && item->avoidBarLines()) {
45 1145 : manageBarlineCollisions(item, ldata);
46 : }
47 1145 : }
48 :
49 1216 : void DynamicsLayout::doLayoutDynamic(Dynamic* item, Dynamic::LayoutData* ldata, const LayoutConfiguration& conf)
50 : {
51 1216 : ldata->disconnectSnappedItems();
52 :
53 1216 : HairpinSegment* snapBeforeHairpinAcrossSysBreak = item->findSnapBeforeHairpinAcrossSystemBreak();
54 1216 : if (snapBeforeHairpinAcrossSysBreak) {
55 0 : ldata->connectItemSnappedBefore(snapBeforeHairpinAcrossSysBreak);
56 : }
57 :
58 1216 : const StaffType* stType = item->staffType();
59 1216 : if (stType && stType->isHiddenElementOnTab(Sid::dynamicsShowTabCommon, Sid::dynamicsShowTabSimple)) {
60 0 : ldata->setIsSkipDraw(true);
61 0 : return;
62 : }
63 1216 : ldata->setIsSkipDraw(false);
64 :
65 1216 : item->setPlacementBasedOnVoiceAssignment(conf.styleV(Sid::dynamicsHairpinVoiceBasedPlacement).value<DirectionV>());
66 :
67 : // If "Center on notehead" is on, override user position. Restore later
68 1216 : AlignH userPosition = item->getProperty(Pid::POSITION).value<AlignH>();
69 1216 : AlignH hPos = item->centerOnNotehead() ? AlignH::HCENTER : item->position();
70 1216 : item->setPosition(hPos);
71 1216 : TextLayout::layoutBaseTextBase(item, ldata);
72 1216 : item->setPosition(userPosition);
73 :
74 1216 : const Segment* s = item->segment();
75 1216 : if (!s || (!item->centerOnNotehead() && item->position() == AlignH::LEFT)) {
76 0 : return;
77 : }
78 :
79 1216 : if (item->anchorToEndOfPrevious()) {
80 1 : layoutDynamicToEndOfPrevious(item, ldata);
81 1 : return;
82 : }
83 :
84 1215 : if (!item->centerOnNotehead()) {
85 0 : return;
86 : }
87 :
88 : // Use SMuFL optical center for dynamic if available
89 1215 : SymId symId = TConv::symId(item->dynamicType());
90 1215 : double opticalCenter = item->symSmuflAnchor(symId, SmuflAnchorId::opticalCenter).x();
91 1215 : if (symId != SymId::noSym && opticalCenter) {
92 1138 : double symWidth = item->symBbox(symId).width();
93 1138 : double offset = symWidth / 2 - opticalCenter + item->symBbox(symId).left();
94 1138 : double spatiumScaling = item->spatium() / conf.spatium();
95 1138 : offset *= spatiumScaling;
96 1138 : ldata->moveX(offset);
97 : }
98 : }
99 :
100 1145 : double DynamicsLayout::computeCustomTextOffset(Dynamic* item, Dynamic::LayoutData* ldata, const LayoutConfiguration& conf)
101 : {
102 1145 : if (!item->centerOnNotehead() || item->dynamicType() == DynamicType::OTHER) {
103 20 : return 0.0;
104 : }
105 :
106 1125 : String referenceString = String::fromUtf8(Dynamic::dynInfo(item->dynamicType()).text);
107 1125 : if (item->xmlText() == referenceString) {
108 1054 : return 0.0;
109 : }
110 :
111 71 : Dynamic referenceDynamic(*item);
112 71 : referenceDynamic.setXmlText(referenceString);
113 71 : doLayoutDynamic(&referenceDynamic, referenceDynamic.mutldata(), conf);
114 :
115 71 : TextFragment referenceFragment;
116 71 : if (!referenceDynamic.ldata()->blocks.empty()) {
117 71 : TextBlock referenceBlock = referenceDynamic.ldata()->blocks.front();
118 71 : if (!referenceBlock.fragments().empty()) {
119 71 : referenceFragment = referenceDynamic.ldata()->blocks.front().fragments().front();
120 : }
121 71 : }
122 :
123 123 : for (const TextBlock& block : ldata->blocks) {
124 144 : for (const TextFragment& fragment : block.fragments()) {
125 92 : if (fragment.text == referenceFragment.text) {
126 19 : return fragment.pos.x() - referenceFragment.pos.x();
127 : }
128 : }
129 : }
130 :
131 52 : return 0.0;
132 1125 : }
133 :
134 1 : void DynamicsLayout::layoutDynamicToEndOfPrevious(const Dynamic* item, TextBase::LayoutData* ldata)
135 : {
136 1 : Segment* curSegment = item->segment();
137 1 : Segment* leftMostSegment = curSegment;
138 1 : Segment* prevSeg = curSegment;
139 : while (true) {
140 1 : prevSeg = prevSeg->prev1enabled();
141 1 : if (!prevSeg || prevSeg->tick() != curSegment->tick()) {
142 1 : break;
143 : }
144 0 : if (prevSeg->isActive() && prevSeg->hasElements(item->staffIdx())) {
145 0 : leftMostSegment = prevSeg;
146 0 : break;
147 : }
148 : }
149 :
150 1 : double xDiff = curSegment->x() + curSegment->measure()->x() - (leftMostSegment->x() + leftMostSegment->measure()->x());
151 1 : ldata->setPosX(-xDiff - ldata->bbox().right() - 0.50 * item->spatium());
152 1 : }
153 :
154 1145 : void DynamicsLayout::manageBarlineCollisions(const Dynamic* item, TextBase::LayoutData* ldata)
155 : {
156 1145 : if (item->score()->nstaves() <= 1 || item->anchorToEndOfPrevious()) {
157 1045 : return;
158 : }
159 :
160 493 : Segment* thisSegment = item->segment();
161 493 : if (!thisSegment) {
162 0 : return;
163 : }
164 :
165 493 : System* system = thisSegment->measure()->system();
166 493 : if (!system) {
167 0 : return;
168 : }
169 :
170 493 : staff_idx_t staffIdx = item->staffIdx();
171 493 : staff_idx_t barLineStaff = muse::nidx;
172 493 : if (item->placeAbove()) {
173 : // need to find the barline from the staff above
174 : // taking into account there could be invisible staves
175 133 : if (staffIdx == 0) {
176 43 : return;
177 : }
178 92 : for (int staffIndex = static_cast<int>(staffIdx) - 1; staffIndex >= 0; --staffIndex) {
179 92 : if (system->staff(staffIndex)->show()) {
180 90 : barLineStaff = staffIndex;
181 90 : break;
182 : }
183 : }
184 : } else {
185 360 : barLineStaff = staffIdx;
186 : }
187 :
188 450 : if (barLineStaff == muse::nidx) {
189 0 : return;
190 : }
191 :
192 450 : if (!item->score()->staff(barLineStaff)->barLineSpan()) {
193 350 : return; // Barline doesn't extend through staves
194 : }
195 :
196 100 : const double minBarLineDistance = 0.25 * item->spatium();
197 :
198 100 : RectF referenceBBox = ldata->bbox();
199 :
200 : // Check barlines to the right
201 100 : Segment* rightBarLineSegment = nullptr;
202 523 : for (Segment* segment = thisSegment; segment && segment->measure()->system() == system; segment = segment->next1enabled()) {
203 523 : if (segment->segmentType() & SegmentType::BarLineTypes) {
204 100 : rightBarLineSegment = segment;
205 100 : break;
206 : }
207 : }
208 :
209 100 : if (rightBarLineSegment) {
210 100 : EngravingItem* e = rightBarLineSegment->element(barLineStaff * VOICES);
211 100 : if (e) {
212 100 : double rightMargin = e->ldata()->bbox().translated(e->pagePos()).left()
213 100 : - referenceBBox.translated(item->pagePos() - item->offset()).right()
214 100 : - minBarLineDistance;
215 100 : if (rightMargin < 0) {
216 0 : ldata->moveX(rightMargin);
217 : }
218 : }
219 : }
220 :
221 : // Check barlines to the left
222 100 : Segment* leftBarLineSegment = nullptr;
223 357 : for (Segment* segment = thisSegment; segment && segment->measure()->system() == system; segment = segment->prev1enabled()) {
224 357 : if (segment->segmentType() & SegmentType::BarLineTypes) {
225 100 : leftBarLineSegment = segment;
226 100 : break;
227 : }
228 : }
229 100 : if (leftBarLineSegment) {
230 100 : EngravingItem* e = leftBarLineSegment->element(barLineStaff * VOICES);
231 100 : if (e) {
232 100 : double leftMargin = referenceBBox.translated(item->pagePos() - item->offset()).left()
233 100 : - e->ldata()->bbox().translated(e->pagePos()).right()
234 100 : - minBarLineDistance;
235 100 : if (leftMargin < 0) {
236 6 : ldata->moveX(-leftMargin);
237 : }
238 : }
239 : }
240 : }
|