Skip to content

Commit bab1b29

Browse files
committed
fix tests and type hints
1 parent adfa0ab commit bab1b29

File tree

93 files changed

+107
-57
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+107
-57
lines changed

arcade/examples/gui/exp_controller_support.py

+30-19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
"""
2+
Example demonstrating controller support in an Arcade GUI.
3+
4+
This example shows how to integrate controller input with the Arcade GUI framework.
5+
It includes a controller indicator widget that displays the last controller input,
6+
and a modal dialog that can be navigated using a controller.
7+
8+
If Arcade and Python are properly installed, you can run this example with:
9+
python -m arcade.examples.gui.exp_controller_support
10+
"""
11+
112
from typing import Optional
213

314
import arcade
@@ -34,7 +45,7 @@ class ControllerIndicator(UIAnchorLayout):
3445
"""
3546

3647
BLANK_TEX = Texture.create_empty("empty", (40, 40), arcade.color.TRANSPARENT_BLACK)
37-
TEXTURE_CACHE = {}
48+
TEXTURE_CACHE: dict[str, Texture] = {}
3849

3950
def __init__(self):
4051
super().__init__()
@@ -54,53 +65,53 @@ def input_prompts(cls, event: UIControllerEvent) -> Texture | None:
5465
if isinstance(event, UIControllerButtonEvent):
5566
match event.button:
5667
case "a":
57-
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_a.png")
68+
return cls.get_texture(":resources:input_prompt/xbox/button_a.png")
5869
case "b":
59-
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_b.png")
70+
return cls.get_texture(":resources:input_prompt/xbox/button_b.png")
6071
case "x":
61-
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_x.png")
72+
return cls.get_texture(":resources:input_prompt/xbox/button_x.png")
6273
case "y":
63-
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_y.png")
74+
return cls.get_texture(":resources:input_prompt/xbox/button_y.png")
6475
case "rightshoulder":
65-
return cls.get_texture(":resources:input_prompt/xbox/xbox_rb.png")
76+
return cls.get_texture(":resources:input_prompt/xbox/rb.png")
6677
case "leftshoulder":
67-
return cls.get_texture(":resources:input_prompt/xbox/xbox_lb.png")
78+
return cls.get_texture(":resources:input_prompt/xbox/lb.png")
6879
case "start":
69-
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_start.png")
80+
return cls.get_texture(":resources:input_prompt/xbox/button_start.png")
7081
case "back":
71-
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_back.png")
82+
return cls.get_texture(":resources:input_prompt/xbox/button_back.png")
7283

7384
if isinstance(event, UIControllerTriggerEvent):
7485
match event.name:
7586
case "lefttrigger":
76-
return cls.get_texture(":resources:input_prompt/xbox/xbox_lt.png")
87+
return cls.get_texture(":resources:input_prompt/xbox/lt.png")
7788
case "righttrigger":
78-
return cls.get_texture(":resources:input_prompt/xbox/xbox_rt.png")
89+
return cls.get_texture(":resources:input_prompt/xbox/rt.png")
7990

8091
if isinstance(event, UIControllerDpadEvent):
8192
match event.vector:
8293
case (1, 0):
83-
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_right.png")
94+
return cls.get_texture(":resources:input_prompt/xbox/dpad_right.png")
8495
case (-1, 0):
85-
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_left.png")
96+
return cls.get_texture(":resources:input_prompt/xbox/dpad_left.png")
8697
case (0, 1):
87-
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_up.png")
98+
return cls.get_texture(":resources:input_prompt/xbox/dpad_up.png")
8899
case (0, -1):
89-
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_down.png")
100+
return cls.get_texture(":resources:input_prompt/xbox/dpad_down.png")
90101

91102
if isinstance(event, UIControllerStickEvent) and event.vector.length() > 0.2:
92103
stick = "l" if event.name == "leftstick" else "r"
93104

94105
# map atan2(y, x) to direction string (up, down, left, right)
95106
heading = event.vector.heading()
96107
if 0.785 > heading > -0.785:
97-
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_right.png")
108+
return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_right.png")
98109
elif 0.785 < heading < 2.356:
99-
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_up.png")
110+
return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_up.png")
100111
elif heading > 2.356 or heading < -2.356:
101-
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_left.png")
112+
return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_left.png")
102113
elif -2.356 < heading < -0.785:
103-
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_down.png")
114+
return cls.get_texture(f":resources:input_prompt/xbox/stick_{stick}_down.png")
104115

105116
return None
106117

arcade/examples/gui/exp_controller_support_grid.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
"""
2+
Example demonstrating a grid layout with focusable buttons in an Arcade GUI.
3+
4+
This example shows how to create a grid layout with buttons that can be navigated using a controller.
5+
It includes a focus transition setup to allow smooth navigation between buttons in the grid.
6+
7+
If Arcade and Python are properly installed, you can run this example with:
8+
python -m arcade.examples.gui.exp_controller_support_grid
9+
"""
10+
111
from typing import Dict, Tuple
212

313
import arcade
@@ -6,6 +16,7 @@
616
UIFlatButton,
717
UIGridLayout,
818
UIView,
19+
UIWidget,
920
)
1021
from arcade.gui.experimental.controller import (
1122
UIControllerBridge,
@@ -17,7 +28,7 @@ class FocusableButton(Focusable, UIFlatButton):
1728
pass
1829

1930

20-
def setup_grid_focus_transition(grid: Dict[Tuple[int, int], Focusable]):
31+
def setup_grid_focus_transition(grid: Dict[Tuple[int, int], UIWidget]):
2132
"""Setup focus transition in grid.
2233
2334
Connect focus transition between `Focusable` in grid.

