godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.77k stars 21.13k forks source link

Mouse Force Pass Scroll Events is not working on Emulate Touch from Mouse #94141

Open Has-if36 opened 3 months ago

Has-if36 commented 3 months ago

Tested versions

Reproducible in: v4.2.1.stable.mono.official [b09f793f5], v4.3.beta2.mono.official [b75f0485b]

System information

Godot v4.3.beta2.mono - Windows 10.0.19045 - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 1650 (NVIDIA; 31.0.15.3114) - Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz (12 Threads)

Issue description

mouse_force_pass_scroll_events seems not working. As far as I understand, mouse_force_pass_scroll_events should force mouse filter Pass to its parent even if it set to stop so that the scroll container can scroll.

Steps to reproduce

I enabled emulate_touch_from_mouse before testing it. I used ScrollContainer, followed by VBoxContainer and fill it with ColorRect to test it. During Enabling mouse_force_pass_scroll_events while putting stop on mouse_filter, scrolling with mouse drag does nothing, except for the small gap between the ColorRects. As a reference for my own, I set pass on mouse_filter and it works fine.

Minimal reproduction project (MRP)

scrollcontainer.zip

Edit: I've done a little bit more testing on android (for Godot v4.2.1) with both enabling/disabling emulate_mouse_from_touch. During Enabling mouse_force_pass_scroll_events while putting stop on mouse_filter, it gives similar result. As another reference for my own, I set pass on mouse_filter and it works fine, but doesn't work when I disabled emulate_mouse_from_touch.

maran commented 3 months ago

Can confirm this is a problem for our app as well. We have a mobile app and scroll events are not propagating. Tried it on 4.3dev5 / 6 / beta2 and RC1.

maran commented 1 month ago

Any way I can do to help resolving this issue? It's making parts of my application very hard to use on mobile phones which is our primary deployment target.

Has-if36 commented 1 month ago

The issue is that mouse_force_pass_scroll_events fails to set its children/grandchildren's mouse filter to PASS. So, we need to do that when giving the input, and set it back to STOP when releasing it. So you can do something like this.

public partial class CustomScrollContainer : Godot.ScrollContainer
{
    public override void _Input(InputEvent @event)
    {
        if (@event is InputEventScreenTouch eventTouch) {
            Vector2 _pos = eventTouch.Position;
        if (eventTouch.IsPressed()) _ForcePassScrollPressed(_pos);
        else _ForcePassScrollUnpressed();
        }
    }

    private Godot.Collections.Array<Godot.Control> _stopFilterControl = new();

    /**
     * Get Controls Children/GrandChildren that has 'Stop' Mouse Filter
     * Set 'Stop' Filter to 'Pass'
     */
    private void _ForcePassScrollPressed(Vector2 _pos)
    {
        if (!MouseForcePassScrollEvents || !GetGlobalRect().HasPoint(_pos)) return;

        _stopFilterControl = new Godot.Collections.Array<Godot.Control>(_GetGrandchildrenControl(this).Where(x => x.MouseFilter == MouseFilterEnum.Stop).ToArray());
    foreach (var each in _stopFilterControl) {
            each.MouseFilter = MouseFilterEnum.Pass;
        }
    }

    /**
     * Revert controls which had set 'Pass' back to 'Stop'
     */
    private void _ForcePassScrollUnpressed()
    {
        // Delay by One Frame, to give time to complete Unpress Event from Godot
        GetTree().CreateTimer(double.NegativeInfinity).Timeout += () => {
        foreach (var each in _stopFilterControl)
        {
            each.MouseFilter = MouseFilterEnum.Stop;
        }
        };
    }

    private Godot.Collections.Array<Godot.Control> _GetGrandchildrenControl(Godot.Control parentNode) {
        var _array = new Godot.Collections.Array<Godot.Control>(parentNode
            .GetChildren()
            .Where(x => x is Godot.Control xControl)
            .SelectMany(x => _GetGrandchildrenControl((Godot.Control)x))
            .ToArray());
        if (!GodotObject.Equals(this, parentNode)) _array.Add(parentNode);
        return _array;
    }
}

Hope this helps