Loading...
Searching...
No Matches
DefaultGraphicsSliderImpl.hpp
1#pragma once
2#include <score/graphics/WidgetPresentation.hpp>
3#include <score/graphics/DefaultControlImpl.hpp>
4#include <score/graphics/RightClickWidget.hpp>
5#include <score/model/Skin.hpp>
6#include <score/widgets/DoubleSpinBox.hpp>
7#include <score/widgets/SignalUtils.hpp>
8
9#include <ossia/detail/math.hpp>
10
11#include <QGraphicsProxyWidget>
12#include <QGraphicsScene>
13#include <QGraphicsSceneMouseEvent>
14#include <QGuiApplication>
15#include <QKeyEvent>
16#include <QPainter>
17#include <QPointer>
18#include <QTimer>
19
20#include <score_lib_base_export.h>
21
22#include <verdigris>
23namespace score
24{
25
27{
28 QPointer<DoubleSpinboxWithEnter> spinbox{};
29 QPointer<QGraphicsProxyWidget> spinboxProxy{};
30};
31
33{
34 template <typename T>
35 static void paint(
36 T& self, const score::Skin& skin, const QString& text, QPainter* painter,
37 QWidget* widget, const QStyleOptionGraphicsItem* option = nullptr)
38 {
39 painter->setRenderHint(QPainter::Antialiasing, false);
40
41 painter->setPen(skin.NoPen);
42 painter->setBrush(skin.Emphasis2.main.brush);
43
44 // Draw rect
45 const QRectF brect = self.boundingRect();
46 const QRectF srect = self.sliderRect();
47 painter->drawRoundedRect(srect, 1, 1);
48
49 // Draw text
50#if defined(__linux__)
51 // widget is null when painting through QGraphicsScene::render
52 static const auto dpi_adjust
53 = (widget ? widget->devicePixelRatioF() : painter->device()->devicePixelRatioF())
54 > 1
55 ? 0
56 : -1;
57#elif defined(_WIN32)
58 static const constexpr auto dpi_adjust = 0;
59#else
60 static const constexpr auto dpi_adjust = -2;
61#endif
62 // Non-item wrappers (multi-slider rows) always show their value
63 bool showValue = true;
64 if constexpr(std::is_base_of_v<QGraphicsItem, std::remove_cvref_t<T>>)
65 {
66 bool grabbed = false;
67 if constexpr(requires { self.m_grab; })
68 grabbed = self.m_grab;
69 showValue = showsValue(self, grabbed, option);
70 }
71 if(showValue)
72 {
73 painter->setPen(skin.Base4.lighter180.pen1);
74 painter->setFont(skin.Medium8Pt);
75 const auto textrect
76 = brect.adjusted(2, srect.height() + 2 + dpi_adjust, -2, -1);
77 painter->drawText(textrect, text, QTextOption(Qt::AlignCenter));
78 }
79
80 // Draw handle
81 painter->fillRect(self.handleRect(), skin.Base4);
82 if(self.m_hasExec)
83 painter->fillRect(self.execHandleRect(), skin.Base4.lighter180.brush);
84 // painter->setPen(QPen(Qt::green, 1));
85 // painter->setBrush(QBrush(Qt::transparent));
86 // painter->drawRect(textrect);
87 //
88 // painter->setPen(QPen(Qt::red, 1));
89 // painter->setBrush(QBrush(Qt::transparent));
90 // painter->drawRect(self.boundingRect());
91 }
92
93 template <typename T>
94 static void mousePressEvent(T& self, QGraphicsSceneMouseEvent* event)
95 {
96 if(event->button() == Qt::LeftButton && self.isInHandle(event->pos()))
97 {
98 self.m_grab = true;
99
100 const auto srect = self.sliderRect();
101 const auto posX = event->pos().x() - srect.x();
102 double curPos = self.from01(ossia::clamp(posX, 0., srect.width()) / srect.width());
103 if(curPos != self.m_value)
104 {
105 self.m_value = curPos;
106 self.sliderMoved();
107 self.update();
108 }
109 }
110
111 event->accept();
112 }
113
114 template <typename T>
115 static void mouseMoveEvent(T& self, QGraphicsSceneMouseEvent* event)
116 {
117 if((event->buttons() & Qt::LeftButton) && self.m_grab)
118 {
119 const auto srect = self.sliderRect();
120 const auto posX = event->pos().x() - srect.x();
121 double curPos = self.from01(ossia::clamp(posX, 0., srect.width()) / srect.width());
122 if(curPos != self.m_value)
123 {
124 if constexpr(std::is_same_v<decltype(self.m_value), int>)
125 {
126 self.m_value = curPos;
127 }
128 else
129 {
130 double ratio = qGuiApp->keyboardModifiers() & Qt::CTRL ? 0.1 : 1.;
131 self.m_value = std::clamp(
132 double(self.m_value + ((curPos - self.m_value) * ratio)), 0., 1.);
133 }
134
135 self.sliderMoved();
136 self.update();
137 }
138 }
139 event->accept();
140 }
141
142 template <typename T>
143 static void mouseReleaseEvent(T& self, QGraphicsSceneMouseEvent* event)
144 {
145 if(self.m_grab)
146 {
147 const auto srect = self.sliderRect();
148 const auto posX = event->pos().x() - srect.x();
149 double curPos = self.from01(ossia::clamp(posX, 0., srect.width()) / srect.width());
150 if(curPos != self.m_value)
151 {
152 if constexpr(std::is_same_v<decltype(self.m_value), int>)
153 {
154 self.m_value = curPos;
155 }
156 else
157 {
158 double ratio = qGuiApp->keyboardModifiers() & Qt::CTRL ? 0.1 : 1.;
159 self.m_value = std::clamp(
160 double(self.m_value + ((curPos - self.m_value) * ratio)), 0., 1.);
161 }
162 self.update();
163 }
164 }
165
166 self.m_grab = false;
167 self.sliderReleased();
168
169 if(event->button() == Qt::RightButton)
170 {
171 contextMenuEvent(self, event->scenePos());
172 }
173 event->accept();
174 }
175
184 template <typename T>
185 static void ungrabMouseEvent(T& self, QEvent* event)
186 {
187 if(!self.m_grab)
188 return;
189
190 self.m_grab = false;
191 self.sliderReleased();
192 }
193
194 template <typename T>
195 static void contextMenuEvent(T& self, QPointF pos)
196 {
197 auto build = [&, self_p = &self, pos] {
198 // Whatever was open belongs to the previous right-click, including one
199 // on this same control.
201 self_p->impl->spinbox = nullptr;
202 self_p->impl->spinboxProxy = nullptr;
203
204 auto w = new DoubleSpinboxWithEnter;
205 self.impl->spinbox = w;
206 w->setRange(self.min, self.max);
207
208 w->setDecimals(6);
209 w->setValue(self.map(self.m_value));
210 auto obj = self.scene()->addWidget(
211 w, Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
212 obj->setPos(pos);
213 self.impl->spinboxProxy = obj;
215
216#if defined(__EMSCRIPTEN__)
217 w->setFocus();
218#else
219 QTimer::singleShot(0, w, [w] { w->setFocus(); });
220#endif
221
222 auto con = QObject::connect(
223 w, SignalUtils::QDoubleSpinBox_valueChanged_double(), &self,
224 [&self, obj, scene = self.scene()](double v) {
225 DefaultControlImpl::editWidgetInContextMenu(self, scene, obj, v);
226 });
227
228 QObject::connect(
229 w, &DoubleSpinboxWithEnter::editingFinished, &self, [obj, con, self_p] {
230 if(self_p->impl->spinbox)
231 {
232 self_p->sliderReleased();
233 QObject::disconnect(con);
234 // obj is the timer's context, not self: if the next right-click
235 // took this box down first, through closeRightClickWidget(), the
236 // teardown goes with it rather than freeing it a second time.
237 QTimer::singleShot(0, obj, [scene = self_p->scene(), obj] {
238 scene->removeItem(obj);
239 delete obj;
240 });
241 self_p->impl->spinbox = nullptr;
242 self_p->impl->spinboxProxy = nullptr;
243 }
244 });
245 };
246#if defined(__EMSCRIPTEN__)
247 build();
248#else
249 QTimer::singleShot(0, &self, build);
250#endif
251 }
252
253 template <typename T>
254 static void mouseDoubleClickEvent(T& self, QGraphicsSceneMouseEvent* event)
255 {
256 self.m_value = self.unmap(self.init);
257
258 self.m_grab = true;
259 self.sliderMoved();
260 self.sliderReleased();
261 self.m_grab = false;
262
263 self.update();
264
265 event->accept();
266 }
267};
268
269}
Definition Skin.hpp:100
Base toolkit upon which the software is built.
Definition Application.cpp:108
bool showsValue(const QGraphicsItem &item, bool grabbed, const QStyleOptionGraphicsItem *option) noexcept
Whether to draw the value text. Called on every paint, must stay cheap.
Definition WidgetPresentation.hpp:21
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 DefaultGraphicsSliderImpl.hpp:33
static void ungrabMouseEvent(T &self, QEvent *event)
Definition DefaultGraphicsSliderImpl.hpp:185
Definition DoubleSpinBox.hpp:17
Definition DefaultGraphicsSliderImpl.hpp:27