arcade/examples/gui/exp_inventory_demo.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
- Move items between slots
1111
- Controller support
1212
13+
If Arcade and Python are properly installed, you can run this example with:
14+
python -m arcade.examples.gui.exp_inventory_demo
1315
"""
1416

17+
from functools import partial
1518
# TODO: Drag and Drop
1619

1720
from typing import List
@@ -204,7 +207,7 @@ def __init__(self, inventory: Inventory, **kwargs):
204207
# fill left to right, bottom to top (6x5 grid)
205208
self.add(slot, column=i % 6, row=i // 6)
206209
self.grid[(i % 6, i // 6)] = slot
207-
slot.on_click = self._on_slot_click
210+
slot.on_click = self._on_slot_click # type: ignore
208211

209212
InventoryUI.register_event_type("on_slot_clicked")
210213

@@ -235,13 +238,13 @@ def __init__(self, **kwargs):
235238
equipment = Equipment()
236239

237240
self.head_slot = self.add(EquipmentSlotUI(equipment, 0))
238-
self.head_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.head_slot)
241+
self.head_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.head_slot)
239242

240243
self.chest_slot = self.add(EquipmentSlotUI(equipment, 1))
241-
self.chest_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.chest_slot)
244+
self.chest_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.chest_slot)
242245

243246
self.legs_slot = self.add(EquipmentSlotUI(equipment, 2))
244-
self.legs_slot.on_click = lambda _: self.dispatch_event("on_slot_clicked", self.legs_slot)
247+
self.legs_slot.on_click = partial(self.dispatch_event, "on_slot_clicked", self.legs_slot)
245248

246249
EquipmentUI.register_event_type("on_slot_clicked")
247250

@@ -251,7 +254,7 @@ class ActiveSlotTrackerMixin(UIWidget):
251254
Mixin class to track the active slot.
252255
"""
253256

254-
active_slot = Property(None)
257+
active_slot = Property[InventorySlotUI | None](None)
255258

256259
def __init__(self, *args, **kwargs):
257260
super().__init__(*args, **kwargs)
@@ -307,14 +310,16 @@ def __init__(self, inventory: Inventory, **kwargs):
307310
self.add(content, anchor_y="bottom")
308311

309312
inv_ui = content.add(InventoryUI(inventory))
310-
inv_ui.on_slot_clicked = self.on_slot_clicked
313+
inv_ui.on_slot_clicked = self.on_slot_clicked # type: ignore
311314

312315
eq_ui = content.add(EquipmentUI())
313-
eq_ui.on_slot_clicked = self.on_slot_clicked
316+
eq_ui.on_slot_clicked = self.on_slot_clicked # type: ignore
314317

315318
# prepare focusable widgets
316319
widget_grid = inv_ui.grid
317-
setup_grid_focus_transition(widget_grid) # setup default transitions in a grid
320+
setup_grid_focus_transition(
321+
widget_grid # type: ignore
322+
) # setup default transitions in a grid
318323

319324
# add transitions to equipment slots
320325
cols = max(x for x, y in widget_grid.keys())
@@ -343,7 +348,7 @@ def __init__(self, inventory: Inventory, **kwargs):
343348
anchor_x="right",
344349
anchor_y="top",
345350
)
346-
close_button.on_click = lambda _: self.close()
351+
close_button.on_click = lambda _: self.close() # type: ignore
347352

348353
def close(self):
349354
self.trigger_full_render()
@@ -379,6 +384,8 @@ def on_key_press(self, symbol: int, modifiers: int) -> bool | None:
379384
print(i, item.symbol if item else "-")
380385
return True
381386

387+
return super().on_key_press(symbol, modifiers)
388+
382389
def on_draw_before_ui(self):
383390
pass
384391

arcade/gui/experimental/focus.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ class Focusable(UIWidget):
5050
@property
5151
def ui(self) -> UIManager | None:
5252
"""The UIManager this widget is attached to."""
53-
w = self
54-
while w.parent:
55-
if isinstance(w.parent, UIManager):
56-
return w.parent
57-
w = self.parent
53+
w: UIWidget | None = self
54+
while w and w.parent:
55+
parent = w.parent
56+
if isinstance(parent, UIManager):
57+
return parent
58+
59+
w = parent
5860
return None
5961

6062
def _render_focus(self, surface: Surface):
@@ -191,10 +193,11 @@ def _ensure_focused_property(self):
191193
focused = self._get_focused_widget()
192194

