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

Lines changed: 30 additions & 19 deletions
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

Lines changed: 12 additions & 1 deletion
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

Lines changed: 16 additions & 9 deletions
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

Lines changed: 19 additions & 15 deletions
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

Lines changed: 28 additions & 11 deletions
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.

0 commit comments

Comments
 (0)