h2d.Interactive not active outside h2d.Mask bounds

Hello et bonjour!

I am some weeks in with heaps.io and making good progress.
Docs, community (passive) and source code helped me a lot to figure things out myself.
But now I have a problem which I am not sure how to tackle it.

Sorry for the wall of text…
Its hard to describe in fewer words.

So I made my own scrollable widget using h2d.Mask and it works fine for now.
Within this I have children to render. Say a scrollable list of text items.
Now I attach an h2d.Interactive for each item, so when user clicks on it something will happen.
For debugging purposes I added a background color, so I can see whats going on.

I defined the mask with a height of 300px.
All Interactive that get a y-coordinate < 300px get rendered/are active.
So at 290px it is still active, because its partial visible.

Now the problem:
If I scroll down it doesn’t update in a way, that the now (possible) visible Interactive get active.
I checked with vs code that the object are there and they have valid coordinates.
I tried re-adding to parent and reattaching as EventTarget.
I skipped through Mask, Interactive, SceneEvents to figure it out, but no luck so far.
How do I activate those Interactives beyond y=300, when they come in visible range?
Should this happen automatically, or do I need to update/trigger something?
Is this even the right way to do it?
I checked Mask.scrollBounds and it its null. Thats okay?

A workaround on one occassion was to put a single Interactive
on either mask or its parent and calculate the list position with the help of mask.scrollY.
While this doesn’t sound so bad for fixed-sized items, there may be other situations and so
I would like to know what I am doing wrong here.

Thanks in advance!

I should add that I use HL/JIT 1.11.0 on Windows as target.
And Heaps version is 1.9.1.

I reduced my code to a bare minimum example, which still not works.
Or does this “work as intended”?

I also noted, that when the Interactive is partial visible,
then even after scrolling only that former partial part is clickable.

Green boxes are just visuals, the red and blue are the Interactives:

import hxd.Event;
import h2d.TileGroup;
import h2d.Interactive;
import h2d.Tile;
import h2d.Bitmap;
import h2d.Object;
import hxd.App;

class MaskTest extends hxd.App {       

    var base : Bitmap;
    var mask : h2d.Mask;
    var childrenContainer : h2d.TileGroup;
    var item1 : Interactive;
    var item2 : Interactive;
    var width = 150;
    var height = 200;           

    // 2nd item still partial visible, 
    // but only the partial part is clickable then!
    var bottom = 200 - 20; 

    // not visible, and not clickable when visible
    // var bottom = 400;

    override function init() {
        var whiteTile = Tile.fromColor(0xFFFFFF, width, height);
        var greenTile = Tile.fromColor(0x00FF00, 50, 50);

        // some background, not really relevant
        base = new Bitmap(whiteTile, s2d);
        base.x = 50;
        base.y = 50;                    

        mask = new h2d.Mask(width, height, base);

        // scroll wheel interaction to scroll down
        var maskMouse = new Interactive(width, height, base);
        maskMouse.propagateEvents = true;
        maskMouse.onWheel = function(e : Event) {
            mask.scrollY += e.wheelDelta * 20;

        var p : Object;
        // two different attempts to attach the children        
        p = childrenContainer = new TileGroup(null, mask);
        // p = mask;

        // draw boxes at the top and the bottom (y = 400)
        var b1 = new Bitmap(greenTile, p);
        var b2 = new Bitmap(greenTile, p);
        b2.y = bottom;


        // setup clickables for each block

        var pp1 : Object; var pp2 : Object;
        // two different attempts to attach interactive        
        pp1 = p; pp2 = p;
        // pp1 = b1; pp2 = b2;
        item1 = new Interactive(50, 50, pp1);
        // for this example 
        // display the interactive 
        // to the right of the box
        item1.x = 50;
        item1.onClick = function(e : Event) {
        item1.backgroundColor = 0x60FF0000;        

        item2 = new Interactive(50, 50, pp2);
        item2.x = 50;
        item2.onClick = function(e : Event) {
        item2.backgroundColor = 0x600000FF;        

        // disable this line with pp2 = b2;
        if (pp2 == p) {            
            item2.y = bottom; 

    static function main() {
        new MaskTest();


I believe I tracked it down:

In h2d.Interactive.handleEvent() there is this check, if there is a parent mask:
(I haven’t checked how parentMask gets filled but it is true for this case)

			if( pt.x < 0 || pt.y < 0 || pt.x > p.width || pt.y > p.height ) {
				e.cancel = true;

This check doesn’t seem to take into account scrolling/offsets.
If I scroll down and click, my “translated” (via localToGlobal, globalToLocal)
event y is for example pt.y=236, but the height stays at static p.height=200.
So I think this should take into account scrollY (and scrollX).
Probably for all 4 checks in this line.


The following hack gets the job down for now.

class MaskHack extends h2d.Mask {
    override public function globalToLocal( pt : h2d.col.Point ) : h2d.col.Point {
        var pt = super.globalToLocal(pt);
        pt.y -= scrollY;
        return pt;

But I think that scrollY should be in above mentioned check, not here.