Skip to content

Commit 6abb648

Browse files
committed
experimental controller support incl inventory example
1 parent 72373c1 commit 6abb648

File tree

96 files changed

+886
-173
lines changed

Some content is hidden

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

96 files changed

+886
-173
lines changed

arcade/examples/gui/exp_controller_support.py

+80-21
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,107 @@
1414
)
1515
from arcade.gui.experimental.controller import (
1616
UIControllerBridge,
17+
UIControllerButtonEvent,
1718
UIControllerButtonPressEvent,
1819
UIControllerDpadEvent,
19-
UIFocusGroup,
20+
UIControllerEvent,
21+
UIControllerStickEvent,
22+
UIControllerTriggerEvent,
2023
)
24+
from arcade.gui.experimental.focus import UIFocusGroup
25+
from arcade.types import Color
2126

2227

2328
class ControllerIndicator(UIAnchorLayout):
29+
"""
30+
A widget that displays the last controller input.
31+
"""
32+
2433
BLANK_TEX = Texture.create_empty("empty", (40, 40), arcade.color.TRANSPARENT_BLACK)
34+
TEXTURE_CACHE = {}
2535

2636
def __init__(self):
2737
super().__init__()
2838

2939
self._indicator = self.add(UIImage(texture=self.BLANK_TEX), anchor_y="bottom", align_y=10)
40+
self._indicator.with_background(color=Color(0, 0, 0, 0))
41+
self._indicator._strong_background = True
42+
43+
@classmethod
44+
def get_texture(cls, path: str) -> Texture:
45+
if path not in cls.TEXTURE_CACHE:
46+
cls.TEXTURE_CACHE[path] = arcade.load_texture(path)
47+
return cls.TEXTURE_CACHE[path]
48+
49+
@classmethod
50+
def input_prompts(cls, event: UIControllerEvent) -> Texture | None:
51+
if isinstance(event, UIControllerButtonEvent):
52+
match event.button:
53+
case "a":
54+
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_a.png")
55+
case "b":
56+
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_b.png")
57+
case "x":
58+
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_x.png")
59+
case "y":
60+
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_y.png")
61+
case "rightshoulder":
62+
return cls.get_texture(":resources:input_prompt/xbox/xbox_rb.png")
63+
case "leftshoulder":
64+
return cls.get_texture(":resources:input_prompt/xbox/xbox_lb.png")
65+
case "start":
66+
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_start.png")
67+
case "back":
68+
return cls.get_texture(":resources:input_prompt/xbox/xbox_button_back.png")
69+
70+
if isinstance(event, UIControllerTriggerEvent):
71+
match event.name:
72+
case "lefttrigger":
73+
return cls.get_texture(":resources:input_prompt/xbox/xbox_lt.png")
74+
case "righttrigger":
75+
return cls.get_texture(":resources:input_prompt/xbox/xbox_rt.png")
76+
77+
if isinstance(event, UIControllerDpadEvent):
78+
match event.vector:
79+
case (1, 0):
80+
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_right.png")
81+
case (-1, 0):
82+
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_left.png")
83+
case (0, 1):
84+
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_up.png")
85+
case (0, -1):
86+
return cls.get_texture(":resources:input_prompt/xbox/xbox_dpad_down.png")
87+
88+
if isinstance(event, UIControllerStickEvent) and event.vector.length() > 0.2:
89+
stick = "l" if event.name == "leftstick" else "r"
90+
91+
# map atan2(y, x) to direction string (up, down, left, right)
92+
heading = event.vector.heading()
93+
if 0.785 > heading > -0.785:
94+
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_right.png")
95+
elif 0.785 < heading < 2.356:
96+
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_up.png")
97+
elif heading > 2.356 or heading < -2.356:
98+
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_left.png")
99+
elif -2.356 < heading < -0.785:
100+
return cls.get_texture(f":resources:input_prompt/xbox/xbox_stick_{stick}_down.png")
101+
102+
return None
30103

