diff --git a/pd.lua b/pd.lua index 28b5dd1..e9665da 100644 --- a/pd.lua +++ b/pd.lua @@ -115,6 +115,12 @@ pd._mouseevent = function (object, x, y, event_type) if event_type == 3 and type(obj.mouse_drag) == "function" then obj:mouse_drag(x, y) end + if event_type == 4 and type(obj.mouse_enter) == "function" then + obj:mouse_enter(x, y) + end + if event_type == 5 and type(obj.mouse_exit) == "function" then + obj:mouse_exit(x, y) + end end end diff --git a/pdlua.c b/pdlua.c index 649ec2f..37d0de9 100644 --- a/pdlua.c +++ b/pdlua.c @@ -238,6 +238,15 @@ typedef struct pdlua_proxyclock struct pdlua *owner; /**< Object to forward messages to. */ t_clock *clock; /** Pd clock to use. */ } t_pdlua_proxyclock; + +/** Proxy canvas object data. */ +typedef struct pdlua_proxycanvas +{ + t_pd pd; /**< Minimal Pd object. */ + t_symbol *p_sym; /**< The name of the canvas. */ + struct pdlua *p_parent; /**< The parent object. */ + t_clock *p_clock; /**< clock for deferred cleanup */ +} t_pdlua_proxycanvas; /* prototypes*/ static const char *pdlua_reader (lua_State *L, void *rr, size_t *size); @@ -263,6 +272,14 @@ static t_pdlua_proxyclock *pdlua_proxyclock_new (struct pdlua *owner); static void pdlua_proxyclock_setup (void); /** Dump an array of atoms into a Lua table. */ static void pdlua_pushatomtable (int argc, t_atom *argv); +/** Proxy canvas 'anything' method. */ +static void pdlua_proxycanvas_anything(t_pdlua_proxycanvas *p, t_symbol *s, int argc, t_atom *argv); +/** Proxy canvas cleanup and deallocation. */ +static void pdlua_proxycanvas_free(t_pdlua_proxycanvas *p); +/** Proxy canvas allocation and initialization. */ +static t_pdlua_proxycanvas *pdlua_proxycanvas_new(struct pdlua *owner, t_symbol *name); +/** Register the proxy canvas class with Pd. */ +static void pdlua_proxycanvas_setup(void); /** Pd object constructor. */ static t_pdlua *pdlua_new (t_symbol *s, int argc, t_atom *argv); /** Pd object destructor. */ @@ -346,12 +363,15 @@ void pdlua_setup (void); struct pdlua_proxyinlet; struct pdlua_proxyreceive; struct pdlua_proxyclock; +struct pdlua_proxycanvas; /** Proxy inlet class pointer. */ static t_class *pdlua_proxyinlet_class; /** Proxy receive class pointer. */ static t_class *pdlua_proxyreceive_class; /** Proxy clock class pointer. */ static t_class *pdlua_proxyclock_class; +/** Proxy canvas class pointer. */ +static t_class *pdlua_proxycanvas_class; /** Lua file reader callback. */ static const char *pdlua_reader @@ -378,6 +398,70 @@ static const char *pdlua_reader } } +static void pdlua_proxycanvas_anything(t_pdlua_proxycanvas *p, t_symbol *s, int argc, t_atom *argv) { +#if !PLUGDATA + // Early returns for invalid conditions + if (!p->p_parent) return; + if (s != gensym("motion")) return; + if (argc != 3) return; + + t_pdlua *x = p->p_parent; + if (!x->has_gui || x->gfx.mouse_down) return; + + float new_x = atom_getfloat(argv); + float new_y = atom_getfloat(argv + 1); + + int zoom = glist_getzoom(x->canvas); + int obj_x = text_xpix(&x->pd, x->canvas); + int obj_y = text_ypix(&x->pd, x->canvas); + + int xpos = (new_x - obj_x) / zoom; + int ypos = (new_y - obj_y) / zoom; + + int inside = (xpos >= 0 && xpos < x->gfx.width && + ypos >= 0 && ypos < x->gfx.height); + + // Handle state changes first + if (!inside && x->gfx.mouse_inside) { + pdlua_gfx_mouse_exit(x, xpos, ypos); + x->gfx.mouse_inside = 0; + } else if (inside && !x->gfx.mouse_inside) { + pdlua_gfx_mouse_enter(x, xpos, ypos); + x->gfx.mouse_inside = 1; + } + + // Only then send move event if we're inside + if (inside) { + pdlua_gfx_mouse_move(x, xpos, ypos); + } + + x->gfx.mouse_x = new_x; + x->gfx.mouse_y = new_y; +#endif +} + +static t_pdlua_proxycanvas *pdlua_proxycanvas_new(struct pdlua *owner, t_symbol *s) { + if (!owner || !s || s == &s_) return NULL; + + t_pdlua_proxycanvas *p = (t_pdlua_proxycanvas *)pd_new(pdlua_proxycanvas_class); + if (!p) return NULL; + + memset(p, 0, sizeof(t_pdlua_proxycanvas)); + p->pd = pdlua_proxycanvas_class; + p->p_sym = s; + p->p_parent = owner; + p->p_clock = clock_new(p, (t_method)pdlua_proxycanvas_free); + pd_bind(&p->pd, p->p_sym); + + return p; +} + +static void pdlua_proxycanvas_free(t_pdlua_proxycanvas *p) { + if (!p) return; + if (p->p_sym) pd_unbind(&p->pd, p->p_sym); + if (p->p_clock) clock_free(p->p_clock); +} + /** Proxy inlet 'anything' method. */ static void pdlua_proxyinlet_anything ( @@ -494,6 +578,19 @@ static void pdlua_proxyclock_setup(void) pdlua_proxyclock_class = class_new(gensym("pdlua proxy clock"), 0, 0, sizeof(t_pdlua_proxyclock), 0, 0); } +/** Setup the proxy class for canvas events. */ +static void pdlua_proxycanvas_setup(void) +{ + pdlua_proxycanvas_class = class_new( + gensym("pdlua proxy canvas"), 0, + (t_method)pdlua_proxycanvas_free, + sizeof(t_pdlua_proxycanvas), + CLASS_NOINLET | CLASS_PD, + 0); + if (pdlua_proxycanvas_class) + class_addanything(pdlua_proxycanvas_class, (t_method)pdlua_proxycanvas_anything); +} + /** Dump an array of atoms into a Lua table. */ static void pdlua_pushatomtable ( @@ -714,9 +811,24 @@ static t_pdlua *pdlua_new if (lua_islightuserdata(__L(), -1)) { object = lua_touserdata(__L(), -1); + + // Create canvas proxy if we have GUI + if (object->has_gui) { + t_canvas *parent_canvas = glist_getcanvas(object->canvas); + char buf[MAXPDSTRING]; + snprintf(buf, MAXPDSTRING-1, ".x%lx", (unsigned long)parent_canvas); + object->gfx.proxycanvas = pdlua_proxycanvas_new(object, gensym(buf)); + if (!object->gfx.proxycanvas) { + pd_error(NULL, "pdlua: failed to create canvas proxy"); + pd_free((t_pd *)object); + lua_pop(__L(), 2); + return NULL; + } + } + lua_pop(__L(), 2);/* pop the userdata and the global "pd" */ PDLUA_DEBUG2("pdlua_new: before returning object %p stack top %d", object, lua_gettop(__L())); - return object; + return object; } else { @@ -749,7 +861,6 @@ static void pdlua_free( t_pdlua *o /**< The object to destruct. */) } void pdlua_vis(t_gobj *z, t_glist *glist, int vis){ - t_pdlua* x = (t_pdlua *)z; // If there's no gui, use default text vis behavior if(!x->has_gui) @@ -768,17 +879,25 @@ void pdlua_vis(t_gobj *z, t_glist *glist, int vis){ } } -static void pdlua_delete(t_gobj *z, t_glist *glist){ - if(!((t_pdlua *)z)->has_gui) +static void pdlua_delete(t_gobj *z, t_glist *glist) { + t_pdlua *x = (t_pdlua *)z; + if(!x->has_gui) { text_widgetbehavior.w_deletefn(z, glist); return; } + if(glist_isvisible(glist) && gobj_shouldvis(z, glist)) { pdlua_vis(z, glist, 0); } canvas_deletelinesfor(glist, (t_text *)z); + + if (x->gfx.proxycanvas) { + // Schedule deferred cleanup (similar to receivecanvas external code) + clock_delay(x->gfx.proxycanvas->p_clock, 0); + x->gfx.proxycanvas = NULL; + } } #ifdef PURR_DATA // Purr Data uses an older version of this API @@ -791,7 +910,6 @@ static void pdlua_motion(t_gobj *z, t_floatarg dx, t_floatarg dy, #if !PLUGDATA t_pdlua *x = (t_pdlua *)z; if (!x->has_gui) return; - #ifndef PURR_DATA // Handle mouse up immediately if (up && x->gfx.mouse_down) { @@ -800,6 +918,14 @@ static void pdlua_motion(t_gobj *z, t_floatarg dx, t_floatarg dy, int ypos = (x->gfx.mouse_y - text_ypix(&x->pd, x->canvas)) / zoom; pdlua_gfx_mouse_up(x, xpos, ypos); x->gfx.mouse_down = 0; + + // After mouse up, check if we need to send exit + int inside = (xpos >= 0 && xpos < x->gfx.width && + ypos >= 0 && ypos < x->gfx.height); + if (!inside && x->gfx.mouse_inside) { + pdlua_gfx_mouse_exit(x, xpos, ypos); + x->gfx.mouse_inside = 0; + } return; } #endif @@ -835,11 +961,11 @@ static int pdlua_click(t_gobj *z, t_glist *gl, int xpos, int ypos, int shift, in { pdlua_gfx_mouse_up(x, xpix, ypix); x->gfx.mouse_down = 0; - } else { - x->gfx.mouse_x = xpos; - x->gfx.mouse_y = ypos; - pdlua_gfx_mouse_move(x, xpix, ypix); } + // no move events generated here + // Let the proxy handle all move events + x->gfx.mouse_x = xpos; + x->gfx.mouse_y = ypos; } return 1; } else @@ -3072,7 +3198,9 @@ void pdlua_setup(void) PDLUA_DEBUG("pdlua pdlua_proxyreceive_setup done", 0); pdlua_proxyclock_setup(); PDLUA_DEBUG("pdlua pdlua_proxyclock_setup done", 0); - if (! pdlua_proxyinlet_class || ! pdlua_proxyreceive_class || ! pdlua_proxyclock_class) + pdlua_proxycanvas_setup(); + PDLUA_DEBUG("pdlua pdlua_proxycanvas_setup done", 0); + if (! pdlua_proxyinlet_class || ! pdlua_proxyreceive_class || ! pdlua_proxyclock_class || ! pdlua_proxycanvas_class) { pd_error(NULL, "lua: error creating proxy classes"); pd_error(NULL, "lua: loader will not be registered!"); diff --git a/pdlua.h b/pdlua.h index fa4a9a0..04dcf51 100644 --- a/pdlua.h +++ b/pdlua.h @@ -43,8 +43,9 @@ typedef struct _pdlua_gfx char current_color[10]; // Keep track of current color // Variables to keep track of mouse button state and drag position - int mouse_x, mouse_y, mouse_down; + int mouse_x, mouse_y, mouse_down, mouse_inside; int first_draw; + struct pdlua_proxycanvas *proxycanvas; #else int current_layer; void(*plugdata_draw_callback)(void*, int, t_symbol*, int, t_atom*); // Callback to perform drawing in plugdata diff --git a/pdlua_gfx.h b/pdlua_gfx.h index a3f32ab..2a32933 100644 --- a/pdlua_gfx.h +++ b/pdlua_gfx.h @@ -149,6 +149,14 @@ void pdlua_gfx_mouse_drag(t_pdlua *o, int x, int y) { pdlua_gfx_mouse_event(o, x, y, 3); } +void pdlua_gfx_mouse_enter(t_pdlua *x, int xpos, int ypos) { + pdlua_gfx_mouse_event(x, xpos, ypos, 4); +} + +void pdlua_gfx_mouse_exit(t_pdlua *x, int xpos, int ypos) { + pdlua_gfx_mouse_event(x, xpos, ypos, 5); +} + // Represents a path object, created with path.new(x, y) // for pd-vanilla, this contains all the points that the path contains. bezier curves are flattened out to points before being added // for plugdata, it only contains a unique ID to the juce::Path that this is mapped to @@ -793,6 +801,7 @@ static int gfx_initialize(t_pdlua *obj) gfx->num_transforms = 0; gfx->num_layers = 0; gfx->layer_tags = NULL; + gfx->mouse_inside = 0; pdlua_gfx_repaint(obj, 0); return 0;