Skip to content

UI: Support layer in walk_widgets and get_widgets_at #1761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions arcade/gui/ui_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,24 @@ def remove(self, child: UIWidget):
child.parent = None
self.trigger_render()

def walk_widgets(self, *, root: Optional[UIWidget] = None) -> Iterable[UIWidget]:
"""walks through widget tree, in reverse draw order (most top drawn widget first)"""
layer = 0
children = root.children if root else self.children[layer]
for child in reversed(children):
yield from self.walk_widgets(root=child)
yield child
def walk_widgets(self, *, root: Optional[UIWidget] = None, layer=0) -> Iterable[UIWidget]:
"""
walks through widget tree, in reverse draw order (most top drawn widget first)

:param root: root widget to start from, if None, the layer is used
:param layer: layer to search, None will search through all layers
"""
if layer is None:
layers = sorted(self.children.keys(), reverse=True)
else:
layers = [layer]

for layer in layers:

children = root.children if root else self.children[layer]
for child in reversed(children):
yield from self.walk_widgets(root=child)
yield child

def clear(self):
"""
Expand All @@ -144,18 +155,19 @@ def clear(self):
for widget in layer[:]:
self.remove(widget)

def get_widgets_at(self, pos, cls: Type[W] = UIWidget) -> Iterable[W]:
def get_widgets_at(self, pos, cls: Type[W] = UIWidget, layer=0) -> Iterable[W]:
"""
Yields all widgets containing a position, returns first top laying widgets which is instance of cls.

:param pos: Pos within the widget bounds
:param cls: class which the widget should be instance of
:param cls: class which the widget should be an instance of
:param layer: layer to search, None will search through all layers
:return: iterator of widgets of given type at position
"""
def check_type(widget) -> W: # should be TypeGuard[W]
return isinstance(widget, cls) # type: ignore

for widget in self.walk_widgets():
for widget in self.walk_widgets(layer=layer):
if check_type(widget) and widget.rect.collide_with_point(*pos):
yield widget

Expand Down
63 changes: 60 additions & 3 deletions tests/unit/gui/test_uimanager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from arcade.gui import UIManager, UIDummy


def test_iterate_children_flat(window):
def test_walk_widgets(window):
manager = UIManager()
widget1 = UIDummy()
manager.add(widget1)
Expand All @@ -11,7 +11,31 @@ def test_iterate_children_flat(window):
assert children == [widget1]


def test_iterate_children_tree(window):
def test_walk_widgets_of_specific_layer(window):
manager = UIManager()
widget1 = UIDummy()
widget2 = UIDummy()
manager.add(widget1)
manager.add(widget2, layer=1)

children = list(child for child in manager.walk_widgets(layer=1))

assert children == [widget2]


def test_walk_widgets_of_all_layers(window):
manager = UIManager()
widget1 = UIDummy()
widget2 = UIDummy()
manager.add(widget1)
manager.add(widget2, layer=1)

children = list(child for child in manager.walk_widgets(layer=None))

assert children == [widget2, widget1]


def test_walk_widgets_down_the_tree(window):
manager = UIManager()
widget1 = UIDummy()
widget2 = UIDummy()
Expand All @@ -23,7 +47,7 @@ def test_iterate_children_tree(window):
assert children == [widget2, widget1]


def test_get_top_widget(window):
def test_get_widgets_at(window):
manager = UIManager()
widget1 = UIDummy(x=50, y=50, width=100, height=100)
widget2 = UIDummy(x=75, y=75, width=50, height=50)
Expand All @@ -37,6 +61,39 @@ def test_get_top_widget(window):
assert children == [widget1]


def test_get_widgets_at_from_layer_0_by_default(window):
manager = UIManager()
widget1 = UIDummy(x=50, y=50, width=100, height=100)
widget2 = UIDummy(x=50, y=50, width=100, height=100)
manager.add(widget1, layer=0)
manager.add(widget2, layer=1)

children = list(manager.get_widgets_at(pos=(100, 100)))
assert children == [widget1]


def test_get_widgets_at_from_specific_layer(window):
manager = UIManager()
widget1 = UIDummy(x=50, y=50, width=100, height=100)
widget2 = UIDummy(x=50, y=50, width=100, height=100)
manager.add(widget1, layer=0)
manager.add(widget2, layer=1)

children = list(manager.get_widgets_at(pos=(60, 60), layer=1))
assert children == [widget2]


def test_get_widgets_at_from_all_layers(window):
manager = UIManager()
widget1 = UIDummy(x=50, y=50, width=100, height=100)
widget2 = UIDummy(x=50, y=50, width=100, height=100)
manager.add(widget1, layer=0)
manager.add(widget2, layer=1)

children = list(manager.get_widgets_at(pos=(60, 60), layer=None))
assert children == [widget2, widget1]


def test_get_top_widget_by_cls(window):
class MyUIDummy(UIDummy):
pass
Expand Down
25 changes: 3 additions & 22 deletions tests/unit/gui/test_widget_inputtext.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
from arcade.gui import UILabel
from arcade.gui.events import UIEvent
from arcade.gui import UIInputText

from arcade.gui.widgets import UIDummy


# TODO add tests

def test_widget():
# GIVEN
widget = UIDummy()

# WHEN
widget.on_event(UIEvent(widget))

# THEN
assert widget.rect == (0, 0, 100, 100)

def test_uilabel_support_multiline(window):
def test_uilabel_support_multiline(uimanager):
# WHEN
widget = UILabel(
text="Lorem ipsum dolor",
width=20,
multiline=True,
)
widget = UIInputText()

# THEN
assert widget is not None