@soundor/vst3-host 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +194 -0
- package/CMakePresets.json +115 -0
- package/LICENSE +21 -0
- package/NOTICE.md +17 -0
- package/README.md +40 -2
- package/cmake/Vst3HostDependencies.cmake +179 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +88 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +586 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/prepare-ayjTVhfk.mjs +1149 -0
- package/dist/prepare-ayjTVhfk.mjs.map +1 -0
- package/native/src/Bundle.cpp +253 -0
- package/native/src/Bundle.h +32 -0
- package/native/src/EditorArea.h +92 -0
- package/native/src/EditorAreaMac.mm +52 -0
- package/native/src/EditorAreaWindows.cpp +66 -0
- package/native/src/EditorAreaX11.cpp +512 -0
- package/native/src/EditorWindow.h +72 -0
- package/native/src/EditorWindowMac.mm +134 -0
- package/native/src/EditorWindowWindows.cpp +148 -0
- package/native/src/EventLoop.h +50 -0
- package/native/src/EventLoopLinux.cpp +204 -0
- package/native/src/EventLoopMac.mm +146 -0
- package/native/src/EventLoopWindows.cpp +132 -0
- package/native/src/HostContext.cpp +217 -0
- package/native/src/HostContext.h +116 -0
- package/native/src/HostInterface.cpp +1026 -0
- package/native/src/HostInterface.h +138 -0
- package/native/src/HostWindow.cpp +901 -0
- package/native/src/HostWindow.h +187 -0
- package/native/src/Ipc.cpp +158 -0
- package/native/src/Ipc.h +91 -0
- package/native/src/Json.cpp +426 -0
- package/native/src/Json.h +85 -0
- package/native/src/LiveAudio.cpp +416 -0
- package/native/src/LiveAudio.h +121 -0
- package/native/src/Main.cpp +178 -0
- package/native/src/Paths.h +20 -0
- package/native/src/Plugin.cpp +1865 -0
- package/native/src/Plugin.h +321 -0
- package/native/src/Session.cpp +656 -0
- package/native/src/Session.h +82 -0
- package/native/src/Settings.cpp +93 -0
- package/native/src/Settings.h +23 -0
- package/native/src/Version.h.in +11 -0
- package/native/src/X11Library.cpp +127 -0
- package/native/src/X11Library.h +83 -0
- package/native/tools/Embed.cpp +49 -0
- package/package.json +58 -3
- package/protocol.json +7 -0
- package/targets.json +33 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
#include "HostWindow.h"
|
|
2
|
+
|
|
3
|
+
#include <SDL3/SDL.h>
|
|
4
|
+
#include <imgui.h>
|
|
5
|
+
#include <imgui_impl_sdl3.h>
|
|
6
|
+
#include <imgui_impl_sdlrenderer3.h>
|
|
7
|
+
|
|
8
|
+
#include <algorithm>
|
|
9
|
+
#include <cmath>
|
|
10
|
+
#include <cstdio>
|
|
11
|
+
#include <cstdlib>
|
|
12
|
+
#include <cstring>
|
|
13
|
+
#include <utility>
|
|
14
|
+
|
|
15
|
+
namespace soundor::vst3host
|
|
16
|
+
{
|
|
17
|
+
namespace fonts
|
|
18
|
+
{
|
|
19
|
+
// Inter (OFL-1.1), embedded by the build (Vst3HostDependencies.cmake).
|
|
20
|
+
extern const unsigned char regular[];
|
|
21
|
+
extern const std::size_t regularSize;
|
|
22
|
+
extern const unsigned char semibold[];
|
|
23
|
+
extern const std::size_t semiboldSize;
|
|
24
|
+
} // namespace fonts
|
|
25
|
+
|
|
26
|
+
namespace
|
|
27
|
+
{
|
|
28
|
+
// Values the interface shows also change without input (the API, the
|
|
29
|
+
// editor, the plugin): it builds a frame this often to see, which is
|
|
30
|
+
// cheap, and presents it only if it differs.
|
|
31
|
+
constexpr auto refreshInterval = std::chrono::milliseconds(100);
|
|
32
|
+
// A window manager's drag has no end to see: the geometry has stopped
|
|
33
|
+
// changing for this long.
|
|
34
|
+
constexpr auto liveQuiet = std::chrono::milliseconds(150);
|
|
35
|
+
// A view that goes live this soon after a resize is answering it.
|
|
36
|
+
constexpr auto windowAnswer = std::chrono::milliseconds(200);
|
|
37
|
+
// How long a bare window waits for a size it asked for.
|
|
38
|
+
constexpr auto askedSizeTimeout = std::chrono::milliseconds(500);
|
|
39
|
+
// How often it asks again for the editor's size after a drag: a window
|
|
40
|
+
// manager may refuse while it holds the window.
|
|
41
|
+
constexpr int settleAsks = 8;
|
|
42
|
+
// How long the stage waits for the window to take the panel's width.
|
|
43
|
+
constexpr auto stageFollowTimeout = std::chrono::milliseconds(500);
|
|
44
|
+
|
|
45
|
+
bool initVideo(std::string& error)
|
|
46
|
+
{
|
|
47
|
+
if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
|
|
48
|
+
return true;
|
|
49
|
+
#if defined(__linux__)
|
|
50
|
+
// VST3 editors on Linux are X11 windows.
|
|
51
|
+
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11");
|
|
52
|
+
#endif
|
|
53
|
+
SDL_SetHint(SDL_HINT_APP_NAME, "Soundor VST3 host");
|
|
54
|
+
SDL_SetHint(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "0");
|
|
55
|
+
// Renderers that leave the editor's child view alone: Direct3D 9
|
|
56
|
+
// presents by blitting, around child windows.
|
|
57
|
+
const char* renderer = std::getenv("SOUNDOR_VST3_HOST_RENDERER"); // NOLINT(concurrency-mt-unsafe)
|
|
58
|
+
#if defined(_WIN32)
|
|
59
|
+
SDL_SetHint(SDL_HINT_RENDER_DRIVER, renderer != nullptr ? renderer : "direct3d,direct3d11,software");
|
|
60
|
+
#elif defined(__APPLE__)
|
|
61
|
+
SDL_SetHint(SDL_HINT_RENDER_DRIVER, renderer != nullptr ? renderer : "metal,opengl,software");
|
|
62
|
+
#else
|
|
63
|
+
SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0");
|
|
64
|
+
// The window manager waits for the frame of each size it asks for
|
|
65
|
+
// (OpenGL windows only: SDL answers it when they swap).
|
|
66
|
+
SDL_SetHint(SDL_HINT_VIDEO_X11_ENABLE_XSYNC_EXT, "1");
|
|
67
|
+
SDL_SetHint(SDL_HINT_RENDER_DRIVER, renderer != nullptr ? renderer : "opengl,software");
|
|
68
|
+
#endif
|
|
69
|
+
if (! SDL_InitSubSystem(SDL_INIT_VIDEO))
|
|
70
|
+
{
|
|
71
|
+
error = std::string("no display is available for the host interface (") + SDL_GetError()
|
|
72
|
+
+ "; is DISPLAY set? Use headless mode without one)";
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void applyStyle(float scale)
|
|
79
|
+
{
|
|
80
|
+
ImGuiStyle& style = ImGui::GetStyle();
|
|
81
|
+
style = ImGuiStyle();
|
|
82
|
+
ImGui::StyleColorsDark(&style);
|
|
83
|
+
style.WindowRounding = 0;
|
|
84
|
+
style.ChildRounding = 0;
|
|
85
|
+
style.FrameRounding = 6;
|
|
86
|
+
style.PopupRounding = 6;
|
|
87
|
+
style.GrabRounding = 6;
|
|
88
|
+
style.ScrollbarRounding = 6;
|
|
89
|
+
style.FramePadding = ImVec2(8, 5);
|
|
90
|
+
style.ItemSpacing = ImVec2(8, 6);
|
|
91
|
+
style.FrameBorderSize = 1;
|
|
92
|
+
style.PopupBorderSize = 1;
|
|
93
|
+
style.GrabMinSize = 12;
|
|
94
|
+
style.ScrollbarSize = 10;
|
|
95
|
+
style.SeparatorTextBorderSize = 1;
|
|
96
|
+
auto rgb = [](int r, int g, int b, float a = 1) {
|
|
97
|
+
return ImVec4(static_cast<float>(r) / 255, static_cast<float>(g) / 255, static_cast<float>(b) / 255, a);
|
|
98
|
+
};
|
|
99
|
+
ImVec4* colors = style.Colors;
|
|
100
|
+
colors[ImGuiCol_Text] = rgb(0xe6, 0xe7, 0xea);
|
|
101
|
+
colors[ImGuiCol_TextDisabled] = rgb(0x8b, 0x8f, 0x98);
|
|
102
|
+
colors[ImGuiCol_WindowBg] = rgb(0x15, 0x16, 0x1a);
|
|
103
|
+
colors[ImGuiCol_ChildBg] = rgb(0x15, 0x16, 0x1a, 0);
|
|
104
|
+
colors[ImGuiCol_PopupBg] = rgb(0x1d, 0x1f, 0x24);
|
|
105
|
+
colors[ImGuiCol_Border] = rgb(0x2a, 0x2c, 0x33);
|
|
106
|
+
colors[ImGuiCol_FrameBg] = rgb(0x1d, 0x1f, 0x24);
|
|
107
|
+
colors[ImGuiCol_FrameBgHovered] = rgb(0x24, 0x26, 0x2c);
|
|
108
|
+
colors[ImGuiCol_FrameBgActive] = rgb(0x24, 0x26, 0x2c);
|
|
109
|
+
colors[ImGuiCol_CheckMark] = rgb(0x8f, 0xb3, 0xff);
|
|
110
|
+
colors[ImGuiCol_SliderGrab] = rgb(0x3a, 0x7b, 0xfd);
|
|
111
|
+
colors[ImGuiCol_SliderGrabActive] = rgb(0x8f, 0xb3, 0xff);
|
|
112
|
+
colors[ImGuiCol_Button] = rgb(0x1d, 0x1f, 0x24);
|
|
113
|
+
colors[ImGuiCol_ButtonHovered] = rgb(0x26, 0x28, 0x2f);
|
|
114
|
+
colors[ImGuiCol_ButtonActive] = rgb(0x3a, 0x7b, 0xfd, 0.3F);
|
|
115
|
+
colors[ImGuiCol_Header] = rgb(0x3a, 0x7b, 0xfd, 0.2F);
|
|
116
|
+
colors[ImGuiCol_HeaderHovered] = rgb(0xff, 0xff, 0xff, 0.08F);
|
|
117
|
+
colors[ImGuiCol_HeaderActive] = rgb(0x3a, 0x7b, 0xfd, 0.3F);
|
|
118
|
+
colors[ImGuiCol_ScrollbarBg] = rgb(0, 0, 0, 0);
|
|
119
|
+
colors[ImGuiCol_ScrollbarGrab] = rgb(0x2a, 0x2c, 0x33);
|
|
120
|
+
colors[ImGuiCol_ScrollbarGrabHovered] = rgb(0x3a, 0x3c, 0x44);
|
|
121
|
+
colors[ImGuiCol_ScrollbarGrabActive] = rgb(0x4a, 0x4c, 0x54);
|
|
122
|
+
colors[ImGuiCol_Separator] = rgb(0x2a, 0x2c, 0x33);
|
|
123
|
+
colors[ImGuiCol_NavCursor] = rgb(0x3a, 0x7b, 0xfd);
|
|
124
|
+
style.ScaleAllSizes(scale);
|
|
125
|
+
style.FontSizeBase = 13;
|
|
126
|
+
style.FontScaleDpi = scale;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** FNV-1a over the bytes of the frame's drawing. */
|
|
130
|
+
uint64_t hashDrawData(const ImDrawData* data)
|
|
131
|
+
{
|
|
132
|
+
uint64_t hash = 1469598103934665603ULL;
|
|
133
|
+
auto mix = [&hash](const void* bytes, size_t size)
|
|
134
|
+
{
|
|
135
|
+
const auto* at = static_cast<const unsigned char*>(bytes);
|
|
136
|
+
for (size_t index = 0; index < size; ++index)
|
|
137
|
+
hash = (hash ^ at[index]) * 1099511628211ULL;
|
|
138
|
+
};
|
|
139
|
+
mix(&data->DisplaySize, sizeof data->DisplaySize);
|
|
140
|
+
mix(&data->FramebufferScale, sizeof data->FramebufferScale);
|
|
141
|
+
for (const ImDrawList* list : data->CmdLists)
|
|
142
|
+
{
|
|
143
|
+
mix(list->VtxBuffer.Data, static_cast<size_t>(list->VtxBuffer.Size) * sizeof(ImDrawVert));
|
|
144
|
+
mix(list->IdxBuffer.Data, static_cast<size_t>(list->IdxBuffer.Size) * sizeof(ImDrawIdx));
|
|
145
|
+
for (const ImDrawCmd& command : list->CmdBuffer)
|
|
146
|
+
{
|
|
147
|
+
mix(&command.ClipRect, sizeof command.ClipRect);
|
|
148
|
+
// The texture's identity, uploaded or not yet.
|
|
149
|
+
mix(&command.TexRef, sizeof command.TexRef);
|
|
150
|
+
mix(&command.ElemCount, sizeof command.ElemCount);
|
|
151
|
+
mix(&command.IdxOffset, sizeof command.IdxOffset);
|
|
152
|
+
mix(&command.VtxOffset, sizeof command.VtxOffset);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return hash;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
bool texturesChanging(const ImDrawData* data)
|
|
159
|
+
{
|
|
160
|
+
if (data->Textures == nullptr)
|
|
161
|
+
return false;
|
|
162
|
+
return std::ranges::any_of(*data->Textures, [](const ImTextureData* texture)
|
|
163
|
+
{ return texture->Status != ImTextureStatus_OK; });
|
|
164
|
+
}
|
|
165
|
+
} // namespace
|
|
166
|
+
|
|
167
|
+
std::unique_ptr<EditorWindow> HostWindow::create(EventLoop& loop, HostModel& model, const std::string& title,
|
|
168
|
+
int width, int height, bool bare, Callbacks callbacks,
|
|
169
|
+
std::string& error)
|
|
170
|
+
{
|
|
171
|
+
if (! initVideo(error))
|
|
172
|
+
return nullptr;
|
|
173
|
+
auto window = std::make_unique<HostWindow>(loop, model, bare, std::move(callbacks));
|
|
174
|
+
if (! window->open(title, width, height, error))
|
|
175
|
+
return nullptr;
|
|
176
|
+
return window;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
HostWindow::HostWindow(EventLoop& loop, HostModel& model, bool bare, Callbacks callbacks)
|
|
180
|
+
: loop_(loop), model_(model), callbacks_(std::move(callbacks)), bare_(bare)
|
|
181
|
+
{
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
bool HostWindow::open(const std::string& title, int width, int height, std::string& error)
|
|
185
|
+
{
|
|
186
|
+
editorWidth_ = width;
|
|
187
|
+
editorHeight_ = height;
|
|
188
|
+
constexpr SDL_WindowFlags flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
|
189
|
+
#if defined(__linux__)
|
|
190
|
+
window_ = SDL_CreateWindow(title.c_str(), width, height, flags | SDL_WINDOW_OPENGL);
|
|
191
|
+
if (window_ != nullptr)
|
|
192
|
+
{
|
|
193
|
+
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
|
194
|
+
// Only an OpenGL renderer answers the window manager's requests.
|
|
195
|
+
if (renderer_ == nullptr || std::strcmp(SDL_GetRendererName(renderer_), "opengl") != 0)
|
|
196
|
+
{
|
|
197
|
+
if (renderer_ != nullptr)
|
|
198
|
+
SDL_DestroyRenderer(renderer_);
|
|
199
|
+
renderer_ = nullptr;
|
|
200
|
+
SDL_DestroyWindow(window_);
|
|
201
|
+
window_ = nullptr;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
#endif
|
|
205
|
+
if (window_ == nullptr)
|
|
206
|
+
{
|
|
207
|
+
SDL_SetHint(SDL_HINT_VIDEO_X11_ENABLE_XSYNC_EXT, "0");
|
|
208
|
+
window_ = SDL_CreateWindow(title.c_str(), width, height, flags);
|
|
209
|
+
}
|
|
210
|
+
if (window_ == nullptr)
|
|
211
|
+
{
|
|
212
|
+
error = std::string("the host interface's window could not be created: ") + SDL_GetError();
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (renderer_ == nullptr)
|
|
216
|
+
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
|
217
|
+
if (renderer_ == nullptr)
|
|
218
|
+
{
|
|
219
|
+
error = std::string("the host interface could not draw: ") + SDL_GetError();
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
std::fprintf(stderr, "[vst3-host] host interface renderer: %s\n", SDL_GetRendererName(renderer_));
|
|
223
|
+
area_ = EditorArea::create(window_, error);
|
|
224
|
+
if (area_ == nullptr)
|
|
225
|
+
return false;
|
|
226
|
+
if (bare_)
|
|
227
|
+
{
|
|
228
|
+
area_->allowOnlyMoveResizeClose();
|
|
229
|
+
area_->keepTopLeft(true);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
IMGUI_CHECKVERSION();
|
|
233
|
+
context_ = ImGui::CreateContext();
|
|
234
|
+
ImGui::SetCurrentContext(context_);
|
|
235
|
+
ImGuiIO& io = ImGui::GetIO();
|
|
236
|
+
io.IniFilename = nullptr;
|
|
237
|
+
io.LogFilename = nullptr;
|
|
238
|
+
ImFontConfig config;
|
|
239
|
+
config.FontDataOwnedByAtlas = false;
|
|
240
|
+
// ImGui does not write to font data it does not own.
|
|
241
|
+
ImFont* regular = io.Fonts->AddFontFromMemoryTTF(const_cast<unsigned char*>(fonts::regular),
|
|
242
|
+
static_cast<int>(fonts::regularSize), 13, &config);
|
|
243
|
+
ImFont* semibold = io.Fonts->AddFontFromMemoryTTF(const_cast<unsigned char*>(fonts::semibold),
|
|
244
|
+
static_cast<int>(fonts::semiboldSize), 13, &config);
|
|
245
|
+
io.FontDefault = regular;
|
|
246
|
+
ImGui_ImplSDL3_InitForSDLRenderer(window_, renderer_);
|
|
247
|
+
ImGui_ImplSDLRenderer3_Init(renderer_);
|
|
248
|
+
|
|
249
|
+
interface_ = std::make_unique<HostInterface>(loop_, model_, window_, semibold, bare_);
|
|
250
|
+
panelOpen_ = interface_->panelOpen();
|
|
251
|
+
stagePanelOpen_ = panelOpen_;
|
|
252
|
+
applyScale();
|
|
253
|
+
fitWindow();
|
|
254
|
+
applyRefreshRate();
|
|
255
|
+
#if defined(__APPLE__)
|
|
256
|
+
// SDL reads Cocoa's events itself (its keyboard handling depends on
|
|
257
|
+
// it), and sends every event on to the application, the editor's
|
|
258
|
+
// included.
|
|
259
|
+
loop_.setWaiter(
|
|
260
|
+
[this, alive = alive_](double seconds)
|
|
261
|
+
{
|
|
262
|
+
SDL_WaitEventTimeout(nullptr, static_cast<Sint32>(seconds * 1000));
|
|
263
|
+
if (*alive)
|
|
264
|
+
pump();
|
|
265
|
+
});
|
|
266
|
+
#endif
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
HostWindow::~HostWindow()
|
|
271
|
+
{
|
|
272
|
+
closing_ = true;
|
|
273
|
+
*alive_ = false;
|
|
274
|
+
#if defined(__APPLE__)
|
|
275
|
+
loop_.setWaiter(nullptr);
|
|
276
|
+
#endif
|
|
277
|
+
if (timer_ != 0)
|
|
278
|
+
loop_.removeTimer(timer_);
|
|
279
|
+
interface_.reset();
|
|
280
|
+
if (live_)
|
|
281
|
+
endLive();
|
|
282
|
+
if (context_ != nullptr)
|
|
283
|
+
{
|
|
284
|
+
ImGui::SetCurrentContext(context_);
|
|
285
|
+
ImGui_ImplSDLRenderer3_Shutdown();
|
|
286
|
+
ImGui_ImplSDL3_Shutdown();
|
|
287
|
+
ImGui::DestroyContext(context_);
|
|
288
|
+
}
|
|
289
|
+
area_.reset();
|
|
290
|
+
if (renderer_ != nullptr)
|
|
291
|
+
SDL_DestroyRenderer(renderer_);
|
|
292
|
+
if (window_ != nullptr)
|
|
293
|
+
SDL_DestroyWindow(window_);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
void HostWindow::setResizable(bool resizable)
|
|
297
|
+
{
|
|
298
|
+
resizable_ = resizable;
|
|
299
|
+
// Only a bare window is the editor: the host's has a stage around it,
|
|
300
|
+
// which the user can size whichever editor it shows.
|
|
301
|
+
SDL_SetWindowResizable(window_, resizable || ! bare_);
|
|
302
|
+
fitWindow();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
void HostWindow::setContentSize(int width, int height)
|
|
306
|
+
{
|
|
307
|
+
if (width == editorWidth_ && height == editorHeight_)
|
|
308
|
+
return;
|
|
309
|
+
editorWidth_ = width;
|
|
310
|
+
editorHeight_ = height;
|
|
311
|
+
fitWindow();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
std::optional<std::pair<int, int>> HostWindow::editorRoom() const
|
|
315
|
+
{
|
|
316
|
+
// A bare window grows with the editor, and so does one not yet shown,
|
|
317
|
+
// for the size the editor opens at (its scale).
|
|
318
|
+
if (bare_ || ! shown_)
|
|
319
|
+
return std::nullopt;
|
|
320
|
+
const auto [width, height] = stageEditorSize();
|
|
321
|
+
return std::pair { std::max(width, editorWidth_), std::max(height, editorHeight_) };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
void HostWindow::constrainContentSize(int width, int height)
|
|
325
|
+
{
|
|
326
|
+
editorWidth_ = width;
|
|
327
|
+
editorHeight_ = height;
|
|
328
|
+
// A bare window is the editor's size, whatever its limits made of a drag.
|
|
329
|
+
if (bare_)
|
|
330
|
+
{
|
|
331
|
+
applyLimits();
|
|
332
|
+
// The window manager owns the window through a drag: it takes the
|
|
333
|
+
// editor's size once the drag has stopped (settleWindow()).
|
|
334
|
+
if (settling_)
|
|
335
|
+
layout();
|
|
336
|
+
else
|
|
337
|
+
fitWindow();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// The window manager owns a border drag. Keep any extra stage space
|
|
341
|
+
// rather than resize the outer window back on every constraint check.
|
|
342
|
+
applyLimits();
|
|
343
|
+
const auto required = requiredSize();
|
|
344
|
+
const auto current = windowSize();
|
|
345
|
+
if (required.first > current.first || required.second > current.second)
|
|
346
|
+
fitWindow();
|
|
347
|
+
else
|
|
348
|
+
layout();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
void HostWindow::show()
|
|
352
|
+
{
|
|
353
|
+
SDL_ShowWindow(window_);
|
|
354
|
+
SDL_RaiseWindow(window_);
|
|
355
|
+
SDL_SyncWindow(window_);
|
|
356
|
+
draggedSize_ = windowSize();
|
|
357
|
+
layout();
|
|
358
|
+
shown_ = true;
|
|
359
|
+
forcePresent_ = true;
|
|
360
|
+
invalidate();
|
|
361
|
+
render();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
double HostWindow::scale() const
|
|
365
|
+
{
|
|
366
|
+
// The interface's: the editor and what is around it are the same size
|
|
367
|
+
// on the screen, as in the web host.
|
|
368
|
+
return scale_;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
std::pair<int, int> HostWindow::windowSize() const
|
|
372
|
+
{
|
|
373
|
+
int width = 0;
|
|
374
|
+
int height = 0;
|
|
375
|
+
SDL_GetWindowSize(window_, &width, &height);
|
|
376
|
+
return { width, height };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
void HostWindow::applyScale()
|
|
380
|
+
{
|
|
381
|
+
#if defined(__APPLE__)
|
|
382
|
+
// Points: the system scales them.
|
|
383
|
+
scale_ = 1;
|
|
384
|
+
#else
|
|
385
|
+
scale_ = std::max(1.0F, SDL_GetWindowDisplayScale(window_));
|
|
386
|
+
#endif
|
|
387
|
+
ImGui::SetCurrentContext(context_);
|
|
388
|
+
applyStyle(scale_);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
void HostWindow::applyRefreshRate()
|
|
392
|
+
{
|
|
393
|
+
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window_));
|
|
394
|
+
const double rate = mode != nullptr && std::isfinite(mode->refresh_rate) && mode->refresh_rate > 0
|
|
395
|
+
? static_cast<double>(mode->refresh_rate)
|
|
396
|
+
: 60.0;
|
|
397
|
+
frameInterval_ =
|
|
398
|
+
std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / rate));
|
|
399
|
+
// Wake just before a frame is due. The deadline below retains fractional
|
|
400
|
+
// milliseconds (59.94 Hz, 144 Hz, etc.) instead of accumulating drift.
|
|
401
|
+
const auto milliseconds = static_cast<unsigned>(std::max(1.0, std::floor(1000.0 / rate)));
|
|
402
|
+
if (timer_ != 0)
|
|
403
|
+
loop_.removeTimer(timer_);
|
|
404
|
+
timer_ = loop_.addTimer(milliseconds, [this] { pump(); });
|
|
405
|
+
nextFrame_ = std::chrono::steady_clock::now();
|
|
406
|
+
invalidate();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
std::pair<int, int> HostWindow::naturalSize() const
|
|
410
|
+
{
|
|
411
|
+
const float padding = paddingSpace();
|
|
412
|
+
const float stage = std::max(static_cast<float>(editorWidth_), dockSpan()) + 2 * padding;
|
|
413
|
+
const float panel = panelOpen_ ? HostInterface::panelWidth * scale_ : 0;
|
|
414
|
+
float height = static_cast<float>(editorHeight_) + padding + dockHeight();
|
|
415
|
+
if (panelOpen_)
|
|
416
|
+
height = std::max(height, HostInterface::panelMinHeight * scale_);
|
|
417
|
+
return { static_cast<int>(std::ceil(stage + panel)), static_cast<int>(std::ceil(height)) };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
std::pair<int, int> HostWindow::requiredSize() const
|
|
421
|
+
{
|
|
422
|
+
const float padding = paddingSpace();
|
|
423
|
+
const float panel = panelOpen_ ? HostInterface::panelWidth * scale_ : 0;
|
|
424
|
+
const float stage = std::max(static_cast<float>(editorWidth_), dockSpan()) + 2 * padding;
|
|
425
|
+
return { static_cast<int>(std::ceil(stage + panel)),
|
|
426
|
+
static_cast<int>(std::ceil(static_cast<float>(editorHeight_) + padding + dockHeight())) };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
void HostWindow::setEditorMinimum(int width, int height)
|
|
430
|
+
{
|
|
431
|
+
minimumWidth_ = width;
|
|
432
|
+
minimumHeight_ = height;
|
|
433
|
+
limits_ = { 0, 0 };
|
|
434
|
+
applyLimits();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
void HostWindow::setEditorMaximum(int width, int height)
|
|
438
|
+
{
|
|
439
|
+
if (! bare_)
|
|
440
|
+
return;
|
|
441
|
+
maximumWidth_ = width;
|
|
442
|
+
maximumHeight_ = height;
|
|
443
|
+
limits_ = { 0, 0 };
|
|
444
|
+
applyLimits();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
void HostWindow::askWindowSize(int width, int height)
|
|
448
|
+
{
|
|
449
|
+
askedSize_ = std::pair { width, height };
|
|
450
|
+
askedUntil_ = std::chrono::steady_clock::now() + askedSizeTimeout;
|
|
451
|
+
SDL_SetWindowSize(window_, width, height);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
void HostWindow::applyLimits()
|
|
455
|
+
{
|
|
456
|
+
// A bare window is the editor, so no smaller than the smallest editor.
|
|
457
|
+
// The host's window leaves the editor's size alone: no smaller than
|
|
458
|
+
// this editor, with its dock and its panel.
|
|
459
|
+
const std::pair smallest { minimumWidth_, minimumHeight_ };
|
|
460
|
+
const bool fixed = bare_ && ! resizable_;
|
|
461
|
+
const auto minimum = fixed ? naturalSize() : bare_ ? smallest : requiredSize();
|
|
462
|
+
if (minimum == limits_)
|
|
463
|
+
return;
|
|
464
|
+
limits_ = minimum;
|
|
465
|
+
SDL_SetWindowMinimumSize(window_, minimum.first, minimum.second);
|
|
466
|
+
if (fixed)
|
|
467
|
+
SDL_SetWindowMaximumSize(window_, minimum.first, minimum.second);
|
|
468
|
+
else
|
|
469
|
+
SDL_SetWindowMaximumSize(window_, bare_ ? maximumWidth_ : 0, bare_ ? maximumHeight_ : 0);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
void HostWindow::fitWindow(int extraWidth)
|
|
473
|
+
{
|
|
474
|
+
const auto natural = naturalSize();
|
|
475
|
+
fitted_ = natural;
|
|
476
|
+
limits_ = { 0, 0 };
|
|
477
|
+
applyLimits();
|
|
478
|
+
if ((SDL_GetWindowFlags(window_) & SDL_WINDOW_MAXIMIZED) == 0)
|
|
479
|
+
{
|
|
480
|
+
// Space the user made stays. Window-manager requests are
|
|
481
|
+
// asynchronous: waiting here can block all plugin input and timers.
|
|
482
|
+
const auto [width, height] = windowSize();
|
|
483
|
+
const std::pair wanted =
|
|
484
|
+
! bare_ ? std::pair { std::max(natural.first, width + extraWidth), std::max(natural.second, height) }
|
|
485
|
+
: natural;
|
|
486
|
+
if (wanted != std::pair { width, height })
|
|
487
|
+
{
|
|
488
|
+
area_->keepTopLeft(true);
|
|
489
|
+
if (bare_)
|
|
490
|
+
askWindowSize(wanted.first, wanted.second);
|
|
491
|
+
else
|
|
492
|
+
SDL_SetWindowSize(window_, wanted.first, wanted.second);
|
|
493
|
+
// The window has not changed yet, and may take a while: the
|
|
494
|
+
// stage stays until it has, or the editor would move away and
|
|
495
|
+
// back.
|
|
496
|
+
if (stagePanelOpen_ != panelOpen_)
|
|
497
|
+
{
|
|
498
|
+
stageFollowsAt_ = std::chrono::steady_clock::now() + stageFollowTimeout;
|
|
499
|
+
layout();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
stagePanelOpen_ = panelOpen_;
|
|
505
|
+
layout();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
float HostWindow::paddingSpace() const
|
|
509
|
+
{
|
|
510
|
+
return bare_ ? 0 : HostInterface::padding * scale_;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
float HostWindow::dockHeight() const
|
|
514
|
+
{
|
|
515
|
+
return bare_ ? 0 : HostInterface::dockSpace * scale_;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
float HostWindow::dockSpan() const
|
|
519
|
+
{
|
|
520
|
+
return bare_ ? 0 : interface_->dockWidth() * scale_;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
float HostWindow::panelSpace() const
|
|
524
|
+
{
|
|
525
|
+
return stagePanelOpen_ ? std::ceil(HostInterface::panelWidth * scale_) : 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
void HostWindow::layout()
|
|
529
|
+
{
|
|
530
|
+
const auto [width, height] = windowSize();
|
|
531
|
+
const float padding = paddingSpace();
|
|
532
|
+
const float panel = panelSpace();
|
|
533
|
+
const float stage = static_cast<float>(width) - panel;
|
|
534
|
+
const float free = static_cast<float>(height) - padding - dockHeight() - static_cast<float>(editorHeight_);
|
|
535
|
+
// Centred on the stage, as the web host's view is.
|
|
536
|
+
const int x = std::max(0, static_cast<int>(std::floor((stage - static_cast<float>(editorWidth_)) / 2)));
|
|
537
|
+
const int y = static_cast<int>(padding + std::max(0.0F, std::floor(free / 2)));
|
|
538
|
+
const std::array frame { x, y, editorWidth_, editorHeight_ };
|
|
539
|
+
if (frame != editorFrame_ || ! area_->isAt(x, y))
|
|
540
|
+
{
|
|
541
|
+
touchLive();
|
|
542
|
+
editorFrame_ = frame;
|
|
543
|
+
area_->setFrame(x, y, editorWidth_, editorHeight_);
|
|
544
|
+
}
|
|
545
|
+
forcePresent_ = true;
|
|
546
|
+
invalidate();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
void HostWindow::touchLive()
|
|
550
|
+
{
|
|
551
|
+
if (! shown_)
|
|
552
|
+
return;
|
|
553
|
+
if (! live_)
|
|
554
|
+
{
|
|
555
|
+
viewHidden_ = false;
|
|
556
|
+
hideViewFirst_ = false;
|
|
557
|
+
live_ = area_->beginLive();
|
|
558
|
+
// Before the editor takes a size: its image as it stands.
|
|
559
|
+
if (live_)
|
|
560
|
+
updateLiveImage();
|
|
561
|
+
}
|
|
562
|
+
liveTouched_ = std::chrono::steady_clock::now();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
void HostWindow::endLive()
|
|
566
|
+
{
|
|
567
|
+
live_ = false;
|
|
568
|
+
area_->endLive();
|
|
569
|
+
if (liveTexture_ != nullptr)
|
|
570
|
+
SDL_DestroyTexture(liveTexture_);
|
|
571
|
+
liveTexture_ = nullptr;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
void HostWindow::updateLiveImage()
|
|
575
|
+
{
|
|
576
|
+
const auto image = area_->takeImage();
|
|
577
|
+
if (! image)
|
|
578
|
+
return;
|
|
579
|
+
if (image->partial && liveTexture_ != nullptr)
|
|
580
|
+
{
|
|
581
|
+
hideViewFirst_ = true;
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (liveTexture_ == nullptr || liveWidth_ != image->width || liveHeight_ != image->height)
|
|
585
|
+
{
|
|
586
|
+
if (liveTexture_ != nullptr)
|
|
587
|
+
SDL_DestroyTexture(liveTexture_);
|
|
588
|
+
liveTexture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_XRGB8888, SDL_TEXTUREACCESS_STREAMING,
|
|
589
|
+
image->width, image->height);
|
|
590
|
+
if (liveTexture_ == nullptr)
|
|
591
|
+
return;
|
|
592
|
+
liveWidth_ = image->width;
|
|
593
|
+
liveHeight_ = image->height;
|
|
594
|
+
}
|
|
595
|
+
SDL_UpdateTexture(liveTexture_, nullptr, image->pixels, image->pitch);
|
|
596
|
+
livePartial_ = image->partial;
|
|
597
|
+
// The window manager has resized the window and waits for this frame,
|
|
598
|
+
// which it shows as the new size: the final layout, not the view's.
|
|
599
|
+
if (image->first && ! image->partial && std::chrono::steady_clock::now() - windowResizedAt_ < windowAnswer)
|
|
600
|
+
{
|
|
601
|
+
hideViewFirst_ = true;
|
|
602
|
+
livePart_.reset();
|
|
603
|
+
}
|
|
604
|
+
else if (image->first)
|
|
605
|
+
{
|
|
606
|
+
const auto width = static_cast<float>(image->width);
|
|
607
|
+
const auto height = static_cast<float>(image->height);
|
|
608
|
+
livePart_ = LivePart { static_cast<float>(image->shownX),
|
|
609
|
+
static_cast<float>(image->shownY),
|
|
610
|
+
static_cast<float>(image->validWidth),
|
|
611
|
+
static_cast<float>(image->validHeight),
|
|
612
|
+
static_cast<float>(image->validX) / width,
|
|
613
|
+
static_cast<float>(image->validY) / height,
|
|
614
|
+
static_cast<float>(image->validX + image->validWidth) / width,
|
|
615
|
+
static_cast<float>(image->validY + image->validHeight) / height };
|
|
616
|
+
}
|
|
617
|
+
else
|
|
618
|
+
{
|
|
619
|
+
livePart_.reset();
|
|
620
|
+
}
|
|
621
|
+
forcePresent_ = true;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
std::pair<int, int> HostWindow::stageEditorSize() const
|
|
625
|
+
{
|
|
626
|
+
const auto [width, height] = windowSize();
|
|
627
|
+
const float padding = paddingSpace();
|
|
628
|
+
const float panel = panelSpace();
|
|
629
|
+
return { std::max(1, static_cast<int>(static_cast<float>(width) - panel - 2 * padding)),
|
|
630
|
+
std::max(1, static_cast<int>(static_cast<float>(height) - padding - dockHeight())) };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
void HostWindow::pump()
|
|
634
|
+
{
|
|
635
|
+
if (closing_)
|
|
636
|
+
return;
|
|
637
|
+
ImGui::SetCurrentContext(context_);
|
|
638
|
+
const auto alive = alive_;
|
|
639
|
+
SDL_Event event;
|
|
640
|
+
#if defined(_WIN32)
|
|
641
|
+
// The host's message loop dispatches the window's messages; SDL's
|
|
642
|
+
// window procedure queues what they mean.
|
|
643
|
+
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) > 0)
|
|
644
|
+
#else
|
|
645
|
+
while (SDL_PollEvent(&event))
|
|
646
|
+
#endif
|
|
647
|
+
{
|
|
648
|
+
if (! *alive)
|
|
649
|
+
return;
|
|
650
|
+
handle(event);
|
|
651
|
+
if (closing_)
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (! *alive)
|
|
655
|
+
return;
|
|
656
|
+
const auto now = std::chrono::steady_clock::now();
|
|
657
|
+
settleWindow(now);
|
|
658
|
+
// A window manager that does not resize the window as asked.
|
|
659
|
+
if (stagePanelOpen_ != panelOpen_ && now >= stageFollowsAt_)
|
|
660
|
+
{
|
|
661
|
+
stagePanelOpen_ = panelOpen_;
|
|
662
|
+
layout();
|
|
663
|
+
}
|
|
664
|
+
// Only once the geometry changes: a press alone changes nothing, and
|
|
665
|
+
// starts nothing. Held, it goes on through a pause in the drag.
|
|
666
|
+
if (gripSize_ || (live_ && interface_->resizingEditor()))
|
|
667
|
+
touchLive();
|
|
668
|
+
else if (live_ && now - liveTouched_ >= liveQuiet && area_->whole())
|
|
669
|
+
{
|
|
670
|
+
endLive();
|
|
671
|
+
forcePresent_ = true;
|
|
672
|
+
invalidate();
|
|
673
|
+
}
|
|
674
|
+
if (now - lastFrame_ >= refreshInterval || interface_->animating() || live_)
|
|
675
|
+
invalidate(1);
|
|
676
|
+
if (pending_ > 0 && now >= nextFrame_)
|
|
677
|
+
{
|
|
678
|
+
// Skip missed deadlines rather than rendering catch-up frames.
|
|
679
|
+
nextFrame_ += frameInterval_ * ((now - nextFrame_) / frameInterval_ + 1);
|
|
680
|
+
render();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
void HostWindow::settleWindow(std::chrono::steady_clock::time_point now)
|
|
685
|
+
{
|
|
686
|
+
if (! settling_)
|
|
687
|
+
return;
|
|
688
|
+
if (askedSize_ && now >= askedUntil_)
|
|
689
|
+
askedSize_.reset();
|
|
690
|
+
// Not before the editor has its last size from the drag.
|
|
691
|
+
if (askedSize_ || gripSize_ || now - windowResizedAt_ < liveQuiet)
|
|
692
|
+
return;
|
|
693
|
+
// Fullscreen, the window's size is not its own to change.
|
|
694
|
+
const bool matches = windowSize() == std::pair { editorWidth_, editorHeight_ };
|
|
695
|
+
if (matches || settlingAsks_ >= settleAsks || (SDL_GetWindowFlags(window_) & SDL_WINDOW_FULLSCREEN) != 0)
|
|
696
|
+
{
|
|
697
|
+
settling_ = false;
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
++settlingAsks_;
|
|
701
|
+
fitWindow();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
void HostWindow::windowResized()
|
|
705
|
+
{
|
|
706
|
+
area_->windowChanged();
|
|
707
|
+
windowResizedAt_ = std::chrono::steady_clock::now();
|
|
708
|
+
stagePanelOpen_ = panelOpen_;
|
|
709
|
+
// Maximized, the next size is the restore's, which cuts the editor off
|
|
710
|
+
// whichever way it goes: its top left is the part that says what it is.
|
|
711
|
+
area_->keepTopLeft(bare_ || (SDL_GetWindowFlags(window_) & SDL_WINDOW_MAXIMIZED) != 0);
|
|
712
|
+
if (bare_)
|
|
713
|
+
bareWindowResized();
|
|
714
|
+
// The stage around the editor changes, and its place on it.
|
|
715
|
+
layout();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
void HostWindow::bareWindowResized()
|
|
719
|
+
{
|
|
720
|
+
const auto [width, height] = windowSize();
|
|
721
|
+
const auto last = std::exchange(draggedSize_, std::pair { width, height });
|
|
722
|
+
// On its way to a size the host asked for: what comes before it is
|
|
723
|
+
// older than the editor's size, and must not change the editor.
|
|
724
|
+
if (askedSize_)
|
|
725
|
+
{
|
|
726
|
+
const bool arrived = std::abs(width - askedSize_->first) <= 2 && std::abs(height - askedSize_->second) <= 2;
|
|
727
|
+
if (arrived || std::chrono::steady_clock::now() >= askedUntil_)
|
|
728
|
+
askedSize_.reset();
|
|
729
|
+
if (arrived || askedSize_)
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (! resizable_ || std::pair { width, height } == std::pair { editorWidth_, editorHeight_ })
|
|
733
|
+
return;
|
|
734
|
+
// Shown at once, and handed to the editor in render(), once it can take it.
|
|
735
|
+
touchLive();
|
|
736
|
+
settling_ = true;
|
|
737
|
+
settlingAsks_ = 0;
|
|
738
|
+
model_.plugin.previewEditorDrag(width, height, last.first, last.second);
|
|
739
|
+
gripSize_ = std::pair { editorWidth_, editorHeight_ };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
void HostWindow::handle(const SDL_Event& event)
|
|
743
|
+
{
|
|
744
|
+
const SDL_WindowID id = SDL_GetWindowID(window_);
|
|
745
|
+
switch (event.type)
|
|
746
|
+
{
|
|
747
|
+
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
|
748
|
+
if (event.window.windowID == id && callbacks_.closed)
|
|
749
|
+
{
|
|
750
|
+
// The callback destroys this window.
|
|
751
|
+
closing_ = true;
|
|
752
|
+
loop_.post(callbacks_.closed);
|
|
753
|
+
}
|
|
754
|
+
return;
|
|
755
|
+
case SDL_EVENT_WINDOW_RESIZED:
|
|
756
|
+
if (event.window.windowID == id)
|
|
757
|
+
windowResized();
|
|
758
|
+
return;
|
|
759
|
+
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
|
760
|
+
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
|
761
|
+
if (event.window.windowID == id && callbacks_.focusChanged)
|
|
762
|
+
callbacks_.focusChanged(event.type == SDL_EVENT_WINDOW_FOCUS_GAINED);
|
|
763
|
+
break;
|
|
764
|
+
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
|
|
765
|
+
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
|
|
766
|
+
if (event.display.displayID == SDL_GetDisplayForWindow(window_))
|
|
767
|
+
applyRefreshRate();
|
|
768
|
+
return;
|
|
769
|
+
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
|
|
770
|
+
if (event.window.windowID == id)
|
|
771
|
+
applyRefreshRate();
|
|
772
|
+
return;
|
|
773
|
+
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
|
774
|
+
if (event.window.windowID != id)
|
|
775
|
+
return;
|
|
776
|
+
applyScale();
|
|
777
|
+
fitWindow();
|
|
778
|
+
#if ! defined(__APPLE__)
|
|
779
|
+
if (callbacks_.scaleChanged)
|
|
780
|
+
callbacks_.scaleChanged(scale());
|
|
781
|
+
#endif
|
|
782
|
+
return;
|
|
783
|
+
case SDL_EVENT_WINDOW_EXPOSED:
|
|
784
|
+
case SDL_EVENT_WINDOW_SHOWN:
|
|
785
|
+
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
|
786
|
+
forcePresent_ = true;
|
|
787
|
+
invalidate();
|
|
788
|
+
return;
|
|
789
|
+
default:
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
ImGui_ImplSDL3_ProcessEvent(&event);
|
|
793
|
+
// Input moves hover and focus: a few frames let ImGui settle.
|
|
794
|
+
invalidate(3);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
void HostWindow::render()
|
|
798
|
+
{
|
|
799
|
+
// Apply the previous input frame's grip request before measuring and
|
|
800
|
+
// drawing. The native editor and the host frame then share one size.
|
|
801
|
+
if (const auto wanted = interface_->takeEditorSize())
|
|
802
|
+
{
|
|
803
|
+
gripSize_ = wanted;
|
|
804
|
+
model_.plugin.previewEditorSize(wanted->first, wanted->second);
|
|
805
|
+
}
|
|
806
|
+
if (live_)
|
|
807
|
+
updateLiveImage();
|
|
808
|
+
pending_ = std::max(0, pending_ - 1);
|
|
809
|
+
lastFrame_ = std::chrono::steady_clock::now();
|
|
810
|
+
ImGui::SetCurrentContext(context_);
|
|
811
|
+
|
|
812
|
+
const auto [width, height] = windowSize();
|
|
813
|
+
const float panel = panelSpace();
|
|
814
|
+
InterfaceLayout layout;
|
|
815
|
+
layout.width = static_cast<float>(width);
|
|
816
|
+
layout.height = static_cast<float>(height);
|
|
817
|
+
layout.scale = scale_;
|
|
818
|
+
layout.panelWidth = panel;
|
|
819
|
+
layout.editorX = static_cast<float>(editorFrame_[0]);
|
|
820
|
+
layout.editorY = static_cast<float>(editorFrame_[1]);
|
|
821
|
+
layout.editorWidth = static_cast<float>(editorWidth_);
|
|
822
|
+
layout.editorHeight = static_cast<float>(editorHeight_);
|
|
823
|
+
layout.resizable = resizable_;
|
|
824
|
+
layout.editorImage = live_ ? liveTexture_ : nullptr;
|
|
825
|
+
const bool part = livePart_.has_value();
|
|
826
|
+
layout.editorImageX = part ? livePart_->x : layout.editorX;
|
|
827
|
+
layout.editorImageY = part ? livePart_->y : layout.editorY;
|
|
828
|
+
layout.editorImageWidth = part ? livePart_->width : layout.editorWidth;
|
|
829
|
+
layout.editorImageHeight = part ? livePart_->height : layout.editorHeight;
|
|
830
|
+
layout.editorImageU0 = part ? livePart_->u0 : 0;
|
|
831
|
+
layout.editorImageV0 = part ? livePart_->v0 : 0;
|
|
832
|
+
layout.editorImageU1 = part ? livePart_->u1 : 1;
|
|
833
|
+
layout.editorImageV1 = part ? livePart_->v1 : 1;
|
|
834
|
+
|
|
835
|
+
ImGui_ImplSDLRenderer3_NewFrame();
|
|
836
|
+
ImGui_ImplSDL3_NewFrame();
|
|
837
|
+
ImGui::NewFrame();
|
|
838
|
+
interface_->draw(layout);
|
|
839
|
+
ImGui::Render();
|
|
840
|
+
|
|
841
|
+
ImDrawData* data = ImGui::GetDrawData();
|
|
842
|
+
const uint64_t hash = hashDrawData(data);
|
|
843
|
+
if (hash != lastHash_ || forcePresent_ || texturesChanging(data))
|
|
844
|
+
{
|
|
845
|
+
lastHash_ = hash;
|
|
846
|
+
forcePresent_ = false;
|
|
847
|
+
SDL_SetRenderScale(renderer_, data->FramebufferScale.x, data->FramebufferScale.y);
|
|
848
|
+
SDL_SetRenderDrawColor(renderer_, 0x0b, 0x0c, 0x0f, 0xff);
|
|
849
|
+
SDL_RenderClear(renderer_);
|
|
850
|
+
ImGui_ImplSDLRenderer3_RenderDrawData(data, renderer_);
|
|
851
|
+
// A development aid: SOUNDOR_VST3_HOST_CAPTURE=<file.png> keeps
|
|
852
|
+
// the interface's last frame (without the editor, which draws
|
|
853
|
+
// itself).
|
|
854
|
+
if (const char* capture = std::getenv("SOUNDOR_VST3_HOST_CAPTURE"); // NOLINT(concurrency-mt-unsafe)
|
|
855
|
+
capture != nullptr && *capture != '\0')
|
|
856
|
+
if (SDL_Surface* pixels = SDL_RenderReadPixels(renderer_, nullptr))
|
|
857
|
+
{
|
|
858
|
+
SDL_SavePNG(pixels, capture);
|
|
859
|
+
SDL_DestroySurface(pixels);
|
|
860
|
+
}
|
|
861
|
+
// The last whole image is not where the view is: the view goes first.
|
|
862
|
+
const bool shown = live_ && layout.editorImage != nullptr;
|
|
863
|
+
if (shown && hideViewFirst_)
|
|
864
|
+
viewHidden_ = area_->hideView();
|
|
865
|
+
SDL_RenderPresent(renderer_);
|
|
866
|
+
// The image is exactly what the view shows, so nothing changes.
|
|
867
|
+
if (shown && ! viewHidden_ && area_->hideView())
|
|
868
|
+
{
|
|
869
|
+
viewHidden_ = true;
|
|
870
|
+
if (! livePartial_)
|
|
871
|
+
livePart_.reset();
|
|
872
|
+
forcePresent_ = true;
|
|
873
|
+
invalidate();
|
|
874
|
+
}
|
|
875
|
+
++presented_;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// After presenting: the frame at the new size shows the editor's last
|
|
879
|
+
// image while the editor takes its time over the size.
|
|
880
|
+
if (gripSize_ && area_->settled())
|
|
881
|
+
{
|
|
882
|
+
const std::pair wanted = *gripSize_;
|
|
883
|
+
gripSize_.reset();
|
|
884
|
+
model_.emit("editorResized", model_.plugin.requestEditorSize(wanted.first, wanted.second));
|
|
885
|
+
area_->editorGiven(editorWidth_, editorHeight_);
|
|
886
|
+
}
|
|
887
|
+
// The panel opened or closed: the window grows or shrinks beside the
|
|
888
|
+
// stage, which stays as it is. A dock wider than the window makes room
|
|
889
|
+
// for itself.
|
|
890
|
+
if (interface_->panelOpen() != panelOpen_)
|
|
891
|
+
{
|
|
892
|
+
panelOpen_ = interface_->panelOpen();
|
|
893
|
+
const int change = static_cast<int>(std::ceil(HostInterface::panelWidth * scale_));
|
|
894
|
+
fitWindow(panelOpen_ ? change : -change);
|
|
895
|
+
}
|
|
896
|
+
else if (const auto natural = naturalSize();
|
|
897
|
+
! settling_ && (SDL_GetWindowFlags(window_) & SDL_WINDOW_MAXIMIZED) == 0 && natural != fitted_
|
|
898
|
+
&& (width < requiredSize().first || height < requiredSize().second))
|
|
899
|
+
fitWindow();
|
|
900
|
+
}
|
|
901
|
+
} // namespace soundor::vst3host
|