From d58835aefffb68fe70762f60dd511c98eb393f86 Mon Sep 17 00:00:00 2001 From: Maic Siemering Date: Mon, 8 May 2023 10:48:54 +0200 Subject: [PATCH] UI: Support layer in walk_widgets and get_widgets_at --- arcade/gui/ui_manager.py | 32 +++++++++---- tests/unit/gui/test_uimanager.py | 63 +++++++++++++++++++++++-- tests/unit/gui/test_widget_inputtext.py | 25 ++-------- 3 files changed, 85 insertions(+), 35 deletions(-) diff --git a/arcade/gui/ui_manager.py b/arcade/gui/ui_manager.py index 27b7a10a6..5069673e9 100644 --- a/arcade/gui/ui_manager.py +++ b/arcade/gui/ui_manager.py @@ -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): """ @@ -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 diff --git a/tests/unit/gui/test_uimanager.py b/tests/unit/gui/test_uimanager.py index 0c5cae684..cc961fa31 100644 --- a/tests/unit/gui/test_uimanager.py +++ b/tests/unit/gui/test_uimanager.py @@ -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) @@ -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() @@ -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) @@ -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 diff --git a/tests/unit/gui/test_widget_inputtext.py b/tests/unit/gui/test_widget_inputtext.py index a18c83a8b..967b9a683 100644 --- a/tests/unit/gui/test_widget_inputtext.py +++ b/tests/unit/gui/test_widget_inputtext.py @@ -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