+2 votes

Hi everyone,

this is one of those issues where I believe there must be an incredibly simple way to do it and I still can't figure it out. Is there any way to figure out if my mouse (or finger on a touchscreen) is above a Button (any Button-Node) or not while it's being held pressed? Maybe something like "self global position"...? Something that immediately recognizes when leaving or entering the area while being held down.

The problem is that func onButtonmouseexited(): does only fire at release but not at the moment of exiting. It works with CollisionShape2D, but obviously not with Buttons. I want it to emit a Signal the moment my finger slips off the Button.

Any help is much appreciated!

in Engine by (313 points)

I've tested it and I think there is a problem with is_hover after pressing the button because it seems not to update while the button is being pressed... I've made a test creating a "custom hovered" function and seems to work, maybe it can be usefull:

extends Button

var pres=false
var is_hov=false

func _input(event):
    if event is InputEventMouseButton:
        if event.is_pressed():
            pres=true
        else:
            pres=false
    if not pres and self.is_hovered():
        self.modulate=Color(0,0,1)
    if pres and self.is_hovered():
        self.modulate=Color(1,0,0)
    if pres and not _hovered(event.position):
        self.modulate=Color(0,0,1)

func _hovered(pos):
    if pos.x>self.rect_global_position.x and pos.x<self.rect_global_position.x+self.rect_size.x and pos.y>self.rect_global_position.y and pos.y<self.rect_global_position.y+self.rect_size.y:
        return true
    else:
        return false

2 Answers

0 votes
Best answer

Thanks for all your thoughts, guys!
The "hovering" seems to be the way to go here, and yes, building one's own way around this issue seems inevitable. RattleyCooper suggested this, which did the trick for me:

extends Button

class_name AbortableButton

var mouse_is_down = false
var has_aborted = false

signal abort

func _on_Button_gui_input(event: InputEvent):
    if event.is_action_pressed("lclick"):
        mouse_is_down = true
        has_aborted = false
    elif event.is_action_released("lclick"):
        mouse_is_down = false
    if mouse_is_down and event is InputEventMouseMotion:
        if not get_global_rect().has_point(get_global_mouse_position()) and not has_aborted:
            emit_signal("abort")
            has_aborted = true

func _on_Button_abort():
    print('Abort!')

I only wonder why such an essential thing doesn't come with the on-board buttons...

by (313 points)
selected by
0 votes

If you want to use the provided Buttons they have and Action Mode property you can set to "Button Press". You can also connect to the signal button_down() to execute logic right as it goes down.

If you're looking for something a bit more custom, it'll probably be most straight forward to write your own UI widgets that track and handle input logic to get a specific behavior.

by (5,192 points)

Hi avencherus,

yes, the buttons are indeed easy to implement and basically do what they are supposed to. I can press them and hold them, have them trigger at release and all that. The only thing they just don't seem to support is a proper "your pressed mouse cursor just crossed the border to the outside of the button"-signal. (At least I can't figure out how to implement this.)

As I say, it works with a collision shape, but not with a button.What I'm aiming at is a button which I can hold down and then (instead of releasing) slide off of, to "abort the operation". The button goes back to normal without triggering and my mouse-release outside the button doesn't do anything as well. As "if I change my mind", I want to "slide-off-cancel".

Actually, "mouseexited" does work with buttons, but only with mouse-over, not when being pressed down! Weirdly enough, "mouseentered" does work while being pressed down...

I did build my very own buttons with Area2Ds and CollisionShape2Ds and they do all I want in the way I want them to. There only occasionally pop up some issues here and there in combination with other things (like overlapping control nodes), where "normal" buttons don't bother, so I thought building my own stuff on top of on-board-buttons would be perfect. Alas!

In my experience the built-in Controls work well for only the most straight forward usages, and even then you'll find awkward holes in their functionality.

A lot of what you're wanting does exist in the CPP implementation, but isn't exposed.

It often ends up being best to go the route of writing your own. If you use Area2Ds and need to have legitimate overlapping of controls, you'll want to keep and list and implement a priority (height) check of your own. This isn't implemented in the engine currently.

To go down the path of repurposing the built-in Controls... it tends to venture into hacking territory. I have an example below, that responds to holding a click while hovering over a Button.

It only gets your part the way there. You'd have to ignore the pressed signal and implement your own pressed signal based around internal flags and such.

This code exploits calling into the virtual function to call into some of the desired internal functions. There are side effects, so a lot of the other functionality will likely misbehave or stop working. You'd have to work around that too.

That said, it may be more work, but writing your own widgets over something like the code below is probably the better bet.

extends Button


func _ready() -> void:

    connect("mouse_entered", self, "check_press")
    connect("mouse_exited", self, "clear_press")


func clear_press() -> void:

    if(pressed):
        set_faux_press(false)

func check_press() -> void:

    if(Input.is_mouse_button_pressed(BUTTON_LEFT)):
        set_faux_press(true)


func _physics_process(delta: float) -> void:

    if(pressed and not Input.is_mouse_button_pressed(BUTTON_LEFT)):
        set_faux_press(false)


func set_faux_press(p_state : bool) -> void:

    var e = InputEventMouseButton.new()
    e.button_index = BUTTON_LEFT
    e.pressed = p_state

    _gui_input(e)


func _gui_input(e) -> void:
    ._gui_input(e) # Hacky, be careful here.
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.