1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2010-2019 Werner Schweer and others
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 #include "easeinoutcanvas.h"
21 #include "libmscore/easeInOut.h"
22 #include "preferences.h"
23 
24 namespace Ms {
25 
26 //---------------------------------------------------------
27 //   EaseInOutCanvas
28 //---------------------------------------------------------
29 
EaseInOutCanvas(QWidget * parent)30 EaseInOutCanvas::EaseInOutCanvas(QWidget* parent)
31    : QFrame(parent),
32      m_easeIn(0.0),
33      m_easeOut(0.0),
34      m_pitchDelta(0)
35       {
36       setFrameStyle(QFrame::NoFrame);
37       }
38 
39 //---------------------------------------------------------
40 //   paintEvent
41 //---------------------------------------------------------
42 
paintEvent(QPaintEvent * ev)43 void EaseInOutCanvas::paintEvent(QPaintEvent* ev)
44       {
45       // not qreal here, even though elsewhere yes,
46       // because width and height return a number of pixels,
47       // hence integers.
48       const int w = width();
49       const int h = height();
50       const int border = 1;
51       const int nPitches = std::abs(m_pitchDelta) + 1;
52 
53       const qreal graphWidth = static_cast<qreal>(w - 2 * border);
54       const qreal graphHeight = static_cast<qreal>(h - 2 * border);
55       const qreal nbEvents = static_cast<qreal>(m_events.size());
56       const qreal pitchDelta = static_cast<qreal>(nPitches);
57 
58       // let half a column of margin around
59       const qreal leftPos = static_cast<qreal>(border);           // also left margin
60       const qreal topPos = leftPos;                               // also top margin
61       const qreal bottomPos = static_cast<qreal>(h - border);     // bottom end position of graph
62       const qreal rightPos = static_cast<qreal>(w - border);      // right end position of graph
63 
64       EaseInOut eio(static_cast<qreal>(m_easeIn) / 100.0, static_cast<qreal>(m_easeOut) / 100.0);
65 
66       char noteNames[] = ("C D EF G A B");
67 
68       QPainter painter(this);
69       painter.setRenderHint(QPainter::Antialiasing, preferences.getBool(PREF_UI_CANVAS_MISC_ANTIALIASEDDRAWING));
70 
71       painter.fillRect(rect(), QApplication::palette().color(QPalette::Window).lighter());
72       QPen pen = painter.pen();
73       pen.setWidth(1);
74 
75       QColor eventLinesColor(Qt::gray);
76       eventLinesColor.setAlphaF(0.5);
77       QColor pitchLinesColor(Qt::gray);
78       pitchLinesColor.setAlphaF(0.25);
79 
80       // Color scheme based on the MuseScore blue.
81       QColor borderLinesColor, warpLineColor, pitchFillColor, pitchGraphColor, pitchNameColor;
82       if (preferences.isThemeDark()) {
83             pitchFillColor.setRgbF(0.078, 0.284, 0.463);
84             pitchGraphColor.setRgbF(0.125, 0.455, 0.741);
85             warpLineColor.setRgbF(0.621, 0.315, 0.132);
86             borderLinesColor.setRgbF(0.891, 0.932, 0.968);
87             pitchNameColor.setRgbF(0.935, 0.782, 0.691);
88             }
89       else {
90             pitchFillColor.setRgbF(0.891, 0.932, 0.968);
91             pitchGraphColor.setRgbF(0.125, 0.455, 0.741);
92             warpLineColor.setRgbF(0.914, 0.710, 0.588);
93             borderLinesColor.setRgbF(0.016, 0.057, 0.093);
94             pitchNameColor.setRgbF(0.310, 0.157, 0.066);
95             }
96 
97       // this lambda takes as input a pitch value, and determines where what are its x and y coordinates
98       auto getPosition = [graphWidth, graphHeight, leftPos, bottomPos](const QPointF& p) -> QPointF {
99             return { leftPos + p.x() * graphWidth, bottomPos - p.y() * graphHeight };
100             };
101 
102       std::vector<QPointF> pitchPoints;
103       qreal offset = m_pitchDelta < 0 ? 1.0 : 0.0;
104       QPointF prevPoint, currPoint;
105       for (size_t i = 0; i < m_events.size(); i++) {
106             currPoint = getPosition({ eio.XfromY(static_cast<qreal>(i) / nbEvents), offset + static_cast<qreal>(m_events[i]) / static_cast<qreal>(pitchDelta) });
107             pitchPoints.push_back(currPoint);
108             }
109 
110       // Draw the pitches barchart graph in the background first
111       if (pitchPoints.size() > 1) {
112             prevPoint = pitchPoints[0];
113             for (size_t i = 1; i < pitchPoints.size(); i++) {
114                   currPoint = pitchPoints[i];
115                   painter.fillRect(prevPoint.x(), bottomPos, (currPoint.x() - prevPoint.x()) + 1, prevPoint.y() - bottomPos, pitchFillColor);
116                   prevPoint = currPoint;
117                   }
118             painter.fillRect(prevPoint.x(), bottomPos, (rightPos - prevPoint.x()) + 1, prevPoint.y() - bottomPos, pitchFillColor);
119 
120             // draw time-warped vertical lines in lighter gray.
121             // These lines will move as ease-in and ease-out are adjusted.
122             pen.setWidth(0);
123             pen.setColor(eventLinesColor);
124             painter.setPen(pen);
125             for (size_t i = 1; i < pitchPoints.size(); ++i) {
126                   qreal xPos = pitchPoints[i].x();
127                   painter.drawLine(xPos, topPos, xPos, bottomPos);
128                   }
129             }
130 
131       // draw half step horigontal lines in even lighter gray
132       pen.setWidth(0);
133       pen.setColor(pitchLinesColor);
134       painter.setPen(pen);
135       for (int i = 1; i < nPitches; ++i) {
136             qreal yPos = topPos + (static_cast<qreal>(i) / pitchDelta) * graphHeight;
137             painter.drawLine(leftPos, yPos, rightPos, yPos);
138             }
139 
140       // draw note names
141       pen.setColor(pitchNameColor);
142       painter.setPen(pen);
143       QFont font;
144       qreal fontHeight = std::min(12.0, (graphHeight * 0.875) / pitchDelta);
145       font.setPixelSize(fontHeight);
146       painter.setFont(font);
147       int curPitch = m_bottomPitch;
148       for (int i = 0; i <= nPitches; ++i) {
149             QString pitchName(noteNames[(curPitch - 60) % 12]);
150             QPointF pos = { 4, topPos + fontHeight * 0.3 + (1.0 - (static_cast<qreal>(i) / pitchDelta)) * graphHeight };
151             painter.drawText(pos, pitchName);
152             curPitch++;
153             }
154 
155       if (m_events.size()) {
156             // Not a portamento style glissando.
157             // Draw the Bezier transfer curve only in ease-in or ease-out are not zero. This warps
158             // the event times so this curve always go from lower left corner to upper-right corner.
159             if (m_easeIn != 0 || m_easeOut != 0) {
160                   pen.setWidth(2);
161                   pen.setColor(warpLineColor);
162                   painter.setPen(pen);
163                   prevPoint = { leftPos, bottomPos };
164                   for (int i = 1; i <= 33; i++) {
165                         currPoint = getPosition(eio.Eval(static_cast<qreal>(i) / 33.0));
166                         painter.drawLine(prevPoint, currPoint);
167                         prevPoint = currPoint;
168                         }
169                   }
170             }
171       else {
172             // Draw the portamento style glissando curve instead of the transfer curve
173             pen.setWidth(3);
174             pen.setColor(pitchGraphColor);
175             painter.setPen(pen);
176             prevPoint = { leftPos, m_pitchDelta > 0 ? bottomPos : topPos };
177             for (int i = 1; i <= 33; i++) {
178                   currPoint = eio.Eval(static_cast<qreal>(i) / 33.0);
179                   if (m_pitchDelta < 0)
180                         currPoint.setY(1.0 - currPoint.y());
181                   currPoint = getPosition(currPoint);
182                   painter.drawLine(prevPoint, currPoint);
183                   prevPoint = currPoint;
184                   }
185             }
186 
187       // Draw the pitches level lines next so they cover the Bezier transfer curve.
188       if (pitchPoints.size() > 1) {
189             pen.setWidth(3);
190             pen.setCapStyle(Qt::FlatCap);
191             pen.setColor(pitchGraphColor);
192             painter.setPen(pen);
193             prevPoint = pitchPoints[0];
194             for (size_t i = 1; i < pitchPoints.size(); i++) {
195                   currPoint = pitchPoints[i];
196                   painter.drawLine(prevPoint, { currPoint.x(), prevPoint.y() });
197                   prevPoint = currPoint;
198                   }
199             painter.drawLine(prevPoint, { rightPos, prevPoint.y() });
200             }
201 
202       // draw the graph frame after all the other graphics elements to cover them
203       pen.setColor(borderLinesColor);
204       pen.setWidth(1);
205       painter.setPen(pen);
206       painter.drawRect(border, border, w - 2 * border, h - 2 * border);
207 
208       QFrame::paintEvent(ev);
209       }
210 
211 } // namespace Ms
212