Loading...
Searching...
No Matches
ControlViews.hpp
1#pragma once
2#include <Process/Commands/SetControlValue.hpp>
3#include <Process/Dataflow/Port.hpp>
4
5#include <score/command/Dispatchers/SendStrategy.hpp>
6#include <score/model/Skin.hpp>
7
8#include <ossia/network/domain/domain.hpp>
9
10#include <Crousti/Painter.hpp>
11
12#include <QGraphicsItem>
13#include <QObject>
14#include <QPainter>
15
16#include <algorithm>
17#include <vector>
18
19namespace oscr
20{
22class ValueBarItem final
23 : public QObject
24 , public QGraphicsItem
25{
26public:
27 explicit ValueBarItem(QGraphicsItem* parent = nullptr)
28 : QGraphicsItem{parent}
29 {
30 }
31
32 void setValue(double normalized)
33 {
34 // Values stream in at the control rate: repaint only on a change
35 normalized = std::clamp(normalized, 0., 1.);
36 if(normalized == m_value)
37 return;
38 m_value = normalized;
39 update();
40 }
41
42 QRectF boundingRect() const override { return {0., 0., 52., 4.}; }
43
44 void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override
45 {
46 auto& skin = score::Skin::instance();
47 const QRectF r = boundingRect();
48 painter->fillRect(r, skin.Background2.darker.brush);
49 painter->fillRect(QRectF{r.x(), r.y(), r.width() * m_value, r.height()}, skin.Base4);
50 }
51
52private:
53 double m_value{};
54};
55
59inline double scalarValue(const ossia::value& v)
60{
61 if(auto t = v.target<ossia::vec2f>())
62 return (*t)[1] != 0.f ? (*t)[0] * 2. : (*t)[0]; // a whole note: 2 s
63 return ossia::convert<double>(v);
64}
65
67inline double normalizedValue(const Process::ControlInlet& port, const ossia::value& v)
68{
69 const auto& dom = port.domain().get();
70 const auto lo = ossia::get_min(dom);
71 const auto hi = ossia::get_max(dom);
72 if(!lo.valid() || !hi.valid())
73 return std::clamp(scalarValue(v), 0., 1.);
74 const double l = scalarValue(lo), h = scalarValue(hi);
75 // A time chooser's domain is {min, 0} .. {max, 1}: its x is the range
76 if(auto a = lo.target<ossia::vec2f>())
77 if(auto b = hi.target<ossia::vec2f>())
78 {
79 const double tl = (*a)[0], th = (*b)[0];
80 return th == tl ? 0. : std::clamp((scalarValue(v) - tl) / (th - tl), 0., 1.);
81 }
82 return h == l ? 0. : std::clamp((scalarValue(v) - l) / (h - l), 0., 1.);
83}
84
86template <typename Item>
88 : public QObject
89 , public CustomItem<Item>
90{
91public:
93 Item item_init, std::vector<Process::ControlInlet*> ports,
94 const score::DocumentContext& ctx)
95 : CustomItem<Item>{item_init}
96 , m_ports{std::move(ports)}
97 , m_ctx{ctx}
98 , m_cmds(m_ports.size(), nullptr)
99 {
100 const std::size_t n = std::min(m_ports.size(), std::size(this->impl.values));
101 for(std::size_t i = 0; i < n; i++)
102 {
103 auto* port = m_ports[i];
104 auto show = [this, i, port](const ossia::value& v) {
105 const auto n = normalize(*port, v);
106 if(n == this->impl.values[i])
107 return;
108 this->impl.values[i] = n;
109 this->update();
110 };
111 show(port->value());
112 QObject::connect(port, &Process::ControlInlet::valueChanged, this, show);
113 }
114
115 this->impl.transaction.start = [this] {
116 m_ctx.commandStack.disableActions();
117 m_active = true;
118 };
119 this->impl.transaction.update = [this, n](int i, double normalized) {
120 if(i < 0 || std::size_t(i) >= n)
121 return;
122 auto& port = *m_ports[i];
123 const ossia::value v = denormalize(port, normalized);
124 if(!m_cmds[i])
125 m_cmds[i] = new Process::SetControlValue{port, v};
126 else
127 m_cmds[i]->update(port, v);
128 m_cmds[i]->redo(m_ctx);
129 };
130 this->impl.transaction.commit = [this] {
131 // A click that moved nothing leaves no undo step
132 if(std::ranges::any_of(m_cmds, [](auto* cmd) { return cmd != nullptr; }))
133 {
134 auto* all = new Process::SetControlValues;
135 for(auto& cmd : m_cmds)
136 {
137 if(cmd)
138 all->addCommand(std::exchange(cmd, nullptr));
139 }
140 SendStrategy::Quiet::send(m_ctx.commandStack, all);
141 }
142 m_ctx.commandStack.enableActions();
143 m_active = false;
144 };
145 this->impl.transaction.rollback = [this] { rollback(); };
146 }
147
149 {
150 // Destroyed mid-gesture (UI rebuilt on inlet change): roll back
151 if(m_active)
152 rollback();
153 }
154
155private:
156 void rollback()
157 {
158 for(auto it = m_cmds.rbegin(); it != m_cmds.rend(); ++it)
159 {
160 if(auto cmd = std::exchange(*it, nullptr))
161 {
162 cmd->undo(m_ctx);
163 delete cmd;
164 }
165 }
166 m_ctx.commandStack.enableActions();
167 m_active = false;
168 }
169
170 static std::pair<double, double> range(const Process::ControlInlet& port)
171 {
172 const auto& dom = port.domain().get();
173 const auto lo = ossia::get_min(dom);
174 const auto hi = ossia::get_max(dom);
175 if(!lo.valid() || !hi.valid())
176 return {0., 1.};
177 // A time chooser's domain is {min, 0} .. {max, 1}
178 if(auto a = lo.target<ossia::vec2f>())
179 if(auto b = hi.target<ossia::vec2f>())
180 return {(*a)[0], (*b)[0]};
181 return {ossia::convert<double>(lo), ossia::convert<double>(hi)};
182 }
183
184 static double normalize(const Process::ControlInlet& port, const ossia::value& v)
185 {
186 return normalizedValue(port, v);
187 }
188
189 static ossia::value denormalize(const Process::ControlInlet& port, double n)
190 {
191 const auto [lo, hi] = range(port);
192 const double v = lo + std::clamp(n, 0., 1.) * (hi - lo);
193 // Keep the control's own type
194 if(port.value().get_type() == ossia::val_type::INT)
195 return int(std::lround(v));
196 // A time chooser: dragged on a graph, a length in seconds (a synced
197 // stage becomes free there)
198 if(port.value().get_type() == ossia::val_type::VEC2F)
199 return ossia::vec2f{float(v), 0.f};
200 return float(v);
201 }
202
203 std::vector<Process::ControlInlet*> m_ports;
204 const score::DocumentContext& m_ctx;
205 std::vector<Process::SetControlValue*> m_cmds;
206 bool m_active{};
207};
208}
Definition Port.hpp:242
Definition SetControlValue.hpp:14
Several controls changed by one gesture: one undo step.
Definition SetControlValue.hpp:36
Definition Painter.hpp:365
halp::custom_multi_control. Values are normalized; a gesture is one undo step.
Definition ControlViews.hpp:90
halp::display_style::bar
Definition ControlViews.hpp:25
Definition Controls.hpp:27
double normalizedValue(const Process::ControlInlet &port, const ossia::value &v)
A control's value, normalized over its range.
Definition ControlViews.hpp:67
double scalarValue(const ossia::value &v)
Definition ControlViews.hpp:59
Definition MIDISync.hpp:128
Definition DocumentContext.hpp:18