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 "tlayout.h"
24 : #include "textlayout.h"
25 : #include "guitarbendlayout.h"
26 : #include "guitardivelayout.h"
27 :
28 : #include "../../dom/chord.h"
29 : #include "../../dom/guitarbend.h"
30 : #include "../../dom/measure.h"
31 : #include "../../dom/note.h"
32 : #include "../../dom/score.h"
33 : #include "../../dom/segment.h"
34 : #include "../../dom/staff.h"
35 : #include "../../dom/system.h"
36 :
37 : using namespace mu::engraving;
38 : using namespace muse::draw;
39 : using namespace mu::engraving::rendering::score;
40 :
41 1256 : void GuitarBendLayout::updateSegmentsAndLayout(SLine* item, LayoutContext& ctx, System* system)
42 : {
43 1256 : Note* startNote = toNote(item->startElement());
44 1256 : Note* endNote = toNote(item->endElement());
45 1256 : if (!startNote || !endNote) {
46 0 : return;
47 : }
48 :
49 1256 : System* system1 = startNote->findMeasure()->system();
50 1256 : System* system2 = endNote->findMeasure()->system();
51 :
52 1256 : if (!system1) { // system2 may be null if the end note's system hasn't been laid out yet
53 0 : return;
54 : }
55 :
56 1256 : item->setTick(startNote->tick());
57 1256 : item->setTick2(endNote->tick());
58 :
59 1256 : unsigned int segmentsNeeded = system1 == system2 ? 1 : 2;
60 1256 : size_t segmentCount = item->spannerSegments().size();
61 1256 : if (segmentCount != segmentsNeeded) {
62 790 : item->fixupSegments(segmentsNeeded, [item]() { return item->createLineSegment(); });
63 : }
64 :
65 1256 : item->frontSegment()->moveToSystem(system1);
66 1256 : if (segmentsNeeded == 1) {
67 1256 : item->frontSegment()->setSpannerSegmentType(SpannerSegmentType::SINGLE);
68 : } else {
69 0 : item->frontSegment()->setSpannerSegmentType(SpannerSegmentType::BEGIN);
70 0 : item->backSegment()->moveToSystem(system2);
71 0 : item->backSegment()->setSpannerSegmentType(SpannerSegmentType::END);
72 : }
73 :
74 2512 : for (SpannerSegment* segment : item->spannerSegments()) {
75 1256 : if (!segment->system() || (system && segment->system() != system)) {
76 : /* this segment belongs to a system which either has already been
77 : * laid out or will be laid out in a later pass */
78 0 : continue;
79 : }
80 1256 : segment->setTrack(item->track());
81 1256 : TLayout::layoutLineSegment(toLineSegment(segment), ctx);
82 : }
83 :
84 1256 : TLayout::fillNoteShape(startNote, startNote->mutldata());
85 : }
86 :
87 1156 : void GuitarBendLayout::layoutStandardStaff(GuitarBendSegment* item, LayoutContext& ctx)
88 : {
89 1156 : GuitarBend* bend = item->guitarBend();
90 :
91 1156 : if (bend->bendType() == GuitarBendType::DIP) {
92 6 : GuitarDiveLayout::layoutDip(item, ctx);
93 1150 : } else if (bend->bendType() == GuitarBendType::SCOOP) {
94 3 : GuitarDiveLayout::layoutScoop(item);
95 1147 : } else if (bend->bendType() == GuitarBendType::SLIGHT_BEND) {
96 52 : layoutSlightBend(item, ctx);
97 : } else {
98 1095 : layoutAngularBend(item, ctx);
99 : }
100 1156 : }
101 :
102 1095 : void GuitarBendLayout::layoutAngularBend(GuitarBendSegment* item, LayoutContext& ctx)
103 : {
104 1095 : GuitarBend* bend = item->guitarBend();
105 :
106 1095 : computeUp(bend);
107 1095 : computeIsInside(bend);
108 :
109 1095 : Note* startNote = bend->startNote();
110 1095 : Note* endNote = bend->endNote();
111 1095 : if (!startNote || !endNote) {
112 0 : return;
113 : }
114 :
115 2100 : if ((bend->bendType() == GuitarBendType::PRE_BEND || bend->bendType() == GuitarBendType::PRE_DIVE)
116 2100 : && !startNote->parenthesisInfo()) {
117 67 : startNote->setGhost(true);
118 67 : startNote->mutldata()->reset();
119 67 : TLayout::layoutChord(startNote->chord(), ctx);
120 : }
121 :
122 1095 : System* startSystem = startNote->chord()->segment()->system();
123 1095 : if (!startSystem) {
124 0 : return;
125 : }
126 :
127 1095 : const bool up = bend->ldata()->up();
128 1095 : const int upSign = up ? -1 : 1;
129 1095 : const bool isInside = bend->ldata()->isInside();
130 1095 : const double spatium = item->spatium();
131 1095 : const double verticalPadding = 0.2 * spatium;
132 1095 : const double horizontalIndent = 0.2 * spatium;
133 1095 : double heightDiff = endNote->y() - startNote->y();
134 :
135 1095 : PointF startPos;
136 1095 : PointF endPos;
137 :
138 1095 : if (item->isSingleBeginType()) {
139 1095 : Chord* startChord = startNote->chord();
140 1095 : PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
141 1095 : startPos = startNotePos;
142 :
143 1095 : double xOff = (isInside ? 1 : 0.5) * startNote->width() + horizontalIndent * startNote->mag();
144 1095 : double yOff = upSign * verticalPadding * startNote->mag();
145 1095 : if (!isInside) {
146 1074 : yOff += upSign * 0.5 * startNote->height();
147 : }
148 :
149 1095 : startPos += PointF(xOff, yOff);
150 : } else {
151 0 : System* endSystem = endNote->chord()->segment()->system();
152 0 : if (!endSystem) {
153 0 : return;
154 : }
155 0 : double x = endSystem->firstNoteRestSegmentX(true);
156 0 : double y = endNote->y() + upSign * 0.5 * endNote->height() - 0.5 * heightDiff;
157 0 : startPos = PointF(x, y);
158 : }
159 :
160 1095 : if (item->isSingleEndType()) {
161 1095 : Chord* endChord = endNote->chord();
162 1095 : PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
163 1095 : endPos = endNotePos;
164 :
165 1095 : double xOff = (isInside ? 0 : 0.5) * endNote->width() - horizontalIndent * endNote->mag();
166 1095 : double yOff = upSign * verticalPadding * endNote->mag();
167 1095 : if (!isInside) {
168 1074 : yOff += upSign * 0.5 * endNote->height();
169 : }
170 :
171 1095 : endPos += PointF(xOff, yOff);
172 : } else {
173 0 : double x = startSystem->endingXForOpenEndedLines();
174 0 : double y = startNote->y() + upSign * 0.5 * startNote->height() + 0.5 * heightDiff;
175 0 : endPos = PointF(x, y);
176 : }
177 :
178 1095 : avoidBadStaffLineIntersection(item, startPos);
179 1095 : avoidBadStaffLineIntersection(item, endPos);
180 1095 : adjustX(item, startPos, endPos, startNote, endNote);
181 :
182 1095 : if (bend->isDive() && bend->overlappingBendOrDive()) {
183 0 : double yOff = (up ? -1 : 1) * spatium;
184 0 : startPos.setY(startPos.y() + yOff);
185 0 : endPos.setY(endPos.y() + yOff);
186 : }
187 :
188 1095 : item->setPos(startPos);
189 1095 : item->setPos2(endPos - startPos);
190 :
191 1095 : const double vertexHeightMin = 0.75 * spatium;
192 1095 : const double vertexHeightMax = 2.0 * spatium;
193 :
194 1095 : PointF relEndPoint = endPos - startPos;
195 1095 : double angle = -atan(relEndPoint.y() / relEndPoint.x());
196 :
197 1095 : double baseLength = relEndPoint.x();
198 1095 : double minLength = spatium;
199 1095 : double vertexHeight = vertexHeightMin + 0.1 * (baseLength - minLength);
200 1095 : vertexHeight = std::min(vertexHeight, vertexHeightMax);
201 :
202 1095 : PointF vertex = 0.5 * relEndPoint;
203 1095 : PointF vertexDispl = PointF(upSign * vertexHeight * sin(angle), upSign * vertexHeight * cos(angle));
204 1095 : vertex += vertexDispl;
205 :
206 1095 : PointF vertexAbsCoord = vertex + startPos;
207 1095 : avoidBadStaffLineIntersection(item, vertexAbsCoord);
208 1095 : vertex = vertexAbsCoord - startPos;
209 :
210 1095 : item->mutldata()->setVertexPoint(vertex);
211 :
212 1095 : if (item->isSingleBeginType()) {
213 1095 : startNote->addStartLineAttachPoint(item->pos(), bend);
214 : }
215 1095 : if (item->isSingleEndType()) {
216 1095 : endNote->addEndLineAttachPoint(item->pos() + item->pos2(), bend);
217 : }
218 :
219 1095 : PainterPath path;
220 1095 : path.lineTo(vertex + item->vertexPointOff());
221 1095 : path.lineTo(item->pos2());
222 1095 : item->mutldata()->setPath(path);
223 :
224 1095 : RectF r = RectF(PointF(0.0, 0.0), vertex + item->vertexPointOff());
225 1095 : r.unite(RectF(vertex + item->vertexPointOff(), item->pos2()));
226 1095 : item->setbbox(r);
227 1095 : }
228 :
229 1095 : void GuitarBendLayout::computeUp(GuitarBend* item)
230 : {
231 : // NOTE: at some point, it would be good to find a way to reuse the logic of Tie::computeUp() (M.S.)
232 :
233 1095 : GuitarBend::LayoutData* layoutData = item->mutldata();
234 :
235 1095 : if (item->direction() != DirectionV::AUTO) {
236 0 : layoutData->setUp(item->direction() == DirectionV::UP);
237 0 : return;
238 : }
239 :
240 1095 : Measure* measure = item->score()->tick2measure(item->tick());
241 1095 : if (!measure) {
242 0 : return;
243 : }
244 1095 : if (measure->hasVoices(item->staffIdx())) {
245 6 : layoutData->setUp(item->track() % 2);
246 6 : return;
247 : }
248 :
249 1089 : Note* startN = item->startNote();
250 1089 : Chord* startChord = startN->chord();
251 1089 : Note* endN = item->endNote();
252 1089 : Chord* endChord = endN->chord();
253 :
254 1089 : if (startChord->up() != endChord->up()) {
255 444 : layoutData->setUp(true);
256 444 : return;
257 : }
258 :
259 645 : if (startChord->notes().size() == 1 && startChord->stem()) {
260 320 : layoutData->setUp(!startChord->up());
261 320 : return;
262 : }
263 :
264 325 : if (endChord->notes().size() == 1 && endChord->stem()) {
265 124 : layoutData->setUp(!endChord->up());
266 124 : return;
267 : }
268 :
269 201 : int bendCount = 0;
270 201 : int indexOfThis = 0;
271 603 : for (Note* note : (startChord->notes().size() > 1 ? startChord->notes() : endChord->notes())) {
272 693 : for (Spanner* spanner : note->spannerFor()) {
273 291 : if (spanner->isGuitarBend()) {
274 291 : if (spanner == item) {
275 114 : indexOfThis = bendCount;
276 : }
277 291 : ++bendCount;
278 : }
279 : }
280 : }
281 :
282 201 : bool up = indexOfThis >= floor(double(bendCount) / 2);
283 201 : layoutData->setUp(up);
284 : }
285 :
286 1095 : void GuitarBendLayout::computeIsInside(GuitarBend* item)
287 : {
288 1095 : GuitarBend::LayoutData* layoutData = item->mutldata();
289 1095 : Note* startN = item->startNote();
290 1095 : Chord* startChord = startN->chord();
291 1095 : Note* endN = item->endNote();
292 1095 : Chord* endChord = endN->chord();
293 :
294 1095 : if (startChord->notes().size() <= 2 || endChord->notes().size() <= 2) {
295 1035 : layoutData->setIsInside(false);
296 1035 : return;
297 : }
298 :
299 102 : if (startN == startChord->upNote() || startN == startChord->downNote()
300 102 : || endN == endChord->upNote() || endN == endChord->downNote()) {
301 39 : layoutData->setIsInside(false);
302 39 : return;
303 : }
304 :
305 21 : layoutData->setIsInside(true);
306 : }
307 :
308 3285 : void GuitarBendLayout::avoidBadStaffLineIntersection(GuitarBendSegment* item, PointF& point)
309 : {
310 3285 : int upSign = item->guitarBend()->ldata()->up() ? -1 : 1;
311 3285 : double spatium = item->spatium();
312 3285 : double lineDist = spatium * item->staff()->lineDistance(item->tick());
313 3285 : double closestStaffLineY = lineDist * round(point.y() / lineDist);
314 :
315 3285 : bool isInsideStaff = closestStaffLineY >= 0 && closestStaffLineY <= 4 * spatium;
316 3285 : if (isInsideStaff && item->autoplace()) {
317 1855 : const double minLineDist = 0.5 * item->style().styleAbsolute(Sid::staffLineWidth) + 0.15 * spatium;
318 1855 : double pointLineDist = point.y() - closestStaffLineY;
319 1855 : if (std::abs(pointLineDist) < minLineDist) {
320 564 : point.setY(closestStaffLineY + upSign * minLineDist);
321 : }
322 : }
323 3285 : }
324 :
325 1095 : void GuitarBendLayout::adjustX(GuitarBendSegment* item, PointF& startPos, PointF& endPos, const Note* startNote, const Note* endNote)
326 : {
327 1095 : GuitarBend* bend = item->guitarBend();
328 1095 : GuitarBend::LayoutData* layoutData = bend->mutldata();
329 1095 : bool up = item->guitarBend()->ldata()->up();
330 1095 : Chord* startChord = startNote->chord();
331 1095 : Chord* endChord = endNote->chord();
332 :
333 2169 : bool adjustStart = item->isSingleBeginType() && ((startChord->stem() && startChord->up() && up)
334 1074 : || (up && startNote != startChord->upNote())
335 1026 : || (!up && startNote != startChord->downNote()));
336 2175 : bool adjustEnd = item->isSingleEndType() && ((endChord->stem() && !endChord->up() && !up)
337 1080 : || (up && endNote != endChord->upNote())
338 1023 : || (!up && endNote != endChord->downNote()));
339 :
340 1095 : if (!layoutData->isInside() && !adjustStart && !adjustEnd) {
341 987 : return;
342 : }
343 :
344 108 : const double vertMargin = 0.1 * item->spatium();
345 108 : const double padding = 0.2 * item->spatium();
346 :
347 108 : if (item->isSingleBeginType()) {
348 216 : Shape startChordShape = startChord->shape().translate(
349 216 : startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos());
350 108 : double pointToClear = startChordShape.rightMostEdgeAtHeight(startPos.y() - vertMargin, startPos.y() + vertMargin);
351 108 : pointToClear += padding;
352 108 : double resultingX = std::max(startPos.x(), pointToClear);
353 108 : startPos.setX(resultingX);
354 108 : }
355 :
356 108 : if (item->isSingleEndType()) {
357 216 : Shape endChordShape = endChord->shape().translate(
358 216 : endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos());
359 108 : double pointToClear = endChordShape.leftMostEdgeAtHeight(endPos.y() - vertMargin, endPos.y() + vertMargin);
360 108 : pointToClear -= padding;
361 108 : double resultingX = std::min(endPos.x(), pointToClear);
362 108 : endPos.setX(resultingX);
363 108 : }
364 : }
365 :
366 52 : void GuitarBendLayout::layoutSlightBend(GuitarBendSegment* item, LayoutContext&)
367 : {
368 52 : GuitarBend* bend = item->guitarBend();
369 52 : Note* startNote = bend->startNote();
370 52 : assert(startNote);
371 :
372 52 : Chord* startChord = startNote->chord();
373 52 : PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
374 :
375 52 : double spatium = item->spatium();
376 52 : PointF startPos = startNotePos + PointF(startNote->width() + 0.25 * spatium, -0.25 * spatium);
377 52 : PointF endPos(1.25 * spatium, -1.0 * spatium);
378 52 : PointF curve(endPos.x(), 0.0);
379 :
380 52 : item->setPos(startPos);
381 52 : item->setPos2(endPos);
382 52 : item->mutldata()->setVertexPoint(curve);
383 :
384 52 : item->mutldata()->setBbox(RectF(PointF(), item->pos2()).normalized());
385 :
386 52 : PainterPath path;
387 52 : path.cubicTo(PointF(0.0, 0.0), curve + item->vertexPointOff(), item->pos2());
388 52 : item->mutldata()->setPath(path);
389 52 : }
390 :
391 94 : void GuitarBendLayout::checkConflictWithOtherBends(GuitarBendSegment* item)
392 : {
393 94 : GuitarBend* thisBend = item->guitarBend();
394 94 : Note* startNote = thisBend->startNote();
395 94 : Chord* startChord = startNote->chord();
396 :
397 94 : GuitarBend* otherBend = nullptr;
398 194 : for (Note* note : startChord->notes()) {
399 103 : if (note->string() >= startNote->string()) {
400 97 : continue;
401 : }
402 6 : GuitarBend* bendFor = note->bendFor();
403 6 : if (bendFor) {
404 3 : otherBend = bendFor;
405 3 : break;
406 : }
407 : }
408 :
409 94 : if (!otherBend) {
410 91 : return;
411 : }
412 :
413 3 : if (otherBend->ldata()->bendDigit() != thisBend->ldata()->bendDigit()) {
414 1 : if (thisBend->bendType() != GuitarBendType::PRE_BEND) {
415 1 : GuitarBendSegment::LayoutData* ldata = item->mutldata();
416 1 : double spatium = item->spatium();
417 1 : PointF endPointMove(spatium, spatium);
418 1 : item->setPos2(item->pos2() + endPointMove);
419 1 : item->mutldata()->arrow().translate(endPointMove);
420 1 : ldata->setVertexPoint(ldata->vertexPoint() + PointF(endPointMove.x(), 0.0));
421 :
422 1 : GuitarBendText* text = item->bendText();
423 1 : text->mutldata()->setPos(text->ldata()->pos() + endPointMove + PointF(0.35 * text->width(), 0.0));
424 : }
425 : }
426 : }
427 :
428 164 : bool GuitarBendLayout::startOnEndNote(GuitarBend* bend)
429 : {
430 164 : GuitarBendType bendT = bend->bendType();
431 164 : if (!(bendT == GuitarBendType::PRE_BEND || bendT == GuitarBendType::GRACE_NOTE_BEND || bendT == GuitarBendType::PRE_DIVE)) {
432 118 : return false;
433 : }
434 :
435 46 : bool preBendsAlignToGrace = bend->style().styleB(Sid::alignPreBendAndPreDiveToGraceNote);
436 46 : if (!preBendsAlignToGrace && (bendT == GuitarBendType::PRE_BEND || bendT == GuitarBendType::PRE_DIVE)) {
437 18 : return true;
438 : }
439 :
440 28 : if (bendT == GuitarBendType::PRE_BEND || bendT == GuitarBendType::GRACE_NOTE_BEND) {
441 28 : Chord* startChord = bend->startNote()->chord();
442 28 : size_t bendsCount = 0;
443 56 : for (Note* note : startChord->notes()) {
444 28 : if (note->bendFor()) {
445 28 : ++bendsCount;
446 : }
447 : }
448 :
449 28 : if (bendsCount < bend->endNote()->chord()->notes().size()) {
450 0 : return true;
451 : }
452 : }
453 :
454 28 : return false;
455 : }
456 :
457 94 : void GuitarBendLayout::layoutTabStaff(GuitarBendSegment* item, LayoutContext& ctx)
458 : {
459 94 : if (item->guitarBend()->isDive()) {
460 0 : GuitarDiveLayout::layoutDiveTabStaff(item, ctx);
461 : } else {
462 94 : layoutBendTabStaff(item, ctx);
463 : }
464 94 : }
465 :
466 94 : void GuitarBendLayout::layoutBendTabStaff(GuitarBendSegment* item, LayoutContext& ctx)
467 : {
468 94 : GuitarBend* bend = item->guitarBend();
469 94 : GuitarBendSegment::LayoutData* ldata = item->mutldata();
470 :
471 94 : Note* startNote = bend->startNote();
472 94 : Note* endNote = bend->endNote();
473 94 : if (!startNote || !endNote) {
474 0 : return;
475 : }
476 :
477 94 : double spatium = bend->spatium();
478 94 : const MStyle& style = item->style();
479 94 : const double verticalPad = 0.25 * spatium;
480 94 : const double distAboveTab = style.styleD(Sid::guitarBendHeightAboveTABStaff) * spatium * item->staff()->lineDistance(item->tick());
481 94 : const double arrowWidth = style.styleAbsolute(Sid::guitarBendArrowWidth);
482 94 : const double arrowHeight = style.styleAbsolute(Sid::guitarBendArrowHeight);
483 94 : const double lineWidth = item->lineWidth();
484 :
485 94 : PointF startPos = PointF(0.0, 0.0);
486 94 : PointF endPos = PointF(0.0, 0.0);
487 94 : PointF vertex = PointF(0.0, 0.0);
488 94 : PolygonF arrow;
489 :
490 94 : GuitarBend* prevBend = bend->findPrecedingBend();
491 29 : GuitarBendSegment* prevBendSeg = prevBend && !prevBend->segmentsEmpty() && !prevBend->isFullRelease()
492 123 : ? toGuitarBendSegment(prevBend->backSegment()) : nullptr;
493 29 : GuitarBendHoldSegment* prevHoldLine = prevBend && prevBend->holdLine() && !prevBend->holdLine()->segmentsEmpty()
494 6 : && prevBend->endNote() != bend->startNote()
495 123 : ? toGuitarBendHoldSegment(prevBend->holdLine()->backSegment()) : nullptr;
496 :
497 94 : PointF prevEndPoint = PointF(0.0, 0.0);
498 94 : if (prevHoldLine) {
499 6 : prevEndPoint = prevHoldLine->pos2() + prevHoldLine->pos();
500 88 : } else if (prevBendSeg) {
501 18 : const PolygonF& prevArrow = prevBendSeg->ldata()->arrow();
502 18 : if (!prevArrow.empty()) {
503 18 : prevEndPoint = prevArrow.front() + prevBendSeg->pos();
504 18 : prevEndPoint += PointF(0.0, (prevBend->isReleaseBend() ? -0.5 : 0.5) * lineWidth);
505 : }
506 : }
507 :
508 94 : if (item->isSingleBeginType() && bend->bendType() != GuitarBendType::PRE_BEND && !prevEndPoint.isNull()) {
509 24 : startPos = prevEndPoint;
510 : } else {
511 70 : startPos = computeStartPos(item, startNote, distAboveTab, verticalPad, arrowHeight);
512 : }
513 :
514 94 : endPos = computeEndPos(item, endNote, distAboveTab, verticalPad, arrowHeight, arrowWidth, startPos, prevEndPoint);
515 94 : endPos.setX(std::max(startPos.x(), endPos.x())); // Edge case for ultra-ultra-narrow situations: ensure bend doesn't end before its start
516 :
517 103 : vertex = bend->bendType() == GuitarBendType::PRE_BEND && !bend->angledPreBend()
518 103 : ? 0.5 * (startPos + endPos) : PointF(endPos.x(), startPos.y());
519 :
520 94 : arrow = bend->isReleaseBend()
521 16 : ? arrow << PointF(0, arrowHeight) << PointF(arrowWidth * .5, 0.0) << PointF(-arrowWidth * .5, 0.0) // arrow-down
522 110 : : arrow << PointF(0, -arrowHeight) << PointF(arrowWidth * .5, 0.0) << PointF(-arrowWidth * .5, 0.0); // arrow-up
523 :
524 94 : if (bend->isReleaseBend() && !endNote->ghost()) {
525 0 : endNote->setGhost(true);
526 0 : endNote->mutldata()->reset();
527 0 : TLayout::layoutChord(endNote->chord(), ctx);
528 : }
529 :
530 94 : if (startOnEndNote(bend)) {
531 9 : if (!startNote->overrideBendVisibilityRules()) {
532 9 : startNote->setVisible(false);
533 : }
534 9 : if (!endNote->overrideBendVisibilityRules()) {
535 9 : endNote->setVisible(true);
536 : }
537 85 : } else if (bend->bendType() != GuitarBendType::SLIGHT_BEND
538 85 : && (!style.styleB(Sid::showFretOnFullBendRelease) || !bend->isFullRelease())) {
539 62 : if (bend->bendType() == GuitarBendType::GRACE_NOTE_BEND || bend->bendType() == GuitarBendType::PRE_BEND) {
540 15 : if (!startNote->overrideBendVisibilityRules()) {
541 15 : startNote->setVisible(true);
542 : }
543 : }
544 62 : if (!endNote->overrideBendVisibilityRules()) {
545 62 : endNote->setVisible(false);
546 : }
547 : }
548 :
549 94 : item->setPos(startPos);
550 94 : item->setPos2(endPos - startPos);
551 94 : ldata->setVertexPoint(vertex - startPos);
552 :
553 94 : arrow.translate(item->pos2());
554 94 : item->mutldata()->setArrow(arrow);
555 :
556 94 : GuitarBendText* guitarBendText = item->bendText();
557 94 : guitarBendText->setXmlText(bend->ldata()->bendDigit());
558 94 : TextLayout::layoutBaseTextBase(toTextBase(guitarBendText), ctx);
559 94 : double verticalTextPad = 0.2 * spatium;
560 94 : PointF centering(-0.5 * guitarBendText->width(),
561 188 : (bend->isReleaseBend() ? verticalTextPad : -(guitarBendText->height() + verticalTextPad)));
562 94 : guitarBendText->mutldata()->setPos(arrow.front() + centering);
563 :
564 94 : GuitarBendLayout::checkConflictWithOtherBends(item);
565 :
566 94 : PainterPath path;
567 94 : if (bend->angledPreBend()) {
568 0 : path.lineTo(ldata->vertexPoint() + item->vertexPointOff());
569 0 : path.lineTo(item->pos2());
570 : } else {
571 94 : path.cubicTo(PointF(0.0, 0.0), ldata->vertexPoint() + item->vertexPointOff(), item->pos2());
572 : }
573 94 : ldata->setPath(path);
574 :
575 94 : RectF r;
576 94 : if (muse::RealIsNull(item->pos2().x())) {
577 13 : r = RectF(-0.5 * lineWidth, 0.0, lineWidth, arrow.front().y());
578 : } else {
579 81 : r = RectF(PointF(), PointF(arrow[1].x(), arrow[0].y()));
580 : }
581 94 : item->mutldata()->setBbox(r.normalized());
582 94 : }
583 :
584 70 : PointF GuitarBendLayout::computeStartPos(GuitarBendSegment* item, Note* startNote, double distAboveTab, double verticalPad,
585 : double arrowHeight)
586 : {
587 70 : GuitarBend* bend = item->guitarBend();
588 70 : Chord* startChord = startNote->chord();
589 70 : PointF startNotePos = startNote->pos() + startChord->pos() + startChord->segment()->pos() + startChord->measure()->pos();
590 :
591 70 : const double spatium = item->spatium();
592 70 : const double lineWidth = item->lineWidth();
593 70 : const double horizontalIndent = 0.25 * spatium;
594 70 : System* system = item->system();
595 :
596 70 : PointF startPos;
597 :
598 70 : if (startOnEndNote(bend)) {
599 9 : Note* endN = bend->endNote();
600 9 : startPos = endN->systemPos();
601 9 : Chord* endChord = endN->chord();
602 9 : bool startFromTheSide = bend->bendType() == GuitarBendType::GRACE_NOTE_BEND
603 9 : || (endChord->notes().size() > 1 && endN->string() > endChord->upString());
604 9 : if (startFromTheSide) {
605 0 : startPos += PointF(endN->width() + horizontalIndent, -0.5 * endN->height() + 0.5 * lineWidth);
606 : } else {
607 9 : startPos += PointF(0.5 * endN->width(), -0.5 * endN->height() - verticalPad);
608 : }
609 9 : return startPos;
610 : }
611 :
612 61 : if (bend->bendType() == GuitarBendType::PRE_BEND) {
613 0 : startPos = startNote->systemPos();
614 0 : startPos += PointF(0.5 * startNote->width(), -0.5 * startNote->height() - verticalPad);
615 0 : return startPos;
616 : }
617 :
618 61 : if (bend->isReleaseBend()) {
619 1 : if (item->isSingleBeginType()) {
620 1 : startPos.setX(startNotePos.x());
621 1 : startPos.setY(-distAboveTab - arrowHeight + 0.5 * lineWidth);
622 : } else {
623 0 : startPos.setX(system->firstNoteRestSegmentX(true));
624 0 : startPos.setY(-distAboveTab);
625 : }
626 1 : return startPos;
627 : }
628 :
629 60 : if (item->isSingleBeginType()) {
630 60 : startPos = startNotePos;
631 60 : startPos += PointF(startNote->width() + horizontalIndent, -0.5 * startNote->height() + 0.5 * lineWidth);
632 : } else {
633 0 : startPos.setX(system->firstNoteRestSegmentX(true));
634 0 : startPos.setY(0.5 * spatium);
635 : }
636 :
637 60 : return startPos;
638 : }
639 :
640 94 : PointF GuitarBendLayout::computeEndPos(GuitarBendSegment* item, Note* endNote, double distAboveTab, double verticalPad, double arrowHeight,
641 : double arrowWidth, const PointF& startPos, const PointF& prevEndPoint)
642 : {
643 94 : GuitarBend* bend = item->guitarBend();
644 94 : Chord* endChord = endNote->chord();
645 94 : PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
646 :
647 94 : const MStyle& style = item->style();
648 94 : const double partialBendHeight = style.styleAbsolute(Sid::guitarBendPartialBendHeight);
649 94 : const double spatium = item->spatium();
650 94 : System* system = item->system();
651 :
652 94 : PointF endPos;
653 :
654 94 : if (bend->bendType() == GuitarBendType::PRE_BEND && !bend->angledPreBend()) {
655 9 : endPos = PointF(startPos.x(), -distAboveTab); // TODO: style
656 9 : return endPos;
657 : }
658 :
659 85 : if (bend->isReleaseBend()) {
660 16 : if (item->isSingleEndType()) {
661 16 : endPos = endNotePos;
662 16 : endPos += PointF(0.5 * endNote->width(), 0.0);
663 16 : if (bend->isFullRelease()) {
664 7 : endPos.setY(endPos.y() - 0.5 * endNote->height() - arrowHeight - verticalPad);
665 : } else {
666 9 : endPos.setY(startPos.y() + partialBendHeight);
667 : }
668 : } else {
669 0 : endPos.setX(system->endingXForOpenEndedLines() - 0.5 * arrowWidth);
670 0 : endPos.setY(-arrowHeight);
671 : }
672 16 : return endPos;
673 : }
674 :
675 69 : if (bend->bendType() == GuitarBendType::SLIGHT_BEND) {
676 16 : const double slightBendWidth = 1.25 * spatium;
677 16 : endPos = startPos + PointF(slightBendWidth, 0.0);
678 : } else {
679 53 : if (item->isSingleEndType()) {
680 53 : endPos = endNotePos;
681 53 : endPos += PointF(0.5 * endNote->width(), 0.0);
682 : } else {
683 0 : endPos.setX(system->endingXForOpenEndedLines() - 0.5 * arrowWidth);
684 : }
685 : }
686 69 : endPos = PointF(endPos.x(), prevEndPoint.isNull() ? -distAboveTab : startPos.y() - partialBendHeight); // TODO: style
687 69 : endPos.setX(std::max(endPos.x(), startPos.x() + 0.75 * spatium));
688 :
689 69 : return endPos;
690 : }
691 :
692 6 : void GuitarBendLayout::layoutHoldLine(GuitarBendHoldSegment* item)
693 : {
694 6 : if (!item->system()) {
695 0 : return;
696 : }
697 :
698 6 : GuitarBend* startBend = item->guitarBendHold()->guitarBend();
699 6 : if (!startBend || startBend->segmentsEmpty()) {
700 0 : return;
701 : }
702 :
703 6 : const double spatium = item->spatium();
704 6 : const double lineWidth = item->lineWidth();
705 :
706 6 : PointF startPos = PointF();
707 6 : PointF endPos = PointF();
708 :
709 6 : Note* endNote = item->guitarBendHold()->endNote();
710 :
711 6 : Chord* endChord = endNote->chord();
712 6 : PointF endNotePos = endNote->pos() + endChord->pos() + endChord->segment()->pos() + endChord->measure()->pos();
713 :
714 6 : GuitarBendSegment* startBendSegment = nullptr;
715 6 : if (!startBend->isFullRelease() && !startBend->segmentsEmpty()) {
716 6 : startBendSegment = toGuitarBendSegment(startBend->backSegment());
717 : } else {
718 0 : return;
719 : }
720 6 : item->setStartBendSeg(startBendSegment);
721 :
722 6 : GuitarBendSegment* endBendSegment = nullptr;
723 6 : if (item->isSingleEndType()) {
724 6 : GuitarBend* bend = endNote->bendFor();
725 6 : if (bend && bend != startBend && !bend->segmentsEmpty()) {
726 4 : endBendSegment = toGuitarBendSegment(bend->frontSegment());
727 : }
728 : }
729 6 : item->setEndBendSeg(endBendSegment);
730 :
731 6 : if (item->isSingleBeginType()) {
732 6 : startPos = startBendSegment->pos() + (startBend->isDive() ? startBendSegment->pos2() : startBendSegment->ldata()->arrow().front());
733 6 : if (!startBend->isDive()) {
734 6 : double gap = item->dashLength() * lineWidth;
735 6 : startPos += PointF(gap, (startBend->isReleaseBend() ? -0.5 : +0.5) * lineWidth);
736 : }
737 : } else {
738 0 : startPos.setX(item->system()->firstNoteRestSegmentX(true));
739 0 : startPos.setY(item->guitarBendHold()->frontSegment()->pos().y());
740 : }
741 :
742 6 : if (item->isSingleEndType()) {
743 6 : if (endBendSegment) {
744 4 : GuitarBend* endBend = endBendSegment->guitarBend();
745 4 : if (endBend->bendType() == GuitarBendType::PRE_DIVE || endBend->bendType() == GuitarBendType::PRE_BEND) {
746 0 : Note* note = startOnEndNote(endBend) ? endBend->endNote() : endBend->startNote();
747 0 : endPos.setX(note->systemPos().x() + 0.5 * note->ldata()->bbox().width());
748 : } else {
749 4 : endPos.setX(endNotePos.x());
750 : }
751 : } else {
752 2 : endPos.setX(endNotePos.x() + endNote->chord()->segment()->width() - 1.5 * spatium);
753 : }
754 : } else {
755 0 : endPos.setX(item->system()->endingXForOpenEndedLines());
756 : }
757 :
758 6 : const double minLen = spatium;
759 6 : endPos.setX(std::max(startPos.x() + minLen, endPos.x()));
760 :
761 6 : endPos.setY(startPos.y());
762 :
763 6 : item->setPos(startPos);
764 6 : item->setPos2(endPos - startPos);
765 :
766 6 : if (startBend->dipVibratoType() != VibratoType::NONE) {
767 0 : SymId symId = SymId::noSym;
768 0 : switch (startBend->dipVibratoType()) {
769 0 : case VibratoType::GUITAR_VIBRATO:
770 0 : symId = SymId::guitarVibratoStroke;
771 0 : break;
772 0 : case VibratoType::GUITAR_VIBRATO_WIDE:
773 0 : symId = SymId::guitarWideVibratoStroke;
774 0 : break;
775 0 : default:
776 0 : symId = SymId::noSym;
777 : }
778 0 : double lineLength = item->pos2().x();
779 0 : double symAdvance = item->symAdvance(symId);
780 0 : int symsCount = std::floor(lineLength / symAdvance);
781 0 : symsCount = std::max(symsCount, 1);
782 0 : item->mutldata()->setSymIds(SymIdList(symsCount, symId));
783 0 : item->mutldata()->moveY(0.5 * item->symHeight(symId));
784 0 : RectF bbox = item->symBbox(symId);
785 0 : for (size_t i = 1; i < item->ldata()->symIds().size(); ++i) {
786 0 : bbox.unite(bbox.translated(PointF(symAdvance, 0.0)));
787 : }
788 0 : item->mutldata()->setBbox(bbox);
789 0 : return;
790 : }
791 :
792 6 : RectF r(0.0, -0.5 * lineWidth, item->pos2().x(), lineWidth);
793 6 : item->mutldata()->setBbox(r);
794 : }
|