[Solved] Making Itemlist behave more like PopupMenu

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By bloqm

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!

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 = ~

dewcked | 2021-11-25 09:12

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

bloqm | 2021-11-25 16:03

:bust_in_silhouette: Reply From: dewcked

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.

Thank you! This was an easy and clean solution.

bloqm | 2021-11-26 12:48