31104
def on_event(self, event: UIEvent) -> Optional[bool]:
32-
if isinstance(event, UIControllerButtonPressEvent):
33-
self._indicator.texture = arcade.load_texture(
34-
f":resources:onscreen_controls/flat_dark/{event.button}.png"
35-
)
36-
arcade.unschedule(self.reset)
37-
arcade.schedule_once(self.reset, 0.5)
38-
elif isinstance(event, UIControllerDpadEvent):
39-
tex_map = {
40-
(1, 0): "right",
41-
(-1, 0): "left",
42-
(0, 1): "up",
43-
(0, -1): "down",
44-
}
45-
46-
if event.vector in tex_map:
47-
self._indicator.texture = arcade.load_texture(
48-
f":resources:onscreen_controls/flat_dark/{tex_map[event.vector]}.png"
49-
)
105+
if isinstance(event, UIControllerEvent):
106+
input_texture = self.input_prompts(event)
107+
108+
if input_texture:
109+
self._indicator.texture = input_texture
110+
50111
arcade.unschedule(self.reset)
51112
arcade.schedule_once(self.reset, 0.5)
52113

53114
return super().on_event(event)
54115

55116
def reset(self, *_):
56-
print("Reset")
57117
self._indicator.texture = self.BLANK_TEX
58-
self.trigger_full_render()
59118

60119

61120
class ControllerModal(UIMouseFilterMixin, UIFocusGroup):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from typing import Dict, Tuple
2+
3+
import arcade
4+
from arcade.examples.gui.exp_controller_support import ControllerIndicator
5+
from arcade.gui import (
6+
UIFlatButton,
7+
UIGridLayout,
8+
UIView,
9+
)
10+
from arcade.gui.experimental.controller import (
11+
UIControllerBridge,
12+
)
13+
from arcade.gui.experimental.focus import Focusable, UIFocusGroup
14+
15+
16+
class FocusableButton(Focusable, UIFlatButton):
17+
pass
18+
19+
20+
def setup_grid_focus_transition(grid: Dict[Tuple[int, int], Focusable]):
21+
"""Setup focus transition in grid.
22+
23+
Connect focus transition between `Focusable` in grid.
24+
25+
Args:
26+
grid: Dict[Tuple[int, int], Focusable]: grid of Focusable widgets.
27+
key represents position in grid (x,y)
28+
29+
"""
30+
31+
cols = max(x for x, y in grid.keys()) + 1
32+
rows = max(y for x, y in grid.keys()) + 1
33+
for c in range(cols):
34+
for r in range(rows):
35+
btn = grid.get((c, r))
36+
if btn is None or not isinstance(btn, Focusable):
37+
continue
38+
39+
if c > 0:
40+
btn.neighbor_left = grid.get((c - 1, r))
41+
else:
42+
btn.neighbor_left = grid.get((cols - 1, r))
43+
44+
if c < cols - 1:
45+
btn.neighbor_right = grid.get((c + 1, r))
46+
else:
47+
btn.neighbor_right = grid.get((0, r))
48+
49+
if r > 0:
50+
btn.neighbor_up = grid.get((c, r - 1))
51+
else:
52+
btn.neighbor_up = grid.get((c, rows - 1))
53+
54+
if r < rows - 1:
55+
btn.neighbor_down = grid.get((c, r + 1))
56+
else:
57+
btn.neighbor_down = grid.get((c, 0))
58+
59+
60+
class MyView(UIView):
61+
def __init__(self):
62+
super().__init__()
63+
arcade.set_background_color(arcade.color.AMAZON)
64+
65+
self.controller_bridge = UIControllerBridge(self.ui)
66+
67+
self.root = self.add_widget(ControllerIndicator())
68+
self.root = self.root.add(UIFocusGroup())
69+
grid = self.root.add(
70+
UIGridLayout(column_count=3, row_count=3, vertical_spacing=10, horizontal_spacing=10)
71+
)
72+
73+
_grid = {}
74+
for i in range(9):
75+
btn = FocusableButton(text=f"Button {i}")
76+
_grid[(i % 3, i // 3)] = btn
77+
grid.add(btn, column=i % 3, row=i // 3)
78+
79+
# connect focus transition in grid
80+
setup_grid_focus_transition(_grid)
81+
82+
self.root.detect_focusable_widgets()
83+
84+
85+
if __name__ == "__main__":
86+
window = arcade.Window(title="Controller UI Example")
87+
window.show_view(MyView())
88+
arcade.run()

0 commit comments

Comments
 (0)