193195
for widget in self._focusable_widgets:
194-
if widget == focused:
195-
widget.focused = True
196-
else:
197-
widget.focused = False
196+
if isinstance(widget, Focusable):
197+
if widget == focused:
198+
widget.focused = True
199+
else:
200+
widget.focused = False
198201

199202
def _get_focused_widget(self) -> UIWidget | None:
200203
if len(self._focusable_widgets) == 0:
@@ -215,7 +218,7 @@ def _walk_widgets(cls, root: UIWidget):
215218
yield child
216219
yield from cls._walk_widgets(child)
217220

218-
def detect_focusable_widgets(self, root: UIWidget = None):
221+
def detect_focusable_widgets(self, root: UIWidget | None = None):
219222
"""Automatically detect focusable widgets."""
220223
if root is None:
221224
root = self
@@ -286,8 +289,8 @@ def start_interaction(self):
286289
widget.dispatch_ui_event(
287290
UIMousePressEvent(
288291
source=self,
289-
x=widget.rect.center_x,
290-
y=widget.rect.center_y,
292+
x=int(widget.rect.center_x),
293+
y=int(widget.rect.center_y),
291294
button=MOUSE_BUTTON_LEFT,
292295
modifiers=0,
293296
)
@@ -312,15 +315,16 @@ def end_interaction(self):
312315
widget.dispatch_ui_event(
313316
UIMouseReleaseEvent(
314317
source=self,
315-
x=x,
316-
y=y,
318+
x=int(x),
319+
y=int(y),
317320
button=MOUSE_BUTTON_LEFT,
318321
modifiers=0,
319322
)
320323
)
321324

322325
def _do_render(self, surface: Surface, force=False) -> bool:
323-
self._ensure_focused_property() # TODO this is a hack, to set the focused property on the focused widget
326+
# TODO this is a hack, to set the focused property on the focused widget
327+
self._ensure_focused_property()
324328

325329
# TODO: add a post child render hook to UIWidget
326330
rendered = super()._do_render(surface, force)

arcade/gui/ui_manager.py

+28-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing_extensions import TypeGuard
1919

2020
import arcade
21+
from arcade.experimental.controller_window import ControllerWindow
2122
from arcade.gui import UIEvent
2223
from arcade.gui.events import (
2324
UIKeyPressEvent,
@@ -41,7 +42,7 @@
4142
)
4243
from arcade.gui.surface import Surface
4344
from arcade.gui.widgets import UIWidget
44-
from arcade.types import LBWH, AnchorPoint, Point2, Rect
45+
from arcade.types import AnchorPoint, LBWH, Point2, Rect
4546

4647
W = TypeVar("W", bound=UIWidget)
4748

@@ -288,6 +289,18 @@ def enable(self) -> None:
288289
"""
289290
if not self._enabled:
290291
self._enabled = True
292+
293+
if isinstance(self.window, ControllerWindow):
294+
controller_handlers = {
295+
self.on_stick_motion,
296+
self.on_trigger_motion,
297+
self.on_button_press,
298+
self.on_button_release,
299+
self.on_dpad_motion,
300+
}
301+
else:
302+
controller_handlers = set()
303+
291304
self.window.push_handlers(
292305
self.on_resize,
293306
self.on_update,
@@ -301,11 +314,7 @@ def enable(self) -> None:
301314
self.on_text,
302315
self.on_text_motion,
303316
self.on_text_motion_select,
304-
self.on_stick_motion,
305-
self.on_trigger_motion,
306-
self.on_button_press,
307-
self.on_button_release,
308-
self.on_dpad_motion,
317+
*controller_handlers,
309318
)
310319

311320
def disable(self) -> None:
@@ -316,6 +325,18 @@ def disable(self) -> None:
316325
"""
317326
if self._enabled:
318327
self._enabled = False
328+
329+
if isinstance(self.window, ControllerWindow):
330+
controller_handlers = {
331+
self.on_stick_motion,
332+
self.on_trigger_motion,
333+
self.on_button_press,
334+
self.on_button_release,
335+
self.on_dpad_motion,
336+
}
337+
else:
338+
controller_handlers = set()
339+
319340
self.window.remove_handlers(
320341
self.on_resize,
321342
self.on_update,
@@ -329,11 +350,7 @@ def disable(self) -> None:
329350
self.on_text,
330351
self.on_text_motion,
331352
self.on_text_motion_select,
332-
self.on_stick_motion,
333-
self.on_trigger_motion,
334-
self.on_button_press,
335-
self.on_button_release,
336-
self.on_dpad_motion,
353+
*controller_handlers,
337354
)
338355

339356
def on_update(self, time_delta):
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

tests/unit/resources/test_list_resources.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010

1111
def test_all():
1212
resources = arcade.resources.list_built_in_assets()
13-
assert len(resources) == pytest.approx(770, abs=10)
13+
assert len(resources) == pytest.approx(863, abs=10)
1414

1515

1616
def test_png():
1717
resources = arcade.resources.list_built_in_assets(extensions=(".png",))
18-
assert len(resources) == pytest.approx(630, abs=10)
18+
assert len(resources) == pytest.approx(723, abs=10)
1919

2020

2121
def test_audio():

0 commit comments

Comments
 (0)