Loading...
Searching...
No Matches
DefaultGraphicsSpinboxImpl.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/model/Skin.hpp>
6#include <score/tools/Cursor.hpp>
7#include <score/widgets/DoubleSpinBox.hpp>
8#include <score/widgets/SignalUtils.hpp>
9
10#include <ossia/detail/math.hpp>
11
12#include <QDoubleSpinBox>
13#include <QGraphicsProxyWidget>
14#include <QGraphicsScene>
15#include <QGraphicsSceneMouseEvent>
16#include <QGuiApplication>
17#include <QPainter>
18#include <QPointer>
19#include <QScreen>
20#include <QTimer>
21
22namespace score
23{
25{
26 template <typename T>
27 static void paint(
28 T& self, const score::Skin& skin, const QString& text, QPainter* painter,
29 QWidget* widget)
30 {
31 painter->setRenderHint(QPainter::Antialiasing, true);
32
33 painter->setPen(skin.NoPen);
34 painter->setBrush(skin.Emphasis2.main.brush);
35
36 // Draw rect
37 const QRectF brect = self.boundingRect();
38 painter->drawRoundedRect(brect, 1, 1);
39
40 // Draw text
41 painter->setPen(skin.Base4.main.pen1);
42 painter->setFont(skin.Medium8Pt);
43 const auto textrect = brect.adjusted(2, 3, -2, -2);
44 painter->drawText(textrect, text, QTextOption(Qt::AlignLeft));
45
46 painter->setRenderHint(QPainter::Antialiasing, false);
47 }
48
49 template <typename T>
50 static void mousePressEvent(T& self, QGraphicsSceneMouseEvent* event)
51 {
52 if(event->button() == Qt::LeftButton)
53 {
54 self.m_grab = true;
55 InfiniteScroller::start(self, (self.max - self.min) * self.m_value + self.min);
56 }
57
58 event->accept();
59 }
60
61 // Inverse of the mapping below: the accumulated drag that lands exactly on
62 // `bound`. speed is 1 + |delta|, so the value is orig - (1 + |d|) * d / h and
63 // d follows from the quadratic.
64 static double deltaFor(double orig, double bound, double height) noexcept
65 {
66 const double k = (orig - bound) * height;
67 return k >= 0. ? (-1. + std::sqrt(1. + 4. * k)) / 2.
68 : (1. - std::sqrt(1. - 4. * k)) / 2.;
69 }
70
71 template <typename T>
72 static double mapValue(T& self, QGraphicsSceneMouseEvent* event) noexcept
73 {
74 InfiniteScroller::move_free(event);
75 const auto speed
76 = std::pow(10., std::log10(1. + std::abs(InfiniteScroller::currentDelta)));
77
78 const double height = double(InfiniteScroller::currentGeometry.height());
79 auto v = InfiniteScroller::origValue - speed * InfiniteScroller::currentDelta / height;
80
81 // Hold the accumulator at the end rather than letting it run past: a drag
82 // that overshoots would otherwise have to be walked all the way back before
83 // the value moved again.
84 const double bounded = std::clamp(v, double(self.min), double(self.max));
85 if(bounded != v)
86 {
87 InfiniteScroller::currentDelta
88 = deltaFor(InfiniteScroller::origValue, bounded, height);
89 v = bounded;
90 }
91
92 v = (v - self.min) / (self.max - self.min);
93 return std::clamp(v, 0., 1.);
94 }
95
96 template <typename T>
97 static void mouseMoveEvent(T& self, QGraphicsSceneMouseEvent* event)
98 {
99 if((event->buttons() & Qt::LeftButton) && self.m_grab)
100 {
101 if(const auto v = mapValue(self, event); v != self.m_value)
102 {
103 self.m_value = v;
104 if(!self.m_noValueChangeOnMove)
105 self.sliderMoved();
106 self.update();
107 }
108 }
109 event->accept();
110 }
111
112 template <typename T>
113 static void mouseReleaseEvent(T& self, QGraphicsSceneMouseEvent* event)
114 {
115 if(self.m_grab)
116 {
117 if(const auto v = mapValue(self, event); v != self.m_value)
118 {
119 self.m_value = v;
120 self.update();
121 }
122 InfiniteScroller::stop(self, event);
123 }
124
125 if(self.m_noValueChangeOnMove)
126 self.sliderMoved();
127 self.m_grab = false;
128 self.sliderReleased();
129
130 if(event->button() == Qt::RightButton)
131 {
132 contextMenuEvent(self, event->scenePos());
133 }
134
135 event->accept();
136 }
137
141 template <typename T>
142 static void ungrabMouseEvent(T& self, QEvent* event)
143 {
144 if(!self.m_grab)
145 return;
146
147 self.m_grab = false;
149 if(self.m_noValueChangeOnMove)
150 self.sliderMoved();
151 self.sliderReleased();
152 }
153
154 template <typename T>
155 requires std::is_integral_v<std::decay_t<decltype(std::declval<T>().value())>>
156 static void contextMenuEvent(T& self, QPointF pos)
157 {
158 auto build = [&, self_p = &self, pos] {
159 // Whatever box is open belongs to the previous right-click.
161
162 auto w = new SpinboxWithEnter;
163 w->setRange(self.min, self.max);
164
165 w->setValue(self.map(self.m_value));
166 auto obj = self.scene()->addWidget(
167 w, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
168 obj->setPos(pos);
170
171#if defined(__EMSCRIPTEN__)
172 w->setFocus();
173#else
174 QTimer::singleShot(0, w, [w] { w->setFocus(); });
175#endif
176
177 auto con = QObject::connect(
178 w, SignalUtils::QSpinBox_valueChanged_int(), &self,
179 [&self, obj, scene = self.scene()](double v) {
180 DefaultControlImpl::editWidgetInContextMenu(self, scene, obj, v);
181 });
182
183 QObject::connect(
184 w, &SpinboxWithEnter::editingFinished, &self, [obj, con, self_p]() mutable {
185 if(obj != nullptr)
186 {
187 if(self_p->m_noValueChangeOnMove)
188 self_p->sliderMoved();
189 self_p->sliderReleased();
190 QObject::disconnect(con);
191 QTimer::singleShot(0, obj, [scene = self_p->scene(), obj] {
192 scene->removeItem(obj);
193 delete obj;
194 });
195 }
196 obj = nullptr;
197 });
198 };
199#if defined(__EMSCRIPTEN__)
200 build();
201#else
202 QTimer::singleShot(0, &self, build);
203#endif
204 }
205
206 template <typename T>
207 requires std::is_floating_point_v<std::decay_t<decltype(std::declval<T>().value())>>
208 static void contextMenuEvent(T& self, QPointF pos)
209 {
210 // FIXME to be safe we have to locate the object by path on every click as
211 // some control changes may cause entire GUI rebuilds
212 auto build = [&, self_p = &self, pos] {
213 // Whatever box is open belongs to the previous right-click.
215
216 auto w = new DoubleSpinboxWithEnter;
217 w->setRange(self.min, self.max);
218
219 w->setDecimals(6);
220 w->setValue(self.map(self.m_value));
221 auto obj = self.scene()->addWidget(
222 w, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
223 obj->setPos(pos);
225
226#if defined(__EMSCRIPTEN__)
227 w->setFocus();
228#else
229 QTimer::singleShot(0, w, [w] { w->setFocus(); });
230#endif
231
232 auto con = QObject::connect(
233 w, SignalUtils::QDoubleSpinBox_valueChanged_double(), &self,
234 [&self, obj, scene = self.scene()](double v) {
235 DefaultControlImpl::editWidgetInContextMenu(self, scene, obj, v);
236 });
237
238 QObject::connect(
239 w, &DoubleSpinboxWithEnter::editingFinished, &self,
240 [obj, con, self_p]() mutable {
241 if(obj != nullptr)
242 {
243 if(self_p->m_noValueChangeOnMove)
244 self_p->sliderMoved();
245 self_p->sliderReleased();
246 QObject::disconnect(con);
247 QTimer::singleShot(0, obj, [scene = self_p->scene(), obj] {
248 scene->removeItem(obj);
249 delete obj;
250 });
251 }
252 obj = nullptr;
253 });
254 };
255#if defined(__EMSCRIPTEN__)
256 build();
257#else
258 QTimer::singleShot(0, &self, build);
259#endif
260 }
261
262 template <typename T>
263 static void mouseDoubleClickEvent(T& self, QGraphicsSceneMouseEvent* event)
264 {
265 self.m_value = self.unmap(self.init);
266
267 self.m_grab = true;
268 self.sliderMoved();
269 self.sliderReleased();
270 self.m_grab = false;
271
272 self.update();
273
274 event->accept();
275 }
276};
277}
Definition Skin.hpp:94
Base toolkit upon which the software is built.
Definition Application.cpp:113
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 DefaultGraphicsSpinboxImpl.hpp:25
static void ungrabMouseEvent(T &self, QEvent *event)
Definition DefaultGraphicsSpinboxImpl.hpp:142
static void abort(QGraphicsItem &self)
Definition InfiniteScroller.cpp:350
Definition DoubleSpinBox.hpp:9