Skip to content

Commit b6832d6

Browse files
committed
Add [gemcanvas] for on-canvas Gem rendering
1 parent f852ed8 commit b6832d6

4 files changed

Lines changed: 230 additions & 2 deletions

File tree

Libraries/Gem

Source/Objects/GemWindowObject.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
// Copyright (c) 2026 Timothy Schoen
3+
// For information on usage and redistribution, and for a DISCLAIMER OF ALL
4+
// WARRANTIES, see the file, "LICENSE.txt," in this distribution.
5+
*/
6+
#pragma once
7+
8+
#define GEM_NO_SETUP 1
9+
#include <Gem/src/Output/gemjucewindow.h>
10+
11+
12+
// plugdata exclusive Gem object: renders the content of the currently active gem window to the plugdata canvas
13+
class GemCanvasObject final : public ObjectBase, private Timer
14+
{
15+
Value sizeProperty = SynchronousValue();
16+
17+
int nvgImage = -1;
18+
int texW = 0, texH = 0;
19+
std::vector<uint8_t> rgbaBuffer;
20+
GemCanvas* gemCanvas = nullptr;
21+
22+
Array<KeyPress> heldKeys;
23+
bool shiftDown = false, ctrlDown = false, altDown = false, cmdDown = false;
24+
25+
public:
26+
GemCanvasObject(pd::WeakReference ptr, Object* object)
27+
: ObjectBase(ptr, object)
28+
{
29+
objectParameters.addParamSize(&sizeProperty);
30+
startTimerHz(60);
31+
}
32+
33+
~GemCanvasObject() override
34+
{
35+
stopTimer();
36+
}
37+
38+
void updateCanvas()
39+
{
40+
41+
}
42+
43+
void update() override
44+
{
45+
if (auto obj = ptr.get<t_gemcanvas>()) {
46+
gemCanvas = &obj->canvas;
47+
sizeProperty = VarArray { var(obj->canvas.requestedWidth.load()), var(obj->canvas.requestedHeight.load()) };
48+
}
49+
}
50+
51+
void updateSizeProperty() override
52+
{
53+
setPdBounds(object->getObjectBounds());
54+
setParameterExcludingListener(sizeProperty,
55+
VarArray { var(getWidth()), var(getHeight()) });
56+
}
57+
58+
void render(NVGcontext* nvg) override
59+
{
60+
auto const b = getLocalBounds().toFloat();
61+
62+
// Dark background placeholder
63+
nvgDrawRoundedRect(nvg,
64+
b.getX(), b.getY(), b.getWidth(), b.getHeight(),
65+
nvgRGBf(0.05f, 0.05f, 0.05f),
66+
nvgRGBf(0.15f, 0.15f, 0.15f),
67+
Corners::objectCornerRadius);
68+
69+
if (!gemCanvas) return;
70+
71+
// Pull new pixel data if available
72+
if (gemCanvas->frameDirty.load()) {
73+
int w = 0, h = 0;
74+
if (gemCanvas->pullPixels(rgbaBuffer, w, h) && !rgbaBuffer.empty()) {
75+
if (nvgImage < 0 || w != texW || h != texH) {
76+
if (nvgImage >= 0)
77+
nvgDeleteImage(nvg, nvgImage);
78+
nvgImage = nvgCreateImageARGB(nvg, w, h, 0, rgbaBuffer.data());
79+
texW = w;
80+
texH = h;
81+
} else {
82+
nvgUpdateImage(nvg, nvgImage, rgbaBuffer.data());
83+
}
84+
}
85+
}
86+
87+
if (nvgImage < 0) return;
88+
89+
NVGpaint paint = nvgImagePattern(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), 0.0f, nvgImage, 1.0f);
90+
91+
nvgBeginPath(nvg);
92+
nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius);
93+
nvgFillPaint(nvg, paint);
94+
nvgFill(nvg);
95+
}
96+
97+
void timerCallback() override
98+
{
99+
// Repaint when a new frame is available
100+
if (gemCanvas && gemCanvas->frameDirty.load())
101+
repaint();
102+
103+
if (gemCanvas && gemCanvas->framerate != (1000.0f / getTimerInterval()))
104+
startTimerHz(gemCanvas->framerate);
105+
106+
if (!gemCanvas) return;
107+
108+
auto mods = ModifierKeys::getCurrentModifiers();
109+
auto hasFocus = hasKeyboardFocus(true);
110+
111+
auto checkModifier = [&](bool& currentState, bool newState, int code, std::string name) {
112+
if (newState != currentState) {
113+
currentState = newState;
114+
gemCanvas->keyCallback(KeyPress(code));
115+
}
116+
};
117+
118+
for (int i = heldKeys.size() - 1; i >= 0; --i) {
119+
auto key = heldKeys[i];
120+
if (!KeyPress::isKeyCurrentlyDown(key.getKeyCode())) {
121+
if (gemCanvas->keyUpCallback)
122+
gemCanvas->keyUpCallback(key);
123+
heldKeys.remove(i);
124+
}
125+
}
126+
127+
auto checkMod = [&](bool& state, bool pressed, int code, std::string name) {
128+
if (pressed != state) {
129+
state = pressed;
130+
if (state) {
131+
if (gemCanvas->keyCallback) gemCanvas->keyCallback(KeyPress(code));
132+
} else {
133+
if (gemCanvas->keyUpCallback) gemCanvas->keyUpCallback(KeyPress(code));
134+
}
135+
}
136+
};
137+
138+
checkMod(shiftDown, hasFocus && mods.isShiftDown(), 340, "Shift");
139+
checkMod(ctrlDown, hasFocus && mods.isCtrlDown(), 341, "Control");
140+
checkMod(altDown, hasFocus && mods.isAltDown(), 342, "Alt");
141+
checkMod(cmdDown, hasFocus && mods.isCommandDown(), 343, "Super");
142+
}
143+
144+
void mouseDown(MouseEvent const& e) override
145+
{
146+
if (gemCanvas && gemCanvas->mouseCallback)
147+
gemCanvas->mouseCallback(e, true);
148+
}
149+
150+
void mouseUp(MouseEvent const& e) override
151+
{
152+
if (gemCanvas && gemCanvas->mouseCallback)
153+
gemCanvas->mouseCallback(e, false);
154+
}
155+
156+
void mouseMove(MouseEvent const& e) override
157+
{
158+
if (gemCanvas && gemCanvas->motionCallback)
159+
gemCanvas->motionCallback(e);
160+
}
161+
162+
void mouseDrag(MouseEvent const& e) override
163+
{
164+
if (gemCanvas && gemCanvas->motionCallback)
165+
gemCanvas->motionCallback(e);
166+
}
167+
168+
void mouseWheelMove(MouseEvent const& e, MouseWheelDetails const& wheel) override
169+
{
170+
if (gemCanvas && gemCanvas->scrollCallback)
171+
gemCanvas->scrollCallback(e, wheel);
172+
}
173+
174+
bool keyPressed(KeyPress const& key) override
175+
{
176+
if (!gemCanvas || !gemCanvas->keyCallback) return false;
177+
gemCanvas->keyCallback(key);
178+
heldKeys.add(key);
179+
return false;
180+
}
181+
182+
void propertyChanged(Value& v) override
183+
{
184+
if (v.refersToSameSourceAs(sizeProperty)) {
185+
auto const& arr = *sizeProperty.getValue().getArray();
186+
auto const* constrainer = getConstrainer();
187+
auto const width = std::max(static_cast<int>(arr[0]), constrainer->getMinimumWidth());
188+
auto const height = std::max(static_cast<int>(arr[1]), constrainer->getMinimumHeight());
189+
190+
setParameterExcludingListener(sizeProperty, VarArray { var(width), var(height) });
191+
192+
if (auto obj = ptr.get<t_gemcanvas>()) {
193+
obj->canvas.requestedWidth = width;
194+
obj->canvas.requestedHeight = height;
195+
}
196+
197+
object->updateBounds();
198+
}
199+
}
200+
201+
void setPdBounds(Rectangle<int> const b) override
202+
{
203+
if (auto obj = ptr.get<t_gemcanvas>()) {
204+
obj->x_obj.te_xpix = b.getX();
205+
obj->x_obj.te_ypix = b.getY();
206+
obj->canvas.requestedWidth = b.getWidth();
207+
obj->canvas.requestedHeight = b.getHeight();
208+
}
209+
}
210+
211+
Rectangle<int> getPdBounds() override
212+
{
213+
if (auto gobj = ptr.get<t_gobj>()) {
214+
auto* patch = cnv->patch.getRawPointer();
215+
int x = 0, y = 0, w = 0, h = 0;
216+
pd::Interface::getObjectBounds(patch, gobj.get(), &x, &y, &w, &h);
217+
218+
int const fw = gemCanvas ? gemCanvas->requestedWidth.load() : w;
219+
int const fh = gemCanvas ? gemCanvas->requestedHeight.load() : h;
220+
return { x, y, fw > 0 ? fw : w, fh > 0 ? fh : h };
221+
}
222+
return {};
223+
}
224+
};

