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) 2025 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 "notationruler.h"
24 :
25 : #include "draw/types/color.h"
26 : #include "draw/types/drawtypes.h"
27 :
28 : #include "engraving/dom/measure.h"
29 : #include "engraving/dom/score.h"
30 : #include "engraving/dom/segment.h"
31 : #include "engraving/dom/staff.h"
32 : #include "engraving/dom/sig.h"
33 : #include "engraving/dom/system.h"
34 : #include "engraving/dom/timesig.h"
35 :
36 : #include "ui/view/iconcodes.h"
37 : #include "draw/painter.h"
38 :
39 : using namespace mu::notation;
40 : using namespace mu::engraving;
41 : using namespace muse::draw;
42 :
43 0 : void NotationRuler::paint(Painter* painter, const NoteInputState& state)
44 : {
45 0 : TRACEFUNC;
46 :
47 0 : if (!state.isValid()) {
48 0 : return;
49 : }
50 :
51 0 : const Staff* staff = state.staff();
52 0 : const Segment* currSegment = state.segment();
53 0 : const System* system = currSegment->system();
54 0 : const Measure* measure = currSegment->measure();
55 0 : const SysStaff* sysStaff = system ? system->staff(state.staffIdx()) : nullptr;
56 0 : const TimeSig* timeSig = staff ? staff->timeSig(state.tick()) : nullptr;
57 :
58 0 : if (!measure || !sysStaff || !timeSig) {
59 0 : return;
60 : }
61 :
62 0 : const muse::PointF measurePos = measure->canvasPos();
63 0 : const size_t lineCount = timeSig->numerator() * 2;
64 0 : const double spatium = staff->score()->style().spatium();
65 0 : const double aboveStaffSpacing = spatium * 2;
66 0 : const double lineY = measurePos.y() + sysStaff->y() - aboveStaffSpacing;
67 :
68 0 : if (state.beyondScore()) {
69 0 : const Measure* lastMeasure = state.staff()->score()->lastMeasure();
70 0 : const double lineX = lastMeasure->pageBoundingRect().right() + spatium;
71 0 : paintLine(painter, LineType::CurrentPosition, PointF(lineX, lineY), spatium, state.voice());
72 0 : return;
73 : }
74 :
75 0 : const int inputTicks = state.tick().ticks();
76 0 : const int measureTicks = measure->tick().ticks();
77 0 : const int ticksBeat = mu::engraving::ticks_beat(timeSig->denominator());
78 0 : const int subdivisionTicks = ticksBeat / 2;
79 :
80 0 : std::map<size_t, const Segment*> lineToSegment;
81 :
82 0 : for (const Segment& segment : measure->segments()) {
83 0 : if (!segment.isChordRestType()) {
84 0 : continue;
85 : }
86 :
87 0 : const int segmentTicks = segment.tick().ticks();
88 0 : if (segmentTicks % subdivisionTicks != 0) {
89 0 : continue; // this segment does not correspond to any line
90 : }
91 :
92 0 : const size_t line = (segmentTicks - measureTicks) / subdivisionTicks;
93 0 : lineToSegment[line] = &segment;
94 : }
95 :
96 0 : size_t prevAccurateLine = muse::nidx;
97 0 : bool currentPositionPainted = false;
98 :
99 0 : for (size_t i = 0; i < lineCount; ++i) {
100 0 : const int lineTicks = measureTicks + subdivisionTicks * static_cast<int>(i);
101 0 : double lineX = 0.0;
102 :
103 0 : auto it = lineToSegment.find(i);
104 0 : if (it != lineToSegment.end()) {
105 0 : lineX = measurePos.x() + it->second->x();
106 0 : prevAccurateLine = i;
107 0 : } else if (prevAccurateLine != muse::nidx) {
108 0 : auto prevIt = lineToSegment.find(prevAccurateLine);
109 0 : auto nextIt = std::next(prevIt);
110 :
111 0 : if (nextIt != lineToSegment.end()) {
112 0 : const size_t dist = nextIt->first - prevIt->first;
113 0 : const double segmentWidth = (nextIt->second->x() - prevIt->second->x()) / static_cast<double>(dist);
114 0 : lineX = measurePos.x() + prevIt->second->x() + segmentWidth * (i % dist);
115 : } else {
116 0 : const size_t dist = lineCount - prevIt->first;
117 0 : const double segmentWidth = (measure->width() - prevIt->second->x()) / static_cast<double>(dist);
118 0 : lineX = measurePos.x() + prevIt->second->x() + segmentWidth * (i % dist);
119 : }
120 : } else {
121 0 : UNREACHABLE;
122 : continue;
123 : }
124 :
125 0 : const LineType type = lineType(lineTicks, inputTicks, i);
126 0 : paintLine(painter, type, PointF(lineX, lineY), spatium, state.voice());
127 0 : currentPositionPainted |= type == LineType::CurrentPosition;
128 : }
129 :
130 0 : if (!currentPositionPainted) {
131 0 : const double lineX = measurePos.x() + currSegment->x();
132 0 : paintLine(painter, LineType::CurrentPosition, PointF(lineX, lineY), spatium, state.voice());
133 : }
134 0 : }
135 :
136 0 : NotationRuler::LineType NotationRuler::lineType(int lineTicks, int inputTicks, size_t lineIdx)
137 : {
138 0 : if (lineTicks == inputTicks) {
139 0 : return LineType::CurrentPosition;
140 : }
141 :
142 0 : return lineIdx % 2 == 0 ? LineType::MainBeat : LineType::Subdivision;
143 : }
144 :
145 0 : void NotationRuler::paintLine(Painter* painter, LineType type, const PointF& point, double spatium, voice_idx_t voiceIdx)
146 : {
147 0 : muse::RectF rect;
148 0 : muse::Color color = configuration()->selectionColor(voiceIdx);
149 :
150 0 : switch (type) {
151 0 : case LineType::CurrentPosition:
152 0 : rect.setHeight(spatium * 4.);
153 0 : rect.setWidth(spatium * 2.);
154 0 : rect.setX(point.x() - rect.width() / 2);
155 0 : color.setAlpha(192);
156 0 : break;
157 0 : case LineType::MainBeat:
158 0 : rect.setHeight(spatium * 2.);
159 0 : rect.setWidth(spatium / 6.);
160 0 : rect.setX(point.x());
161 0 : color.setAlpha(192);
162 0 : break;
163 0 : case LineType::Subdivision:
164 0 : rect.setHeight(spatium);
165 0 : rect.setWidth(spatium / 6.);
166 0 : rect.setX(point.x());
167 0 : color.setAlpha(128);
168 0 : break;
169 : }
170 :
171 0 : rect.setY(point.y() - rect.height());
172 :
173 0 : if (type == LineType::CurrentPosition) {
174 0 : Font font(uiConfiguration()->iconsFontFamily(), Font::Type::Icon);
175 0 : font.setPointSizeF(spatium / 6.);
176 :
177 0 : painter->setPen(color);
178 0 : painter->setFont(font);
179 0 : painter->drawText(rect, AlignHCenter | AlignBottom, TextDontClip,
180 0 : Char(static_cast<char16_t>(muse::ui::IconCode::Code::DURATION_CURSOR)));
181 0 : } else {
182 0 : painter->fillRect(rect, color);
183 : }
184 0 : }
|