Loading...
Searching...
No Matches
Controls.hpp
1#pragma once
2#include <Process/Dataflow/ControlWidgets.hpp>
3#include <Process/Dataflow/StringListEditor.hpp>
4
5#include <Scenario/Commands/SetControllerControlValue.hpp>
6
7#include <Crousti/Executor.hpp>
8#include <Crousti/Layer.hpp>
9#include <Crousti/ProcessModel.hpp>
10#include <Crousti/ScoreLayer.hpp>
11#include <Dataflow/WidgetInletFactory.hpp>
12
13#include <score/graphics/DefaultGraphicsKnobImpl.hpp>
14#include <score/graphics/DefaultGraphicsSliderImpl.hpp>
15#include <score/graphics/GraphicsSliderBaseImpl.hpp>
16#include <score/graphics/widgets/QGraphicsRangeSlider.hpp>
17#include <score/graphics/widgets/QGraphicsSlider.hpp>
18
19#include <ossia/detail/for_each.hpp>
20
21#include <boost/mp11/algorithm.hpp>
22
23#include <QGraphicsProxyWidget>
24#include <QPointer>
25
26namespace oscr
27{
28
29template <typename N>
30using reflect_mapped_controls =
31 typename avnd::mapped_control_input_introspection<N>::field_reflections_type;
32
33template <typename Field>
35{
36 static inline constexpr auto mapper = avnd::get_mapper<Field>();
37 static float map(float val) noexcept { return mapper.map(val); }
38 static float unmap(float val) noexcept { return mapper.unmap(val); }
39
40 template <typename T>
41 static float to01(const T& slider, float val) noexcept
42 {
43 const auto min = slider.getMin();
44 const auto max = slider.getMax();
45
46 // [a; b] -> [0; 1] in linear space
47 const auto in_01 = (val - min) / (max - min);
48
49 // [0; 1] -> [0; 1] in e.g. log space
50 return map(in_01);
51 }
52
53 template <typename T>
54 static float from01(const T& slider, float in_01) noexcept
55 {
56 const auto min = slider.getMin();
57 const auto max = slider.getMax();
58
59 // [0; 1] in e.g. log space -> [0; 1] in linear space
60 const auto unmapped = unmap(in_01);
61
62 // [0; 1] in linear space -> [a; b]
63 return min + unmapped * (max - min);
64 }
65};
66
67template <typename Field>
69{
70public:
71 using score::QGraphicsSlider::QGraphicsSlider;
72
73 double getMin() const noexcept { return this->min; }
74 double getMax() const noexcept { return this->max; }
75 double unmap(double v) const noexcept
76 {
77 return NormalizerFromMapper<Field>::to01(*this, v);
78 }
79 double map(double v) const noexcept
80 {
82 }
83
84private:
85 void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
86 override
87 {
88 score::DefaultGraphicsSliderImpl::paint(
89 *this, score::Skin::instance(), QString::number(map(m_value), 'f', 3), painter,
90 widget);
91 }
92
93 void mousePressEvent(QGraphicsSceneMouseEvent* event) override
94 {
95 score::DefaultGraphicsSliderImpl::mousePressEvent(*this, event);
96 }
97
98 void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override
99 {
100 score::DefaultGraphicsSliderImpl::mouseMoveEvent(*this, event);
101 }
102
103 void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override
104 {
105 score::DefaultGraphicsSliderImpl::mouseReleaseEvent(*this, event);
106 }
107
108 void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override
109 {
110 event->accept();
111 }
112};
113
114template <typename Field>
116{
117public:
118 using score::QGraphicsKnob::QGraphicsKnob;
119
120 double getMin() const noexcept { return this->min; }
121 double getMax() const noexcept { return this->max; }
122 double unmap(double v) const noexcept
123 {
124 return NormalizerFromMapper<Field>::to01(*this, v);
125 }
126 double map(double v) const noexcept
127 {
129 }
130
131private:
132 void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
133 override
134 {
135 const double val = map(m_value);
136 const double abs = std::abs(val);
137 int pres = abs < 10. ? 3 : abs < 100. ? 2 : abs < 1000. ? 1 : 0;
138 score::DefaultGraphicsKnobImpl::paint(
139 *this, score::Skin::instance(), QString::number(val, 'f', pres), painter,
140 widget);
141 }
142
143 void mousePressEvent(QGraphicsSceneMouseEvent* event) override
144 {
145 score::DefaultGraphicsKnobImpl::mousePressEvent(*this, event);
146 }
147
148 void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override
149 {
150 score::DefaultGraphicsKnobImpl::mouseMoveEvent(*this, event);
151 }
152
153 void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override
154 {
155 score::DefaultGraphicsKnobImpl::mouseReleaseEvent(*this, event);
156 }
157
158 void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override
159 {
160 event->accept();
161 }
162};
163
164template <typename Field>
169template <typename Field>
170 requires requires { Field::widget::knob; }
171struct MatchingWidget<Field>
172{
174};
175
176template <typename Field>
177concept string_list_field = requires { Field::widget::string_list; };
178
179template <typename Node, typename Refl>
181
182template <typename Node, std::size_t N, typename Field>
183 requires(!avnd::controller_interaction_port<Field> && !string_list_field<Field>)
184struct CustomControlFactory<Node, avnd::field_reflection<N, Field>>
185 : public Dataflow::WidgetInletFactory<
186 oscr::CustomFloatControl<Node, avnd::field_index<N>>,
187 WidgetFactory::FloatControl<
188 typename MatchingWidget<Field>::type, NormalizerFromMapper<Field>, true>>
189{
190};
191
197template <typename Field>
199 = avnd::controller_interaction_port<Field> && avnd::has_widget<Field>
200 && std::is_integral_v<as_type(Field::value)>
201 && (avnd::get_widget<Field>().widget == avnd::widget_type::spinbox);
202
203template <typename Field>
205 = avnd::controller_interaction_port<Field> && avnd::has_widget<Field>
206 && avnd::string_parameter<Field> && !avnd::program_parameter<Field>
207 && (avnd::get_widget<Field>().widget == avnd::widget_type::lineedit);
208
222template <typename Control_T>
223static void submitControllerValue(
224 Control_T& inlet, ossia::value val, const score::DocumentContext& ctx)
225{
226 ossia::qt::run_async(&inlet, [inlet = &inlet, val = std::move(val), &ctx]() mutable {
227 if(inlet->value() == val)
228 return;
230 *inlet, std::move(val), ctx);
231 });
232}
233
235{
236 static Process::PortItemLayout layout() noexcept
237 {
238 return Process::DefaultControlLayouts::spinbox();
239 }
240
241 template <typename T>
242 static auto make_widget(
243 T& inlet, const score::DocumentContext& ctx, QWidget* parent, QObject* context)
244 {
245 auto sl = new QSpinBox{parent};
246 WidgetFactory::bindIntDomain(inlet, inlet, *sl);
247 sl->setValue(ossia::convert<int>(inlet.value()));
248 sl->setContentsMargins(0, 0, 0, 0);
249 // One command per edit, not one per keystroke
250 sl->setKeyboardTracking(false);
251
252 QObject::connect(
253 sl, SignalUtils::QSpinBox_valueChanged_int(), context,
254 [&inlet, &ctx](int val) { submitControllerValue(inlet, val, ctx); });
255
256 QObject::connect(&inlet, &T::valueChanged, sl, [sl](const ossia::value& val) {
257 const int v = ossia::convert<int>(val);
258 if(sl->value() != v)
259 {
260 QSignalBlocker b{sl};
261 sl->setValue(v);
262 }
263 });
264
265 return sl;
266 }
267
268 template <typename T, typename Control_T>
269 static QGraphicsItem* make_item(
270 const T& slider, Control_T& inlet, const score::DocumentContext& ctx,
271 QGraphicsItem* parent, QObject* context)
272 {
273 auto sl = new score::QGraphicsIntSpinbox{nullptr};
274 WidgetFactory::initWidgetProperties(inlet, *sl);
275 WidgetFactory::bindIntDomain(slider, inlet, *sl);
276 sl->setValue(ossia::convert<int>(inlet.value()));
277
278 if(inlet.noValueChangeOnMove)
279 {
280 // The default for a controller: the widget only reports a value on
281 // release, so a single, deferred command.
282 QObject::connect(
283 sl, &score::QGraphicsIntSpinbox::sliderMoved, context,
284 [sl, &inlet, &ctx] { submitControllerValue(inlet, sl->value(), ctx); });
285 }
286 else
287 {
288 QObject::connect(
289 sl, &score::QGraphicsIntSpinbox::sliderMoved, context, [=, &inlet, &ctx] {
290 sl->moving = true;
291 ctx.dispatcher.submit<Scenario::SetControllerControlValue>(
292 inlet, sl->value(), ctx);
293 });
294 QObject::connect(
295 sl, &score::QGraphicsIntSpinbox::sliderReleased, context, [&ctx, sl]() {
296 ctx.dispatcher.commit();
297 sl->moving = false;
298 });
299 }
300
301 QObject::connect(&inlet, &Control_T::valueChanged, sl, [=](const ossia::value& val) {
302 if(!sl->moving)
303 sl->setValue(ossia::convert<int>(val));
304 });
305
306 QObject::connect(
307 &inlet, &Control_T::executionValueChanged, sl, [=](const ossia::value& val) {
308 if(!sl->moving)
309 sl->setExecutionValue(ossia::convert<int>(val));
310 });
311 QObject::connect(
312 &inlet, &Control_T::executionReset, sl,
313 &score::QGraphicsIntSpinbox::resetExecution);
314
315 return sl;
316 }
317};
318
320{
321 static Process::PortItemLayout layout() noexcept
322 {
323 return Process::DefaultControlLayouts::lineedit();
324 }
325
326 template <typename T>
327 static auto make_widget(
328 T& inlet, const score::DocumentContext& ctx, QWidget* parent, QObject* context)
329 {
330 auto sl = new QLineEdit{parent};
331 sl->setText(QString::fromStdString(ossia::convert<std::string>(inlet.value())));
332 sl->setContentsMargins(0, 0, 0, 0);
333 sl->setMaximumWidth(70);
334 QObject::connect(sl, &QLineEdit::editingFinished, context, [sl, &inlet, &ctx]() {
335 submitControllerValue(inlet, sl->text().toStdString(), ctx);
336 });
337
338 QObject::connect(&inlet, &T::valueChanged, sl, [sl](const ossia::value& val) {
339 const auto str = QString::fromStdString(ossia::convert<std::string>(val));
340 if(sl->text() != str)
341 sl->setText(str);
342 });
343
344 return sl;
345 }
346
347 template <typename T, typename Control_T>
348 static score::QGraphicsLineEdit* make_item(
349 const T& slider, Control_T& inlet, const score::DocumentContext& ctx,
350 QGraphicsItem* parent, QObject* context)
351 {
352 auto sl = new score::QGraphicsLineEdit{parent};
353 WidgetFactory::initWidgetProperties(inlet, *sl);
354 sl->setTextWidth(180.);
355 sl->setDefaultTextColor(QColor{"#E0B01E"});
356 sl->setCursor(Qt::IBeamCursor);
357
358 sl->setPlainText(QString::fromStdString(ossia::convert<std::string>(inlet.value())));
359
360 auto doc = sl->document();
361 auto on_edit = [doc, &inlet, &ctx] {
362 submitControllerValue(inlet, doc->toPlainText().toStdString(), ctx);
363 };
364
365 if(!inlet.noValueChangeOnMove)
366 {
367 QObject::connect(doc, &QTextDocument::contentsChanged, context, on_edit);
368 }
369 else
370 {
371 QObject::connect(sl, &score::QGraphicsLineEdit::editingFinished, context, on_edit);
372 }
373
374 if(auto obj = dynamic_cast<score::ResizeableItem*>(context))
375 {
376 QObject::connect(
377 sl, &score::QGraphicsLineEdit::sizeChanged, obj,
378 &score::ResizeableItem::childrenSizeChanged);
379 }
380
381 QObject::connect(&inlet, &Control_T::valueChanged, sl, [=](const ossia::value& val) {
382 auto str = QString::fromStdString(ossia::convert<std::string>(val));
383 if(str != doc->toPlainText())
384 doc->setPlainText(str);
385 });
386
387 return sl;
388 }
389};
390
392{
393 static Process::PortItemLayout layout() noexcept
394 {
395 return Process::DefaultControlLayouts::lineedit();
396 }
397
398 template <typename T>
399 static auto make_widget(
400 T& inlet, const score::DocumentContext& ctx, QWidget* parent, QObject* context)
401 {
402 auto editor = new Process::StringListEditor{parent};
403 editor->setValue(inlet.value());
404 editor->on_edited = [&inlet, &ctx](ossia::value value) {
405 // The controller command snapshots ports and cables. Deferral also keeps
406 // the editor alive until its event has finished when a resize rebuilds UI.
407 submitControllerValue(inlet, std::move(value), ctx);
408 };
409 QObject::connect(
410 &inlet, &T::valueChanged, editor,
411 [editor](const ossia::value& value) { editor->setValue(value); });
412 return editor;
413 }
414
415 template <typename T, typename Control_T>
416 static auto make_item(
417 const T&, Control_T& inlet, const score::DocumentContext& ctx,
418 QGraphicsItem* parent, QObject* context)
419 {
420 auto proxy = new QGraphicsProxyWidget{parent};
421 proxy->setWidget(make_widget(inlet, ctx, nullptr, context));
422 return proxy;
423 }
424};
425
426template <typename Node, std::size_t N, typename Field>
428struct CustomControlFactory<Node, avnd::field_reflection<N, Field>>
430 CustomGenericControl<Process::MultiSlider, Node, Field, avnd::field_index<N>>,
431 StringListControl>
432{
433};
434
435template <typename Node, std::size_t N, typename Field>
436 requires controller_spinbox_field<Field>
437struct CustomControlFactory<Node, avnd::field_reflection<N, Field>>
439 CustomGenericControl<Process::IntSpinBox, Node, Field, avnd::field_index<N>>,
440 ControllerIntSpinBox>
441{
442};
443
444template <typename Node, std::size_t N, typename Field>
445 requires controller_lineedit_field<Field>
446struct CustomControlFactory<Node, avnd::field_reflection<N, Field>>
448 CustomGenericControl<Process::LineEdit, Node, Field, avnd::field_index<N>>,
449 ControllerLineEdit>
450{
451};
452
453template <typename Refl>
454struct controller_needs_factory : std::false_type
455{
456};
457template <std::size_t N, typename Field>
458struct controller_needs_factory<avnd::field_reflection<N, Field>>
459 : std::bool_constant<
460 controller_spinbox_field<Field> || controller_lineedit_field<Field>
461 || string_list_field<Field>>
462{
463};
464
466template <typename N>
467using reflect_controller_controls = boost::mp11::mp_copy_if<
468 typename avnd::control_input_introspection<N>::field_reflections_type,
470
471}
The CommandDispatcher class.
Definition CommandDispatcher.hpp:13
Definition StringListEditor.hpp:17
Definition SetControllerControlValue.hpp:20
Definition Controls.hpp:116
Definition Controls.hpp:69
Definition QGraphicsSpinbox.hpp:65
Definition QGraphicsKnob.hpp:16
Definition QGraphicsSlider.hpp:17
Definition RectItem.hpp:12
Definition Controls.hpp:205
Definition Controls.hpp:199
Definition Controls.hpp:177
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Controls.hpp:27
boost::mp11::mp_copy_if< typename avnd::control_input_introspection< N >::field_reflections_type, controller_needs_factory > reflect_controller_controls
The controller fields that get a CustomGenericControl, and only those.
Definition Controls.hpp:469
Definition WidgetInletFactory.hpp:15
Definition Port.hpp:91
Definition Controls.hpp:235
Definition Controls.hpp:320
Definition Controls.hpp:180
Definition Concepts.hpp:101
Definition Controls.hpp:166
Definition Controls.hpp:35
Definition Controls.hpp:392
Definition Controls.hpp:455
Definition DocumentContext.hpp:18
Definition QGraphicsLineEdit.hpp:17