Loading...
Searching...
No Matches
DefaultGraphicsKnobImpl.hpp
1#pragma once
2#include <score/graphics/DefaultControlImpl.hpp>
3#include <score/graphics/RightClickWidget.hpp>
4#include <score/graphics/InfiniteScroller.hpp>
5#include <score/graphics/widgets/Constants.hpp>
6#include <score/model/Skin.hpp>
7#include <score/tools/Cursor.hpp>
8#include <score/widgets/DoubleSpinBox.hpp>
9#include <score/widgets/SignalUtils.hpp>
10
11#include <ossia/detail/math.hpp>
12
13#include <QDoubleSpinBox>
14#include <QGraphicsProxyWidget>
15#include <QGraphicsScene>
16#include <QGraphicsSceneMouseEvent>
17#include <QGuiApplication>
18#include <QPainter>
19#include <QPointer>
20#include <QScreen>
21#include <QTimer>
22
23namespace score
24{
26{
27 template <typename T>
28 static void paint(
29 T& self, const score::Skin& skin, const QString& text, QPainter* painter,
30 QWidget* widget)
31 {
32 painter->setRenderHint(QPainter::Antialiasing, true);
33
34 constexpr const double adj = 6.;
35 constexpr const double space = 50.;
36 constexpr const double start = (270. - space) * 16.;
37 constexpr const double totalSpan = (360. - 2. * space) * 16.;
38
39 const QRectF srect = defaultKnobSize;
40 const QRectF r = srect.adjusted(adj, adj, -adj, -adj);
41 const double rw = r.width();
42
43 // Draw knob
44 painter->setPen(skin.Emphasis2.main.pen1);
45 painter->setBrush(skin.Emphasis2.main.brush);
46 painter->drawChord(r, start, -totalSpan);
47
48 const double valueSpan = -self.m_value * totalSpan;
49 double textDelta = 0.;
50 if(rw >= 30.)
51 {
52 painter->setPen(skin.Base4.main.pen3_solid_round_round);
53 textDelta = -10;
54 }
55 else if(rw >= 20.)
56 {
57 painter->setPen(skin.Base4.main.pen2_solid_round_round);
58 textDelta = -9;
59 }
60 else if(rw >= 10.)
61 {
62 painter->setPen(skin.Base4.main.pen1_5);
63 textDelta = -8;
64 }
65 else if(rw >= 5.)
66 {
67 painter->setPen(skin.Base4.main.pen1);
68 textDelta = -7;
69 }
70 painter->drawArc(r, start, valueSpan);
71
72 // Draw knob indicator
73 const double r1 = 0.5 * rw;
74 const double x0 = r.center().x();
75 const double y0 = r.center().y();
76 const double theta = -0.0174533 * (start + valueSpan) / 16.;
77 const double x1 = r.center().x() + r1 * cos(theta);
78 const double y1 = r.center().y() + r1 * sin(theta);
79
80 painter->drawLine(QPointF{x0, y0}, QPointF{x1, y1});
81
82 painter->setPen(skin.Base4.lighter180.pen1);
83 if(self.m_hasExec)
84 {
85 const QRectF er = r.adjusted(1.5, 1.5, -1.5, -1.5);
86 const double valueSpan = -self.m_execValue * totalSpan;
87 painter->drawArc(er, start, valueSpan);
88 }
89
90 // Draw text
91 painter->setFont(skin.Medium8Pt);
92 painter->drawText(
93 QRectF{0., srect.height() + textDelta, srect.width(), 10.}, text,
94 QTextOption(Qt::AlignCenter));
95
96 painter->setRenderHint(QPainter::Antialiasing, false);
97 }
98
99 template <typename T>
100 static void mousePressEvent(T& self, QGraphicsSceneMouseEvent* event)
101 {
102 if(event->button() == Qt::LeftButton)
103 {
104 self.m_grab = true;
105 InfiniteScroller::start(self, self.m_value);
106 }
107
108 event->accept();
109 }
110
111 template <typename T>
112 static void mouseMoveEvent(T& self, QGraphicsSceneMouseEvent* event)
113 {
114 if((event->buttons() & Qt::LeftButton) && self.m_grab)
115 {
116 double v = InfiniteScroller::move(event);
117 if(v != self.m_value)
118 {
119 self.m_value = v;
120 self.sliderMoved();
121 self.update();
122 }
123 }
124 event->accept();
125 }
126
127 template <typename T>
128 static void mouseReleaseEvent(T& self, QGraphicsSceneMouseEvent* event)
129 {
130 // Read the value before ending the session: once the relative-motion
131 // session is over, move() falls back to pointer positions, and under a
132 // pointer lock those never left the press point -- applying that as a delta
133 // unwinds the whole drag.
134 if(self.m_grab)
135 {
136 double v = InfiniteScroller::move(event);
137 if(v != self.m_value)
138 {
139 self.m_value = v;
140 self.update();
141 }
142 InfiniteScroller::stop(self, event);
143 }
144
145 self.m_grab = false;
146 self.sliderReleased();
147
148 if(event->button() == Qt::RightButton)
149 {
150 contextMenuEvent(self, event->scenePos());
151 }
152
153 event->accept();
154 }
155
159 template <typename T>
160 static void ungrabMouseEvent(T& self, QEvent* event)
161 {
162 if(!self.m_grab)
163 return;
164
165 self.m_grab = false;
167 self.sliderReleased();
168 }
169
170 template <typename T>
171 requires std::is_integral_v<std::decay_t<decltype(std::declval<T>().value())>>
172 static void contextMenuEvent(T& self, QPointF pos)
173 {
174 auto build = [&, self_p = &self, pos] {
175 // Whatever box is open belongs to the previous right-click.
177
178 auto w = new SpinboxWithEnter;
179 w->setRange(self.min, self.max);
180
181 w->setValue(self.map(self.m_value));
182 auto obj = self.scene()->addWidget(
183 w, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
184 obj->setPos(pos);
186
187#if defined(__EMSCRIPTEN__)
188 w->setFocus();
189#else
190 QTimer::singleShot(0, w, [w] { w->setFocus(); });
191#endif
192
193 auto con = QObject::connect(
194 w, SignalUtils::QSpinBox_valueChanged_int(), &self,
195 [&self, obj, scene = self.scene()](double v) {
196 DefaultControlImpl::editWidgetInContextMenu(self, scene, obj, v);
197 });
198
199 QObject::connect(
200 w, &SpinboxWithEnter::editingFinished, &self, [obj, con, self_p]() mutable {
201 if(obj != nullptr)
202 {
203 self_p->sliderReleased();
204 QObject::disconnect(con);
205 QTimer::singleShot(0, obj, [scene = self_p->scene(), obj] {
206 scene->removeItem(obj);
207 delete obj;
208 });
209 }
210 obj = nullptr;
211 });
212 };
213#if defined(__EMSCRIPTEN__)
214 build();
215#else
216 QTimer::singleShot(0, &self, build);
217#endif
218 }
219
220 template <typename T>
221 requires std::is_floating_point_v<std::decay_t<decltype(std::declval<T>().value())>>
222 static void contextMenuEvent(T& self, QPointF pos)
223 {
224 auto build = [&, self_p = &self, pos] {
225 // Whatever box is open belongs to the previous right-click.
227
228 auto w = new DoubleSpinboxWithEnter;
229 w->setRange(self.min, self.max);
230
231 w->setDecimals(6);
232 w->setValue(self.map(self.m_value));
233 auto obj = self.scene()->addWidget(
234 w, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
235 obj->setPos(pos);
237
238#if defined(__EMSCRIPTEN__)
239 w->setFocus();
240#else
241 QTimer::singleShot(0, w, [w] { w->setFocus(); });
242#endif
243
244 auto con = QObject::connect(
245 w, SignalUtils::QDoubleSpinBox_valueChanged_double(), &self,
246 [&self, obj, scene = self.scene()](double v) {
247 DefaultControlImpl::editWidgetInContextMenu(self, scene, obj, v);
248 });
249
250 QObject::connect(
251 w, &DoubleSpinboxWithEnter::editingFinished, &self,
252 [obj, con, self_p]() mutable {
253 if(obj != nullptr)
254 {
255 self_p->sliderReleased();
256 QObject::disconnect(con);
257 QTimer::singleShot(0, obj, [scene = self_p->scene(), obj] {
258 scene->removeItem(obj);
259 delete obj;
260 });
261 }
262 obj = nullptr;
263 });
264 };
265#if defined(__EMSCRIPTEN__)
266 build();
267#else
268 QTimer::singleShot(0, &self, build);
269#endif
270 }
271
272 template <typename T>
273 static void mouseDoubleClickEvent(T& self, QGraphicsSceneMouseEvent* event)
274 {
275 self.m_value = self.unmap(self.init);
276
277 self.m_grab = true;
278 self.sliderMoved();
279 self.sliderReleased();
280 self.m_grab = false;
281
282 self.update();
283
284 event->accept();
285 }
286};
287}
Definition Skin.hpp:100
Base toolkit upon which the software is built.
Definition Application.cpp:117
QPointer< QGraphicsProxyWidget > & currentRightClickWidget()
The one type-in box a right-click may have open.
Definition GraphicWidgets.cpp:26
void closeRightClickWidget()
Takes down whatever right-click box is open, if any.
Definition RightClickWidget.hpp:23
Definition DefaultGraphicsKnobImpl.hpp:26
static void ungrabMouseEvent(T &self, QEvent *event)
Definition DefaultGraphicsKnobImpl.hpp:160
static void abort(QGraphicsItem &self)
Definition InfiniteScroller.cpp:350
Definition DoubleSpinBox.hpp:9