Loading...
Searching...
No Matches
WindowContainer.hpp
1#pragma once
2#include <QApplication>
3#include <QDebug>
4#include <QDialog>
5#include <QWindow>
6
7#include <algorithm>
8#include <chrono>
9#include <cmath>
10#include <memory>
11#include <vector>
12
13#include <score/widgets/PluginWindow.hpp>
14
15#include <pluginterfaces/gui/iplugview.h>
16#include <pluginterfaces/gui/iplugviewcontentscalesupport.h>
17
18namespace vst3
19{
20class PlugFrame;
21
22inline const char* currentPlatform()
23{
24#if defined(__APPLE__)
25 return Steinberg::kPlatformTypeNSView;
26#elif defined(_WIN32)
27 return Steinberg::kPlatformTypeHWND;
28#elif (!(defined(__APPLE__) || defined(_WIN32))) && __has_include(<xcb/xcb.h>)
29 return Steinberg::kPlatformTypeX11EmbedWindowID;
30#endif
31 return "";
32}
33
37inline bool applyContentScaleFactor(Steinberg::IPlugView& view, const QWidget& w)
38{
39#if defined(__APPLE__)
40 // NSViews are scaled by the system: there is nothing for the plug-in to do
41 return false;
42#else
43 // On Windows and X11 the plug-in has no way to find this out by itself
44 Steinberg::IPlugViewContentScaleSupport* scaling{};
45 if(view.queryInterface(Steinberg::IPlugViewContentScaleSupport::iid, (void**)&scaling)
46 != Steinberg::kResultOk
47 || !scaling)
48 return false;
49
50 const auto res = scaling->setContentScaleFactor(w.devicePixelRatioF());
51 scaling->release();
52 return res == Steinberg::kResultTrue;
53#endif
54}
55
59{
61 Steinberg::ViewRect current{};
64 struct Asked
65 {
66 QSize size;
67 std::chrono::steady_clock::time_point when;
68 };
69 std::vector<Asked> asked;
72 bool attached{};
74 bool pending{};
79};
80
82{
83 WId nativeId{};
84 QWindow* qwindow{};
85 QWidget* container{};
86 vst3::PlugFrame* frame{};
87 std::shared_ptr<EditorSize> size = std::make_shared<EditorSize>();
88
92 static constexpr int maxOnSizeDepth = 2;
93
97 static double qtScaleFactor(const QWidget& w) noexcept
98 {
100 }
101
104 static int toQt(int editorPixels, double scale) noexcept
105 {
106 return std::max(1, int(std::lround(editorPixels * scale)));
107 }
108 static int toEditor(int qtPixels, double scale) noexcept
109 {
110 return std::max(1, int(std::lround(qtPixels / scale)));
111 }
112
113 static bool sameSize(const Steinberg::ViewRect& a, const Steinberg::ViewRect& b) noexcept
114 {
115 return a.getWidth() == b.getWidth() && a.getHeight() == b.getHeight();
116 }
117
122 Steinberg::IPlugView& view, int qw, int qh, QDialog& parentWindow) const
123 {
124 auto& s = *size;
125 const bool was = s.resizingWindow;
126 s.resizingWindow = true;
127 const QSize sz{qw, qh};
128 if(parentWindow.size() != sz)
129 {
130 s.asked.push_back({sz, std::chrono::steady_clock::now()});
131 if(s.asked.size() > 8)
132 s.asked.erase(s.asked.begin());
133 }
134 if(view.canResize() == Steinberg::kResultTrue)
135 {
136 if(parentWindow.size() != sz)
137 parentWindow.resize(sz);
138 }
139 else
140 {
141 parentWindow.setFixedSize(sz);
142 }
143 if(qwindow && qwindow->size() != sz)
144 {
145 qwindow->resize(qw, qh);
146 }
147 if(container)
148 {
149 container->move(0, 0);
150 container->setFixedSize(qw, qh);
151 }
152 s.resizingWindow = was;
153 }
154
156 void sendSize(Steinberg::IPlugView& view, Steinberg::ViewRect r) const
157 {
158 auto& s = *size;
159 s.current = r;
160 s.onSizeDepth++;
161 view.onSize(&r);
162 s.onSizeDepth--;
163 }
164
167 void setAttached() const noexcept { size->attached = true; }
168
170 void sendPendingSize(Steinberg::IPlugView& view) const
171 {
172 auto& s = *size;
173 if(!s.pending)
174 return;
175 s.pending = false;
176 if(s.current.getWidth() > 0 && s.current.getHeight() > 0)
177 sendSize(view, s.current);
178 }
179
182 Steinberg::IPlugView& view, const Steinberg::ViewRect& r,
183 QDialog& parentWindow) const
184 {
185 const double scale = qtScaleFactor(parentWindow);
186 int w = r.getWidth();
187 int h = r.getHeight();
188
189 if(w <= 0 || h <= 0)
190 {
191 return std::make_pair(w, h);
192 }
193
194 size->current = r;
195 resizeWindow(view, toQt(w, scale), toQt(h, scale), parentWindow);
196
197 return std::make_pair(w, h);
198 }
199
203 void
204 setSizeFromUser(Steinberg::IPlugView& view, const QSize& sz, QDialog& parentWindow)
205 {
206 auto& s = *size;
207 // Before attached(), and while the window is resized here, the size is
208 // ours and the editor already has it
209 if(!s.attached || s.resizingWindow)
210 return;
211 if(view.canResize() != Steinberg::kResultTrue)
212 return;
213 if(sz.width() <= 0 || sz.height() <= 0)
214 return;
215
216 // A size given here and reported by the window system once done, as X11
217 // does: the editor had it, or has a newer one if it asked for several in a
218 // row. The window system reports the newer one next. One that does not
219 // report them, as the resize is done by then, keeps none for long.
220 const auto now = std::chrono::steady_clock::now();
221 auto it = std::find_if(s.asked.begin(), s.asked.end(), [&](const auto& a) {
222 return a.size == sz && now - a.when < std::chrono::seconds(1);
223 });
224 if(it != s.asked.end())
225 {
226 s.asked.erase(s.asked.begin(), it + 1);
227 return;
228 }
229 s.asked.clear();
230
231 const double scale = qtScaleFactor(parentWindow);
232
233 // The size the editor has
234 if(s.current.getWidth() > 0 && s.current.getHeight() > 0
235 && sz.width() == toQt(s.current.getWidth(), scale)
236 && sz.height() == toQt(s.current.getHeight(), scale))
237 return;
238
239 const Steinberg::ViewRect asked{
240 0, 0, toEditor(sz.width(), scale), toEditor(sz.height(), scale)};
241 Steinberg::ViewRect r = asked;
242 // The plug-in may only take some sizes
243 view.checkSizeConstraint(&r);
244 if(r.getWidth() <= 0 || r.getHeight() <= 0)
245 r = asked;
246 r = Steinberg::ViewRect{0, 0, r.getWidth(), r.getHeight()};
247
248 // Only a size the plug-in changed goes back to the window: one that only
249 // went through the rounding stays as the user made it
250 if(!sameSize(r, asked))
251 resizeWindow(view, toQt(r.getWidth(), scale), toQt(r.getHeight(), scale), parentWindow);
252 else if(container)
253 {
254 container->move(0, 0);
255 container->setFixedSize(sz);
256 }
257
258 // A size the plug-in corrected back to the one it has
259 if(sameSize(r, s.current))
260 return;
261
262 sendSize(view, r);
263 }
264
268 Steinberg::IPlugView& view, Steinberg::ViewRect& r, QDialog& parentWindow) const
269 {
270 auto& s = *size;
271 const double scale = qtScaleFactor(parentWindow);
272 const int w = r.getWidth();
273 const int h = r.getHeight();
274
275 if(w <= 0 || h <= 0)
276 return false;
277
278 // A plug-in answering the onSize() sent from here with the size it was
279 // just given: it has it
280 if(s.onSizeDepth > 0 && sameSize(r, s.current))
281 return true;
282 if(s.onSizeDepth >= maxOnSizeDepth)
283 return false;
284
285 s.current = Steinberg::ViewRect{0, 0, w, h};
286 resizeWindow(view, toQt(w, scale), toQt(h, scale), parentWindow);
287
288 // The view has no window yet to resize
289 if(!s.attached)
290 {
291 s.pending = true;
292 return true;
293 }
294
295 sendSize(view, s.current);
296 return true;
297 }
298};
299
300class Window;
301}
static double editorPixelRatio(const QWidget &w)
Definition PluginWindow.cpp:16
Definition PlugFrame.hpp:14
QString currentPlatform() noexcept
"windows", "macos", "linux", "bsd", "android", "ios" or "web".
Definition Platforms.cpp:9
Definition WindowContainer.hpp:65
Definition WindowContainer.hpp:59
bool resizingWindow
The window is being resized here: the resize events it sends are ours.
Definition WindowContainer.hpp:76
Steinberg::ViewRect current
The size the editor was last given or asked for, in its own pixels.
Definition WindowContainer.hpp:61
int onSizeDepth
How many onSize() sent from here the editor is in.
Definition WindowContainer.hpp:78
bool pending
The plug-in asked for a size before attached(): it gets it once attached.
Definition WindowContainer.hpp:74
bool attached
Definition WindowContainer.hpp:72
Definition WindowContainer.hpp:82
void resizeWindow(Steinberg::IPlugView &view, int qw, int qh, QDialog &parentWindow) const
Definition WindowContainer.hpp:121
void setSizeFromUser(Steinberg::IPlugView &view, const QSize &sz, QDialog &parentWindow)
Definition WindowContainer.hpp:204
auto setSizeFromQt(Steinberg::IPlugView &view, const Steinberg::ViewRect &r, QDialog &parentWindow) const
The window takes the size the editor says it has (getSize)
Definition WindowContainer.hpp:181
static double qtScaleFactor(const QWidget &w) noexcept
Definition WindowContainer.hpp:97
void sendPendingSize(Steinberg::IPlugView &view) const
To call once attached(): the size the plug-in asked for before.
Definition WindowContainer.hpp:170
static int toQt(int editorPixels, double scale) noexcept
Definition WindowContainer.hpp:104
static constexpr int maxOnSizeDepth
Definition WindowContainer.hpp:92
void sendSize(Steinberg::IPlugView &view, Steinberg::ViewRect r) const
The editor takes the size r.
Definition WindowContainer.hpp:156
bool setSizeFromVst(Steinberg::IPlugView &view, Steinberg::ViewRect &r, QDialog &parentWindow) const
Definition WindowContainer.hpp:267
void setAttached() const noexcept
Definition WindowContainer.hpp:167