Source/Objects/ObjectBase.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ void canvas_click(t_canvas* x, t_floatarg xpos, t_floatarg ypos, t_floatarg shif
8383
#include "PopMenu.h"
8484
#include "LuaObject.h"
8585
#include "DropzoneObject.h"
86+
#include "GemWindowObject.h"
8687

8788
// Class for non-patchable objects
8889
class NonPatchable final : public ObjectBase {
@@ -394,7 +395,6 @@ void ObjectBase::closeOpenedSubpatchers()
394395
bool ObjectBase::click(Point<int> const position, bool const shift, bool const alt)
395396
{
396397
if (auto obj = ptr.get<t_text>()) {
397-
398398
t_text* x = obj.get();
399399
if (x->te_type == T_OBJECT) {
400400
t_symbol* clicksym = gensym("click");
@@ -691,6 +691,8 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
691691
return new KnobObject(ptr, parent);
692692
case hash("popmenu"):
693693
return new PopMenu(ptr, parent);
694+
case hash("gemcanvas"):
695+
return new GemCanvasObject(ptr, parent);
694696
// case hash("dropzone"):
695697
// return new DropzoneObject(ptr, parent);
696698
case hash("openfile"): {

Source/Pd/Setup.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,8 @@ void Setup::initialiseGem(std::string const& gemPluginPath)
18501850
gemmanager_setup();
18511851
gemreceive_setup();
18521852
gemjucewindow_setup();
1853+
gemcanvas_setup();
1854+
18531855
modelfiler_setup();
18541856
render_trigger_setup();
18551857
GemSplash_setup();

0 commit comments

Comments
 (0)