pythonarcade / arcade

Easy to use Python library for creating 2D arcade games.
http://arcade.academy
Other
1.72k stars 329 forks source link

arcade.gui.UIBoxLayout.height value is incorrect at init #2068

Closed ksetrae closed 6 months ago

ksetrae commented 7 months ago

Bug Report

System Info

Arcade 3.0.0.dev20

vendor: NVIDIA Corporation renderer: GeForce GTX 1660 SUPER/PCIe/SSE2 version: (3, 3) python: 3.10.10 (tags/v3.10.10:aad5f6a, Feb 7 2023, 17:20:36) [MSC v.1929 64 bit (AMD64)] platform: win32 pyglet version: 2.0.5 PIL version: 9.4.0

Actual behavior:

arcade.gui.UIBoxLayout.height is not set immediatly after adding children to the box. It remains at 0, despite children having non-zero height.

What is also weird, it changes to a correct value during runtime after several frames, but I wonder why exactly after that number of frames.

This is the output of the code below. You can see attribute being set on frame 3:

v_box height after init: 0
v_box children heights after init: [50]

v_box height after frame 1: 0
v_box children heights after frame 1: [50]

v_box height after frame 2: 0
v_box children heights after frame 2: [50]

v_box height after frame 3: 50
v_box children heights after frame 3: [50]

v_box height after frame 4: 50
v_box children heights after frame 4: [50]

v_box height after frame 5: 50
v_box children heights after frame 5: [50]

Expected behavior:

height attribute is set to a correct value after adding elements to the layout.

Steps to reproduce/example code:

import arcade
import arcade.gui

class MyView(arcade.View):
    def __init__(self):
        super().__init__()

        self.ui = arcade.gui.UIManager()
        self.ui.enable()

        self.v_box = arcade.gui.UIBoxLayout(space_between=20)

        start_button = arcade.gui.UIFlatButton(text="Start Game", width=200)
        self.v_box.add(start_button)

        self.ui.add(self.v_box)

        self.frame_count = 0

        print(f'v_box height after init: {self.v_box.height}')
        print(f'v_box children heights after init: {[child.height for child in self.v_box.children]}')
        print()

    def on_draw(self):
        self.clear()
        self.ui.draw()

    def on_update(self, delta_time: float):
        self.frame_count += 1
        if self.frame_count in (1, 2, 3, 4, 5):
            print(f'v_box height after frame {self.frame_count}: {self.v_box.height}')
            print(f'v_box children heights after frame {self.frame_count}: {[child.height for child in self.v_box.children]}')
            print()

if __name__ == '__main__':
    window = arcade.Window(800, 600)
    window.show_view(MyView())
    window.run()
eruvanos commented 6 months ago

Hi,

UIWidgets live within the GUI lifecycle. The docs contain some description regarding that behaviour: GUI Concept

UILayouts change their size depending on the space they get. The steps to calculate all of this is executed before drawing on screen.

In your code the on_update is executed before the first draw call. Which explains, why the height is changed after the second on_update call. In between the ui.draw() was executed, ensured a up-to-date layouting, which actually sets the size.

If you want, use self.ui.execute_layout() after you added the box to the UIManager.


class MyView(arcade.View):
    def __init__(self):
        super().__init__()

        self.ui = arcade.gui.UIManager()
        self.ui.enable()

        self.v_box = arcade.gui.UIBoxLayout(space_between=20)

        start_button = arcade.gui.UIFlatButton(text="Start Game", width=200)
        self.v_box.add(start_button)

        self.ui.add(self.v_box)
        self.ui.execute_layout()

        self.frame_count = 0

        print(f'v_box height after init: {self.v_box.height}')
        print(f'v_box children heights after init: {[child.height for child in self.v_box.children]}')
        print()

...

That should lead to your expected result.

eruvanos commented 6 months ago

Please close the issue, in case your question is answered.

ksetrae commented 6 months ago

Thanks for the explanation.