+2 votes

I need an UI element that lists selectable items, a.k.a. the basic videogame menu.

PopupMenu lets you select an item with a single click, and as you hover its items get highlighted -- this is exactly what I need.
The problem is that popups can't be really nested and doing so breaks the layout.

ItemList plays nicely with the layout, but by default it seems to work more like a filesystem folder, with doubleclick to confirm, mouse hover revealing tooltips but not hightlighting... etc.

I haven't found another built-in node that does what I want. So:
- Is there a way to make a PopupMenu fit inside a layout?
- If that's not possible (or a bad idea), how can I modify ItemList to work as I want? (highlight items on hover, single-click to confirm)

I should note that I need it to work with both keyboard and mouse. PopupMenu does this by default, but I had trouble changing the input of ItemList in a consistent way.

Thanks!

Godot version 3.3.4
in Engine by (191 points)
edited by

What you mean "The problem is that popups can't be really nested and doing so breaks the layout."? Nested popups can be implemented as submenus. And you can set the position by setting anchors and margins in code:

Menu.anchor_left = ~
Menu.margin_top = ~

Thanks. Nested was a poor word choice; I mean that Popups cannot be integrated inside other containers such as VBox, like in my example below (those are ItemLists).
Unless I'm mistaken, a popup always "floats" above other elements disregarding its parent, and containers don't account for its size when spacing elements.

example

1 Answer

0 votes
Best answer

Given node tree

Parent
    VBoxContainer (state window)
        ItemList

add following script to the ItemList:

extends ItemList

var item

func _ready() -> void:
    pass

func _on_ItemList_gui_input(event: InputEvent) -> void:
    item = get_item_at_position(get_local_mouse_position(), true)
    if event is InputEventMouseMotion:
        if item != -1:
            select(item, true)
    if event is InputEventMouseButton and event.is_pressed() and event.button_index == BUTTON_LEFT:
        if item != -1:
            pass
            # here, item is selected (you can add or fix code here)

Then in the Itemlist Node dock, connect gui_input(event: InputEvent) signal to _on_ItemList_gui_input(event: InputEvent) function in the script attached to ItemList.

It's all done! You can even customize anything as you wish.

get_local_mouse_position() returns current mouse position. get_item_at_position returns the item index in the given position. If second parameter is set to true(), returns -1 when there is no item in current mouse position. If second parameter is set to false(), returns closest item to current mouse position.

by (236 points)
selected by

Thank you! This was an easy and clean solution.

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 Frequently asked questions and 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.